<moonxml/>

the selector board

Every selector, run
against one document.

These tables are not examples of what the engines should answer — they are what they answered. Each row is a query, the number of nodes it returned, and those nodes, printed by a program that ran against the document in the box below.

88 rows· CSS Selectors· XPath 1.0· the chain
01

The document

the specimen

A page, not a form. The fields take typing, the boxes tick, the list opens — the controls work, and they submit nothing. They are here to be selected.

the source

Under the box, the document in full: the bytes the engine read to make these tables. Parse it with @html.parse and every answer below falls out again.

the answers

Sections 02 to 07 give the selectors, the query as written, the nodes it answered, and those nodes. Section 06 adds what a node yields when read from — its text, its attribute, its markup, its typed XPath value.

the article, rendered42 elements · 88 queries below

Field Notes

Coffee, tea, and the machinery of both

  • Yirgacheffe
  • Huila
  • Sencha
  • Assam
  • House
Per 250 g
ItemPrice
Huila22
Sencha9

Prices in USD, roasted weekly.

Notes

One shipment a week.

the document, in full

<!DOCTYPE html>
<html lang="en">
<head>
<title>Field Notes</title>
</head>
<body>
<article id="shop" class="catalogue" data-region="eu">
  <header id="masthead" class="masthead">
    <h1>Field Notes</h1>
    <p class="dek">Coffee, tea, and the machinery of both</p>
  </header>
  <ul id="items" class="list">
    <li class="item" data-kind="bean" data-price="18">Yirgacheffe</li>
    <li class="item" data-kind="bean" data-price="22" lang="en-GB">Huila</li>
    <li class="item sold" data-kind="leaf" data-price="9">Sencha</li>
    <li class="item" data-kind="leaf" data-price="14">Assam</li>
    <li class="item" data-kind="blend" data-price="16"><a href="https://example.com/b/1">House</a></li>
  </ul>
  <form id="order" action="/order" method="post">
    <label for="qty">Quantity</label>
    <input id="qty" name="qty" type="number" value="2" required>
    <input id="gift" name="gift" type="checkbox" disabled>
    <select name="grind">
      <option value="fine">Fine</option>
      <option value="coarse" selected>Coarse</option>
    </select>
    <button id="send" type="submit" class="btn primary">Order</button>
    <span id="hollow" class="flag"></span>
  </form>
  <table id="prices">
    <caption>Per 250 g</caption>
    <thead><tr><th>Item</th><th>Price</th></tr></thead>
    <tbody>
      <tr data-band="high"><td>Huila</td><td>22</td></tr>
      <tr data-band="low"><td>Sencha</td><td>9</td></tr>
    </tbody>
  </table>
  <p class="note">Prices in <abbr title="United States dollars">USD</abbr>, roasted weekly.</p>
  <section id="aside">
    <h2>Notes</h2>
    <p>One shipment a week.</p>
  </section>
</article>
</body>
</html>

Hover a query — or click to pin it, Esc to let go — and the nodes it answered light up in the document above. The page does not run the query again: it follows the routes the engine answered with.

A node is labelled tag#id.class "text" when it is an element, and #text "…" when it is not — #text (blank) is the whitespace between two tags, which is a node like any other. The head, title and html rows select from the whole document, not from the article shown.

02

The simple selectors

Type, the universal, class, id, and every attribute form: present, equal, a word, a prefix, a suffix, a substring, a language, and the case-sensitivity flag. A selector list answers the union, in document order.

QueryNodesAnswer
*42html · head "Field Notes" · title "Field Notes" · body · article#shop.catalogue · +37
li5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
.item5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
.sold1li.item "Sencha"
#items1ul#items.list
[data-price]5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
[data-price="18"]1li.item "Yirgacheffe"
[data-price^="1"]3li.item "Yirgacheffe" · li.item "Assam" · li.item "House"
[data-price$="2"]1li.item "Huila"
[data-price*="4"]1li.item "Assam"
[lang|="en"]2html · li.item "Huila"
[class~="sold"]1li.item "Sencha"
[data-kind="BEAN" i]2li.item "Yirgacheffe" · li.item "Huila"
[data-kind="BEAN" s]0nothing
[data-kind="bean"]2li.item "Yirgacheffe" · li.item "Huila"
li, td9li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House" · +4
03

Combinators

Descendant, child, the adjacent sibling and the general one. A compound on either side works the same way, so the last row here is a chain of three.

QueryNodesAnswer
#items > li5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
#items li5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
li + li4li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
li ~ li4li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
#shop > ul > li5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
#prices tr > td:last-child2td "22" · td "9"
04

Pseudo-classes

The ones a document can settle: position among siblings, the functionals — :not, :is, :where, :has — and the form states. :hover and :visited are not here because they need a browser, and the engine refuses them by name rather than answering wrongly.

QueryNodesAnswer
li:first-child1li.item "Yirgacheffe"
li:last-child1li.item "House"
li:nth-child(2)1li.item "Huila"
li:nth-child(odd)3li.item "Yirgacheffe" · li.item "Sencha" · li.item "House"
li:nth-child(3n+1)2li.item "Yirgacheffe" · li.item "Assam"
li:nth-last-child(1)1li.item "House"
td:nth-of-type(2)2td "22" · td "9"
p:first-of-type3p.dek · p.note · p "One shipment a week."
td:last-of-type2td "22" · td "9"
#aside > p:only-of-type1p "One shipment a week."
:root1html
#hollow:empty1span#hollow.flag
option:checked1option "Coarse"
input:disabled1input#gift
input:enabled1input#qty
input:required1input#qty
input:optional1input#gift
li > a:only-child1a "House"
td:nth-last-of-type(1)2td "22" · td "9"
li:lang(en-GB)1li.item "Huila"
li:lang(en)5li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House"
a:link1a "House"
a:any-link1a "House"
li:not(.sold)4li.item "Yirgacheffe" · li.item "Huila" · li.item "Assam" · li.item "House"
li:is(.sold, [data-kind="blend"])2li.item "Sencha" · li.item "House"
:where(.item, .btn)6li.item "Yirgacheffe" · li.item "Huila" · li.item "Sencha" · li.item "Assam" · li.item "House" · button#send.btn "Order"
#order:has(option[selected])1form#order "Quantity Fine Coarse Order"
li:has(> a)1li.item "House"
li:has(+ li.sold)1li.item "Huila"
05

The chain

Every query answers a Selection, so queries compose: narrow a selection again, switch engine mid-flight, take the first or the last, step to the parent, to the children, to the nearest ancestor. pricey in one of these rows is a selector registered at run time.

QueryNodesAnswer
page.css("#items").css("li[data-kind=leaf]")2li.item "Sencha" · li.item "Assam"
page.css("tbody tr").xpath("./td[2]")2td "22" · td "9"
page.css("li").first()1li.item "Yirgacheffe"
page.css("li").last()1li.item "House"
page.css("li").eq(2)1li.item "Sencha"
page.css("td").parent()2tr "Huila22" · tr "Sencha9"
page.css("#prices").children()7#text (blank) · caption "Per 250 g" · #text (blank) · thead "ItemPrice" · #text (blank) · +2
page.css("abbr").closest("p")1p.note
page.by_id("order")1form#order "Quantity Fine Coarse Order"
page.by_tag("option")2option "Fine" · option "Coarse"
page.by_class("sold")1li.item "Sencha"
page.by_name("grind")1select "Fine Coarse"
page.selector("pricey", "")3li.item "Yirgacheffe" · li.item "Huila" · li.item "House"
page.css("li").items[4].css("a")1a "House"
xpathpage.xpath("//li[last()]")1li.item "House"
xpathpage.xpath("//tr[td[2] > 15]")1tr "Huila22"
06

Values and attributes

A selection answers the text it holds, the attribute it was asked for, and the markup inside it; xpath_value answers a count, a string, a node-set or a boolean, typed. Note that text() on a group concatenates — the library does not guess where one node's text ends.

QueryNodesAnswer
textpage.css("#items li").text()—YirgacheffeHuilaSenchaAssamHouse
textpage.css("li.sold").text()—Sencha
attrpage.css("li.sold").attr("data-price")—9
attrpage.css("li").attr("data-kind")—bean
attrpage.css("abbr").attr("title")—United States dollars
attrpage.css("option[selected]").attr("value")—coarse
attrpage.css("#prices tbody tr").attr("data-band")—high
htmlpage.css("#items li").last().html()—<a href="https://example.com/b/1">House</a>
htmlpage.css("#order").html()—<label for="qty">Quantity</label> <input id="qty" name="qty" type="number" value="2" required> <input id="gift" name="gift" type="checkbox" disabled> <select name="grind"> <option value="fine">Fine</option> <option value="coarse" selected>Coarse</option> </select> <button id="send" type="submit" class="btn primary">Order</button> <span id="hollow" class="flag"></span>
xpathpage.css("#items").xpath("./li/@data-price")—182291416
xpathpage.xpath_value("count(//li)")—5
xpathpage.xpath_value("string(//abbr/@title)")—United States dollars
xpathpage.xpath_value("//li[@data-price > 15]")—3 nodes
xpathpage.xpath_value("//li[@data-price > 15] and count(//li) = 5")—true
07

One node, seven ways

Seven queries, two engines, one element. This is the point of a single tree: the node does not know or care which query found it.

QueryNodesAnswer
#order1form#order "Quantity Fine Coarse Order"
form1form#order "Quantity Fine Coarse Order"
form[action="/order"]1form#order "Quantity Fine Coarse Order"
#shop > form1form#order "Quantity Fine Coarse Order"
article :is(form)1form#order "Quantity Fine Coarse Order"
xpath//form1form#order "Quantity Fine Coarse Order"
xpath//*[@action]1form#order "Quantity Fine Coarse Order"
08

What is not on this board

:hover, :active, :focus, :visited and :target describe a browser's state, not a document's, so the engine refuses them by name — @moonxml.Refused.BadQuery with the offset — instead of answering a question it cannot see. Everything a tree can settle is above; page.css("a:hover") is a sentence the library will not pretend to understand.

❦