Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In XPath 1.0, select an element whose name is one of several alternatives with a self:: predicate:

<tag> *[self::book or self::article or self::chapter] </tag>

Written as a complete expression, the portable form is //*[self::book or self::article or self::chapter]. If each alternative is already a complete location path, use the union operator instead: //book | //article | //chapter. XPath 2.0 and later also let you compare a name with a sequence, such as //*[local-name() = ('book', 'article', 'chapter')].

The three patterns at a glance

Expression Best for Version and namespace behavior
//*[self::book or self::article or self::chapter] Several element names in one location step XPath 1.0 compatible; unprefixed names match only no-namespace elements
//book | //article | //chapter Separate, complete paths that should be combined XPath 1.0 compatible; results are de-duplicated and returned in document order
//*[name() = ('book', 'article', 'chapter')] A sequence of qualified names in XPath 2.0+ Requires a 2.0-or-newer processor; name comparison is sensitive to the processor’s QName rules
//*[local-name() = ('book', 'article', 'chapter')] Several local names when prefixes vary XPath 2.0+; ignores namespace identity, so add namespace-uri() when needed

Use self:: with or in XPath 1.0

A name test such as book checks one element name. A predicate can apply several name tests to the same candidate node. The self:: axis refers to the node currently being tested:

//*[self::book or self::article or self::chapter]

For every element selected by //*, XPath evaluates the predicate. The predicate is true when that element is a book, an article, or a chapter. This syntax works in XPath 1.0 implementations, including many DOM libraries and XML tools that have not adopted XPath 2.0.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Restrict the candidates to a known parent

Starting with a narrower path is usually clearer and faster than scanning the entire document:

/catalog/*[self::book or self::article or self::chapter]

This selects only direct children of catalog. To search descendants below every section, use:

//section//*[self::book or self::article or self::chapter]

The predicate remains the same; only the context path changes.

Example document

<catalog>
<book id="b1"/>
<article id="a1"/>
<video id="v1"/>
<chapter id="c1"/>
</catalog>

Against this document, /catalog/*[self::book or self::article or self::chapter] returns book, article, and chapter, but not video.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use the union operator for separate paths

The vertical bar (|) combines node selections:

//book | //article | //chapter

Each side is an independent location path. The result is one node sequence with duplicates removed and nodes ordered as they occur in the document. This is useful when the alternatives have different paths, not merely different names in one step:

/library/fiction/book | /library/nonfiction/article | /archive/chapter

Do not write //(book|article|chapter) as a shortcut in XPath 1.0. Parenthesized name-test unions are not portable XPath 1.0 syntax. Use separate paths joined by |, or use a self:: predicate.

| versus or

These operators work at different levels:

  • or combines boolean conditions inside a predicate. It asks whether the current node satisfies at least one test.
  • | combines node selections. It asks for the nodes returned by the left path and the right path together.

For example, //*[self::book or self::article] tests one candidate at a time. //book | //article evaluates two paths and merges their results.

Rank #2
XPath 2.0 Programmer's Reference
  • Used Book in Good Condition

XPath 2.0 and later: compare against a sequence

If your processor supports XPath 2.0 or newer, a list of names can be represented directly as a sequence:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
//*[name() = ('book', 'article', 'chapter')]

This is convenient when the list is generated or passed as a parameter. The equivalent local-name form is:

//*[local-name() = ('book', 'article', 'chapter')]

Use name() when the qualified name, including its prefix as represented by the processor, is meaningful. Use local-name() when prefixes may vary and you intentionally want to ignore them. XPath 3.1 additionally supports a wildcard namespace test such as *:book, which matches the local name book regardless of namespace.

When a variable holds the names

In an XPath 2.0+ host, a sequence variable keeps the expression maintainable:

declare variable $names as xs:string* := ('book', 'article', 'chapter');
//*[local-name() = $names]

The exact variable declaration syntax depends on the host language and whether it supports XQuery declarations. In a plain XPath API, pass the sequence through that API’s variable mechanism rather than copying the declaration literally.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Namespaces change what a name means

An unprefixed test such as //book does not automatically match an element in an arbitrary namespace. Consider:

<x:book xmlns:x="urn:example"/>

//book will not select this namespaced element in a namespace-aware XPath engine. Bind the document namespace URI to a prefix in the host application, then use that prefix:

//x:book | //x:article | //x:chapter

The prefix in the XPath does not have to be the same prefix used in the XML document; it must be bound to the same namespace URI by the XPath host.

When the prefix is unknown or dynamic

If you cannot bind a stable prefix, test both the local name and namespace URI:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
//*[local-name() = 'book' and namespace-uri() = 'urn:example']

For several names in that namespace:

//*[local-name() = ('book', 'article', 'chapter') and namespace-uri() = 'urn:example']

This prevents an unrelated book element from another namespace from being selected. Do not rely on local-name() alone when namespace identity matters.

Choosing the right expression

Choose self:: when alternatives share one step

Use self:: with or when the candidates are interchangeable children or descendants at one point in the path. It is the safest choice for XPath 1.0 portability and makes the context explicit.

Choose | when paths differ

Use a union when each alternative has its own location path, parent, predicate, or axis. For example:

//feed/item[@type='book'] | //catalog/article[@published='yes']

The union result is still one ordered node sequence, even though the paths are unrelated.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose sequence comparison for a maintained list

Use name() = (...) or local-name() = (...) in XPath 2.0+ when names come from a sequence, configuration value, or variable. It avoids a long chain of or clauses, but it is not available in XPath 1.0 engines.

Choose qualified names when namespaces are part of the contract

Use a resolver-bound prefix, such as x:book, when the vocabulary’s namespace is known. This is more precise than stripping namespaces and is the preferred approach for schema-driven XML.

Predicates, attributes, and text after the name test

You can add ordinary predicates after the multi-name test. For example, to select matching elements with an id attribute:

//*[self::book or self::article or self::chapter][@id]

To require non-empty text:

//*[self::book or self::article or self::chapter][normalize-space()]

With a union, put conditions on each path when the rules differ:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
//book[@status='current'] | //article[@published='yes']

Putting one predicate around the entire union is possible in XPath versions that allow a parenthesized path expression, but separate predicates are often easier to read and more portable across older host APIs.

Troubleshooting common failures

No nodes are returned

  • Namespace mismatch: The XML elements are namespaced, but the expression uses unprefixed names. Bind the namespace or add a namespace URI test.
  • Wrong context: The expression starts from a different document node or parent than expected. Test a short path such as /*, then extend it one step at a time.
  • Case mismatch: XPath name tests are case-sensitive. Book and book are different names.
  • Unsupported version: A 1.0 engine will reject sequence syntax such as ('book', 'article'). Replace it with self::book or self::article or a union.

Too many nodes are returned

  • Namespace ignored: A local-name test may match the same local name from several vocabularies. Add namespace-uri() or use a bound prefix.
  • Descendant search too broad: //* examines every descendant. Start from the known parent or use a direct-child step such as /catalog/*.
  • Duplicate-looking results: A union removes the same node if multiple paths reach it, but distinct elements with identical content are still separate nodes.

The expression is rejected as invalid

Check for //(book|article|chapter), which is not the portable XPath 1.0 form. Replace it with //book | //article | //chapter or the self:: predicate. Also verify whether the host expects an XPath expression, an XSLT match pattern, or an XML Schema selector; those languages have different namespace and grammar rules.

Performance and maintainability

For a small, fixed list, the performance difference between a self:: predicate and a union is normally less important than the size of the context selected by the path. Avoid starting with //* when the document structure gives you a narrower parent. Namespace-qualified tests also make the intended vocabulary explicit and reduce accidental matches.

Keep a frequently changing candidate list in one variable or configuration value when XPath 2.0+ is available. In XPath 1.0, generate the predicate or union in application code, but validate the names before inserting them into an expression. Never concatenate untrusted input into XPath without escaping or parameterization supported by your host library.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you are documenting XPath results in a web page and need a clean image or PDF of that page, ScreenshotNeo can capture it with one request instead of configuring a headless browser. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes every feature; the free plan includes 1,000 shots per month without a card, and paid plans start at $5 for 3,000 shots.

Use the ScreenshotNeo API documentation for the complete option list, including full-page capture, CSS-selector element capture, device presets, custom JavaScript and CSS, waits, request blocking, cookies, headers, geolocation, PDF controls, caching, signed links, asynchronous jobs, bulk capture, and usage reporting.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Create an account at ScreenshotNeo’s free sign-up page to get the 1,000-shot monthly allowance with no card.

FAQ

Does XPath preserve document order when selecting several names?

Yes. A union returns its combined nodes in document order, and a normal location step evaluates candidates in the axis order defined by XPath.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I select attributes with the same technique?

The expressions here target element names. Attributes are selected with an attribute axis or shorthand, such as //*[@id]; attribute names and element names occupy different axes.

Should I use name() or local-name()?

Use name() when the qualified name is significant. Use local-name() only when you deliberately want to ignore prefixes, and pair it with namespace-uri() when multiple namespaces are possible.

Is | the same as boolean OR?

No. | merges node selections, while or evaluates boolean conditions inside a predicate.

Frequently Asked Questions

Does XPath preserve document order when selecting several names?

Yes. A union returns its combined nodes in document order, and a normal location step evaluates candidates in the axis order defined by XPath.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I select attributes with the same technique?

The expressions here target element names. Attributes are selected with an attribute axis or shorthand, such as //*[@id]; attribute names and element names occupy different axes.

Should I use name() or local-name()?

Use name() when the qualified name is significant. Use local-name() only when you deliberately want to ignore prefixes, and pair it with namespace-uri() when multiple namespaces are possible.

Is | the same as boolean OR?

No. | merges node selections, while or evaluates boolean conditions inside a predicate.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.