某个客户询问我关于SelectSingleNode和SelectNodes的XPath操作,问题如下:
【xml】
<root> <dsobject classname="Rendition"> <contentelements> <contentelement>Tansion</contentelement> </contentelements> </dsobject> <dsobject classname="Rendition"> <contentelements> <contentelement>Herry</contentelement> </contentelements> </dsobject> <dsobject classname="Rendition"> <contentelements> <contentelement>Jack</contentelement> </contentelements> </dsobject> </root>
现在他要求“筛选出所有名称为dsobject,classname等于Rendition”,输出其contentelement的数据。
他这样做:
[C#]
foreach (XmlNode item in document.SelectNodes("//dsobject[@classname='Rendition']")) { XmlNode oneNode = item.SelectSingleNode("//contentelement"); string inner = oneNode.InnerText; }
[VB.NET]
For Each item As XmlNode In document.SelectNodes("//dsobject[@classname='Rendition']") Dim oneNode As XmlNode = item.SelectSingleNode("//contentelement") Dim inner As String = oneNode.InnerText Next
粗看先是从xml文档全部选出classname属性值为Rendition的全部dsobject(外循环),内部再从每个dsobject节点内部筛选出contentelement节点并输出包含的数值。但是实际上输出的结果只是三个“Tansion”。
究其原因,是因为XPath语法的特殊性:凡是以“//”开头的,都是无条件以整个xml的根节点开始遍历的。因此item.SelectSingleNode不要误以为是接着上面的dsobject节点往下检索,而是从头开始的!因此遍历的时候总是遍历到第一个dsobject中的contentelement的内部数值。
同样地,凡是以“/”开头的,都是无条件人为指定从根节点开始的绝对路径搜索,与XmlNode上下文毫无关系(比如要查询第一个contentelement,用绝对路径甚至可以这样写):
[C#]
Console.WriteLine(document.SelectSingleNode("/root/dsobject/contentelements/contentelement").InnerText);
Console.WriteLine(document.DocumentElement.SelectSingleNode("/root/dsobject/contentelements/contentelement").InnerText);
[VB.NET]
Console.WriteLine(document.SelectSingleNode("/root/dsobject/contentelements/contentelement").InnerText) Console.WriteLine(document.DocumentElement.SelectSingleNode("/root/dsobject/contentelements/contentelement").InnerText)
显然,以/开头无论XmlNode是什么,总是被忽略且从指定的root=>dsobject=>contentelements=>contentment这样的顺序开始检索(绝对路径)。
至于和上下文相关的,就是直接写名字;比如:
[C#]
Console.WriteLine(document.SelectSingleNode("root/dsobject/contentelements/contentelement").InnerText); Console.WriteLine(document.DocumentElement.SelectSingleNode("dsobject/contentelements/contentelement").InnerText);
[VB.NET]
Console.WriteLine(document.SelectSingleNode("root/dsobject/contentelements/contentelement").InnerText) Console.WriteLine(document.DocumentElement.SelectSingleNode("dsobject/contentelements/contentelement").InnerText
显然,document的根节点是“#document”(特殊节点,xml中不存在),那么相对它而言下面的节点是root/dsobject/contentelments/contentelement。不过document.DocumentElement本身就是root,因此第二句代码中root就不能再写了,不然会发生找不到节点(因为不存在root=>root=>……这样的节点)。
SelectNodes也符合这样的特点,这里就不再叙述了。