Getting Started beginner operators fundamentals

Operators and Comparisons

Equality, inequality, arithmetic and the union operator, including the surprising way comparisons behave when one side is a node-set.

Operators and Comparisons

XPath has a small set of operators. Most are obvious, but the rules for comparing a node-set are not, and they are behind a whole class of “why did that match?” bugs.

Comparison operators

OperatorMeaningExample
=equal@type='submit'
!=not equal@type!='hidden'
< <= > >=numeric comparisonposition() > 2

There is no == and no ===. A single = is equality.

Inside an attribute-quoted string in Java, C# or JavaScript you may need to write &lt; or escape characters, but in XPath itself < is just <.

String comparison is exact

= compares strings character by character. It is case-sensitive and whitespace-sensitive:

//button[text()='Sign In']     matches "Sign In"
//button[text()='sign in']     does not match "Sign In"
//button[text()='Sign In ']    does not match "Sign In" (trailing space)

For fuzzy matching use contains(), starts-with(), normalize-space() or translate(). See Case-Insensitive Matching.

Numeric comparison

Strings are converted to numbers when compared with <, >, <=, >=:

//tr[td[3] > 100]                rows whose third cell is a number above 100
//li[string-length(.) >= 20]     items with at least 20 characters of text

Anything that cannot be converted becomes NaN, and every comparison with NaN is false. A cell containing “$120” is NaN, not 120.

Arithmetic

+   -   *   div   mod

Division is written div, because / is the path separator. Modulo is mod.

//li[position() mod 2 = 1]                          odd items
//td[count(preceding-sibling::td) + 1 = 3]          third cell, spelled out
//div[@data-price * @data-qty > 500]                computed condition

Watch the spacing around -. In @a-1 the parser reads a-1 as one attribute name. Write @a - 1.

The union operator |

| combines two node-sets into one, in document order, with duplicates removed:

//button | //a[@role='button']
//input[@type='text'] | //textarea

This is how you write “buttons or links that act as buttons”. There is no intersection or difference operator in XPath 1.0. You can usually express those with predicates instead.

Comparing node-sets: the existential rule

This is the important part. When one side of a comparison is a node-set, the comparison is true if any node in the set satisfies it.

<ul>
  <li class="a">One</li>
  <li class="b">Two</li>
</ul>
//ul[li/@class='a']        true: at least one li has class a
//ul[li/@class!='a']       ALSO true: at least one li has a class that is not a
//ul[not(li/@class='a')]   false here, because one li does have class a

!= against a node-set almost never means what you want. It reads as “some node is different”, not “no node is equal”. not(x = 'y') is the correct way to say “none of them equal y”.

The same rule applies to text() when an element has several text nodes: [text()='Save'] is true if any text node equals “Save”.

Boolean operators

and, or and not() are covered in Logic: and, or, not. Note that not is a function, so it needs parentheses: not(@disabled).

Operator precedence

From loosest to tightest binding:

  1. or
  2. and
  3. =, !=
  4. <, <=, >, >=
  5. +, -
  6. *, div, mod
  7. unary -
  8. |

So @a='x' or @b='y' and @c='z' means @a='x' or (@b='y' and @c='z'). When in doubt, add parentheses.

Try It Yourself

Open in Playground →

Change the > to < and the number to see numeric comparison against cell text.

Next Steps

  1. Logic: and, or, not - Combining conditions without surprises
  2. XPath Functions - The complete XPath 1.0 function set
  3. Position and Indexing - position(), last() and arithmetic on them