Skip to content
AZ Tools

XPath Tester

Run an XPath expression against XML or HTML in your browser and see every node it matches, with namespace prefixes and parse errors reported.

How to use

  1. Paste the XML or HTML into the document box and pick the matching parse mode — XML is strict and reports errors, HTML repairs what it can.
  2. Type the XPath expression, or take one of the examples to see how attribute filters, count(), text() nodes and local-name() behave.
  3. If the document has a default namespace, bind a prefix to it in the namespace box and use that prefix in the expression.

About this tool

Show

Paste a document, type an expression, and see what it selects. The evaluation is the browser's own XPath engine, so what you get here is what document.evaluate will give your code — matched nodes listed one by one with their serialised form, or the string, number or boolean that a function like string(), count() or starts-with() returns instead of a node-set.

The engine is XPath 1.0, and that is the first thing to know. Every browser ships 1.0 and nothing more, so the 2.0 and 3.1 functions people reach for are simply not there: matches(), lower-case(), ends-with() and tokenize() are rejected as syntax errors rather than returning nothing. Their 1.0 equivalents are contains(), translate(), the substring() family and a substring(s, string-length(s) - n) idiom for suffixes, and a rejected expression here will be rejected by lxml and by libxml2-based tooling for the same reason.

Namespaces are where most real documents defeat a first attempt. XPath 1.0 has no concept of a default namespace: if a document opens with <feed xmlns="http://www.w3.org/2005/Atom">, then //entry matches nothing at all, because the unprefixed name in the expression means "no namespace" while every element in the document is in the Atom one. There are two ways out, and the tool supports both — bind a prefix in the namespace box and write //a:entry, or sidestep the whole question with //*[local-name()='entry']. When a query returns nothing from a document that declares a default namespace, this says so rather than leaving you to guess.

The parse mode matters for the same reason in reverse. XML parsing is strict, so a stray unclosed tag produces a parser error — which the tool reports, because the browser still hands back a partial document and XPath will run against the fragment that survived, giving answers that look plausible and are not. HTML parsing is forgiving and repairs the markup instead, which is what you want for scraping a real page; note that element names come back uppercase there, since that is what the DOM reports for HTML elements.

Everything runs locally; the document is never uploaded.

Frequently asked questions

Why does my expression with matches() fail?

Because browsers implement XPath 1.0 only, and matches() is an XPath 2.0 function. The same applies to lower-case(), upper-case(), ends-with(), tokenize() and replace(). Use contains() for substring tests, translate() for case folding, and for a suffix test substring(s, string-length(s) - string-length(suffix) + 1) = suffix.

Why does //entry match nothing in my Atom feed?

Because the feed declares a default namespace and XPath 1.0 has no way to inherit it. An unprefixed name in an XPath expression means "in no namespace", which no element of a namespaced document is. Bind a prefix — say a = http://www.w3.org/2005/Atom — and query //a:entry, or write //*[local-name()="entry"] if you would rather not care about the URI.

Should I parse as XML or as HTML?

XML if the document is meant to be well-formed and you want to be told when it is not; HTML for anything scraped from the web, where unclosed tags and implied elements are normal and the parser will insert what the specification says a browser inserts. Be aware that the HTML parser adds html, head and body even when the markup has none, so an expression anchored at /div will not match where //div does.

Why are element names uppercase in HTML mode?

Because that is what nodeName reports for HTML elements in an HTML document — it is a DOM convention, not something XPath did. Matching is still case-insensitive for HTML elements, so //div finds a <DIV> and vice versa. In XML mode names are reported and matched exactly as written.

Does it evaluate the whole document or a selection?

The whole document: the context node is the document itself, so a path starting with / is anchored at the root and one starting with // searches everywhere. If you need a relative context, narrow it inside the expression — //table[1]//td rather than selecting the table first.

Related tools