######################################
Rewrite HTML without building a tree
######################################
Transform a document on the wire -- retag links, lazy-load images, strip trackers, redact text -- in a single pass that
never builds a tree and keeps only the open-element stack in memory. Reach for :func:`turbohtml.rewrite.rewrite` when
you want to *change* markup as it streams, the way Cloudflare's lol-html rewrites at the edge, rather than parse a whole
document to edit a few nodes. For observing without editing, use :doc:`sax`; to load a document you will navigate and
mutate freely, use :func:`turbohtml.parse`.
*****************
Edit attributes
*****************
Register a ``(selector, handler)`` pair under ``elements``. The handler is called with an
:class:`~turbohtml.rewrite.Element` for each match; set or remove attributes on it and the start tag is rebuilt, while
everything you do not touch is copied through verbatim:
.. testcode::
from turbohtml.rewrite import rewrite
def lazy(img):
img.set_attribute("loading", "lazy")
print(rewrite("
", elements=[("img", lazy)]))
.. testoutput::

Read the current value with ``get`` (it takes a default) or test presence with ``has_attribute``; ``tag`` and ``attrs``
expose the name and the ``(name, value)`` pairs.
**********************
Insert markup around
**********************
``before`` and ``after`` insert content just outside the element's tags. Content is HTML-escaped by default; pass
``html=True`` to insert raw markup:
.. testcode::
def spoiler(element):
element.before("spoiler
", html=True)
element.after("
ending
", elements=[("p.spoiler", spoiler)])) .. testoutput::ending
keep
", elements=[("font", unfont)])) .. testoutput::keep
***************************** Text, comments, and doctype ***************************** Beyond elements, pass ``text``, ``comments``, or ``doctype`` handlers. A text handler reads ``text`` and rewrites it with ``set_text``: .. testcode:: def shout(text): text.set_text(text.text.upper()) print(rewrite("hi
", comments=nocomment)) .. testoutput::hi
*************************** What selectors can stream *************************** Because the rewriter never looks ahead, only selectors decidable from an element and its ancestors match: type, universal, id, class, and attribute selectors, the descendant (space) and child (``>``) combinators, ``:root``, and :is()/:where()/:not() over that subset. A sibling combinator (``+``, ``~``), a positional or structural pseudo-class (``:nth-child``, ``:last-of-type``, ``:only-child``, ``:empty``), or ``:has()`` needs content the stream has not reached yet and raises :class:`~turbohtml.SelectorSyntaxError`. :doc:`/explanation/streaming` explains why, and what to do when you need one of them.