问题
I need to modify a xml file (actually a .rdlc report file) and add some nodes which have a lot of children nodes (and those children nodes again have children nodes). Actually they are almost the same strucutre, like this one:
<TablixRow>
<Height>0.23622in</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name="Textbox1">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value/>
<Style/>
</TextRun>
</TextRuns>
<Style/>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox1</rd:DefaultName>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="Textbox5">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value/>
<Style/>
</TextRun>
</TextRuns>
<Style/>
</Paragraph>
</Paragraphs>
<rd:DefaultName>Textbox5</rd:DefaultName>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>
So what is the easiest way to do it? In normal case I just create a XmlNode and some XmlAttribute objects, attach those attributes to the node, and create the children nodes the same way and finally append each child node into its parent. Needless to say it will be tedious to process my example node. Is there an easier way to do it? Like with the class XmlDocument, there is a function LoadXml (xml as string), which takes a string as the whole xml file and construct the strucutre. Is there a similar way to construct a XmlNode object? So that I only need to provide the whole piece of string representing my node, and then navigate to the child node of which I need to change the value. Thanks!
Update: I am using VB.NET. And there is one problem with namespace when using XElement. In this link XName Class, it says for C# it is recommended to just use overriden add operator to combine element and NS, but for VB, it recommends using Import at the top (in the example outside of a Module. I assume it should also work for Class)and then everything will be using this NS automatically. However, this is not the case. For example, if I give
Dim para As XElement = _
<ReportParameter Name="HasErr">
<DataType>Boolean</DataType>
<DefaultValue>
<Values>
<Value>False</Value>
</Values>
</DefaultValue>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
it will automatically attach my specified (and stated as default) NS there. But if I use XElement.Parse(xml As String), where xml is the same string representing the xml, it won't add this NS at all, which will end up using an empty NS (the reason I want to use XElement.Parse is I want to give my customized parameter value there, like & MY_TYPE_NAME & ). The second problem is when using @JohnD 's code, I try
xdoc.Root.Elements("ReportParameters").FirstOrDefault()
which I assume will also use my declared and default NS, will return nothing, i.e., it searches within empty namespace, but it is actually in the NS I mentioned.
Anyone knows that is the reason MS made it so that there is no constructor for XName class, where I can specify a namespace before I use it? It says there is only one implicit convert, so when given a string representing the element name in
xdoc.Root.Elements("ReportParameters")
it will implicitly generates one XName object to index the search in Elements. But it is really clumsy.
Latest update: I now found the solution to solve my first problem in my update: I am now using XML Literals to create XElement, and it is possible to use expression inside it. So it now looks like this:
Dim paraDefNode As XElement = _
<ReportParameter Name=<%= para.Value %>>
<DataType>String</DataType>
<DefaultValue>
<Values>
<Value>False</Value>
</Values>
</DefaultValue>
<Prompt>ReportParameter1</Prompt>
</ReportParameter>
and it will add my specified NS. (like I said, XElement.Parse(string) won't add it) So now I can construct the correct node. For my second problem I still can't figure out: I can't navigate to the target node by using the element name since it won't search for correct NS.
I will anyway mark @JohnD 's post as answer since he suggested to use LINQ to XML.
回答1:
If you can use .NET 4, I would recommend looking at XDocument
. Here is an example for adding elements to a document:
http://msdn.microsoft.com/en-us/library/system.xml.linq.xdocument.add.aspx
You can use XDocument.Parse
to initialize the document from a string, or XDocument.Load
to initialize from a file (there are other overloads too).
Then, you can navigate to the element where you want to insert, and do an XElement.Add
()
Here is some sample code that puts your XML element into another XDocument. I'm just adding the XML to the first "Elem" node of the target XDcoument, but you can tweak the code to add it multiple times, etc...
public static void Main()
{
var xelementToAdd = XElement.Parse(@"
<TablixRow>
<Height>0.23622in</Height>
<TablixCells>
<TablixCell>
<CellContents>
<Textbox Name='Textbox1'>
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value/>
<Style/>
</TextRun>
</TextRuns>
<Style/>
</Paragraph>
</Paragraphs>
<DefaultName>Textbox1</DefaultName>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name='Textbox5'>
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value/>
<Style/>
</TextRun>
</TextRuns>
<Style/>
</Paragraph>
</Paragraphs>
<DefaultName>Textbox5</DefaultName>
<Style>
<Border>
<Style>None</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
</TablixCells>
</TablixRow>");
// you might use XDocument.Load() here instead of Parse()
var xdoc = XDocument.Parse(@"
<Report xmlns='http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition'
xmlns:rd='http://schemas.microsoft.com/SQLServer/reporting/reportdesigner'
xmlns:msxsl='urn:schemas-microsoft-com:xslt'
xmlns:xs='http://www.w3.org/2001/XMLSchema'
xmlns:msdata='urn:schemas-microsoft-com:xml-msdata'>
<rd:DrawGrid>true</rd:DrawGrid>
<ReportParameters></ReportParameters>
</Report>
");
XNamespace ns1 = "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition";
XNamespace ns2 = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
XNamespace ns3 = "urn:schemas-microsoft-com:xslt";
XNamespace ns4 = "http://www.w3.org/2001/XMLSchema";
XNamespace ns5 = "urn:schemas-microsoft-com:xml-msdata";
var e = xdoc.Root.Elements(ns1 + "ReportParameters")
.FirstOrDefault();
e.Add(xelementToAdd);
xdoc.Save(@"c:\temp\foo2.xml");
}
And for what it's worth, I tweaked your sample XML to remove the namespace prefix (namespaces are an issue separate from your question) and replaced the double quotes with single quotes (the XML is otherwise equivalent).
Updated: Yes, you are running into a namespace problem. Even though your <ReportParameters>
element does not have a prefix like <rd:DrawGrid>
, it's using the default namespace, and you must specify it. Take a look at the updated sample, which should do what you want. Hope this helps!
来源:https://stackoverflow.com/questions/7993665/easiest-way-to-add-xml-a-node-with-a-bunch-of-children-nodes-in-net