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 < 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 withdocument.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/titleand//title. - Predicates in
[ ]filter by position, attribute, or value, and functions such ascount()andcontains()add power. - Remember namespaces, counting from 1, and case sensitivity.