C# Xpath来搜索节点的后代

C# Xpath来搜索节点的后代,c#,xml,xpath,xpath-1.0,C#,Xml,Xpath,Xpath 1.0,我有下面的c方法,它通过xpath对所有可从refNode访问的节点执行一些操作 void foo(XmlNode refNode, string xpath) { XmlNodeList list=refNode.SelectNodes(xpath); //perform operation on each element of the list } 我得到的一个输入xml是: <A> <B>*** <C>

我有下面的c方法,它通过xpath对所有可从refNode访问的节点执行一些操作

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>
我尝试了//B,它给我3个结果;//B[notancesotr::B]它返回0个结果

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

编辑

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

编辑2 @Dimitre Novatchev的解决方案适用于我,如果我可以在不更改其签名的情况下在foo中获取refnode的xpath,或者如果有某种方法指定此节点,即xpath应用到的节点

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

我不确定纯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或

选择refNode的所有子节点,但不嵌套在任何其他节点内的xpath

在XSLT中,您可以使用

.//B[generate-id(ancestor::B[1]) = generate-id(current())]
这将选择当前上下文节点的所有B子节点,其最近的封闭B祖先是当前上下文节点本身。但这依赖于特定于XSLT的generate id和current,而不是普通XPath的一部分

以下为两步备选方案:

foo(refNode, ".//B[count(ancestor::B) = " + refNode.SelectNodes("ancestor-or-self::B").Count + "]");
动态生成XPath表达式。如果refNode本身是一个B元素,这将查找refNode的所有B子体,这些子体的B祖先数与refNode加1的B祖先数相同,从而排除了B子体的B深度超过一层。

使用此纯XPath 1.0表达式:

其中需要替换$vrefNode,除非可以将变量引用与选择引用节点的XPath表达式一起使用

基于XSLT的验证:

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

对XPath表达式求值,并将通过此求值选择的元素复制到输出:


您必须使用XPath吗?如果可以使用Linq转换XML

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

Ian,generate id不是XPath函数-它仅在XSLT创建的初始计算上下文中可用。Martin,当然,这可以用纯XPath 1.0表示-请参阅我的答案。感谢您的回复,调用该函数时无法获取refnode的XPath。有什么方法可以从refNode在foo内部获取它吗?@AmitKhanna,将foo定义为:void fooXmlNode refNode,string xpath,string refnodeexpath,并将选择refNode的xpath表达式作为最后一个参数传递。我马上就要睡觉了,大约8小时后就能回答未来的问题。晚安。我不能这么做,因为这是一个公共方法,其他人已经在使用它了。无法更改其签名。@AmitKhanna,然后向类中添加一个新方法,该方法具有新参数-这没有问题。@AmitKhanna,是的,请参阅以下答案:有关为节点构建XPath表达式的方法,即使是在XSLT中。谢谢您的回复。编辑我的问题时,用户可能会将一个节点标记为ref节点,并要求其中的所有C节点,因此这将不起作用。
$vrefNode/descendant::B[count(ancestor::B) - count($vrefNode/ancestor::B) = 1]
<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>
<A>
    <B>***
        <C>
            <B>One</B>
        </C>
        <B>
            <B>Two</B>
        </B>
    </B>
    <B>...</B>
    <B>...</B>
</A>
<B>One</B>
<B>

   <B>Two</B>

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