oga/doc/css_selectors.md

30 KiB

CSS Selectors Specification

This document acts as an alternative specification to the official W3 CSS3 Selectors Specification. This document specifies only the selectors supported by Oga itself. Only CSS3 selectors are covered, CSS4 is not part of this specification.

This document is best viewed in the YARD generated documentation or any other Markdown viewer that supports the Kramdown syntax. Alternatively it can be viewed in its raw form.

Abstract

The official W3 specification on CSS selectors is anything but pleasant to read. A lack of good examples and unspecified behaviour are just two of many problems. This document was written as a reference guide for myself as well as a way for others to more easily understand how CSS selectors work.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Syntax

To describe syntax elements of CSS selectors this document uses the same grammar as Ragel. For example, an integer would be defined as following:

integer = [0-9]+;

In turn an integer that can optionally be prefixed by + or - would be defined as following:

integer = ('+' | '-')* [0-9]+;

A quick and basic crash course of the Ragel grammar:

  • *: zero or more instance of the preceding token(s)
  • +: one or more instances of the preceding token(s)
  • ( and ): used for grouping expressions together
  • ^: inverts a match, thus ^[0-9] means "anything but a single digit"
  • "..." or '...': a literal character, "x" would match the literal "x"
  • |: the OR operator, x | y translates to "x OR y"
  • [...]: used to define a sequence, [0-9] translates to "0 OR 1 OR 2 OR 3..." all the way upto 9

Semicolons are used to terminate lines. While not strictly required in this specification they are included in order to produce a Ragel syntax compatible grammar.

See the Ragel documentation for more information on the grammar.

Terminology

local name
The name of an element without a namespace. For the element <strong> the local name is strong.
namespace prefix
The namespace prefix of an element. For the element <foo:strong> the namespace prefix is foo.
expression
A single or multiple selectors used together to retrieve a set of elements from a document.

Selector Scoping

Whenever a selector is used to match an element the selector applies to all nodes in the context. For example, the selector foo would match all foo elements at any position in the document. On the other hand, the selector foo bar only matches any bar elements that are a descedant of any foo element.

In XPath the corresponding axis for this is descendant. In other words, this CSS expression:

foo

is the same as this XPath expression:

descendant::foo

In turn this CSS expression:

foo bar

is the same as this XPath expression:

descendant::foo/::bar

Note that in the various XPath examples the descendant axis is omitted in order to enhance readability.

Syntax

A CSS expression is made up of multiple selectors separated by one or more spaces. There MUST be at least 1 space between two selectors, there MAY be more than one. Multiple spaces do not alter the behaviour of the expression in any way.

Universal Selector

W3 chapter: http://www.w3.org/TR/css3-selectors/#universal-selector

The universal selector * (also known as the "wildcard selector") can be used to match any element, regardless of its local name or namespace prefix.

Example XML:

<root>
    <foo></foo>
    <bar></bar>
</root>

CSS:

root *

This would return a set containing two elements: <foo> and <bar>

The corresponding XPath is also *.

Syntax

The syntax for the universal selector is very simple:

universal = '*';

Element Selector

W3 chapter: http://www.w3.org/TR/css3-selectors/#type-selectors

The element selector (known as "Type selector" in the official W3 specification) can be used to match a set of elements by their local name or namespace. The selector foo is used to match all elements with the local name being set to foo.

Example XML:

<root>
    <foo />
    <bar />
</root>

CSS:

root foo

This would return a set with only the <foo> element.

This selector can be used in combination with the Universal Selector. This allows one to select elements using both a given local name and namespace. The syntax for this is as following:

ns-prefix|local-name

Here the pipe (|) character separates the namespace prefix and the local name. Both can either be an identifier or a wildcard. For example, the selector rb|foo matches all elements with local name foo and namespace prefix rb.

The namespace prefix MAY be left out producing the selector |local-name. In this case the selector only matches elements without a namespace prefix.

If a namespace prefix is given and it's not a wildcard then elements without a namespace prefix will not be matched.

The corresponding XPath expression for such a selector is ns-prefix:local-name. For example, rb|foo in CSS is the same as rb:foo in XPath.

Syntax

The syntax for just the local name is as following:

identifier = '*' | [a-zA-Z]+ [a-zA-Z\-_0-9]*;

The wildcard is put in place to allow a single rule to be used for both names and wildcards.

The syntax for selecting an element including a namespace prefix is as following:

ns_plus_local_name = identifier* '|' identifier

This would match |foo, *|foo and foo|bar. In order to match foo the regular identifier rule declared above can be used.

Class Selector

Class selectors can be used to select a set of elements based on the values set in the class attribute. Class selectors start with a period (.) followed by an identifier. Multiple class selectors can be chained together, matching only elements that have all the specified classes set.

As an example, .foo can be used to select all elements that have "foo" set in the class attribute, either as the sole or one of many values. In turn, .foo.bar matches elements that have both "foo" and "bar" set as the class.

Example XML:

<root>
    <a class="first" />
    <b class="second" />
</root>

Using the CSS selector .first would return a set containing only the <a> element. Using .first.second would return a set containing both the <a> and <b> nodes.

Syntax

identifier = '*' | [a-zA-Z]+ [a-zA-Z\-_0-9]*;

# .foo, .foo.bar, .foo.bar.baz, etc
class = ('.' identifier)+;

ID Selector

The ID selector can be used to match elements where the value of the id attribute matches whatever is specified in the selector. ID selectors start with a hash sign (#) followed by an identifier.

While technically multiple ID selectors can be chained together, HTML only allows elements to have a single ID. As a result doing so is fairly useless. Unlike classes IDs are globally unique, no two elements can have the same ID.

Example XML:

<root>
    <a id="first" />
    <b id="second" />
</root>

Using the CSS selector #first would return a set containing only the <a> node.

Syntax

identifier = '*' | [a-zA-Z]+ [a-zA-Z\-_0-9]*;

# .foo, .foo.bar, .foo.bar.baz, etc
class = ('#' identifier)+;

Attribute Selector

W3 chapter: http://www.w3.org/TR/css3-selectors/#attribute-selectors

Attribute selectors can be used to further narrow down a set of elements based on their attribute list. In XPath these selectors are known as "predicates". For example, the selector foo[bar] matches all foo elements that have a bar attribute, regardless of the value of said attribute.

Example XML:

<root>
    <foo number="1" />
    <bar />
</root>

CSS:

root foo[number]

This would return a set containing only the <foo> element since the <bar> element has no attributes.

For the CSS expression foo[number] the corresponding XPath expression is the following:

foo[@number]

When specifying an attribute you MAY include an operator and a value to match. In this case you MUST include an attribute value surrounded by either single or double quotes (but not a combination of the two).

There are 6 operators available:

  • =: equals operator
  • ~=: whitespace-in operator
  • ^=: starts-with operator
  • $=: ends-with operator
  • *=: contains operator
  • |=: hyphen-starts-with operator

Equals Operator

The equals operator matches an element if a given attribute value equals the value specified. For example, foo[number="1"] matches all foo elements that have a number attribute who's value is exactly "1".

Example XML:

<root>
    <foo number="1" />
    <foo number="2" />
</root>

CSS:

root foo[number="1"]

This would return a set containing only the first <foo> element.

The corresponding XPath expression is quite similar. For foo[number="1"] this would be:

foo[@number="1"]

Whitespace-in Operator

This operator matches an element if the given attribute value consists out of space separated values of which one is exactly the given value. For example, foo[numbers~="1"] matches all foo elements that have the value "1" in the numbers attribute.

Example XML:

<root>
    <foo numbers="1 2 3" />
    <foo numbers="4 bar 6" />
</root>

CSS:

root foo[numbers~="1"]

This would return a set containing only the first foo element. On the other hand, if one were to use the expression root foo[numbers~="bar"] instead then only the second <foo> element would be matched.

The corresponding XPath expression is quite complex, foo[numbers~="1"] is translated into the following XPath expression:

foo[contains(concat(" ", @numbers, " "), concat(" ", "1", " "))]

The concat calls are used to ensure the expression doesn't match the substring of an attrbitue value and that the expression matches elements of which the attribute only has a single value. If foo[contains(@numbers, ' 1 ')] were to be used then attributes such as <foo numbers="1" /> would not be matched.

Software implementing this selector are free to decide how they concatenate spaces around the value to match. Both Oga and Nokogiri use an extra call to concat but the following would be perfectly valid too:

foo[contains(concat(" ", @numbers, " "), " 1 ")]

Starts-with Operator

This operator matches elements of which the attribute value starts exactly with the given value. For example, foo[numbers^="1"] would match the element <foo numbers="1 2 3" /> but not the element <foo numbers="2 3 1" />.

For foo[numbers^="1"] the corresponding XPath expression is as following:

foo[starts-with(@numbers, "1")]

Ends-with Operator

This operator matches elements of which the attribute value ends exactly with the given value. For example, foo[numbers$="3"] would match the element <foo numbers="1 2 3" /> but not the element <foo numbers="2 3 1" />.

The corresponding XPath expression is quite complex due to a lack of a ends-with function in XPath. Instead one has to resort to using the substring() function. As such the corresponding XPath expression for foo[bar$="baz"] is as following:

foo[substring(@bar, string-length(@bar) - string-length("baz") + 1, string-length("baz")) = "baz"]

Contains Operator

This operator matches elements of which the attribute value contains the given value. For example, foo[bar*="baz"] would match both <foo bar="bazzzz" /> and <foo bar="hello baz" />.

For foo[bar*="baz"] the corresponding XPath expression is as following:

foo[contains(@bar, "baz")]

Hyphen-starts-with Operator

This operator matches elements of which the attribute value is a hyphen separated list of values that starts exactly with the given value. For example, foo[numbers|="1"] matches <foo numbers="1-2-3" /> but not <foo numbers="2-1-3" />.

For foo[numbers|="1"] the corresponding XPath expression is as following:

foo[@numbers = "1" or starts-with(@numbers, concat("1", "-"))]

Note that this selector will also match elements such as <foo numbers="1- foo bar" />.

Syntax

The syntax of the various attribute selectors can be described as following:

# Strings are used for the attribute values

dquote = '"';
squote = "'";

string_dquote = dquote ^dquote* dquote;
string_squote = squote ^squote* squote;

string = string_dquote | string_squote;

# The `identifier` rule is the same as the one used for matching element
# names.
attr_test = identifier '[' space* identifier (space* '=' space* string)* space* ']';

Whitespace inside the brackets does not affect the behaviour of the selector.

Pseudo Classes

W3 chapter: http://www.w3.org/TR/css3-selectors/#structural-pseudos

Pseudo classes can be used to further narrow down elements besides just their names and attribute values. In essence they are a combination of XPath function calls and axes. Some pseudo classes can take an argument to alter their behaviour.

Pseudo classes are often applied to element selectors. For example:

foo:bar

Here :bar would be a pseudo class applied to the foo element. Some pseudo classes (e.g. the :root pseudo class) can also be used on their own, for example:

:root

:root

The :root pseudo class selects an element only if it's the top-level element in a document.

Example XML:

<root>
    <foo />
</root>

Using the CSS expression root foo:root we'd get an empty set as the <foo> element is not the root element. On the other hand, root:root would return a set containing only the <root> element.

This selector can both be applied to an element selector as well as being used on its own.

For the selector foo:root the corresponding XPath expression is as following:

foo[not(parent::*)]

For :root the XPath expression is:

*[not(parent::*)]

:nth-child(n)

The :nth-child(n) pseudo class can be used to select a set of elements based on their position or an interval, skipping elements that occur in a set before the given position or interval.

In the form :nth-child(n) the identifier n is an argument that can be used to specify one of the following:

  1. A literal node set index
  2. A node interval used to match every N nodes
  3. A node interval plus an initial offset

The first element in a node set for :nth-child() is located at position 1, not position 0 (unlike most programming languages). As a result :nth-child(1) matches the first element, not the second. This can be visualized as following:

           :nth-child(2)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 |   | | X | |   | |   | |   | |   |
 +---+ +---+ +---+ +---+ +---+ +---+

Besides using a literal index argument you can also use an interval, optionally with an offset. This can be used to for example match every 2nd element, or every 2nd element starting at element number 4.

The syntax of this argument is as following:

integer  = ('+' | '-')* [0-9]+;
interval = ('n' | '-n' | integer 'n') integer;

Here interval would match any of the following:

n
-n
2n
2n+5
2n-5
-2n+5
-2n-5

Due to integer also matching the + and - it will be part of the same token. If this is not desired the following grammar can be used instead:

integer  = [0-9]+;
modifier = '+' | '-';
interval = ('n' | '-n' | modifier* integer 'n') modifier integer;

To match every 2nd element you'd use the following:

           :nth-child(2n)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 |   | | X | |   | | X | |   | | X |
 +---+ +---+ +---+ +---+ +---+ +---+

To match every 2nd element starting at element 1 you'd instead use this:

          :nth-child(2n+1)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 | X | |   | | X | |   | | X | |   |
 +---+ +---+ +---+ +---+ +---+ +---+

As mentioned the +1 in the above example is the initial offset. This is however only the case if the second number is positive. That means that for :nth-child(2n-2) the offset is not -2. When using a negative offset the actual offset first has to be calculated. When using an argument in the form of An-B we can calculate the actual offset as following:

offset = A - (B % A)

For example, for the selector :nth-child(2n-2) the formula would be:

offset = 2 - (-2 % 2) # => 2

This would result in the selector :nth-child(2n+2).

As an another example, for the selector :nth-child(2n-5) the formula would be:

offset = 2 - (-5 % 2) # => 1

Which would result in the selector :nth-child(2n+1)

To ease the process of selecting even and uneven elements you can also use even and odd as an argument. Using :nth-child(even) is the same as :nth-child(2n) while using :nth-child(odd) in turn is the same as :nth-child(2n+1).

Using :nth-child(n) simply matches all elements in the set. Using :nth-child(-n) doesn't match any elements, though Oga treats it the same as :nth-child(n).

Expressions such as :nth-child(-n-5) are invalid as both parts of the interval (-n and -5) are a negative. However, :nth-child(-n+5) is perfectly valid and would match the first 5 elements in a set:

         :nth-child(-n+5)

  1     2     3     4     5     6
+---+ +---+ +---+ +---+ +---+ +---+
| X | | X | | X | | X | | X | |   |
+---+ +---+ +---+ +---+ +---+ +---+

Using :nth-child(n+5) would match all elements starting at element 5:

                     :nth-child(n+5)

  1     2     3     4     5     6     7     8     9    10
+---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+
|   | |   | |   | |   | | X | | X | | X | | X | | X | | X |
+---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+

To summarize:

:nth-child(n)    => matches all elements
:nth-child(-n)   => matches nothing, though Oga treats it the same as "n"
:nth-child(5)    => matches element #5
:nth-child(2n)   => matches every 2 elements
:nth-child(2n+2) => matches every 2 elements, starting at element 2
:nth-child(2n-2) => matches every 2 elements, starting at element 1
:nth-child(n+5)  => matches all elements, starting at element 5
:nth-child(-n+5) => matches the first 5 elements
:nth-child(even) => matches every 2nd element, starting at element 2
:nth-child(odd)  => matches every 2nd element, starting at element 1

The corresponding XPath expressions are quite complex and differ based on the interval argument used. For the various forms the corresponding XPath expressions are as following:

:nth-child(n)    => *[((count(preceding-sibling::*) + 1) mod 1) = 0]
:nth-child(-n)   => *[((count(preceding-sibling::*) + 1) mod 1) = 0]
:nth-child(5)    => *[count(preceding-sibling::*) = 4]
:nth-child(2n)   => *[((count(preceding-sibling::*) + 1) mod 2) = 0]
:nth-child(2n+2) => *[(count(preceding-sibling::*) + 1) >= 2 and (((count(preceding-sibling::*) + 1) - 2) mod 2) = 0]
:nth-child(2n-6) => *[(count(preceding-sibling::*) + 1) >= 2 and (((count(preceding-sibling::*) + 1) - 2) mod 2) = 0]
:nth-child(n+5)  => *[(count(preceding-sibling::*) + 1) >= 5 and (((count(preceding-sibling::*) + 1) - 5) mod 1) = 0]
:nth-child(-n+6) => *[((count(preceding-sibling::*) + 1) <= 6) and (((count(preceding-sibling::*) + 1) - 6) mod 1) = 0]
:nth-child(even) => *[((count(preceding-sibling::*) + 1) mod 2) = 0]
:nth-child(odd)  => *[(count(preceding-sibling::*) + 1) >= 1 and (((count(preceding-sibling::*) + 1) - 1) mod 2) = 0]

:nth-last-child(n)

The :nth-last-child(n) pseudo class can be used to select a set of elements based on their position or an interval, skipping elements that occur in a set after the given position or interval.

The arguments that can be used by this selector are the same as those mentioned in :nth-child(n).

Because this selectors matches in reverse (compared to :nth-child(n)) using an index such as "1" will match the last element in a set, not the first one:

           :nth-last-child(1)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 |   | |   | |   | |   | |   | | X | <- matching direction
 +---+ +---+ +---+ +---+ +---+ +---+

When using an interval (with or without an offset) the nodes are also matched in reverse order. However, matched nodes should be returned in the order they appear in in the document.

For example, the selector :nth-last-child(2n) would match as following:

           :nth-last-child(2n)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 | X | |   | | X | |   | | X | |   | <- matching direction
 +---+ +---+ +---+ +---+ +---+ +---+

The resulting set however would contain the nodes in the order [1, 3, 5] instead of [5, 3, 1].

When using an interval with an initial offset the offset is also applied in reverse order. For example, the selector :nth-last-child(2n) would match as following:

           :nth-last-child(2n+1)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 |   | | X | |   | | X | |   | | X | <- matching direction
 +---+ +---+ +---+ +---+ +---+ +---+

The corresponding XPath expressions are similar to those used for :nth-child(n):

:nth-last-child(n)    => *[count(following-sibling::*) = -1]
:nth-last-child(-n)   => *[count(following-sibling::*) = -1]
:nth-last-child(5)    => *[count(following-sibling::*) = 4]
:nth-last-child(2n)   => *[((count(following-sibling::*) + 1) mod 2) = 0]
:nth-last-child(2n+2) => *[((count(following-sibling::*) + 1) >= 2) and ((((count(following-sibling::*) + 1) - 2) mod 2) = 0)]
:nth-last-child(2n-6) => *[((count(following-sibling::*) + 1) >= 2) and ((((count(following-sibling::*) + 1) - 2) mod 2) = 0)]
:nth-last-child(n+5)  => *[((count(following-sibling::*) + 1) >= 5) and ((((count(following-sibling::*) + 1) - 5) mod 1) = 0)]
:nth-last-child(-n+6) => *[((count(following-sibling::*) + 1) <= 6) and ((((count(following-sibling::*) + 1) - 6) mod 1) = 0)]
:nth-last-child(even) => *[((count(following-sibling::*) + 1) mod 2) = 0]
:nth-last-child(odd)  => *[((count(following-sibling::*) + 1) >= 1) and ((((count(following-sibling::*) + 1) - 1) mod 2) = 0)]

:nth-of-type(n)

The :nth-of-type(n) pseudo class can be used to select a set of elements that has a set of preceding siblings with the same name. The arguments that can be used by this selector are the same as those mentioned in :nth-child(n).

The matching order of this selector is the same as :nth-child(n).

Example XML:

<root>
    <foo />
    <foo />
    <foo />
    <foo />
    <bar />
</root>

Using the CSS expression root foo:nth-of-type(even) would return a set containing the 2nd and 4th <foo> nodes.

The corresponding XPath expressions for the various forms of this pseudo class are as following:

:nth-of-type(n)    => *[position() = n]
:nth-of-type(-n)   => *[position() = -n]
:nth-of-type(5)    => *[position() = 5]
:nth-of-type(2n)   => *[(position() mod 2) = 0]
:nth-of-type(2n+2) => *[(position() >= 2) and (((position() - 2) mod 2) = 0)]
:nth-of-type(2n-6) => *[(position() >= 2) and (((position() - 2) mod 2) = 0)]
:nth-of-type(n+5)  => *[(position() >= 5) and (((position() - 5) mod 1) = 0)]
:nth-of-type(-n+6) => *[(position() <= 6) and (((position() - 6) mod 1) = 0)]
:nth-of-type(even) => *[(position() mod 2) = 0]
:nth-of-type(odd)  => *[(position() >= 1) and (((position() - 1) mod 2) = 0)]

:nth-last-of-type(n)

The :nth-last-of-type(n) pseudo class behaves the same as :nth-of-type(n) excepts it matches nodes in reverse order similar to :nth-last-child(n). To clarify, this means matching occurs as following:

           :nth-last-of-type(1)

   1     2     3     4     5     6
 +---+ +---+ +---+ +---+ +---+ +---+
 |   | |   | |   | |   | |   | | X | <- matching direction
 +---+ +---+ +---+ +---+ +---+ +---+

Example XML:

<root>
    <foo />
    <foo />
    <foo />
    <foo />
    <bar />
</root>

Using the CSS expression root foo:nth-of-type(even) would return a set containing the 1st and 3rd <foo> nodes.

The corresponding XPath expressions for the various forms of this pseudo class are as following:

:nth-last-of-type(n)    => *[position() = last() - -1]
:nth-last-of-type(-n)   => *[position() = last() - -1]
:nth-last-of-type(5)    => *[position() = last() - 4]
:nth-last-of-type(2n)   => *[((last() - position()+1) mod 2) = 0]
:nth-last-of-type(2n+2) => *[((last() - position()+1) >= 2) and ((((last() - position() + 1) - 2) mod 2) = 0)]
:nth-last-of-type(2n-6) => *[((last() - position()+1) >= 2) and ((((last() - position() + 1) - 2) mod 2) = 0)]
:nth-last-of-type(n+5)  => *[((last() - position()+1) >= 5) and ((((last() - position() + 1) - 5) mod 1) = 0)]
:nth-last-of-type(-n+6) => *[((last() - position()+1) <= 6) and ((((last() - position() + 1) - 6) mod 1) = 0)]
:nth-last-of-type(even) => *[((last() - position()+1) mod 2) = 0]
:nth-last-of-type(odd)  => *[((last() - position()+1) >= 1) and ((((last() - position() + 1) - 1) mod 2) = 0)]

:first-child

The :first-child pseudo class can be used to match a node that is the first child node of another node (= a node without any preceding nodes).

Example XML:

<root>
    <foo />
    <bar />
</root>

Using the CSS selector root :first-child would return a set containing only the <foo> node.

The corresponding XPath expression for this pseudo class is as following:

:first-child => *[count(preceding-sibling::*) = 0]

:last-child

The :last-child pseudo class can be used to match a node that is the last child node of another node (= a node without any following nodes).

Example XML:

<root>
    <foo />
    <bar />
</root>

Using the CSS selector root :last-child would return a set containing only the <bar> node.

The corresponding XPath expression for this pseudo class is as following:

:last-child => *[count(following-sibling::*) = 0]

:first-of-type

The :first-of-type pseudo class matches elements that are the first sibling of its type in the list of elements of its parent element. This selector is the same as :nth-of-type(1).

Example XML:

<root>
  <a id="1" />
  <a id="2">
    <a id="3" />
    <a id="4" />
  </a>
</root>

Using the CSS selector root a:first-of-type would return a node set containing nodes <a id="1"> and <a id="3"> as both nodes are the first siblings of their type.

The corresponding XPath for this pseudo class is as following:

a:first-of-type => a[count(preceding-sibling::a) = 0]

An alternative way is to use the following XPath:

a:first-of-type => //a[position() = 1]

This however relies on the less efficient descendant-or-self::node() selector. For querying larger documents it's recommended to use the first form instead.

:last-of-type

The :last-of-type pseudo class can be used to match elements that are the last sibling of its type in the list of elements of its parent. This selector is the same as :nth-last-of-type(1).

Example XML:

<root>
  <a id="1" />
  <a id="2">
    <a id="3" />
    <a id="4" />
  </a>
</root>

Using the CSS selector root a:last-of-type would return a set containing nodes <a id="2"> and <a id="4"> as both nodes are the last siblings of their type.

The corresponding XPath for this pseudo class is as following:

a:last-of-type => a[count(following-sibling::a) = 0]

Similar to :first-of-type this XPath can alternatively be written as following:

a:last-of-type => //a[position() = last()]

:only-child

The :only-child pseudo class can be used to match elements that are the only child element of its parent.

Example XML:

<root>
  <a id="1" />
  <a id="2">
    <a id="3" />
  </a>
</root>

Using the CSS selector root a:only-child would return a set containing only the <a id="3"> node.

The corresponding XPath for this pseudo class is as following:

a:only-child => a[count(preceding-sibling::*) = 0 and count(following-sibling::*) = 0]

:only-of-type

The :only-of-type pseudo class can be used to match elements that are the only child elements of its type of its parent.

Example XML:

<root>
  <a id="1" />
  <a id="2">
    <a id="3" />
    <b id="4" />
  </a>
</root>

Using the CSS selector root a:only-of-type would return a set containing only the <a id="3"> node due to it being the only <a> node in the list of elements of its parent.

The corresponding XPath for this pseudo class is as following:

a:only-child => a[count(preceding-sibling::a) = 0 and count(following-sibling::a) = 0]

:empty

The :empty pseudo class can be used to match elements that have no child nodes at all.

Example XML:

<root>
    <a />
    <b>10</b>
</root>

Using the CSS selector root :empty would return a set containing only the <a> node.

Syntax

The syntax of the various pseudo classes is as following:

integer = ('+' | '-')* [0-9]+;

odd  = 'odd';
even = 'even';
nth  = 'n';

pseudo_arg_interval = '-'* integer* nth;
pseudo_arg_offset   = ('+' | '-')* integer;

pseudo_arg = odd
  | even
  | '-'* nth
  | integer
  | pseudo_arg_interval
  | pseudo_arg_interval pseudo_arg_offset;

# The `identifier` rule is the same as the one used for element names.
pseudo = ':' identifier ('(' space* pseudo_arg space* ')')*;