XPath Cheatsheet - Axes, Predicates & Functions

For locating nodes in XML or HTML, a regular expression is almost always the wrong instrument: change the line wrapping and the pattern breaks. XPath was designed for a structured tree, so a selector expresses position, attributes and relationships at once and survives reformatting. This sheet covers basic paths, predicates, axes, common functions, and wildcards with operators, applied to two frequent jobs - locating elements in crawlers and reading configuration files. There is a practical test for whether a selector is well written: it should be able to hit without matching visible text. Use a positional predicate for the second match, an attribute check for the tagged element, and an axis for a parent or sibling relationship; matching on wording is fragile because wording changes while structure does not. The performance judgement is equally clear - the descendant axis costs noticeably more on large documents, so when the hierarchy is known prefer the child axis with an exact path. And remember the version boundary: XPath 1.0 has no regex, so fuzzy matching means composing contains calls or moving to a processor that supports 2.0.

Dev Tools·34 commands·Last updated 2026-07-21
xpathxmlhtmlWeb Scraping

Basic Paths 7

/
Select from the root node
//
Select from anywhere (recursive)
.
Current node
..
Parent node
/bookstore/book
book child of bookstore under root
//book
All book elements in the document
book/title
title child of book

Predicate Filtering 6

//book[1]
First book
//book[last()]
Last book
//book[position()<3]
First two books
//book[@lang="zh"]
book whose lang attribute is zh
//book[price>35]
book with price > 35
//book[title]
book containing a title child

Axes 7

ancestor::div
All ancestor div
parent::node()
Parent node
child::title
Child node title
descendant::p
All descendant p
following-sibling::div
Following sibling div
preceding-sibling::h2
Preceding sibling h2
self::a
Self (only if it is a)

Common Functions 8

contains(@class, "active")
class attribute contains active
starts-with(text(), "Hello")
Text starts with Hello
normalize-space()
Trim leading/trailing and collapse inner whitespace
string-length(text())
Text length
count(//book)
Total number of book elements
concat("a", "-", "b")
Concatenate strings
substring("hello", 1, 3)
Substring
translate("abc", "abc", "ABC")
Character replacement

Wildcards & Operators 6

*
Match any element node
@*
Match any attribute
node()
Match any node type
//a | //b
Union of a and b
//div[@id="x" and @class="y"]
and condition
//div[@class="a" or @class="b"]
or condition

Command Examples

Combining predicates: filter first, then take the Nth match

//book[@lang="zh"] //book[price>35] //book[title] //book[last()]

The third and fourth lines are two common filters: books that have a title child, and the last book. Index predicates count from 1 and apply within the filtered set. Attribute comparisons need numeric-convertible values, so a price written as a currency string invalidates the expression.

Axis navigation: upward and sideways from the current node

ancestor::div following-sibling::div preceding-sibling::h2

ancestor collects every matching ancestor, while following-sibling and preceding-sibling take siblings after and before the current position. On adjacent-block layouts they beat // in both speed and precision because no full subtree walk is needed.

Common functions: cleaning text and aggregating values

normalize-space() string-length(text()) count(//book) concat("a", "-", "b")

normalize-space trims and collapses whitespace, which matters because scraped HTML is full of newlines and indentation that silently break text matching. count returns a number and is useful for checking that a node exists before descending into its children.

Wildcards combined with logical operators

//div[@id="x" and @class="y"] //div[@class="a" or @class="b"] //a | //b

and requires both conditions, or accepts either, and the pipe forms a union that de-duplicates and returns nodes in document order. Use equality when you need class to be exactly a, and contains when a looser hit is acceptable.

Common Pitfalls

  • Predicate indices start at 1 and count within the filtered set, so writing the index before the predicate changes the meaning entirely. Confirm that layer before editing an expression.
  • A // buried in deep nesting multiplies traversal cost sharply; on large documents prefer an exact path qualified by attribute predicates.
  • XPath 1.0 has no regular expressions. Use contains combinations for fuzzy matching, and only reach for matching functions once you have confirmed the runtime implements 2.0.
  • class attributes are nearly always composite, so equality rarely matches. contains does match, but it also catches inactive and similar substrings — narrow it with another condition.
  • Newlines and indentation inside text nodes break starts-with and plain text comparison. Run normalize-space over the value before comparing.

Tips

  • // is much slower than /; prefer exact paths for large documents.
  • contains(@class, 'x') matches composite classes like 'x y', more practical than exact match.
  • XPath 1.0 has no regex; for regex matching use XPath 2.0 or combine with contains.

FAQ

What separates // from /, and why is // slow?

They differ in semantics. / is an absolute path from the document root that follows one definite branch downward, while // is shorthand for "search descendants recursively from anywhere" and each evaluation walks the remaining subtree. So /bookstore/book inspects the book elements under a single bookstore, whereas //book has to examine every node in the document. On small documents the difference is negligible; once nodes reach into the tens of thousands, a // nested inside several levels multiplies the traversal cost. Prefer an exact path when the depth is known, fall back to // only when it is not, and place it once at the start of an expression with relative paths expanding from there.

Do predicate indices start at 0 or 1, and which element does //li[2] mean?

Indexing starts at 1, so //li[2] is the second matching li rather than the third — the opposite of array-index intuition. Two details cause most mistakes. First, the index counts within the filtered result set, so predicates come before the index: //li[@class="x"][2] is the second li carrying that class, and reversing the order changes the meaning entirely. Second, a bracket may hold several conditions meaning all must hold, and comparisons like @price>35 require the attribute to be convertible to a number — otherwise the expression is invalid rather than quietly returning an empty set.

Why is contains more practical than exact matching on class attributes?

Real HTML classes are almost always composite: class="btn btn-primary active" holds three classes at once. contains(@class, "active") tests whether the attribute value includes a substring, so it hits that form, whereas exact equality only matches when the whole attribute value is precisely active, which in practice almost never happens. contains is therefore the default choice for scraping. The cost is that it also matches active-tab and inactive by pure containment, so it is looser than exact matching without being precise; combine it with another condition when you need strictness.

How do I do regular-expression matching under XPath 1.0?

1.0 has no regular-expression function — that is a version boundary, not a syntax mistake. There are three workable substitutes: contains combined with length checks for containment matching, which covers most real cases; substring for positional trimming, which suits fixed formats such as codes and dates; or switching to a processor that implements XPath 2.0, where regex-style matching functions exist. Confirm the runtime version before choosing, or the same expression behaves differently in two environments.

How do I extract several fields without re-scanning the whole document each time?

Bind the root node to a variable once, then take relative paths beneath it. Once you hold the product-list node, every field expression is evaluated within that small subtree instead of each field restarting a // traversal across the entire tree. For configuration files there is a related habit: prefer element names over attribute values in paths, because element names are usually the public contract of the schema while attributes are more likely implementation detail — and attributes are what change first.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us