用于搜索节点后代的Xpath

时间:2012-12-29 08:09:10

标签: c# xml xpath xpath-1.0

我有以下c#方法,它可以在从refNode到xpath

的所有节点上执行某些操作
void foo(XmlNode refNode, string xpath)
{
    XmlNodeList list=refNode.SelectNodes(xpath);
    //perform operation on each element of the list
}

我得到的输入xml之一是:

<A>
    <B>***
        <C>
                  <B>One</B>
            </C>
        <B>
                  <B>Two</B>
            </B>
    </B>
    <B>...</B>
    <B>...</B>
</A>

我需要选择一个refNode <B>(标记为***)并将其传递给foo(),其中xpath选择refNode的所有后代<B>节点,但不嵌套在里面任何其他<B>节点

例如,在给定的输入中,结果应包含:

1. <B>One</B>
2. <B><B>Two</B></B>

我试过.//B给了我3个结果而.//B [not (ancesotr :::))]返回0结果。

我应该使用什么Xpath来获得所需的结果?

修改

我可以对方法foo进行更改,但不能对其签名进行更改。此方法是库的一部分,并且正由少数用户使用。上面给出的输入只是一个特定的实例,用户也可以将节点A作为refnode发送并请求所有C节点。

EDIT2 @Dimitre Novatchev的解决方案适用于我,如果我可以在foo中获取refnode的xpath而不更改其签名,或者是否有某种方式来指定this节点,即应用xpath的节点。

.//B[not(ancesotr::B) or ancesotr::B[1]=**this**]

4 个答案:

答案 0 :(得分:2)

使用此纯XPath 1.0表达式

$vrefNode/descendant::B[count(ancestor::B) - count($vrefNode/ancestor::B) = 1]

其中$vrefNode需要替换(除非您可以使用变量引用)与选择“引用节点”的XPath表达式。

基于XSLT的验证

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/">
  <xsl:copy-of select=
  "/*/B[1]/descendant::B[count(ancestor::B) - count(/*/B[1]/ancestor::B) = 1]"/>
 </xsl:template>
</xsl:stylesheet>

在提供的XML文档上应用此转换时:

<A>
    <B>***
        <C>
            <B>One</B>
        </C>
        <B>
            <B>Two</B>
        </B>
    </B>
    <B>...</B>
    <B>...</B>
</A>

评估XPath表达式,并将此评估中选定的元素复制到输出

<B>One</B>
<B>

   <B>Two</B>

</B>

答案 1 :(得分:0)

我不确定纯XPath可以做到这一点。 XQuery具有更强的表达能力,例如,您可以轻松使用let来保存B元素,其子树在变量中搜索更多B个后代,然后您可以确保它是只有B祖先。 所以这是一个XQuery示例:

let $inp := <A>
  <B>
    ***
    <C>
      <B>One</B>
    </C>
    <B>
      <B>Two</B>
    </B>
    <D>
      <E>
        <B>Three</B>
      </E>
    </D>
    <D>
      <B>Four<Foo>
        <B>Five</B>
      </Foo>
    </B>
    </D>
  </B>
  <B>...</B>
  <B>...</B>
</A>
let $b := $inp/B[1]
return $b/descendant::B[count(ancestor::B) = 1 and ancestor::B[1] is $b]

找到节点

<B>One</B>
<B>
<B>Two</B>
</B>
<B>Three</B>
<B>Four<Foo>
<B>Five</B>
</Foo>
</B>

.NET框架有XQuery实现,如XmlPrime,Saxon 9 .NET版本,AltovaXML或http://qm.codeplex.com/

答案 2 :(得分:0)

  

一个xpath,它选择refNode的所有后代<B>节点,但不嵌套在任何其他<B>节点内

在XSLT中你可以使用

.//B[generate-id(ancestor::B[1]) = generate-id(current())]

这将选择当前上下文节点的所有B个后代,其最近的封闭B祖先是当前上下文节点本身。但这取决于generate-id()current(),它们是特定于XSLT的,而不是普通XPath的一部分。

以下是两步替代方案:

foo(refNode, ".//B[count(ancestor::B) = " + refNode.SelectNodes("ancestor-or-self::B").Count + "]");

动态生成XPath表达式。这将找到与B具有相同refNode个祖先B的所有refNode个后代(加上一个,如果refNode本身为{{1}元素),它具有排除多个“B”层深的B后代的效果。

答案 3 :(得分:0)

你必须使用XPath吗?如果您可以使用Linq-to-XML,

XElement refNode = ...
XElement wantedBs = refNode.Descendants("B")
                           .Where(b => parent.Name.LocalName != "B");