使用节点编程

更新:November 2007

需要编写 XML 编辑器、转换系统或报告编写器这类程序的 LINQ to XML 开发人员通常需要编写在比元素和属性更细的粒度下运行的程序。 开发人员通常需要在节点级别上工作,操作文本节点、处理指令和添加注释。 本主题提供有关在节点级别进行编程的一些详细信息。

节点详细信息

在节点级别上工作的程序员需要了解有关编程的许多细节。

XDocument 的子节点的父属性设置为 Null

Parent 属性包含父 XElement,而不是父节点。 XDocument 的子节点没有父 XElement。 它们的父级是文档,因此这些节点的 Parent 属性设置为 null。

下面的示例演示这一操作:

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) 

本示例生成以下输出:

True
True

可以存在相邻文本节点

在许多 XML 编程模型中,相邻的文本节点始终会合并到一起。 这有时也称为文本节点的规范化。LINQ to XML 不规范化文本节点。 如果向同一个元素添加两个文本节点,则会产生相邻文本节点。 但如果将指定内容添加为字符串而不是 XText 节点,则 LINQ to XML 可能会将该字符串与相邻的文本节点合并在一起。

下面的示例演示这一操作:

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

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

// this does not 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 does not 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())

本示例生成以下输出:

1
1
2

可以存在空文本节点

在某些 XML 编程模型中,文本节点保证不包含空字符串。 原因是这种文本节点对 XML 的序列化没有影响。 但由于可以存在相邻文本节点这一相同的原因,如果您通过将文本节点的值设置为空字符串来移除文本节点中的文本,则不会删除文本节点本身。

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

// the following line does not 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 does not cause the removal of the text node.
textNode.Value = ""

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

本示例生成以下输出:

>><<

空文本节点影响序列化

如果一个元素仅包含一个空的文本节点,则会用长标记语法序列化该元素: <Child></Child>. 如果一个元素不包含任何子节点,则会用短标记语法序列化该元素: <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)

本示例生成以下输出:

<Child1></Child1>
<Child2 />

命名空间是 LINQ to XML 树中的属性

即使命名空间声明与属性具有完全相同的语法,在某些编程接口(如 XSLT 和 XPath)中,也不会将命名空间声明视为属性。 但在 LINQ to XML 中,命名空间存储为 XML 树中的 XAttribute 对象。 如果您循环访问包含命名空间声明的元素的属性,则会在返回的集合中看到作为一项列出的命名空间声明。

IsNamespaceDeclaration 属性 (property) 指示某一属性 (attribute) 是否为命名空间声明。

XElement root = XElement.Parse(
@"<Root
    xmlns='https://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='https://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

本示例生成以下输出:

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

XPath 轴方法不返回 XDocument 的子空白

LINQ to XML 允许 XDocument 具有子文本节点,但这些文本节点只能包含空白。 但是,XPath 对象模型不包括空白作为文档的子节点,因此在使用 Nodes 轴循环访问 XDocument 的子级时,将返回空白文本节点。 但在使用 XPath 轴方法循环访问 XDocument 的子级时,不会返回空白文本节点。

// 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())

本示例生成以下输出:

3
0

XDeclaration 对象不是节点

在循环访问 XDocument 的子节点时,将看不到 XML 声明对象。 它是文档的属性,不是文档的子节点。

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())

本示例生成以下输出:

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

请参见

概念

高级 LINQ to XML 编程