Learn R Programming

selectr (version 0.7-0)

selectors: Which CSS selectors selectr supports, and what they translate to

Description

A reference table of every combinator, simple selector, attribute operator and pseudo-class selectr recognises: whether it is supported, restricted to the html/xhtml translators, never matches (a static-document limitation), or is rejected as an error, plus the XPath a representative selector translates to. This complements the prose in css_to_xpath, which explains why the divergences from CSS Selectors Level 4 below exist; this page is the flat list to check "is X supported?" against.

Every example on this page is exercised by tests/testthat/test-selectors-reference.R against a live css_to_xpath call, and that test also asserts every xpath_*_pseudo, xpath_*_function and xpath_*_combinator method of GenericTranslator and HTMLTranslator is represented on this page, so this table cannot silently drift from the translators' code.

Arguments

Combinators

SelectorMeaningExample XPath ("e ? f", generic)
e fdescendantdescendant-or-self::e//f
e > fchilddescendant-or-self::e/f
e + fdirect adjacent siblingdescendant-or-self::e/following-sibling::*[1][self::f]
e ~ findirect (general) siblingdescendant-or-self::e/following-sibling::f
e || fcolumn (Selectors 4)error - not supported, see below

Simple selectors

SelectorMeaningExample XPath
*universaldescendant-or-self::*
etype (no namespace)descendant-or-self::e
.classclass...[contains(concat(' ', normalize-space(@class), ' '), ' class ')]
#idIDdescendant-or-self::*[@id = 'id']

Class matching splits @class on XML whitespace (space/tab/CR/LF) via normalize-space(); HTML's own "set of space-separated tokens" also treats U+000C form feed as a separator, which this does not - negligible in practice, since form feed in a class attribute is vanishingly rare.

Attribute selectors

SelectorMeaningExample XPath ("[attr ? val]")
[attr]has attributedescendant-or-self::*[@attr]
[attr=val]equalsdescendant-or-self::*[@attr = 'val']
[attr~=val]includes a whitespace-separated token...[contains(concat(' ', normalize-space(@attr), ' '), ' val ')]
[attr|=val]equals, or a "val-" prefix...[@attr = 'val' or starts-with(@attr, 'val-')]
[attr^=val]starts withdescendant-or-self::*[starts-with(@attr, 'val')]
[attr$=val]ends with...[substring(@attr, string-length(@attr)-2) = 'val']
[attr*=val]contains substringdescendant-or-self::*[contains(@attr, 'val')]

Every operator above accepts a trailing Selectors 4 case-sensitivity flag, i (ASCII case-insensitive) or s (case-sensitive, the default and so a no-op): [attr=val i] translates to ...[translate(@attr, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = 'val']. Any other flag is a parse error.

Under the html translator, the attributes HTML lists as ASCII case-insensitive (type, rel, lang, hreflang, dir, media, method, target, checked, disabled, readonly, selected, multiple, shape, scope, align, charset, http-equiv, enctype, accept and the rest of HTML's "Case-sensitivity of selectors" list) match their values that way with no flag written, as they do in a browser: input[type=radio] selects <input type="RADIO">. An explicit s restores the exact comparison. class, id, href, name, data-* and every namespaced attribute keep it, and so does every attribute under the generic and xhtml translators, which serve XML rather than HTML documents.

Structural pseudo-classes

SelectorStatusExample XPath
:rootsupporteddescendant-or-self::*[not(parent::*)]
:first-childsupported...[count(preceding-sibling::*) = 0]
:last-childsupported...[count(following-sibling::*) = 0]
:only-childsupportedpreceding- and following-sibling counts both 0
e:first-of-typesupported (needs a named element)...e[count(preceding-sibling::e) = 0]
e:last-of-typesupported (needs a named element)...e[count(following-sibling::e) = 0]
e:only-of-typesupported (needs a named element)preceding- and following-sibling e counts both 0
*:first-of-type etc.errorneeds each sibling's own name; not expressible in XPath 1.0
:nth-child(An+B)supported:nth-child(2n+1) gives ...[count(preceding-sibling::*) mod 2 = 0]; a B outside the first cycle adds a >= B-1 bound and an offset in the mod
:nth-child(An+B of S)supportedpreceding-siblings filtered to S, plus self:: test
:nth-last-child()supportedas :nth-child(), counting from the end
e:nth-of-type(), e:nth-last-of-type()supported (needs a named element)as above, restricted to siblings named e
:emptysupported, Selectors 3 semantics...[not(*) and not(string-length())] - see "Divergences" below
:scopesupported, leading position onlyself::* (replaces the prefix); errors elsewhere in a selector

Selector-list pseudo-classes

SelectorMeaningExample XPath
:not(e)none of the arguments matchdescendant-or-self::*[not(self::e)]
:is(e, f) (alias :matches())any argument matchesdescendant-or-self::*[self::e or self::f]
:where(e, f)any argument matches (zero specificity)same XPath as :is()
:has(> e)a descendant/relative match existsdescendant-or-self::*[child::e]

Each accepts a full selector list, but a :scope inside any of them is an error (see :scope above), and the column combinator inside them is likewise unsupported.

Linguistic and directionality pseudo-classes

SelectorStatusNotes
:lang(range)supported, translator-dependentgeneric: XPath lang(), prefix match only. html/xhtml: RFC 4647 extended filtering for multi-subtag ranges (subtags may be skipped between the ones named, but a leading * consumes the tag's primary subtag, so :lang(*-CH) does not match lang="ch-DE"). Every translator rejects a range that is not an RFC 4647 extended language range (:lang(en-), :lang(en*)); see css_to_xpath for the full rules
:lang("")supported, every translatormatches an element with no content language anywhere in its ancestor-or-self chain
:dir()never matches, every translatordescendant-or-self::*[0]; directionality needs a live DOM (dir="auto", bdi, form controls)

Link and interaction-state pseudo-classes

Selectorgenerichtml / xhtml
:link, :any-linknever matchesmatches a and area elements with an href (a link element is metadata, not a hyperlink)
:visitednever matchesnever matches (no browser history in a static document)
:hover, :active, :focus, :focus-within, :focus-visiblenever matchesnever matches (runtime UI state)
:target, :target-withinnever matchesnever matches (needs the document's URL fragment)
:local-linknever matchesnever matches (needs the document's URL)

"Never matches" translates to descendant-or-self::*[0]: valid CSS, always zero results, rather than an error. In a larger compound the always-false 0 absorbs the compound's other conditions, which cannot change the outcome, so "a.external:visited" translates to descendant-or-self::a[0] as well.

HTML form-state pseudo-classes

These are only meaningfully supported by the html and xhtml translators (under generic they never match, listed above as the general runtime-state case). Every one matches by local name regardless of namespace, so "*|input:disabled" works the same as "input:disabled" on an unnamespaced document. :enabled and :disabled match only the elements listed below - in particular a hyperlink is not :enabled; use :link or :any-link for links.

SelectorElements and condition (html/xhtml)
:enabled / :disabledbutton, input, select, textarea, optgroup, option, fieldset; a disabled ancestor fieldset disables descendants (nested fieldsets included) except inside its first legend, and a disabled select or optgroup disables the optgroups and options below it
:checkedchecked checkbox/radio inputs and selected options; does not infer the implicit default selection of an unadorned single-select or radio group
:required / :optionalinput of a type that takes required (every type but hidden, range, color, submit, image, reset and button), select, textarea, by presence of required
:read-writean input of a type that takes readonly (every type but hidden, color, checkbox, radio, file, submit, image, reset, button and range) or a textarea, that is not readonly/disabled; or an element whose nearest contenteditable ancestor-or-self is not "false" (only "", "true", "plaintext-only" and "false" set the state - "inherit" and unrecognised values inherit)
:read-onlythe negation of :read-write (matches everything else, e.g. a checkbox or a plain div)
:placeholder-showntextarea, or an input of a type that takes placeholder (every type but hidden, checkbox, radio, file, submit, image, reset, button, color, range, date, month, week, time and datetime-local), with a non-empty placeholder and an empty current value
:defaulta selected option, a checked checkbox/radio, or the first submit button in its nearest ancestor form (does not follow a form= attribute)

Because HTML's type is an enumerated attribute, these match its keywords ASCII case-insensitively (<input type="RADIO"> is :checked). An input with no type, or with an unrecognised one, is in the text state, as it is for an HTML parser.

Column combinator and pseudo-classes (unsupported)

The Selectors 4 column combinator (a || b) and the column pseudo-classes :nth-col() / :nth-last-col() are rejected with an error: which column a cell belongs to depends on colspan/rowspan table-layout arithmetic that XPath 1.0 cannot express.

Namespaces

SelectorMeaningExample XPath ("? p")
pp in no namespacedescendant-or-self::p
d|pp in the namespace prefix d resolves to via the ns mapdescendant-or-self::d:p
*|pp in any namespacedescendant-or-self::*[local-name() = 'p']
|pp in no namespace, spelled explicitlydescendant-or-self::p

Prefixes such as d above are resolved through the ns argument passed to xml_find_all / getNodeSet at query time, not through whatever prefix the document itself uses; see querySelectorAll. A prefix is written into the generated XPath as it stands, so it has to be a name XPath can parse (an XML NCName, which is not restricted to ASCII); one that is not, such as the escaped \31 ns|div, is rejected with an error rather than compared against the document's own prefix. An escaped * (\2a|div) is such a prefix too: only the delimiter * of *|p above is the any-namespace wildcard, and a prefix spelled by an identifier is one no @namespace rule could have bound. Local names carry no such restriction: one that cannot be written as a name test is compared with local-name() instead, e.g. d|\31 becomes d:*[local-name() = '1']. HTMLTranslator additionally lower-cases every element and attribute name - folding A-Z only, as an HTML parser does, so a non-ASCII name is left as written - including namespaced ones, so svg|linearGradient becomes svg:lineargradient, which matches libxml2's HTML parser but would be wrong against a tree that restores camelCase SVG/MathML names (browsers, html5ever).

Divergences from CSS Selectors Level 4

  1. :empty keeps Selectors 3 semantics: an element containing only white space, e.g. <p> </p>, does not match. Selectors 4 loosened this to also match white-space-only content, but no browser has shipped that change, so it is treated as not implemented, tracking browser behaviour rather than the spec text.

  2. :checked tests only @checked/@selected. It does not infer the implicit selectedness of an option with no selected attribute anywhere in its select (the first option is selected by default), nor a radio group's mutual exclusivity - both need a live DOM to resolve.

  3. HTMLTranslator lower-cases foreign-content element and attribute names unconditionally, targeting libxml2-style HTML trees (see "Namespaces" above); an HTML5 parser that restores camelCase SVG/MathML names would disagree.

  4. Class/token matching (.foo, [attr~=val]) does not treat U+000C form feed as whitespace, unlike HTML's ASCII whitespace definition (see "Simple selectors" above).

Author

Simon Potter

References

CSS Selectors Level 4 https://www.w3.org/TR/selectors-4/, XPath https://www.w3.org/TR/xpath/.

See Also

css_to_xpath for the full prose explanation of each divergence above and the error classes raised for unsupported selectors; querySelectorAll for namespace and chaining semantics when querying a document.