13. XML XPath

What you will learn

  • What XPath is and why it is useful
  • How to write paths to select elements and attributes
  • How to filter with predicates
  • Useful functions and axes

What is XPath?

XPath is a language for selecting nodes in an XML document. You describe where the nodes are in the tree, much as you describe a file with a folder path. It is used by XSLT, by many programming languages, and by tools that read XML.

If you know CSS selectors or the JavaScript DOM, the idea is familiar. XPath is just more powerful, because it can also test values and move up the tree.

The sample document

All the examples below use this file.

XML code: library.xml

<?xml version="1.0" encoding="UTF-8"?>
<library>
  <book id="b1" category="web">
    <title lang="en">Learning XML</title>
    <author>Sam Rivera</author>
    <price>12.00</price>
  </book>
  <book id="b2" category="web">
    <title lang="en">Learning PHP</title>
    <author>Ali Khan</author>
    <price>35.50</price>
  </book>
  <book id="b3" category="design">
    <title lang="fr">Apprendre le CSS</title>
    <author>Mia Laurent</author>
    <price>28.00</price>
  </book>
</library>

Basic paths

XPath Selects
/library The root element (a leading / starts at the top)
/library/book All book elements directly inside the root
//title Every title anywhere in the document
/library/book/title The title of every book
book Books that are children of the current node (a relative path)
. and .. The current node, and its parent
* Any element
@id The id attribute
//book/@category The category attribute of every book
text() The text inside an element

One slash (/) means “directly inside”. Two slashes (//) mean “anywhere below”.

XPath code

//book/title/text()

What it selects

Three text nodes: Learning XML, Learning PHP, and Apprendre le CSS.

Predicates: filtering with square brackets

A predicate in [ ] narrows the selection.

XPath Selects
/library/book[1] The first book. XPath counts from 1, not 0
/library/book[last()] The last book
/library/book[position() < 3] The first two books
//book[@category='web'] Books whose category is web
//book[price > 20] Books that cost more than 20
//title[@lang='fr'] Titles written in French
//book[author='Ali Khan']/title The title of the book by Ali Khan

XPath code

//book[@category='web' and price < 30]/title

What it selects

One title: Learning XML. Learning PHP is a web book but costs 35.50, and the French book is not in the web category.

You can combine tests with and, or, and not(). Remember to write < as &lt; if the XPath sits inside an XML file, such as an XSLT stylesheet.

Useful functions

Function Does
count(//book) How many nodes
sum(//price) Adds numbers
contains(title, 'XML') True if the text contains the word
starts-with(title, 'Learning') True if it begins with the text
string-length(title) The number of characters
normalize-space(text) Trims and tidies spaces

XPath code

count(//book)
sum(//price)
//book[contains(title, 'Learning')]/author

The results

3
75.5
The authors of the two Learning books: Sam Rivera and Ali Khan

Text matching is case sensitive. contains(title, 'learning') would find nothing.

Axes: moving around the tree

An axis says which direction to move. The short forms above are shortcuts for the most common axes. The full form is axis::test.

Axis Selects Example
child:: Direct children (the default) child::book
descendant:: Anything below // is a short form
parent:: The parent ..
ancestor:: All elements above ancestor::library
following-sibling:: Siblings after this node //book[1]/following-sibling::book
preceding-sibling:: Siblings before this node
attribute:: Attributes @ is a short form

XPath code

//title[. = 'Learning PHP']/../price

What it selects

The price 35.50. It finds the title with that text, goes up to its parent book with .., then down to the price.

Combining paths

The | operator joins results:

XPath code

//title | //author

This returns every title and every author, in document order.

Namespaces and XPath

If a document uses a default namespace (xmlns="..."), a plain path such as //book finds nothing, because the elements belong to a namespace. You must register a prefix for that namespace in your XPath tool and use it, such as //b:book. This catches many beginners, so check for it when a path that looks right returns nothing.

Where XPath is used

  • XSLT, in the next lesson, to pick the data to transform.
  • PHP with DOMXPath, JavaScript with document.evaluate(), and Python, Java, and other languages.
  • Testing tools that find elements in a page.

PHP code

<?php
$doc = new DOMDocument();
$doc->load(__DIR__ . "/library.xml");

$xpath = new DOMXPath($doc);
$nodes = $xpath->query("//book[price > 20]/title");

foreach ($nodes as $node) {
    echo htmlspecialchars($node->textContent, ENT_QUOTES, "UTF-8") . "<br>";
}

Output

Learning PHP
Apprendre le CSS

Lesson 15 shows the same idea in JavaScript.

Common mistakes

  • Counting from 0. In XPath, [1] is the first item.
  • Mixing up / (directly inside) and // (anywhere below).
  • Forgetting quotes around text in a test: @category='web'.
  • Case mistakes, since XPath is case sensitive.
  • Forgetting the namespace when the document has a default namespace.
  • Building an XPath from visitor input, which can be abused. Validate and escape quotes in such values.

Exercise

Using library.xml, write XPath expressions that select: (1) the authors of all books in the design category, (2) the id of every book that costs less than 30, (3) the number of books.

Show solution
1.  //book[@category='design']/author
2.  //book[price < 30]/@id
3.  count(//book)

Results: (1) Mia Laurent, (2) b1 and b3, (3) 3.

Knowledge check

1. What is the difference between / and // in a path?

/ means directly inside. // means anywhere below.

2. How does XPath number the first item in a list?

1, not 0.

3. Which symbol selects an attribute?

@

Summary

  • XPath selects nodes by their place in the tree, using paths such as /library/book/title and //title.
  • Predicates in [ ] filter by position, attribute, or value, and functions such as count() and contains() add power.
  • Remember namespaces, counting from 1, and case sensitivity.
Scroll to Top