6

在徒劳地玩耍之后position(),我在谷歌上四处寻找解决方案,并遇到了这个几乎描述了我的问题的较旧的 stackoverflow问题。

不同之处在于我想要位置的节点集是动态的,而不是文档的连续部分。

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

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

现在,如果我查询,使用 XPatha/b将得到四个<b>节点的节点集。然后我想找到包含字符串的节点在该节点集中'tsr'的位置。另一篇文章中的解决方案在这里分解:count(a/b[.='tsr']/preceding-sibling::*)+1返回1是因为preceding-sibling导航文档而不是上下文节点集。

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

4

6 回答 6

5

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

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

$vNodeSet为节点集,$vNode为该节点集中我们要查找其位置的节点。

然后, let$vPrecNodes包含前面的 XML 文档中的所有节点$vNode

然后, let$vAncNodes包含 XML 文档中的所有节点,它们是$vNode.

以文档顺序排列在$vNodeSet前面的节点集由节点集中也属于的所有节点和节点集中也属于的所有节点组成。$vNode$vPrecNodes$vAncNodes

我将使用著名的 Kaysian 公式计算两个节点集的交集:

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

恰好包含与 的交点中的$ns1节点$ns2

基于所有这些,let是文档顺序$vPrecInNodeSet中的节点集。以下 XPath 表达式定义:$vNodeSet$vNode$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 表达式,用它们的定义替换变量,直到没有更多的变量可以替换。

于 2010-08-25T03:45:12.230 回答
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

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

于 2010-08-18T13:07:40.847 回答
1

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

但在其他情况下,正如您所暗示的那样,在您想要使用的节点集中进行计数确实很难。例如,假设您的工作节点集是 /html/body/div[3]//a(网页<a>第三个中的所有锚点),并且您想在该集中找到位置。如果您尝试使用,您会不小心计算来自其他 div 的锚点,即在您的工作节点集之外。而且,如果您尝试过,您将不会全部获得它们,因为相关元素可能处于任何级别。<div>a[@href="foo.html"]count(preceding::a)<a>count(preceding-sibling::a)<a>

您可以尝试使用限制计数,preceding::a[ancestor::div[count(preceding-sibling::div) = 2]]但它很快就会变得非常尴尬,而且在所有情况下仍然不可能。此外,如果您曾经为您的工作集更新过 XPath 表达式,那么您将不得不重新编写这个表达式,并且保持它们等效将是非常重要的。

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

    <xsl:for-each select="/root/a/b">
        <xsl:if test=". = 'tsr'"><xsl:value-of select="position()"/></xsl:if>
    </xsl:for-each>

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

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

其他人对旧问题的尝试,a/b[.='tsr']/position()不起作用的原因是因为在每个斜线处,都会将一个新的上下文压入堆栈,因此当调用 position() 时,上下文位置始终为 1。(此语法顺便说一下,仅适用于 XPath 2.0。)

于 2010-08-19T16:40:31.917 回答
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'])

ETC

于 2010-07-28T15:01:20.060 回答
0

您获得 1 的原因与上下文与文档无关,而是因为您只计算b一个节点中的a节点(因此您将始终得到 0 的计数,因为之前没有任何“b”节点。

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

就像是:

count(a[b[.='tsr']]/preceding-sibling::a)
于 2010-04-09T09:18:39.883 回答
-1

这个怎么样..

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

统计当前 a 元素中 b 元素之前的兄弟元素,然后统计 a 元素之前所有兄弟元素的 b 元素。或类似的东西。

于 2010-04-09T11:24:47.843 回答