使用xpath查找节点集中节点的位置

时间:2010-04-09 09:11:41

标签: xml xpath position nodesets

在玩position()徒劳无功之后,我正在谷歌上搜寻解决方案,然后到达this older stackoverflow question 几乎描述了我的问题。

区别在于我希望其中的位置是动态的,而不是文档的连续部分。

为了说明我将修改链接问题中的示例以符合我的要求。请注意,每个<b>元素都位于不同的<a>元素中。这是至关重要的一点。

<root>
    <a>
        <b>zyx</b>
    </a>
    <a>
        <b>wvu</b>
    </a>
    <a>
        <b>tsr</b>
    </a>
    <a>
        <b>qpo</b>
    </a>
</root>

现在,如果我查询,使用XPath a/b,我将获得四个<b>节点的节点集。我想在包含字符串'tsr'的节点的节点集中找到位置。其他帖子中的解决方案在此处分解:count(a/b[.='tsr']/preceding-sibling::*)+1返回1,因为preceding-sibling正在导航文档而不是上下文节点集。

是否可以在上下文节点集中工作?

6 个答案:

答案 0 :(得分:4)

这是一个通用解决方案,适用于属于同一文档中任何节点的节点集的任何节点

我正在使用XSLT来实现该解决方案,但最终获得了一个可以与任何其他托管语言一起使用的XPath表达式。

$vNodeSet为节点集,$vNode为此节点集中我们想要找到其位置的节点。

然后,让$vPrecNodes包含$vNode之前的XML文档中的所有节点。

然后,让$vAncNodes包含XML文档中作为$vNode祖先的所有节点。

文档顺序中$vNodeSet之前的$vNode中的节点集包含节点集中属于$vPrecNodes的所有节点以及属于该节点集的所有节点到$vAncNodes

我将使用众所周知的Kaysian公式来交叉两个节点集:

$ns1[count(.|$ns2) = count($ns2)]

恰好包含$ns1$ns2交叉的节点。

基于这一切,让$vPrecInNodeSet$vNodeSet中按文档顺序排在$vNode之前的节点集。以下XPath表达式定义$vPrecInNodeSet

$vNodeSet
      [count(.|$vPrecNodes) = count($vPrecNodes)
      or
       count(.|$vAncNodes) = count($vAncNodes)
      ]

最后,想要的位置是count($vPrecInNodeSet) +1

以下是这一切如何协同工作:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:variable name="vNodeSet" select="/*/a/b"/>

 <xsl:variable name="vNode" select="$vNodeSet[. = 'tsr'][1]"/>

 <xsl:variable name="vPrecNodes" select="$vNode/preceding::node()"/>

 <xsl:variable name="vAncNodes" select="$vNode/ancestor::node()"/>

 <xsl:variable name="vPrecInNodeSet" select=
  "$vNodeSet
      [count(.|$vPrecNodes) = count($vPrecNodes)
      or
       count(.|$vAncNodes) = count($vAncNodes)
      ]
  "/>

 <xsl:template match="/">
   <xsl:value-of select="count($vPrecInNodeSet) +1"/>
 </xsl:template>
</xsl:stylesheet>

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

<root>
    <a>
        <b>zyx</b>
    </a>
    <a>
        <b>wvu</b>
    </a>
    <a>
        <b>tsr</b>
    </a>
    <a>
        <b>qpo</b>
    </a>
</root>

产生了正确的结果

<强> 3

请注意:此解决方案不依赖于XSLT(仅用于说明目的)。您可以组装一个XPath表达式,用它们的定义替换变量,直到没有更多的变量可以替换。

答案 1 :(得分:2)

我认为我有一个有效的解决方案

我们的想法是计算文档中目标元素前面有多少个元素,并计算节点集中有多少或相同多个前面元素的节点数。在XPath中,这是:

count(//a/b[count(./preceding::node()) &lt;= count(//a/b[.='tsr']/preceding::node())])

您还可以使用此表达式中的变量来查找不同的节点集或匹配不同的文本内容。这里重要的部分是变量具有正确的类型。下面是一个XSLT示例和一个示例输出,使用问题的示例文档作为输入文件

XSLT文档

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output encoding="utf-8" method="text"/>

    <xsl:variable name="nodeset" select="//a/b"/>
    <xsl:variable name="path-string">//a/b</xsl:variable>
    <xsl:variable name="text">tsr</xsl:variable>

    <xsl:template match="/">
        <xsl:text>Find and print position of a node within a nodeset&#10;&#10;</xsl:text>

        <xsl:text>Position of "tsr" node in the nodeset = "</xsl:text>
        <xsl:value-of select="count(//a/b[count(./preceding::node()) &lt;= count(//a/b[.='tsr']/preceding::node()) ])"/>
        <xsl:text>"&#10;&#10;</xsl:text>

        <xsl:text>( Try the same using variables "$nodeset" and "$text" )&#10;</xsl:text>
        <xsl:text>Size of nodeset "$nodeset" = "</xsl:text>
        <xsl:value-of select="count($nodeset)"/>
        <xsl:text>"&#10;</xsl:text>
        <xsl:text>Variable "$text" = "</xsl:text>
        <xsl:value-of select="$text"/>
        <xsl:text>"&#10;</xsl:text>
        <xsl:text>Position of "</xsl:text>
        <xsl:value-of select="$text"/>
        <xsl:text>" node in the nodeset = "</xsl:text>
        <xsl:value-of select="count($nodeset[count(./preceding::node()) &lt;= count($nodeset[.=$text]/preceding::node()) ])"/>
        <xsl:text>"&#10;&#10;</xsl:text>

        <xsl:text>( Show that using a variable that has the path as a string does not work )&#10;</xsl:text>
        <xsl:text>Variable "$path-string" = "</xsl:text>
        <xsl:value-of select="$path-string"/>
        <xsl:text>"&#10;</xsl:text>
        <xsl:text>Result of "count($path-string)" = "</xsl:text>
        <xsl:value-of select="count($path-string)"/>
        <xsl:text>"&#10;&#10;</xsl:text>

        <xsl:text>End of tests&#10;</xsl:text>
    </xsl:template>

</xsl:stylesheet>

示例文档的输出

Find and print position of a node within a nodeset

Position of "tsr" node in the nodeset = "3"

( Try the same using variables "$nodeset" and "$text" )
Size of nodeset "$nodeset" = "4"
Variable "$text" = "tsr"
Position of "tsr" node in the nodeset = "3"

( Show that using a variable that has the path as a string does not work )
Variable "$path-string" = "//a/b"
Result of "count($path-string)" = "1"

End of tests

没有广泛测试我的解决方案,所以如果您使用它,请提供反馈。

答案 2 :(得分:1)

在某些情况下,先前的count-before-preceding(-sibling)答案效果很好;您只是从所选项目的角度重新指定上下文节点集,然后将count(preceding:: )应用于它。

但在其他情况下,正如您所暗示的那样,在前面计数很难保留在您想要使用的节点集中。例如。假设您的工作节点集是/ html / body / div [3] // a(网页的第三个<a>中的所有<div>个锚点),并且您希望找到{{的位置1}}在那个集合中。如果您尝试使用a[@href="foo.html"],则会意外地从其他div(即工作节点集之外)计算count(preceding::a)个锚点。如果您尝试<a>,则无法全部获取,因为相关的count(preceding-sibling::a)元素可以处于任何级别。

您可以尝试使用<a>来限制计数,但它很快就会变得很尴尬,但在所有情况下仍然无法实现。此外,如果您更新了工作集的XPath表达式,则必须重新编写此表达式,并保持它们的等效性并非易事。

但是,如果您使用的是XSLT,以下内容可以避免这些问题。如果可以指定工作节点集,则可以在其中找到与提供的条件匹配的节点的位置。而且您不必两次指定节点集:

preceding::a[ancestor::div[count(preceding-sibling::div) = 2]]

这是有效的,因为在for-each中,上下文位置“标识了正在处理的序列中上下文项的位置。”

如果您不在XSLT工作,您在哪个环境?可能有一个类似的构造用于迭代外部XPath表达式的结果,并且您可以维护自己的计数器(如果没有可用的上下文位置),并根据您的内部条件测试每个项目。

other guy's attempt on the older question <xsl:for-each select="/root/a/b"> <xsl:if test=". = 'tsr'"><xsl:value-of select="position()"/></xsl:if> </xsl:for-each> 不起作用的原因是因为在每个斜杠处都会在堆栈上推送一个新的上下文,所以当调用position()时,上下文位置是始终1.(此语法仅适用于XPath 2.0。)

答案 3 :(得分:0)

你获得1的原因与上下文与文档无关,但是因为你只计算一个b节点内的a个节点(因此你总是得到0的计数)因为从来没有任何先前的'b'节点。

相反,您需要在包含'a'的'b'之前找到前面'a'节点的计数。

类似的东西:

count(a[b[.='tsr']]/preceding-sibling::a)

答案 4 :(得分:0)

来自(即反对)根:

count(//a/b[.='tsr']/preceding::b)

如果你说了另一个节点,例如:

<c>
    <b>qqq</b>
</c>

并且想要忽略没有“a”父母的所有b元素,你可以做类似

的事情
count(//a/b[.='tsr']/preceding::b[local-name(parent::node())='a'])

答案 5 :(得分:-1)

这个怎么样..

count(a/b[.='tsr']/preceding-sibling::b) + count(a[b[.='tsr']]/preceding-sibling::a/b) + 1

计算当前a元素中b元素的先前兄弟节点,然后计算a元素的所有先前兄弟节点的b元素。或类似的东西。