What is the difference XElement Nodes() vs Elements()? - c#

Documentation says:
XContainer.Nodes Method ()
Returns a collection of the child nodes of this element or document, in document order.
Remarks
Note that the content does not include attributes. In LINQ to XML, attributes are not considered to be nodes of the tree. They are name/value pairs associated with an element.
XContainer.Elements Method ()
Returns a collection of the child elements of this element or document, in document order.
So it looks like Nodes() has a limitation, but then why does it exist? Are there any possible reasons or advantages of using Nodes()?

The reason is simple: XNode is a base (abstract) class for all xml "parts", and XElement is just one such part (so XElement is subclass of XNode). Consider this code:
XDocument doc = XDocument.Parse("<root><el1 />some text<!-- comment --></root>");
foreach (var node in doc.Root.Nodes()) {
Console.WriteLine(node);
}
foreach (var element in doc.Root.Elements()) {
Console.WriteLine(element);
}
Second loop (over Elements()) will only return one item: <el />
First loop however will return also text node (some text) and comment node (<!-- comment -->), so you see the difference.
You can see what other descendants of XNode there are in documentaiton of XNode class.

It's not the case that Nodes "have a limitation". Nodes are the fundamental building block on which most other things (including Elements) are built.
The XML document is represented as a hierarchy (tree), and the nodes are used to represent the fundamental structure of the hierarchy.
If we consider the following XML document:
<root>
<element>
<child>
Text
</child>
</element>
<!-- comment -->
<element>
<child>
Text
<child>
</element>
</root>
Clearly the whole document cannot be represented as elements, since the comment and the text within the "child" elements are not elements. Instead, it's represented as a hierarchy of nodes.
In this document, there are 5 elements (the root element, two "element" elements and two "child" elements). All of these are nodes, but there are also 3 other nodes: the text within "child" elements, and the comment.
It's misleading to say that nodes have a "limitation" because they don't have attributes. Only elements have attributes, and elements are nodes! But there are other nodes (e.g. the comment) that can't have attributes. So not all types of node have attributes.
In coding terms, Node is the base class on which higher-level types such as Element are built. If you want to enumerate the elements in the document, then using XContainer.Elements() is a nice shortcut to do that - but you could also use XContainer.Nodes() and get all the nodes, including both the elements and the other stuff. (You can check the type of the node to see whether you have an element node, a text node, or whatever; if it's an element, you can up-cast it).

Related

XmlDocument Searching for namespace returns children

I am trying to convert nodes that have a namespace declaration over to use a prefix instead. My first stab at it was to just use xslt to transform the xml, but I started looking at doing it with the XmlDocument class and using the SelectNodes() method. The issue I am seeing is when I try to select nodes that have a namespace, it selects that node AND its children. I assume this is because it is selecting the node which contains children.
<foo xmlns="some url">
<child>child</child>
</foo>
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(xmlstring);
var query = xdoc.SelectNodes("//*[namespace-uri()='some url']");
the query variable will return <foo> and <child> nodes, so when I loop through the nodes and change it to use the prefix, I get the following result.
<prefix:foo>
<prefix:child></prefix:child>
</prefix:foo>
Is there a way to just return just the <foo> node in this case? Is it better to also use xslt to transform it?
I didnt think you could change a namespace or prefix when using XDocument and XElement, so thats why I used XmlDocument.
Update
The result id want would be the prefix only on the node where the declaration was. This is valid xml correct or does the prefix need to be on the children as well to be valid?
<prefix:foo>
<child>child</child>
</prefix:foo>
In the XDM data model used by XPath and XSLT, there is no distinction between
<foo xmlns="some url">
<child>child</child>
</foo>
and
<foo xmlns="some url">
<child xmlns="some url">child</child>
</foo>
Logically the namespace is present on both element nodes, and its omission from the child in the lexical serialization is treated as a convenient abbreviation.
So yes, if you search for things having this namespace, you will get both elements.
Now, what are your requirements? I'm not convinced you fully understand them yourself, because the desired output you have shown is not actually well-formed (the namespace prefix is not declared). In your input, the two elements are in the same namespace; in the output, you seem to want them to be in different namespaces. If you want to process them differently, then you're going to have to use something other than the namespace to discriminate between them.
Remember that in XDM, it's the name of the node that matters, not the namespace declarations or prefixes; those are just ornamental. The name of the node is the combination of its local name and its namespace URI. You've described your requirement in terms of prefixes, but it's namespaces that actually matter.

c# Serialize xml node with different XmlWriterSettings than rest of object

I need to serialize an object as xml, but I need one of the elements and children elements of it to be serialized differently. The xml must be indented, but only a specific node (and its children) must be in 1 line.
so for example
<root>
<a>
<b><c>value</c></b>
</a>
root and a are serialized with indentation, but b and c are all in one line
thanks

xml multiple namespaces in root

I am having trouble generating an xml root. I have to match this structure as the elements of the xml use the prefixs throughout.
<ShipmentReceiptNotification
xmlns="urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02"
xmlns:dacc="urn:rosettanet:specification:domain:Procurement:AccountClassification:xsd:codelist:01.03"
xmlns:dbpq="urn:rosettanet:specification:domain:Procurement:BookPriceQualifier:xsd:codelist:01.04"
xmlns:dccc="urn:rosettanet:specification:domain:Procurement:CreditCardClassification:xsd:codelist:01.03"
xmlns:dcrt="urn:rosettanet:specification:domain:Procurement:CustomerType:xsd:codelist:01.03"
..\..\XML\Interchange\ShipmentReceiptNotification_02_02.xsd">
if I do something like
XmlNode ShipmentReceiptNotification0Node = xmlDoc.CreateElement("ShipmentReceiptNotification", "xmlns=\"urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02\"xmlns:dacc=\"urn:rosettanet:specification:domain:Procurement:AccountClassification:xsd:codelist:01.03\"");
I get
-ShipmentReceiptNotification
xmlns="xmlns="urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02"xmlns:dacc=&
quot;urn:rosettanet:specification:domain:Procurement:AccountClassification:xsd:codelist:01.03"">
The second argument of CreateElement accepts the URI of the namespace that the element being created, that is ShipmentReceiptNotification, belongs to. Not the whole bunch of xmlns attributes. This code:
XmlElement e = xmlDoc.CreateElement(
"ShipmentReceiptNotification",
"urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02");
Produces this XML:
<ShipmentReceiptNotification
xmlns="urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02" />
To produce what you want, you need to add attributes to the element. Like this:
XmlElement e = xmlDoc.CreateElement("ShipmentReceiptNotification");
e.SetAttribute("xmlns", "urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02");
e.SetAttribute("xmlns:dacc", "urn:rosettanet:specification:domain:Procurement:AccountClassification:xsd:codelist:01.03");
Produces this XML:
<ShipmentReceiptNotification
xmlns="urn:rosettanet:specification:interchange:ShipmentReceiptNotification:xsd:schema:02.02"
xmlns:dacc="urn:rosettanet:specification:domain:Procurement:AccountClassification:xsd:codelist:01.03" />
Note that this is the “manual” way. You should play with XmlNamespaceManager to do it “right”. However, that may be a bit more complex task which need not be necessary for your scenario.

Difference between XPathEvaluate on XElement or XDocument?

Somewhere in a C# program, I need to get an attribute value from an xml structure. I can reach this xml structure directly as an XElement and have a simple xpath string to get the attribute. However, using XPathEvaluate, I get an empty array most of the time. (Yes, sometimes, the attribute is returned, but mostly it isn't... for the exact same XElement and xpath string...)
However, if I first convert the xml to string and reparse it as an XDocument, I do always get the attribute back. Can somebody explain this behavior ? (Am using .NET 3.5)
Code that mostly returns an empty IEnumerable:
string xpath = "/exampleRoot/exampleSection[#name='test']/#value";
XElement myXelement = RetrieveXElement();
((IEnumerable)myXElement.XPathEvaluate(xpath)).Cast<XAttribute>().FirstOrDefault().Value;
Code that does always work (I get my attribute value):
string xpath = "/exampleRoot/exampleSection[#name='test']/#value";
string myXml = RetrieveXElement().ToString();
XDocument xdoc = XDocument.Parse(myXml);
((IEnumerable)xdoc.XPathEvaluate(xpath)).Cast<XAttribute>().FirstOrDefault().Value;
With the test xml:
<exampleRoot>
<exampleSection name="test" value="2" />
<exampleSection name="test2" value="2" />
</exampleRoot>
By suggestion related to a surrounding root, I did some 'dry tests' in a test program, using the same xml structure (txtbxXml and txtbxXpath representing the xml and xpath expression described above):
// 1. XDocument Trial:
((IEnumerable)XDocument.Parse(txtbxXml.Text).XPathEvaluate(txtbxXPath.Text)).Cast<XAttribute>().FirstOrDefault().Value.ToString();
// 2. XElement trial:
((IEnumerable)XElement.Parse(txtbxXml.Text).XPathEvaluate(txtbxXPath.Text)).Cast<XAttribute>().FirstOrDefault().Value.ToString();
// 3. XElement originating from other root:
((IEnumerable)(new XElement("otherRoot", XElement.Parse(txtbxXml.Text)).Element("exampleRoot")).XPathEvaluate(txtbxXPath.Text)).Cast<XAttribute>().FirstOrDefault().Value.ToString();
Result : case 1 and 3 produce the correct result, while case 2 throws a nullref exception.
If case 3 would fail and case 2 succeed, it would have made some sense to me, but now I don't get it...
The problem is that the XPath expression is starting with the children of the specified node. If you start with an XDocument, the root element is the child node. If you start with an XElement representing your exampleRoot node, then the children are the two exampleSection nodes.
If you change your XPath expression to "/exampleSection[#name='test']/#value", it will work from the element. If you change it to "//exampleSection[#name='test']/#value", it will work from both the XElement and the XDocument.

How to merge two XmlDocuments in C#

I want to merge two XmlDocuments by inserting a second XML doc to the end of an existing Xmldocument in C#. How is this done?
Something like this:
foreach (XmlNode node in documentB.DocumentElement.ChildNodes)
{
XmlNode imported = documentA.ImportNode(node, true);
documentA.DocumentElement.AppendChild(imported);
}
Note that this ignores the document element itself of document B - so if that has a different element name, or attributes you want to copy over, you'll need to work out exactly what you want to do.
EDIT: If, as per your comment, you want to embed the whole of document B within document A, that's relatively easy:
XmlNode importedDocument = documentA.ImportNode(documentB.DocumentElement, true);
documentA.DocumentElement.AppendChild(importedDocument);
This will still ignore things like the XML declaration of document B if there is one - I don't know what would happen if you tried to import the document itself as a node of a different document, and it included an XML declaration... but I suspect this will do what you want.
Inserting an entire XML document at the end of another XML document is actually guaranteed to produce invalid XML. XML requires that there be one, and only one "document" element. So, assuming that your files were as follows:
A.xml
<document>
<element>value1</element>
<element>value2</element>
</document>
B.xml
<document>
<element>value3</element>
<element>value4</element>
</document>
The resultant document by just appending one at the end of the other:
<document>
<element>value1</element>
<element>value2</element>
</document>
<document>
<element>value3</element>
<element>value4</element>
</document>
Is invalid XML.
Assuming, instead, that the two documents share a common document element, and you want to insert the children of the document element from B into A's document element, you could use the following:
var docA = new XmlDocument();
var docB = new XmlDocument();
foreach (var childEl in docB.DocumentElement.ChildNodes) {
var newNode = docA.ImportNode(childEl, true);
docA.DocumentElement.AppendChild(newNode);
}
This will produce the following document given my examples above:
<document>
<element>value1</element>
<element>value2</element>
<element>value3</element>
<element>value4</element>
</document>
This is the fastest cleanest way to merge xml documents.
XElement xFileRoot = XElement.Load(file1.xml);
XElement xFileChild = XElement.Load(file2.xml);
xFileRoot.Add(xFileChild);
xFileRoot.Save(file1.xml);
Bad news. As long as the xml documents can have only one root element you cannot just put content of one document at the end of the second. Maybe this is what you are looking for? It shows how easily you can merge xml files using Linq-to-XML
Alternatively if you are using XmlDocuments you can try make it like this:
XmlDocument documentA;
XmlDocument documentB;
foreach(var childNode in documentA.DocumentElement.ChildNodes)
documentB.DocumentElement.AppendChild(childNode);

Categories

Resources