Predicates and functions
Filtering node-sets with predicates, positional versus value tests, and the string, number and boolean functions you will use most.
Predicates filter a node-set
A predicate in square brackets keeps only the nodes for which the expression is true. Inside a predicate, the context is the node being tested — so a bare path is relative to it, not to the document.
//book[price > 20] value test
//book[@id] has the attribute
//book[not(@id)] lacks the attribute
//book[position() = 1] same as //book[1], per parent
//book[last()] the last book child of each parent
//book[position() <= 3] the first three
//book[title = 'Alpha'] exact text comparison
//book[contains(title, 'lph')] substring test
//book[starts-with(@id, 'bk-10')] prefix test
//book[price > 20 and @available='true'] boolean combination| Comparison | Meaning in XPath 1.0 |
|---|---|
= | True if any pair of values is equal — not an identity test |
!= | True unless every pair is equal, which surprises almost everyone |
< > <= >= | Numeric comparison after conversion to number |
and or not() | Boolean logic |
| | Union of two node-sets |
⚠️
The
!= operator is not the negation of = when node-sets are involved. Use not(price = 20) when you mean 'no price equals 20'.The functions you will actually use
| Category | Functions | Purpose |
|---|---|---|
| String | string() concat() substring() substring-before() substring-after() string-length() normalize-space() translate() | Cutting and cleaning text |
| Number | number() sum() count() floor() ceiling() round() | Arithmetic over node-sets |
| Boolean | boolean() true() false() not() lang() | Conditions and type coercion |
| Node-set | last() position() local-name() name() namespace-uri() | Filters that need context |
count(//book) how many books
sum(//book/price) total price as a number
round(sum(//book/price) * 100) / 100 two-decimal total
normalize-space(//book[1]/title) trim and collapse spaces
concat(//book[1]/title, ' - ', //book[1]/price) join into one string
substring-before(//book[1]/title, ' ') text before the first space
translate(//book[1]/title, 'abc', 'ABC') character-by-character map
string-length(//book[1]/title) > 4 boolean resultsubstring()in XPath 1.0 is one-based, sosubstring('abc', 1, 2)isab.translate()maps characters, not sequences — it cannot replace a word.- There is no regular expression support in XPath 1.0; 2.0 adds
matches(),replace()andtokenize(). - Comparing a number to a string converts the string to a number, and an unparsable string becomes
NaN, which compares false against everything — including itself.
Debugging an empty result
- Check well-formedness first — a parse error looks like an empty node-set in some tools.
- Test the path one step at a time:
/catalog, then/catalog/book, then the predicate. - Confirm the namespace mapping: an unprefixed name never matches a default namespace.
- Verify the predicate is not applied per parent when you expected document-wide selection.
- Print
count()rather than the value, so an empty result is unambiguous.
FAQ
What does <code>//book[1]</code> versus <code>(//book)[1]</code> really change?
The first filters per parent, the second takes the document-wide first item. This single pair of characters causes a large share of XPath bugs.
Can XPath do case-insensitive matching?
Not directly in 1.0. Fold both sides with
translate(), or use XPath 2.0's matches() with the case-insensitive flag.Related
Location paths and axes Advanced matching and sorting
Last refreshed 2026-09-18.