What is XPath?
XPath is the expression language used to address parts of an XML document. It is the addressing
layer underneath XSLT, XQuery, XML Schema and most XML tooling — every select and
match attribute in a stylesheet is an XPath expression. An XPath expression navigates
a document as a tree of nodes and returns a sequence of whatever it selects: elements, attributes,
text nodes, or atomic values such as numbers and strings.
Most XPath bugs are not syntax errors — they are expressions that parse cleanly and quietly select the wrong nodes, or nothing at all. That is why evaluating an expression against a real document, on a real processor, is the fastest way to debug it. XSLT Playground evaluates your expression with Saxon HE 12.5, so what you see here is exactly what your production pipeline will do.
How to Test an XPath Expression Online
- Open the XPath tester template — it loads with a working stylesheet and sample XML already in place.
- Replace the sample XML in the Data Pipeline panel with your own document.
- Put the expression you want to evaluate in the
expressionvariable. - The result panel lists every matching node, numbered, with a total count at the top.
- If the count is
0, check namespaces first — an unprefixed name never matches a namespaced element.
//book matches only elements with no namespace. If your document declares
xmlns="http://example.com/books", you must bind that URI to a prefix in the stylesheet
and write //b:book, or use the wildcard form //*[local-name() = 'book'].
Common XPath Expressions
The expressions below are the ones that cover the overwhelming majority of real-world selection work. Each is written against the sample library document used in the live example further down.
| Expression | What it selects |
|---|---|
//book | Every book element anywhere in the document. |
/library/book | Only book children of the root library element — faster and safer than //. |
//book[@lang = 'en'] | Books whose lang attribute equals en — an attribute predicate. |
//book/@year | The year attribute nodes themselves, not the elements. |
count(//book) | The number of books, as a number rather than a node sequence. |
//book[price > 40] | Books whose price child compares numerically greater than 40. |
//book[contains(author, 'Martin')] | Books whose author string contains a substring. |
//book[starts-with(title, 'Clean')] | Books whose title begins with a given prefix. |
//book[1] | Every book that is first among its own siblings — see the note below. |
(//book)[1] | The first book in the whole document. |
//book[last()] | The last book among each set of siblings. |
//book[@lang = 'es']/following-sibling::book | Books that come after the Spanish one, using an axis. |
//price/ancestor::book | Walks back up the tree from a price to its containing book. |
//book[not(@lang)] | Books with no lang attribute at all. |
distinct-values(//book/@lang) | The deduplicated set of languages (XPath 2.0 and later). |
Predicates: the Positional Trap
A predicate in square brackets filters the node sequence produced by the step it is attached to — not the final result of the whole path. This distinction catches almost everyone at least once. Given a document where books are split across shelves:
<library>
<shelf id="A"><book>A1</book><book>A2</book></shelf>
<shelf id="B"><book>B1</book><book>B2</book></shelf>
</library>
//book[1]returns two nodes —A1andB1— because each is the first book within its own shelf.(//book)[1]returns one node —A1— because the parentheses build the full sequence first, then take its first item.
The same rule explains why //book[last()] can return several nodes. When you mean
"the Nth thing overall", parenthesise the path.
XPath Axes
An axis says which direction to travel from the current node. child:: is the default
and is almost always written implicitly, but the others become essential once you need to move
sideways or upwards.
child / descendant
child::book (usually just book) and descendant::book, abbreviated .//book, walk downwards.
parent / ancestor
parent::* (abbreviated ..) and ancestor::book walk back up towards the document node.
following-sibling / preceding-sibling
Move sideways among nodes sharing a parent — the basis of pattern-based grouping in XSLT 1.0.
attribute / self
attribute::lang is abbreviated @lang; self::book (abbreviated .) tests the current node itself.
Live XPath Example: Evaluate an Expression and List Every Match
This is the stylesheet behind the XPath tester template. It wraps your expression in a harness that reports the match count and copies out every selected node, so you can see exactly what the expression returned rather than guessing from a single value.
XML input:
<library>
<book lang="en" year="2001">
<title>Refactoring</title>
<author>Martin Fowler</author>
<price>38.50</price>
</book>
<book lang="es" year="2005">
<title>El Quijote</title>
<author>Cervantes</author>
<price>12.00</price>
</book>
<book lang="en" year="2018">
<title>Clean Architecture</title>
<author>Robert C. Martin</author>
<price>41.20</price>
</book>
</library>
Stylesheet:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Put the expression you want to test here. -->
<xsl:variable name="expression" select="//book[@lang = 'en']"/>
<xsl:template match="/">
<matches count="{count($expression)}">
<xsl:for-each select="$expression">
<match position="{position()}">
<xsl:copy-of select="."/>
</match>
</xsl:for-each>
</matches>
</xsl:template>
</xsl:stylesheet>
Output:
<matches count="2">
<match position="1">
<book lang="en" year="2001">
<title>Refactoring</title>
<author>Martin Fowler</author>
<price>38.50</price>
</book>
</match>
<match position="2">
<book lang="en" year="2018">
<title>Clean Architecture</title>
<author>Robert C. Martin</author>
<price>41.20</price>
</book>
</match>
</matches>
Evaluating Several Expressions at Once
When you are comparing candidate expressions it is quicker to evaluate them side by side. This stylesheet runs eight expressions against the same library document and labels each result.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<results>
<r expr="count(//book)">
<xsl:value-of select="count(//book)"/>
</r>
<r expr="//book[@lang = 'en']/title">
<xsl:value-of select="//book[@lang = 'en']/title" separator=", "/>
</r>
<r expr="//book[price > 40]/title">
<xsl:value-of select="//book[price > 40]/title" separator=", "/>
</r>
<r expr="//book[contains(author, 'Martin')]/title">
<xsl:value-of select="//book[contains(author, 'Martin')]/title" separator=", "/>
</r>
<r expr="//book[1]/title">
<xsl:value-of select="//book[1]/title"/>
</r>
<r expr="//book[last()]/@year">
<xsl:value-of select="//book[last()]/@year"/>
</r>
<r expr="//book[@lang = 'es']/following-sibling::book/title">
<xsl:value-of select="//book[@lang = 'es']/following-sibling::book/title" separator=", "/>
</r>
<r expr="//price[. > 40]/ancestor::book/@year">
<xsl:value-of select="//price[. > 40]/ancestor::book/@year"/>
</r>
</results>
</xsl:template>
</xsl:stylesheet>
Output:
<results>
<r expr="count(//book)">3</r>
<r expr="//book[@lang = 'en']/title">Refactoring, Clean Architecture</r>
<r expr="//book[price > 40]/title">Clean Architecture</r>
<r expr="//book[contains(author, 'Martin')]/title">Refactoring, Clean Architecture</r>
<r expr="//book[1]/title">Refactoring</r>
<r expr="//book[last()]/@year">2018</r>
<r expr="//book[@lang = 'es']/following-sibling::book/title">Clean Architecture</r>
<r expr="//price[. > 40]/ancestor::book/@year">2018</r>
</results>
Note that //book[1]/title returns a single title here only because all three books share
one parent. The separator attribute on xsl:value-of requires XSLT 2.0 or later.
XPath 1.0 vs 2.0 vs 3.1 — What Changed
- XPath 1.0 (1999): four types only — node-set, string, number, boolean. No sequences, no date types, no regular expressions. Comparing a node-set with
=is existential, which is why//book/@lang != 'en'does not mean what most people expect. This is the version browsers implement. - XPath 2.0 (2007): introduces sequences and the XML Schema type system (
xs:date,xs:integer…),for/if/some/everyexpressions, regular expressions viamatches(),replace()andtokenize(), the value comparison operatorseq,ne,lt, and functions such asdistinct-values()andstring-join(). - XPath 3.0 (2014): adds inline anonymous functions and higher-order functions,
letexpressions, the string concatenation operator||, the simple mapping operator!, andanalyze-string(). - XPath 3.1 (2017): adds maps and arrays as first-class types, the lookup operator
?, the arrow operator=>, and the JSON functionsparse-json(),json-doc(),xml-to-json()andjson-to-xml().
In XSLT Playground the XPath version follows the XSLT version you select: XSLT 1.0 gives you XPath 1.0, XSLT 2.0 gives XPath 2.0, and XSLT 3.0 gives the full XPath 3.1 surface.
Frequently Asked Questions
- How do I test an XPath expression online?
- Open the XPath tester template, paste your XML into the input panel, and put your expression in the
expressionvariable. The result panel lists every matching node with a total count. - Which XPath versions can I test?
- XPath 1.0, 2.0, 3.0 and 3.1. The version follows the XSLT version you select, and Saxon HE 12.5 evaluates all of them — including maps, arrays and the arrow operator in XPath 3.1.
- Why does
//book[1]return more than one node? - A positional predicate applies to each step, not to the whole result.
//book[1]selects every book that is first among its own siblings. To take the first node of the entire result, parenthesise the path:(//book)[1]. - Can I test XPath axes online?
- Yes. All thirteen axes work, including
ancestor,descendant,following-sibling,preceding-sibling,parent,selfandattribute. The expression runs inside a real Saxon transformation, so behaviour matches production. - Do browsers support XPath 2.0 or 3.1?
- No. Browsers implement XPath 1.0 only, so
matches(),tokenize(),distinct-values()andformat-date()are unavailable there. XSLT Playground runs Saxon on the server, which is why it can evaluate XPath 2.0 and 3.1. - Is the online XPath tester free?
- Yes — completely free, no installation and no account. You can also share a workspace with a single link.
XPath Function Reference
Every XPath function, documented with syntax and a runnable example: browse the complete XSLT & XPath function reference. The functions that show up most often inside predicates:
- count() — how many nodes an expression selected
- contains() — substring test inside a predicate
- starts-with() — prefix matching
- position() and last() — positional predicates
- local-name() — match elements regardless of namespace
- normalize-space() — compare text without whitespace noise
- matches() — regular expression test (XPath 2.0+)
- distinct-values() — deduplicate a sequence (XPath 2.0+)
- exists() and empty() — presence tests that read clearly
Learn More
- XSLT string functions reference (substring, replace, tokenize)
- XSLT grouping with xsl:for-each-group — complete guide
- Transforming XML to JSON and CSV with XSLT
Also Available
Need to turn that XML into JSON once you have selected it? Try the XML to JSON converter →
Testing a full stylesheet rather than a single expression? Use the XSLT 2.0 Online Tester or the XSLT 3.0 Online Tester.