zuhtml implements a deliberately small subset of CSS Selectors and rejects everything else with an error. A selector either works exactly as it would in a browser or fails loudly: it never matches something partly.
library(zuhtml)
doc <- html_parse('
<div id=main class="card wide">
<h2 class=title>One</h2>
<p class="a b" lang=en-US>first</p>
<p title="">second</p>
<ul><li>1<li>2<li>3<li>4</ul>
</div>')| Kind | Examples |
|---|---|
| Type and universal | p, * |
| ID and class | #main, .card.wide |
| Attributes | [title], [lang=en-US],
[class~=a], [lang\|=en],
[href^=http], [href$=".pdf"],
[href*=shop], with an i or s
flag |
| Combinators | div p, div > p, h2 + p,
h2 ~ p, and lists: h2, p |
| Pseudo-classes | :scope, :root, :empty,
:first-child, :last-child,
:only-child, :nth-child(an+b),
:nth-of-type(an+b), :not() of one compound
selector |
:has(), other pseudo-classes, pseudo-elements (including
Scrapy’s ::text and ::attr()), namespace
prefixes and the of S form of :nth-child() are
errors. The error says where:
try(html_elements(doc, "div:has(> h2)"))
#> Error in html_elements(doc, "div:has(> h2)") :
#> Invalid selector at position 4: :has() is not supported.
#> div:has(> h2)
#> ^
try(html_elements(doc, "p::text"))
#> Error in html_elements(doc, "p::text") :
#> Invalid selector at position 2: pseudo-elements are not supported.
#> p::text
#> ^Handle them by class, zuhtml_selector_error; the
condition carries the selector, the position and whether the form is
unsupported or malformed.
As in a browser on an HTML page: element and attribute names match
HTML elements whatever their case, and SVG or MathML elements exactly.
IDs and classes are case-sensitive. Attribute values are case-sensitive,
except for the attributes HTML lists as case-insensitive
(type, lang and others); an i or
s flag overrides either.
Searching a document includes its <html> element.
Searching below an element excludes the element itself, unless
:scope names it:
main <- html_element(doc, "#main")
length(html_elements(main, ".card"))
#> [1] 0
length(html_elements(main, ":scope.card"))
#> [1] 1
html_name(html_elements(main, ":scope > *"))
#> [1] "h2" "p" "p" "ul"The rest of a selector may match outside the context, as
querySelectorAll() does: "body h2" below
#main finds the <h2> even though
<body> is outside it.
html_elements() on several nodes returns the union of
their matches, without duplicates, in document order.
html_element() returns one result per input node, a missing
node where there is no match: use it to pull one field out of each of
many records.
<template> contents are never searched; reach them
with html_template_content().