ChatGPT解决这个技术问题 Extra ChatGPT

How to select the first element with a specific attribute using XPath

The XPath bookstore/book[1] selects the first book node under bookstore.

How can I select the first node that matches a more complicated condition, e.g. the first node that matches /bookstore/book[@location='US']


t
the Tin Man

Use:

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

This will first get the book elements with the location attribute equal to 'US'. Then it will select the first node from that set. Note the use of parentheses, which are required by some implementations.

Note, this is not the same as /bookstore/book[1][@location='US'] unless the first element also happens to have that location attribute.


How I could do same for //bookstore/book[@location='US'] ?
This will get all books from 'US'. (/bookstore/book[@location='US'])[1] will get the first one.
@KevinDriedger /bookstore/book[@location='US'][1] does not return all books from 'US'. I have tested it mutiple times and under different languages' xpath implementations. /bookstore/book[@location='US'][1] returns the first 'US' book under a bookstore. If there are mutiple bookstores, then it will return the first from each. This is what the OP asked for (the first node under bookstore). Your version returns only one book from all bookstores (the first match).
@JonathanFingland you misunderstood - read KevinDriedger's answer again, along with context of AlexanderV.Ilyin's question. You both mean the same thing.
i
iliketocode

/bookstore/book[@location='US'][1] works only with simple structure.

Add a bit more structure and things break.

With-

<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] yields

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

not "the first node that matches a more complicated condition". /bookstore/category/book[@location='US'][2] returns nothing.

With parentheses you can get the result the original question was for:

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

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

and (/bookstore/category/book[@location='US'])[2] works as expected.


Author of the accepted answer here. The OP 's question regarded /bookstore/book[1] and NOT (/bookstore/book)[1]. The case you've provided is not the same as the one OP asked for. Presumably, OP accepted my answer as it did what he expected (and requested).
This answer provided helped me for this peculiar case. Can someone explain why it won't handle "more complicated situations"? Since basically it does find a list with two items, the [2] should just pick it up (in my world)
I also find this answer to be more correct than the selected answer, as in my case, I also had a more complex structure where simply adding [1] returned multiple nodes. Thanks!
Parentheses works! You can also add more path after (..)[1], like: '(//div[text() = "'+ name +'"])[1]/following-sibling::*/div/text()'. In case there are many nodes matches name.
I'm changing my opinion. After some distance, I get what this answer was saying, and if I didn't see the OP's example I woulda voted for this. I suppose I was reacting to the tone of this answer; if @tkurki had explained a little more about separating the condition from the selection of the first node, I woulda instantly seen it. Perhaps the same for JonFingland.
T
Tomalak

As an explanation to Jonathan Fingland's answer:

multiple conditions in the same predicate ([position()=1 and @location='US']) must be true as a whole

multiple conditions in consecutive predicates ([position()=1][@location='US']) must be true one after another

this implies that [position()=1][@location='US'] != [@location='US'][position()=1] while [position()=1 and @location='US'] == [@location='US' and position()=1]

hint: a lone [position()=1] can be abbreviated to [1]

You can build complex expressions in predicates with the Boolean operators "and" and "or", and with the Boolean XPath functions not(), true() and false(). Plus you can wrap sub-expressions in parentheses.


Is it possible to have an array of locations (like [1,3,5:7,9]) without using multiple "and" operators?
@M.HosseinRahimi In XPath 1.0, no. In XPath 2.0, sequences and the = operator do the trick: [position() = (1,3,5,6,7,9)].
G
Gee-Bee

The easiest way to find first english book node (in the whole document), taking under consideration more complicated structered xml file, like:

<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> 

is xpath expression:

/descendant::book[@location='US'][1]


I don't know why you added 'category' to the (presumptive) xml. I'm down voting this because it answers a question that the OP didn't ask.
i
iZian
    <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> 

So Given the above; you can select the first book with

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

And this will find the first one anywhere that has a location US. [A1]

//book[@location='US']

Would return the node set with all books with location US. [A1,B1,C2]

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

Would return the first book location US that exists in a category anywhere in the document. [B1]

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

will return the first book with location US that exists anywhere under the root element bookstore; making the /bookstore part redundant really. [A1]

In direct answer:

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

Will return you the first node for book element with location US that is under bookstore [A1]

Incidentally if you wanted, in this example to find the first US book that was not a direct child of bookstore:

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

I don't know why you added 'category' to the (presumptive) xml. I'm down voting this because it answers a question that the OP didn't ask.
@samwyse because the OP provided no more context around what other information was in their source data. So you answer according to what you think their data might be like, and provide a wider context so that the OP and people finding this question for the same and similar issues can learn more using practical examples. You'll notice I have a book under bookstore. Unlike in your other copy paste response answers.
f
frianH

Use the index to get desired node if xpath is complicated or more than one node present with same xpath.

Ex :

(//bookstore[@location = 'US'])[index]

You can give the number which node you want.


E
Ed Bangga

if namespace is provided on the given xml, its better to use this.

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

E
Exception_al

for ex.

<input b="demo">

And

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

M
Mohsen Abasi

With help of an online xpath tester I'm writing this answer...
For this:

<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>

the following xpath:

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

outputs:

123
foo
bar
xyz

Since 1 means select all td elements which are the first child of their own direct parent.
But the following xpath:

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

outputs:

123