如何:列出树中的所有节点
有时,列出树中的所有节点会有帮助。它可帮助准确了解方法或属性是如何影响树的。以文本形式列出所有节点的一种方法是生成准确逐一地标识树中所有节点的 XPath 表达式。
使用 LINQ to XML 执行 XPath 表达式不特别有用。XPath 表达式的性能不如 LINQ to XML 查询,并且 LINQ to XML 查询的功能更强大。但作为标识 XML 树中节点的一种方式,XPath 可以有效地工作。
示例
本示例演示一个名为 GetXPath 的函数,该函数为 XML 树中的每个节点生成一个特定的 XPath 表达式。即使节点位于命名空间中,此示例也可生成相应的 XPath 表达式。这些 XPath 表达式通过使用命名空间前缀生成。
然后,示例创建一个包含若干类型节点示例的小型 XML 树。然后循环访问后代节点并打印每个节点的 XPath 表达式。
您会注意到,XML 声明不是树中的节点。
下面是一个包含若干类型节点的 XML 文件:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?target data?>
<Root AttName="An Attribute" xmlns:aw="https://www.adventure-works.com">
<!--This is a comment-->
<Child>Text</Child>
<Child>Other Text</Child>
<ChildWithMixedContent>text<b>BoldText</b>otherText</ChildWithMixedContent>
<aw:ElementInNamespace>
<aw:ChildInNamespace />
</aw:ElementInNamespace>
</Root>
下面是上述 XML 树中节点的列表,以 XPath 表达式表示:
/processing-instruction()
/Root
/Root/@AttName
/Root/@xmlns:aw
/Root/comment()
/Root/Child[1]
/Root/Child[1]/text()
/Root/Child[2]
/Root/Child[2]/text()
/Root/ChildWithMixedContent
/Root/ChildWithMixedContent/text()[1]
/Root/ChildWithMixedContent/b
/Root/ChildWithMixedContent/b/text()
/Root/ChildWithMixedContent/text()[2]
/Root/aw:ElementInNamespace
/Root/aw:ElementInNamespace/aw:ChildInNamespace
public static class MyExtensions
{
private static string GetQName(XElement xe)
{
string prefix = xe.GetPrefixOfNamespace(xe.Name.Namespace);
if (xe.Name.Namespace == XNamespace.None || prefix == null)
return xe.Name.LocalName.ToString();
else
return prefix + ":" + xe.Name.LocalName.ToString();
}
private static string GetQName(XAttribute xa)
{
string prefix =
xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace);
if (xa.Name.Namespace == XNamespace.None || prefix == null)
return xa.Name.ToString();
else
return prefix + ":" + xa.Name.LocalName;
}
private static string NameWithPredicate(XElement el)
{
if (el.Parent != null && el.Parent.Elements(el.Name).Count() != 1)
return GetQName(el) + "[" +
(el.ElementsBeforeSelf(el.Name).Count() + 1) + "]";
else
return GetQName(el);
}
public static string StrCat<T>(this IEnumerable<T> source,
string separator)
{
return source.Aggregate(new StringBuilder(),
(sb, i) => sb
.Append(i.ToString())
.Append(separator),
s => s.ToString());
}
public static string GetXPath(this XObject xobj)
{
if (xobj.Parent == null)
{
XDocument doc = xobj as XDocument;
if (doc != null)
return ".";
XElement el = xobj as XElement;
if (el != null)
return "/" + NameWithPredicate(el);
// the XPath data model does not include white space text nodes
// that are children of a document, so this method returns null.
XText xt = xobj as XText;
if (xt != null)
return null;
XComment com = xobj as XComment;
if (com != null)
return
"/" +
(
com
.Document
.Nodes()
.OfType<XComment>()
.Count() != 1 ?
"comment()[" +
(com
.NodesBeforeSelf()
.OfType<XComment>()
.Count() + 1) +
"]" :
"comment()"
);
XProcessingInstruction pi = xobj as XProcessingInstruction;
if (pi != null)
return
"/" +
(
pi.Document.Nodes()
.OfType<XProcessingInstruction>()
.Count() != 1 ?
"processing-instruction()[" +
(pi
.NodesBeforeSelf()
.OfType<XProcessingInstruction>()
.Count() + 1) +
"]" :
"processing-instruction()"
);
return null;
}
else
{
XElement el = xobj as XElement;
if (el != null)
{
return
"/" +
el
.Ancestors()
.InDocumentOrder()
.Select(e => NameWithPredicate(e))
.StrCat("/") +
NameWithPredicate(el);
}
XAttribute at = xobj as XAttribute;
if (at != null)
return
"/" +
at
.Parent
.AncestorsAndSelf()
.InDocumentOrder()
.Select(e => NameWithPredicate(e))
.StrCat("/") +
"@" + GetQName(at);
XComment com = xobj as XComment;
if (com != null)
return
"/" +
com
.Parent
.AncestorsAndSelf()
.InDocumentOrder()
.Select(e => NameWithPredicate(e))
.StrCat("/") +
(
com
.Parent
.Nodes()
.OfType<XComment>()
.Count() != 1 ?
"comment()[" +
(com
.NodesBeforeSelf()
.OfType<XComment>()
.Count() + 1) + "]" :
"comment()"
);
XCData cd = xobj as XCData;
if (cd != null)
return
"/" +
cd
.Parent
.AncestorsAndSelf()
.InDocumentOrder()
.Select(e => NameWithPredicate(e))
.StrCat("/") +
(
cd
.Parent
.Nodes()
.OfType<XText>()
.Count() != 1 ?
"text()[" +
(cd
.NodesBeforeSelf()
.OfType<XText>()
.Count() + 1) + "]" :
"text()"
);
XText tx = xobj as XText;
if (tx != null)
return
"/" +
tx
.Parent
.AncestorsAndSelf()
.InDocumentOrder()
.Select(e => NameWithPredicate(e))
.StrCat("/") +
(
tx
.Parent
.Nodes()
.OfType<XText>()
.Count() != 1 ?
"text()[" +
(tx
.NodesBeforeSelf()
.OfType<XText>()
.Count() + 1) + "]" :
"text()"
);
XProcessingInstruction pi = xobj as XProcessingInstruction;
if (pi != null)
return
"/" +
pi
.Parent
.AncestorsAndSelf()
.InDocumentOrder()
.Select(e => NameWithPredicate(e))
.StrCat("/") +
(
pi
.Parent
.Nodes()
.OfType<XProcessingInstruction>()
.Count() != 1 ?
"processing-instruction()[" +
(pi
.NodesBeforeSelf()
.OfType<XProcessingInstruction>()
.Count() + 1) + "]" :
"processing-instruction()"
);
return null;
}
}
}
class Program
{
static void Main(string[] args)
{
XNamespace aw = "https://www.adventure-works.com";
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XProcessingInstruction("target", "data"),
new XElement("Root",
new XAttribute("AttName", "An Attribute"),
new XAttribute(XNamespace.Xmlns + "aw", aw.ToString()),
new XComment("This is a comment"),
new XElement("Child",
new XText("Text")
),
new XElement("Child",
new XText("Other Text")
),
new XElement("ChildWithMixedContent",
new XText("text"),
new XElement("b", "BoldText"),
new XText("otherText")
),
new XElement(aw + "ElementInNamespace",
new XElement(aw + "ChildInNamespace")
)
)
);
doc.Save("Test.xml");
Console.WriteLine(File.ReadAllText("Test.xml"));
Console.WriteLine("------");
foreach (XObject obj in doc.DescendantNodes())
{
Console.WriteLine(obj.GetXPath());
XElement el = obj as XElement;
if (el != null)
foreach (XAttribute at in el.Attributes())
Console.WriteLine(at.GetXPath());
}
}
}
Module Module1
<System.Runtime.CompilerServices.Extension()> _
Private Function StrCat(Of T)(ByVal source As IEnumerable(Of T), _
ByVal separator As String) As String
Return _
source.Aggregate(New StringBuilder(), _
Function(sb, i) sb _
.Append(i.ToString()) _
.Append(separator), _
Function(s) s.ToString())
End Function
<System.Runtime.CompilerServices.Extension()> _
Public Function GetXPath(ByVal xobj As XObject) As String
Dim retStr As String
If xobj.Parent Is Nothing Then
Dim doc As XDocument = TryCast(xobj, XDocument)
If doc IsNot Nothing Then
Return "."
End If
Dim el As XElement = TryCast(xobj, XElement)
If el IsNot Nothing Then
Return ("/" & NameWithPredicate(el))
End If
' The XPath data model does not include white space text nodes
' that are children of a document, so this method returns null.
Dim xt As XText = TryCast(xobj, XText)
If xt IsNot Nothing Then
Return Nothing
End If
Dim com As XComment = TryCast(xobj, XComment)
If com IsNot Nothing Then
If com.Document().Nodes().OfType(Of XComment)().Count() <> 1 Then
Return "/comment()[" & (com.NodesBeforeSelf().OfType _
(Of XComment)().Count() + 1) & "]"
Else
Return "/comment()"
End If
End If
Dim pi As XProcessingInstruction = TryCast(xobj, XProcessingInstruction)
If pi IsNot Nothing Then
If pi.Document.Nodes().OfType(Of XProcessingInstruction)(). _
Count() <> 1 Then
Return "/processing-instruction()[" & _
(pi.NodesBeforeSelf().OfType(Of XProcessingInstruction)() _
.Count() + 1) & "]"
Else
Return "/processing-instruction()"
End If
End If
Else
Dim el As XElement = TryCast(xobj, XElement)
If el IsNot Nothing Then
Return "/" & el.Ancestors().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)) _
.StrCat("/") & NameWithPredicate(el)
End If
Dim at As XAttribute = TryCast(xobj, XAttribute)
If at IsNot Nothing Then
Return "/" & at.Parent().AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)).StrCat("/") & _
"@" & GetQName(at)
End If
Dim com As XComment = TryCast(xobj, XComment)
If com IsNot Nothing Then
retStr = "/" & com.Parent.AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)).StrCat("/") & "comment()"
If com.Parent().Nodes().OfType(Of XComment)().Count() <> 1 Then
retStr &= "[" & (com.NodesBeforeSelf().OfType(Of XComment)().Count() + 1) & "]"
End If
Return retStr
End If
Dim cd As XCData = TryCast(xobj, XCData)
If cd IsNot Nothing Then
retStr = "/" & cd.Parent.AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)).StrCat("/") & "text()"
If cd.Parent.Nodes().OfType(Of XText)().Count() <> 1 Then
retStr &= "[" & (cd.NodesBeforeSelf().OfType(Of XText)(). _
Count() + 1) & "]"
End If
Return retStr
End If
Dim tx As XText = TryCast(xobj, XText)
If tx IsNot Nothing Then
retStr = "/" & tx.Parent.AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)).StrCat("/") & "text()"
If tx.Parent.Nodes().OfType(Of XText)().Count() <> 1 Then
retStr &= "[" & (tx.NodesBeforeSelf().OfType(Of XText)(). _
Count() + 1) & "]"
End If
Return retStr
End If
Dim pi As XProcessingInstruction = TryCast(xobj, XProcessingInstruction)
If pi IsNot Nothing Then
retStr = "/" & pi.Parent.AncestorsAndSelf().InDocumentOrder(). _
Select(Function(e) NameWithPredicate(e)). _
StrCat("/") & "processing-instruction()"
If pi.Parent.Nodes().OfType(Of XProcessingInstruction)().Count() <> 1 Then
retStr &= "[" & (pi.NodesBeforeSelf().OfType(Of XProcessingInstruction)(). _
Count() + 1) & "]"
End If
End If
End If
Return Nothing
End Function
Private Function GetQName(ByVal xe As XElement) As String
Dim prefix As String = xe.GetPrefixOfNamespace(xe.Name.Namespace)
If xe.Name.Namespace = XNamespace.None Or prefix Is Nothing Then
Return xe.Name.LocalName.ToString()
Else
Return prefix + ":" & xe.Name.LocalName.ToString()
End If
End Function
Private Function GetQName(ByVal xa As XAttribute) As String
Dim prefix As String = _
xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace)
If xa.Name.Namespace = XNamespace.None Or prefix Is Nothing Then
Return xa.Name.ToString()
Else
Return prefix + ":" & xa.Name.LocalName
End If
End Function
Public Function NameWithPredicate(ByVal el As XElement) As String
If el.Parent IsNot Nothing AndAlso el.Parent.Elements(el.Name).Count() <> 1 Then
Return GetQName(el) + "[" & _
(el.ElementsBeforeSelf(el.Name).Count() + 1) & "]"
Else
Return GetQName(el)
End If
End Function
Sub Main()
Dim aw As XNamespace = "https://www.adventure-works.com"
Dim doc As XDocument = _
<?xml version='1.0' encoding="utf-8" standalone='yes'?>
<?target data?>
<Root AttName='An Attribute' xmlns:aw='https://www.adventure-works.com'>
<!--This is a comment-->
<Child>Text</Child>
<Child>Other Text</Child>
<ChildWithMixedContent>text<b>BoldText</b>otherText</ChildWithMixedContent>
<aw:ElementInNamespace>
<aw:ChildInNamespace/>
</aw:ElementInNamespace>
</Root>
doc.Save("Test.xml")
Console.WriteLine(File.ReadAllText("Test.xml"))
Console.WriteLine("------")
For Each obj As XObject In doc.DescendantNodes()
Console.WriteLine(obj.GetXPath())
Dim el As XElement = TryCast(obj, XElement)
If el IsNot Nothing Then
For Each at As XAttribute In el.Attributes()
Console.WriteLine(at.GetXPath())
Next
End If
Next
End Sub
End Module
此示例产生以下输出:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?target data?>
<Root AttName="An Attribute" xmlns:aw="https://www.adventure-works.com">
<!--This is a comment-->
<Child>Text</Child>
<Child>Other Text</Child>
<ChildWithMixedContent>text<b>BoldText</b>otherText</ChildWithMixedContent>
<aw:ElementInNamespace>
<aw:ChildInNamespace />
</aw:ElementInNamespace>
</Root>
------
/processing-instruction()
/Root
/Root/@AttName
/Root/@xmlns:aw
/Root/comment()
/Root/Child[1]
/Root/Child[1]/text()
/Root/Child[2]
/Root/Child[2]/text()
/Root/ChildWithMixedContent
/Root/ChildWithMixedContent/text()[1]
/Root/ChildWithMixedContent/b
/Root/ChildWithMixedContent/b/text()
/Root/ChildWithMixedContent/text()[2]
/Root/aw:ElementInNamespace
/Root/aw:ElementInNamespace/aw:ChildInNamespace