CodeMIRAI
0 XP

XPath predicates

3 min read 6 runnable examples Quiz included

The XPath basics lesson introduced predicates, the conditions in square brackets. This lesson makes them precise.

and, or and not()

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

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

XML
<!-- 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>
FunctionGives
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

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

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

XML
<!-- 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:

AxisSelects
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".

Finished this lesson?Mark it complete to earn +20 XP and move your progress on.