Descendant selectors

h1//h1
div p//div//p
ul > li//ul/li
ul > li > a//ul/li/a
div > *//div/*
:root/
:root > body/body

Attribute selectors

#id//[@id="id"]
.class//[@class="class"] 
input[type="submit"]//input[@type="submit"]
a#abc[for="xyz"]//a[@id="abc"][@for="xyz"]
a[rel]//a[@rel]
a[href^='/']//a[starts-with(@href, '/')]
a[href$='pdf']//a[ends-with(@href, '.pdf')]
a[href~='://']//a[contains(@href, '://')] 

Order selectors

ul > li:first-child//ul/li[1]
ul > li:nth-child(2)//ul/li[2]
ul > li:last-child//ul/li[last()]
li#id:first-child//li[@id="id"][1]
a:first-child//a[1]
a:last-child//a[last()]

Siblings

h1 ~ ul//h1/following-sibling::ul
h1 + ul//h1/following-sibling::ul[1]
h1 ~ #id//h1/following-sibling::[@id="id"]

jQuery

$('ul > li').parent()//ul/li/..
$('li').closest('section')//li/ancestor-or-self::section
$('a').attr('href')//a/@href
$('span').text()//span/text()

Other things

h1:not([id])//h1[not(@id)]
Text match//button[text()="Submit"]
Text match (substring)//button[contains(text(),"Go")]
Arithmetic//product[@price > 2.50]
Has children//ul[*]
Has children (specific)//ul[li]
Or logic//a[@name or @href]
Union (joins results)//a | //div

Class check

Xpath doesn’t have the “check if part of space-separated list” operator, so this is the workaround (source):
//div[contains(concat(' ',normalize-space(@class),' '),' foobar ')]

Expressions

Prefixes

Begin your expression with any of these.
// anywhere//hr[@class='edge']
./ relative./a
/ root/html/body/div

Axes

Separate your steps with /. Use two (//) if you don’t want to select direct children.
/ child//ul/li/a
// descendant//[@id="list"]//a

Steps

A step may have an element name (div) and predicates ([...]). Both are optional.
//div
//div[@name='box']
//[@id='link']
They can also be these other things.
//a/text()              #=> "Go home"
//a/@href               #=> "index.html"
//a/*                   #=> All a's child elements

Predicates

Predicates ([...])

Restricts a nodeset only if some condition is true. They can be chained.
//div[true()] 
//div[@class="head"]
//div[@class="head"][@id="top"]

Operators

Use comparison and logic operators to make conditionals.
# Comparison
  //a[@id = "xyz"]
  //a[@id != "xyz"]
  //a[@price > 25]
# Logic (and/or)
  //div[@id="head" and position()=2]
  //div[(x and y) or not(z)]

Using nodes

You can use nodes inside predicates.
# Use them inside functions
  //ul[count(li) > 2]
  //ul[count(li[@class='hide']) > 0]
# This returns `
    ` that has a `
  • ` child
    //ul[li]

    Indexing

    Use [] with a number, or last() or position().
    //a[1]                  # first 
    //a[last()]             # last 
    //ol/li[2]              # second
    
  • //ol/li[position()=2] # same as above //ol/li[position()>1] # :not(:first-child)

    Chaining order

    Order is significant, these two are different.
    a[1][@href='/']
    a[@href='/'][1]
    

    Nesting predicates

    This returns 

     if it has an 

     descendant with id='hi'.
    //section[//h1[@id='hi']]
    

    Functions

    Node functions

    name()                     # //[starts-with(name(), 'h')]
    text()                     # //button[text()="Submit"]
                               # //button/text()
    lang(str)
    namespace-uri()
    
    count()                    # //table[count(tr)=1]
    position()                 # //ol/li[position()=2]
    

    Boolean functions

    not(expr)                  # button[not(starts-with(text(),"Submit"))]
    

    String functions

    contains()                 # font[contains(@class,"head")]
    starts-with()              # font[starts-with(@class,"head")]
    ends-with()                # font[ends-with(@class,"head")]
    
    concat(x,y)
    substring(str, start, len)
    substring-before("01/02", "/")  #=> 01
    substring-after("01/02", "/")   #=> 02
    translate()
    normalize-space()
    string-length()
    

    Type conversion

    string()
    number()
    boolean()
    

    Axes

    Using axes

    Steps of an expression are separated by /, usually used to pick child nodes. That’s not always true: you can specify a different “axis” with ::.
    //ul/li                       # ul > li
    //ul/child::li                # ul > li (same)
    //ul/following-sibling::li    # ul ~ li
    //ul/descendant-or-self::li   # ul li
    //ul/ancestor-or-self::li     # $('ul').closest('li')
    

    Child axis

    This is the default axis. This makes //a/b/c work.
    # both the same
      //ul/li/a
      //child::ul/child::li/child::a
    
    # both the same
    # this works because `child::li` is truthy, so the predicate succeeds
      //ul[li]
      //ul[child::li]
    
    # both the same
      //ul[count(li) > 2]
      //ul[count(child::li) > 2]
    

    Descendant-or-self axis

    // is short for the descendant-or-self:: axis.
    # both the same
      //div//h4
      //div/descendant-or-self::h4
    
    # both the same
      //ul//[last()]
      //ul/descendant-or-self::[last()]
    

    Other axes

    There are other axes you can use.
    AxisAbbrevDescription
    ancestor
    ancestor-or-self
    attribute@@href is short for attribute::href
    childdiv is short for child::div
    descendant
    descendant-or-self//// is short for /descendant-or-self::node()/
    namespace
    self.. is short for self::node()
    parent.... is short for parent::node()
    following
    following-sibling
    preceding
    preceding-sibling

    Unions

    Use | to join two expressions.
    //a | //span
    

    More examples

    //*                 # all elements
    count(//*)          # count all elements
    (//h1)[1]/text()    # text of the first h1 heading
    //li[span]          # find a
    
  • with an inside it
  • # ...expands to //li[child::span] //ul/li/.. # use .. to select a parent
    # Find a 
    that directly contains h1#section-name
    //section[h1[@id='section-name']]
    # Find a 
    that contains h1#section-name
    # (Same as above, but use descendant-or-self instead of child) //section[//*[@id='section-name']]
    # like jQuery's $().closest('.box')
      ./ancestor-or-self::[@class="box"]
    
    # Find  and check its attributes
      //item[@price > 2*@discount]