2

我有以下 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>

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

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

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

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

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

编辑

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

Edit2 @Dimitre Novatchev 的解决方案适用于我,如果我可以在不更改其签名的情况下获得 foo 内的 refnode 的 xpath,或者如果有某种方法可以指定this节点,即应用 xpath 的节点。

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

4 回答 4

2

使用这个纯 XPath 1.0 表达式

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

where$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>
于 2012-12-29T17:57:44.823 回答
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/

于 2012-12-29T10:11:45.600 回答
0

选择 refNode 的所有后代<B>节点但不嵌套在任何其他<B>节点内的 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 表达式。这将找到具有相同数量的祖先的所有B后代(加上一个,如果它本身就是一个元素),这具有排除超过一层“B层”深度的后代的效果。refNodeBrefNoderefNodeBB

于 2012-12-29T10:24:04.480 回答
0

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

XElement refNode = ...
XElement wantedBs = refNode.Descendants("B")
                           .Where(b => parent.Name.LocalName != "B");
于 2012-12-30T03:18:14.813 回答