在XPath中选择第一个结果

12

我查看了这个问题:如何使用XPath只选择第一个实例?

但是如果我有一个像这样的节点集:


<container value="">
  <data id="1"></data>
  <data id="2">test</data>
  <container>
    <data id="1">test</data>
    <data id="3">test</data>
  </container>
</container>

现在我的情况是,这个节点集深深地嵌套在文档中,而我有一个指向内部容器的指针。因此,我必须在XPath中加上前缀"/container/container"(实际路径更长,但对于此示例,这应该足够)。

编辑: 我想要一个"id"为1的"data"节点,它应该来自最低的节点首先或最近的祖先。因此,如果我在"当前"(/container/container)找不到它,我应该查找祖先并获取最近的一个(或者最后,什么都没有找到)。我尝试过这个:

/container/container/ancestor-or-self::container/data[@id="1"]

它返回了一个包含两个节点的结果集。我以为我可以使用last()来获取最深的节点,所以我将其添加到末尾,但没有成功。


规范需要更加明确。你想要数据节点吗?数据节点必须具有id为1的标识符吗?如果原始容器中的数据节点没有id为1,您希望向上移动文档以查看父容器是否具有id为1的数据节点,如果没有,请继续查找,直到找到具有id为1的数据节点。这样描述准确吗? - AnthonyWJones
我尝试更清楚地描述我正在寻找的内容,虽然有些人已经猜到了。抱歉! - Nick Spacek
5个回答

12

确保 last() 被正确地应用了作用域。尝试:

(/container/container/ancestor-or-self::container/data[@id="1"])[last()]

同样地:

//container[last()]

并且:

(//container)[last()]

两者并不相同。第一个函数将返回每个层级中最后一个容器节点,即多个匹配结果;而第二个函数将返回第一个找到的最后一个容器节点,即单个匹配结果。


实际上,我发现如果在匹配数据元素之前指定子容器节点,则此方法无效。 - Nick Spacek
@Nick Spacek - 我改进了下面的答案,现在甚至涵盖了那个边角情况。 - Daniel Martin

6

你是如何尝试在最后添加last()的?我认为这应该可以解决:

/container/container/ancestor-or-self::container/data[@id="1"][last()]

编辑:

对,当然,我忘记了括号:

(/container/container/ancestor-or-self::container/data[@id="1"])[last()]

这使得它与其他答案相同;然而,正如原帖指出的那样,这个表达式在以下情况下会失败:

<container value="">
  <container>
    <data id="1">a3</data>
    <data id="3">a4</data>
  </container>
  <data id="1">a1</data>
  <data id="2">a2</data>
</container>

秉承Stackoverflow的精神,我可以将我的答案与其他答案结合起来,得到适用于所有情况的解决方案:

(/container/container/ancestor-or-self::container[data[@id="1"]])[last()]/data[@id="1"]

顺便说一下,如果一个容器中有多个@id-is-1的子元素,这将返回所有这些元素。要仅返回第一个这样的元素:
(/container/container/ancestor-or-self::container[data[@id="1"]])[last()]/data[@id="1"][1]

如果我对期望结果的理解是正确的(这是个大前提),那么这段代码不会起作用,因为 last() 函数会匹配到 ancestor-or-self 轴上两个分支 - 这是真正的问题,所以你需要及早或者晚些时候削减结果集合(像我一样及早削减或者像 Dave 一样晚些时候削减)。 - annakata
这个可以做到。非常感谢(大家也是)! - Nick Spacek

2

try [position()=1]

ex.

/container/container/ancestor-or-self::container/data[position()=1]


1

我不完全清楚你在问什么,但如果我理解正确,也许你只是想要:

/container/container/ancestor-or-self::container[1]/data[@id='1']

注意"[1]"

编辑:我再仔细想了一下,以下代码对于所有寻找@id=$N的情况都适用。

/container/container/ancestor-or-self::container[data[@id=$N]][1]/data[@id=$N]

这里使用了相当极端的XPath。基本上,祖先或自身返回的节点集会在位置1返回最低顺序 - 这是您想要的那个 - 但您需要在其上放置第二个条件,即该节点还具有您感兴趣的数据节点。一旦达到了这些条件,您就拥有了正确的节点,现在您只需进一步挖掘实际数据。

99%的情况下,如果您将XPath分解并将其视为算法,则会更容易理解 :)


我尝试过了,但那并不能覆盖所有情况。如果我要查找的是@id='2'呢?我想优先考虑当前节点的结果,然后再考虑祖先节点,但也要有备选方案。 - Nick Spacek

0

而不是

@id="1"

你尝试过了吗:

position() == 1

如果 position() == 1 可以起作用,那就完全可以省略 ancestor-or-self::。我认为要求不仅仅是任何第一个数据节点,而是具有 id 为 1 的数据节点。这个问题的表述可能不够清晰。 - AnthonyWJones

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接