An axis is the direction a location step travels. Every step has one — child is the default, which is why book and child::book mean the same thing. Change the axis and you change which part of the tree you can see: up to ancestors, down into descendants, sideways to siblings, or off the tree onto attributes and namespaces.
XPath on the Recipes sample:
//ingredient[contains(., 'pepper')]/ancestor::recipe/name
Matches an ingredient deep in the tree, then walks up the ancestor axis to the recipe that contains it and returns its name.
The child, attribute, and descendant-or-self axes have shorthand (nothing, @, and //). The rest have to be spelled out with the axis::nodetest form.
| Axis | Selects | Example |
|---|---|---|
child:: | Direct children (default axis) | child::book |
parent:: | Parent node | parent::catalog |
ancestor:: | All ancestors up to root | ancestor::section |
ancestor-or-self:: | Ancestors including self | ancestor-or-self::div |
descendant:: | All descendants | descendant::item |
descendant-or-self:: | Descendants including self | descendant-or-self::* |
following:: | Everything after closing tag | following::chapter |
following-sibling:: | Siblings after current node | following-sibling::p |
preceding:: | Everything before opening tag | preceding::title |
preceding-sibling:: | Siblings before current node | preceding-sibling::li |
self:: | The current node | self::node() |
attribute:: | Attributes (shorthand: @) | @id |
namespace:: | Namespace nodes | namespace::* |
Most real expressions use three: child for going down one level, descendant-or-self (//) for going down any number, and attribute (@) for reading attributes.
The reverse axes earn their place when you have already matched something and need its context — ancestor:: to find the container, preceding-sibling:: to read the label before a value, following:: to grab everything after a marker element.
child:: — or just the element name.//, which is descendant-or-self::node()/.parent:: for one level, ancestor:: for all of them.preceding-sibling:: and following-sibling:: stay within the same parent; preceding:: and following:: cross the whole document in document order.attribute:: (@) and namespace::, which XPath 3.1 drops.ancestor, ancestor-or-self, preceding, and preceding-sibling are reverse axes: they are numbered outward from the context node, so preceding-sibling::item[1] is the sibling *immediately* before you, not the first one in the document.
Every other axis is a forward axis, numbered in document order. This is the single most common source of surprising index results.
The Sandbox has an Axes panel: pick any node in the document and it shows how many nodes each axis reaches from there, with the diagram above highlighting the region. Clicking an axis runs it, so you can see the actual node set rather than reasoning about it.