XPath Tester

A scoped XPath 1.0 evaluator — paths, predicates and text()/@name tests — over this site's own XML parser.

XML
XPath expression
1 match
  • <title lang="en">Harry Potter
Written and maintained by Pura IALast reviewed

A browser already ships a real, complete XPath 1.0 engine — document.evaluate() — but it needs a live DOM, which only exists in a browser tab, not during this site's build-time page generation or in its automated tests. So this tool runs on its own evaluator instead: a deliberately scoped subset of XPath 1.0 that covers what people actually type into an XPath tester — location paths, the handful of predicate forms that come up constantly, and the @name / text() node tests — built over this site's own XML parser rather than the full W3C grammar and function library.

The bookstore example below is loaded into the editor above with /bookstore/book[@category='children']/title as the starting expression — change the expression and the match list below updates immediately.

Worked examples

Each of these evaluates against the bookstore document loaded in the editor above.

/bookstore/book

All three <book> elements, in document order — a plain absolute path.

//title

Every <title>, found at any depth — the // shorthand searches, it doesn't require an exact path.

/bookstore/book[@category='children']

Only the book whose category attribute is exactly "children" — one match.

/bookstore/book[1]

The first book by document position — XPath positions are 1-indexed, not 0-indexed.

/bookstore/book[last()]

The final book, whatever the count — last() adapts if books are added or removed.

/bookstore/book[1]/price/text()

The text content of the first book's <price> element, as its own kind of match — not the element itself.

/bookstore/book[1]/@*

Every attribute of the first book — @category and @id — using the attribute axis rather than a specific attribute's name.

//title/..

The parent of every <title> — each one's enclosing <book> — one level of .. per level written.

//title[contains(text(), 'Potter')]

Only the <title> whose text contains "Potter" as a substring — a partial match, not an exact one.

count(/bookstore/book)

Not a node list at all — a single number, 3, the count of matching elements.

How it works

Predicates run in sequence, each narrowing what came before

/bookstore/book[@category='cooking'][1] applies two predicates: first keep only cooking books, then take the first of what's left. Each [..] filters the result of everything before it, the same way chained .filter() calls would in code — not independent conditions all checked against the original list.

@name and text() read the current element, not its children

book[1]/@category reads book[1]'s own category attribute. This looks like it should be obvious, but it's a real distinction from book[1]/title, which does step down into a child — @ and text() are their own axes (attribute:: and a text node test), not shorthand for "look inside." Writing //@category instead searches every descendant's attributes, which is a genuinely different, broader query.

A path with zero matches is not an error

/bookstore/nonexistent evaluates cleanly to zero matches — the same way a database query that matches no rows isn't a database error. A red error only appears for something this evaluator can't parse or evaluate at all, like an unbalanced [ or a function it doesn't implement.

Known limitations

  • Only one level of ".." chaining is walked per written ".." — //title/../.. correctly walks up two levels because two ".." are written, but this evaluator has no way to walk further than the number of ".." actually in the expression (which matches real XPath semantics; there's no shorthand for "walk up N levels" other than writing ".." N times).
  • A namespace-prefixed tag like soap:Body is matched as the literal string "soap:Body", not resolved against its xmlns declaration — the same simplification this site's XML to JSON converter makes, for the same reason: proper namespace resolution is a meaningfully larger problem than what most XPath testing actually needs.
  • The union operator (|), the following/preceding axes, and most of the XPath function library beyond contains() and count() are not implemented — an expression using them fails to parse with a clear error rather than being silently misinterpreted.

XPath Tester FAQ

Why not just use the browser's built-in XPath support?

document.evaluate() needs a live DOM, which only exists in a browser tab — it can't run during this site's build-time page generation or in its automated tests, both of which need the exact same evaluation logic the interactive tool uses. This evaluator runs identically everywhere.

What happens if my expression uses syntax this doesn't support?

You get a clear parse error naming what's wrong, rather than a silently wrong result. The union operator, most axes beyond child/descendant-or-self/self/parent, and most functions beyond contains() and count() fall into this category — see Known limitations.

Are positions 0-indexed or 1-indexed?

1-indexed, matching real XPath: [1] is the first match, not the second. This is a common source of off-by-one mistakes for anyone used to 0-indexed languages.

Is my XML uploaded anywhere?

No. Both parsing and evaluation run in your browser.