如何使用XPath选择具有特定属性的第一个元素

395
XPath bookstore/book[1] 选择了bookstore下的第一个book节点。
如何选择符合更复杂条件的第一个节点,例如符合/bookstore/book[@location='US']的第一个节点?
9个回答

570

使用:

(/bookstore/book[@location='US'])[1]

这将首先获取位置属性等于“US”的图书元素。然后从该集合中选择第一个节点。请注意使用括号,某些实现需要使用。

请注意,这与/bookstore/book[1][@location='US']不同,除非第一个元素也恰好具有该位置属性。


我该如何将同样的操作用于//bookstore/book[@location='US']? - Alexander V. Ilyin
7
这将获取所有来自“美国”的书籍。(/bookstore/book[@location='US'])[1] 将获取第一本书。 - Kevin Driedger
3
/bookstore/book[@location='US'][1]并不能返回所有来自'US'的书籍。我已经在不同语言的xpath实现下进行了多次测试。 /bookstore/book[@location='US'][1] 只会返回一个书店中第一本'US'书籍。如果有多个书店,则从每个书店返回第一个。这正是原帖所要求的(在书店下的第一个节点)。你的版本只会从所有书店中返回一本书(第一个匹配的结果)。 - Jonathan Fingland
3
@JonathanFingland 你误解了 - 再次阅读KevinDriedger的回答,以及AlexanderV.Ilyin问题的背景。你们两个意思是一样的。 - kiedysktos

206

/bookstore/book[@location='US'][1] 只适用于简单的结构。

增加一点结构会导致出错。

例如-

<bookstore>
 <category>
  <book location="US">A1</book>
  <book location="FIN">A2</book>
 </category>
 <category>
  <book location="FIN">B1</book>
  <book location="US">B2</book>
 </category>
</bookstore> 

/bookstore/category/book[@location='US'][1]返回

<book location="US">A1</book>
<book location="US">B2</book>

不是“匹配更复杂条件的第一个节点”。/bookstore/category/book[@location='US'][2]返回空值。

使用括号可以得到原始问题所需的结果:

(/bookstore/category/book[@location='US'])[1] 返回

<book location="US">A1</book>

并且(/bookstore/category/book[@location='US'])[2]按预期工作。


12
这里是被接受回答的作者。OP的问题涉及/bookstore/book[1]而不是(/bookstore/book)[1]。你提供的情况与OP所问的不同。可以推测,OP接受了我的答案,因为它达到了他的期望(并且请求)。 - Jonathan Fingland
这个答案对我在这种特殊情况下非常有帮助。有人能解释一下为什么它不能处理“更复杂的情况”吗?因为基本上它确实找到了一个包含两个项目的列表,[2] 应该只是把它拿走(在我的世界里)。 - Skurpi
我也认为这个答案比被选中的答案更正确,因为在我的情况下,我也有一个更复杂的结构,在这种情况下,简单地添加[1]会返回多个节点。谢谢! - mydoghasworms
2
括号很有用!您还可以在(..)[1]之后添加更多路径,例如:'(//div[text() = "'+ name +'"])[1]/following-sibling::*/div/text()'。如果有许多节点与“name”匹配。 - Hlung
这个答案并不实用。它并不是“更好的方法”,而是一种不同的方法,取决于OP关于他想要什么的更多具体信息。在这个例子中,() [2]将返回美国任何书店的第二本书。但是没有括号会返回美国所有书店的第二本书,正如Jon指出的那样,这更接近他最初的例子。对于这个例子,添加类别没有任何作用 - Gerard ONeill
2
我改变了我的看法。经过一段时间,我明白了这个答案的意思,如果我没有看到OP的例子,我会投票支持这个答案。我想我是在反应这个答案的语气;如果@tkurki多解释一下如何将条件与选择第一个节点分开,我会立刻看出来的。也许JonFingland也是这样。 - Gerard ONeill

62

对于Jonathan Fingland的回答解释如下:

  • 在同一个谓词中出现的多个条件([position()=1 and @location='US'])必须作为一个整体都是真的
  • 在连续的谓词中出现的多个条件([position()=1][@location='US'])必须一个接一个地为真
  • 这意味着[position()=1][@location='US'] != [@location='US'][position()=1]
    [position()=1 and @location='US'] == [@location='US' and position()=1]
  • 提示: 孤立的[position()=1]可以缩写为[1]

您可以使用布尔运算符 "and" 和 "or" 以及布尔XPath函数 not(), true()false() 在谓词中构建复杂表达式。此外,您还可以将子表达式放在括号中。


是否可以在不使用多个“and”运算符的情况下拥有一个位置数组(例如[1,3,5:7,9])? - M.Hossein Rahimi
1
在XPath 1.0中不行。但在XPath 2.0中,序列和=运算符可以解决问题:[position() = (1,3,5,6,7,9)] - Tomalak

16

在考虑更复杂的结构化xml文件的情况下,找到整个文档中第一个英语书籍节点的最简单方法是:

<bookstore>
 <category>
  <book location="US">A1</book>
  <book location="FIN">A2</book>
 </category>
 <category>
  <book location="FIN">B1</book>
  <book location="US">B2</book>
 </category>
</bookstore> 

这是XPath表达式:
/descendant::book[@location='US'][1]


我不知道为什么你在(假定的)XML中添加了“类别”。我会给这个回答投反对票,因为它回答了提问者没有问到的问题。 - samwyse

12
    <bookstore>
     <book location="US">A1</book>
     <category>
      <book location="US">B1</book>
      <book location="FIN">B2</book>
     </category>
     <section>
      <book location="FIN">C1</book>
      <book location="US">C2</book>
     </section>
    </bookstore> 

因此,基于上述情况,您可以选择第一本书。

(//book[@location='US'])[1]

这将找到任何具有 US 位置的第一个。[A1]

//book[@location='US']

会返回所有位置为US的图书节点集。[A1,B1,C2]

(//category/book[@location='US'])[1]

将返回文档中任何位置存在的第一个属于类别US的书籍位置。[B1]

(/bookstore//book[@location='US'])[1]

将返回根元素 bookstore 下任何存在的第一本位置为 US 的书籍;实际上 /bookstore 部分是多余的。[A1]

直接回答:

/bookstore/book[@location='US'][1]

将返回位于书店[A1]下的位置为“US”的第一个书籍元素节点

顺带一提,在此示例中,如果您想找到不是书店直接子级的第一个美国书籍:

(/bookstore/*//book[@location='US'])[1]

我不知道为什么您将“category”添加到(假设的)xml中。我会给它点个踩,因为它回答了OP没有提出的问题。 - samwyse
@samwyse因为原帖没有提供更多关于源数据的上下文信息。因此,您需要根据自己对他们的数据可能性的理解来回答,并提供更广泛的上下文,以便原帖作者和其他遇到类似问题的人可以通过实际示例学习更多知识。您会注意到我在书店下面有一本书,这与您的其他复制粘贴回答不同。 - iZian
OP指定了“bookstore/book[1]”选择书店下的第一个书籍节点,这意味着没有中间级别。否则,我认为他们会使用“bookstore//book[1]”。 - samwyse
你可能会期望这样做,但其他人可能不会。我不认为任何人都会从xPath到1个节点推断出模式。我见过很多次,有人由于这个错误而没有意识到他们甚至没有考虑所有的xPath节点。根据整个上下文,可以说一种方法比另一种更适用于未来的模式更改。而且,如果他们在做出决定之前知道所有信息,他们可能不想要那个方法,而选择另一个方法。 - iZian

5
使用索引来获取所需节点,如果XPath过于复杂或存在多个具有相同XPath的节点。
例如:
(//bookstore[@location = 'US'])[index]

您可以提供您想要的节点编号。

2
如果给定的XML中提供了命名空间,最好使用它。最初的回答。
(/*[local-name() ='bookstore']/*[local-name()='book'][@location='US'])[1]

0

借助在线xpath测试工具,我写下了这个答案...
为此:

<table id="t2"><tbody>
<tr><td>123</td><td>other</td></tr>
<tr><td>foo</td><td>columns</td></tr>
<tr><td>bar</td><td>are</td></tr>
<tr><td>xyz</td><td>ignored</td></tr>
</tbody></table>

以下是XPath:

id("t2") / tbody / tr / td[1]

输出:

123
foo
bar
xyz

由于 1 表示选择所有td元素,这些元素是其直接父级的第一个子元素。
但是以下xpath:

(id("t2") / tbody / tr / td)[1]

输出:

123

0

例如。

<input b="demo">

而且

(input[@b='demo'])[1]

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