Match elements the soupsieve way

turbohtml.query exposes turbohtml’s CSS engine in the call shapes of soupsieve, BeautifulSoup’s selector library. Porting a soupsieve (or bs4 Tag.select) codebase is then an import swap: import soupsieve becomes from turbohtml import query, and the calls keep their shape while running on the native engine.

Compile once, reuse

compile() validates the selector up front and returns a Matcher you reuse across trees. Its methods mirror soupsieve’s select / select_one / iselect / match / filter / closest:

from turbohtml import parse, query

doc = parse('<ul><li class="on"><a href="/a">a</a></li><li><a href="/b">b</a></li></ul>')
links = query.compile("li.on a[href]")
print([a.attr("href") for a in links.select(doc)])
print(links.select_one(doc).attr("href"))
['/a']
/a

select takes a limit (0 means all), iselect yields lazily, match tests one element, closest walks up to the nearest matching ancestor, and filter keeps the matching members of an iterable (or, given one element or document, its direct element children):

anchor = links.select_one(doc)
print(query.compile("li").closest(anchor).attr("class"))
print([a.attr("href") for a in query.compile("[href]").filter(doc.select("a"))])
on
['/a', '/b']

One-shot helpers

The module-level functions take (selector, node, ...) and compile internally, matching soupsieve’s free functions for a quick call:

print([a.attr("href") for a in query.select("a[href]", doc, limit=1)])
print(query.match("a", query.select_one("a", doc)))
['/a']
True

Building selectors from data

escape_identifier() turns an arbitrary string into a safe CSS identifier, so an id or class read from data cannot break out of the selector:

raw_id = "12 col"
print(query.escape_identifier(raw_id))
page = parse('<p id="12 col">found</p>')
print(query.select_one(f"#{query.escape_identifier(raw_id)}", page).text)
\31 2\ col
found

A malformed selector raises SelectorSyntaxError (a ValueError subclass, so soupsieve’s exception name and the native engine’s ValueError both catch it). soupsieve’s namespaces and flags arguments are carried on a Matching config for API parity but do not change which elements match – see the reference for that limitation.