Transforming without a full tree¶
Not every job needs a navigable tree. Sometimes you want to change markup as it streams, reshape one document into another, or check a document against a contract. This tutorial runs a streaming rewrite, an XSLT transform, and a schema validation – three transforms that never ask you to walk a tree by hand.
Rewrite as it streams¶
turbohtml.rewrite.rewrite() transforms a document in one pass that never builds a tree, the way Cloudflare’s
lol-html rewrites at the edge. Register a (selector, handler) pair; the handler receives each match and edits it,
while everything you do not touch is copied through. Here every image gets loading="lazy":
from turbohtml.rewrite import rewrite
def lazy(img):
img.set_attribute("loading", "lazy")
print(rewrite("<img src=hero.jpg><img src=x.png loading=eager>", elements=[("img", lazy)]))
<img src="hero.jpg" loading="lazy"><img src="x.png" loading="lazy">
Because the rewriter never looks ahead, only selectors decidable from an element and its ancestors match; Rewrite HTML without building a tree lists which, and The streaming rewrite model explains why.
Transform with XSLT¶
To reshape one document into another, apply an XSLT 1.0 stylesheet, the job lxml’s etree.XSLT does. Parse the
stylesheet with turbohtml.parse_xml(), wrap it in turbohtml.transform.Transform, and call it on a source:
from turbohtml import parse_xml
from turbohtml.transform import Transform
style = parse_xml(
'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">'
'<xsl:output method="html"/>'
'<xsl:template match="/"><ul>'
'<xsl:apply-templates select="list/item"/></ul></xsl:template>'
'<xsl:template match="item"><li><xsl:value-of select="."/></li></xsl:template>'
"</xsl:stylesheet>"
)
print(Transform(style)(parse_xml("<list><item>one</item><item>two</item></list>")))
<ul><li>one</li><li>two</li></ul>
Validate against a schema¶
When the input is XML with a contract, check it with turbohtml.validate.XMLSchema or
RelaxNG. Compile once, then validate; the result carries a valid flag and one
ValidationError per violation, each with the path that located it:
from turbohtml import parse_xml
from turbohtml.validate import XMLSchema
schema = XMLSchema(
'<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">'
'<xs:element name="order"><xs:complexType><xs:sequence>'
'<xs:element name="sku" type="xs:string"/>'
'<xs:element name="qty" type="xs:positiveInteger"/>'
"</xs:sequence></xs:complexType></xs:element></xs:schema>"
)
result = schema.validate(parse_xml("<order><sku>A-1</sku><qty>0</qty></order>"))
print(result.valid)
print(result.errors[0].path, result.errors[0].type)
False
/order/qty datatype
Stream-edit, transform, validate – three ways to move a document forward without materializing a tree to walk. Last, Doing a one-off job from the shell runs these jobs straight from a shell.