################################## Match elements the soupsieve way ################################## :mod:`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 ********************* :func:`~turbohtml.query.compile` validates the selector up front and returns a :class:`~turbohtml.query.Matcher` you reuse across trees. Its methods mirror soupsieve's ``select`` / ``select_one`` / ``iselect`` / ``match`` / ``filter`` / ``closest``: .. testcode:: from turbohtml import parse, query doc = parse('') links = query.compile("li.on a[href]") print([a.attr("href") for a in links.select(doc)]) print(links.select_one(doc).attr("href")) .. testoutput:: ['/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): .. testcode:: 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"))]) .. testoutput:: 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: .. testcode:: print([a.attr("href") for a in query.select("a[href]", doc, limit=1)]) print(query.match("a", query.select_one("a", doc))) .. testoutput:: ['/a'] True ****************************** Building selectors from data ****************************** :func:`~turbohtml.query.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: .. testcode:: raw_id = "12 col" print(query.escape_identifier(raw_id)) page = parse('

found

') print(query.select_one(f"#{query.escape_identifier(raw_id)}", page).text) .. testoutput:: \31 2\ col found A malformed selector raises :class:`~turbohtml.SelectorSyntaxError` (a :class:`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 :class:`~turbohtml.query.Matching` config for API parity but do not change which elements match -- see :doc:`the reference ` for that limitation.