Programación con nodos (LINQ to XML)

Los desarrolladores de LINQ to XML que necesitan escribir programas como un editor XML, un sistema de transformación o un redactor de informes a menudo necesitan código que funcione a un nivel de granularidad más fino que el de los elementos y atributos. A menudo deben trabajar en el nivel del nodo, manipulando nodos de texto, procesando instrucciones y comentarios. Este artículo facilita información sobre programación a nivel nodo.

Ejemplo: Los Parent valores de propiedad de los nodos secundarios de XDocument se establecen en null

La propiedad Parent contiene el XElement primario, no el nodo primario. Los nodos secundarios de XDocument no tienen XElement primario. Su elemento primario es el documento, de forma que la propiedad Parent para esos nodos se establece en null.

En el siguiente ejemplo se muestra esto:

XDocument doc = XDocument.Parse(@"<!-- a comment --><Root/>");
Console.WriteLine(doc.Nodes().OfType<XComment>().First().Parent == null);
Console.WriteLine(doc.Root.Parent == null);
Dim doc As XDocument = XDocument.Parse("<!-- a comment --><Root/>")
Console.WriteLine(doc.Nodes().OfType(Of XComment).First().Parent Is Nothing)
Console.WriteLine(doc.Root.Parent Is Nothing)

Este ejemplo produce el siguiente resultado:

True
True

Ejemplo: Agregar texto puede o no crear un nodo de texto

En varios modelos de programación XML, los nodos de texto adyacente siempre están combinados. A veces se denomina normalización de los nodos de texto. LINQ to XML no normaliza los nodos de texto. Si agrega dos nodos de texto al mismo elemento, tendrá como resultado nodos de texto adyacentes. En cambio, si agrega contenido especificado como una cadena en lugar de un nodo XText, LINQ to XML puede combinar la cadena con un nodo de texto adyacente. En el siguiente ejemplo se muestra cómo hacerlo.

XElement xmlTree = new XElement("Root", "Content");

Console.WriteLine(xmlTree.Nodes().OfType<XText>().Count());

// this doesn't add a new text node
xmlTree.Add("new content");
Console.WriteLine(xmlTree.Nodes().OfType<XText>().Count());

// this does add a new, adjacent text node
xmlTree.Add(new XText("more text"));
Console.WriteLine(xmlTree.Nodes().OfType<XText>().Count());
Dim xmlTree As XElement = <Root>Content</Root>
Console.WriteLine(xmlTree.Nodes().OfType(Of XText)().Count())

' This doesn't add a new text node.
xmlTree.Add("new content")
Console.WriteLine(xmlTree.Nodes().OfType(Of XText)().Count())

'// This does add a new, adjacent text node.
xmlTree.Add(New XText("more text"))
Console.WriteLine(xmlTree.Nodes().OfType(Of XText)().Count())

Este ejemplo produce el siguiente resultado:

1
1
2

Ejemplo: Establecer un valor de nodo de texto en la cadena vacía no elimina el nodo

En algunos modelos de programación XML, se garantiza que los nodos de texto no contienen la cadena vacía. El razonamiento es que ese nodo de texto no tiene ninguna incidencia en la serialización de XML. No obstante, por la misma razón que los nodos de texto adyacentes son posibles, si quitas el texto de un nodo de texto estableciendo su valor en la cadena vacía, el nodo de texto en sí no se eliminará.

XElement xmlTree = new XElement("Root", "Content");
XText textNode = xmlTree.Nodes().OfType<XText>().First();

// the following line doesn't cause the removal of the text node.
textNode.Value = "";

XText textNode2 = xmlTree.Nodes().OfType<XText>().First();
Console.WriteLine(">>{0}<<", textNode2);
Dim xmlTree As XElement = <Root>Content</Root>
Dim textNode As XText = xmlTree.Nodes().OfType(Of XText)().First()

' The following line doesn't cause the removal of the text node.
textNode.Value = ""

Dim textNode2 As XText = xmlTree.Nodes().OfType(Of XText)().First()
Console.WriteLine(">>{0}<<", textNode2)

Este ejemplo produce el siguiente resultado:

>><<

Ejemplo: un elemento con un nodo de texto vacío se serializa de forma diferente de uno sin nodo de texto

Si un elemento contiene solamente un nodo de texto que está vacío, se serializa con la sintaxis de etiqueta larga: <Child></Child>. Si un elemento no contiene ningún nodo secundario, se serializa con la sintaxis de etiqueta corta: <Child />.

XElement child1 = new XElement("Child1",
    new XText("")
);
XElement child2 = new XElement("Child2");
Console.WriteLine(child1);
Console.WriteLine(child2);
Dim child1 As XElement = New XElement("Child1", _
    New XText("") _
)
Dim child2 As XElement = New XElement("Child2")
Console.WriteLine(child1)
Console.WriteLine(child2)

Este ejemplo produce el siguiente resultado:

<Child1></Child1>
<Child2 />

Ejemplo: Los espacios de nombres son atributos en el árbol LINQ to XML

Aunque las declaraciones del espacio de nombres tienen una sintaxis idéntica a los atributos, en algunas interfaces de programación como XSLT y XPath, las declaraciones de espacios de nombres no se consideran atributos. No obstante, en LINQ to XML, los espacios de nombres se almacenan como objetos XAttribute en el árbol XML. Si recorre en iteración los atributos o un elemento que contiene una declaración de espacio de nombres, la declaración de espacio de nombres es uno de los elementos de la recopilación devuelta. La propiedad IsNamespaceDeclaration indica si un atributo es una declaración de espacio de nombres.

XElement root = XElement.Parse(
@"<Root
    xmlns='http://www.adventure-works.com'
    xmlns:fc='www.fourthcoffee.com'
    AnAttribute='abc'/>");
foreach (XAttribute att in root.Attributes())
    Console.WriteLine("{0}  IsNamespaceDeclaration:{1}", att, att.IsNamespaceDeclaration);
Dim root As XElement = _
<Root
    xmlns='http://www.adventure-works.com'
    xmlns:fc='www.fourthcoffee.com'
    AnAttribute='abc'/>
For Each att As XAttribute In root.Attributes()
    Console.WriteLine("{0}  IsNamespaceDeclaration:{1}", att, _
                      att.IsNamespaceDeclaration)
Next

Este ejemplo produce el siguiente resultado:

xmlns="http://www.adventure-works.com"  IsNamespaceDeclaration:True
xmlns:fc="www.fourthcoffee.com"  IsNamespaceDeclaration:True
AnAttribute="abc"  IsNamespaceDeclaration:False

Ejemplo: los métodos del eje XPath no devuelven los nodos de texto secundarios de XDocument

LINQ to XML permite nodos de texto secundarios de un XDocument mientras los nodos de texto contengan solamente espacios en blanco. No obstante, el modelo del objeto XPath no incluye el espacio en blanco como nodos secundarios de un documento, así que cuando recorra en iteración los elementos secundarios de XDocument usando el eje Nodes, se devolverán los nodos de texto del espacio en blanco. Sin embargo, cuando recorra en iteración los elementos secundarios de XDocument usando los métodos del eje de XPath, no se devolverán los nodos de texto de espacio en blanco.

// Create a document with some white space child nodes of the document.
XDocument root = XDocument.Parse(
@"<?xml version='1.0' encoding='utf-8' standalone='yes'?>

<Root/>

<!--a comment-->
", LoadOptions.PreserveWhitespace);

// count the white space child nodes using LINQ to XML
Console.WriteLine(root.Nodes().OfType<XText>().Count());

// count the white space child nodes using XPathEvaluate
Console.WriteLine(((IEnumerable)root.XPathEvaluate("text()")).OfType<XText>().Count());
' Create a document with some white space child nodes of the document.
Dim root As XDocument = XDocument.Parse( _
"<?xml version='1.0' encoding='utf-8' standalone='yes'?>" & _
vbNewLine & "<Root/>" & vbNewLine & "<!--a comment-->" & vbNewLine, _
LoadOptions.PreserveWhitespace)

' Count the white space child nodes using LINQ to XML.
Console.WriteLine(root.Nodes().OfType(Of XText)().Count())

' Count the white space child nodes using XPathEvaluate.
Dim nodes As IEnumerable = CType(root.XPathEvaluate("text()"), IEnumerable)
Console.WriteLine(nodes.OfType(Of XText)().Count())

Este ejemplo produce el siguiente resultado:

3
0

El nodo de declaración XML de un XDocument es una propiedad, no un nodo secundario

Cuando iteres los nodos secundarios de un XDocument, no verás el objeto de declaración XML. Es una propiedad del documento, no un nodo secundario de él.

XDocument doc = new XDocument(
    new XDeclaration("1.0", "utf-8", "yes"),
    new XElement("Root")
);
doc.Save("Temp.xml");
Console.WriteLine(File.ReadAllText("Temp.xml"));

// this shows that there is only one child node of the document
Console.WriteLine(doc.Nodes().Count());
Dim doc As XDocument = _
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<Root/>

doc.Save("Temp.xml")
Console.WriteLine(File.ReadAllText("Temp.xml"))

' This shows that there is only one child node of the document.
Console.WriteLine(doc.Nodes().Count())

Este ejemplo produce el siguiente resultado:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Root />
1