XPath predicates
The XPath basics lesson introduced predicates, the conditions in square brackets. This lesson makes them precise.
and, or and not()
<!-- xpath: //book[price < 35 and @lang='en']/title -->
<!-- xpath: //book[@genre='fiction' or price > 40]/title -->
<!-- xpath: //book[not(@lang='en')]/title -->
<shop>
<book genre="tech" lang="en"><title>Clean Code</title><price>32.50</price></book>
<book genre="fiction" lang="en"><title>Dune</title><price>18.00</price></book>
<book genre="tech" lang="de"><title>Der pragmatische Programmierer</title><price>45.00</price></book>
<book genre="fiction" lang="fr"><title>Le Petit Prince</title><price>12.00</price></book>
</shop>Conditions combine with and, or and not(). Comparisons such as price < 35 convert the element's text to a number.
Several predicates in a row
<!-- xpath: //book[@genre='tech'][2]/title -->
<!-- xpath: //book[2][@genre='tech']/title -->
<shop>
<book genre="tech"><title>Clean Code</title></book>
<book genre="fiction"><title>Dune</title></book>
<book genre="tech"><title>Refactoring</title></book>
</shop>Predicates apply one after another, each to the result of the last, so the order matters. The first query means "of the tech books, the second": Refactoring. The second means "the second book, if it's a tech book", and the second book is Dune, a novel, so it finds nothing.
String functions
<!-- xpath: //book[starts-with(title, 'The')]/title -->
<!-- xpath: //book[string-length(title) > 12]/title -->
<!-- xpath: //book[contains(translate(title, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'code')]/title -->
<!-- xpath: normalize-space(//note) -->
<shop>
<book><title>The Hobbit</title></book>
<book><title>Code Complete</title></book>
<book><title>The Clean Coder</title></book>
<book><title>Dune</title></book>
<note> Prices include tax. </note>
</shop>| Function | Gives |
|---|---|
starts-with(s, 'x') | true if s begins with x |
contains(s, 'x') | true if x appears anywhere in s |
string-length(s) | the number of characters |
normalize-space(s) | s with spaces trimmed and runs of spaces collapsed to one |
translate(s, 'abc', 'ABC') | s with each character in the first list replaced by the matching one in the second |
XPath 1.0 has no lower-case function, so translate is the standard way to compare text regardless of case, as the third query does.
Conditions on child elements
<!-- xpath: //book[author/@country='UK']/title -->
<!-- xpath: //book[count(author) > 1]/title -->
<!-- xpath: //book[review[@stars=5]]/title -->
<shop>
<book>
<title>Good Omens</title>
<author country="UK">Terry Pratchett</author>
<author country="UK">Neil Gaiman</author>
<review stars="5"/>
</book>
<book>
<title>The Martian</title>
<author country="US">Andy Weir</author>
<review stars="4"/>
</book>
<book>
<title>The Name of the Wind</title>
<author country="US">Patrick Rothfuss</author>
<review stars="5"/>
<review stars="3"/>
</book>
</shop>A predicate can test a path inside the element: "books with an author from the UK", "books with more than one author", or "books that have a five-star review", which nests one predicate inside another.
Positions and unions
<!-- xpath: //book[position() <= 2]/title -->
<!-- xpath: //book[position() mod 2 = 0]/title -->
<!-- xpath: //book[last() - 1]/title -->
<!-- xpath: //title | //year -->
<shop>
<book><title>Dune</title><year>1965</year></book>
<book><title>Neuromancer</title><year>1984</year></book>
<book><title>Snow Crash</title><year>1992</year></book>
<book><title>The Martian</title><year>2011</year></book>
</shop>position() is the current position, and mod gives a remainder, so position() mod 2 = 0 picks every second book. last() - 1 is the second-to-last. The | operator combines two queries' results, in document order.
Axes: moving around the document
<!-- xpath: //title[.='Dune']/parent::book/@id -->
<!-- xpath: //title[.='Dune']/../price -->
<!-- xpath: //book[@id='b2']/following-sibling::book/title -->
<!-- xpath: //book[@id='b2']/preceding-sibling::book/title -->
<!-- xpath: //price[. > 40]/ancestor::section/@name -->
<shop>
<section name="Technology">
<book id="b1"><title>Clean Code</title><price>32.50</price></book>
<book id="b2"><title>Refactoring</title><price>41.00</price></book>
<book id="b3"><title>Code Complete</title><price>35.00</price></book>
</section>
<section name="Fiction">
<book id="b4"><title>Dune</title><price>18.00</price></book>
</section>
</shop>An axis says which direction to look in from the current node:
| Axis | Selects |
|---|---|
child:: (the default) | children |
parent::, or .. | the parent |
ancestor:: | the parent, its parent, and so on up to the root |
descendant::, or // | children, their children, and so on down |
following-sibling:: | later elements with the same parent |
preceding-sibling:: | earlier elements with the same parent |
self::, or . | the current node itself |
Axes let a query find something and then step to its relatives, like "the section that contains a book over 40".