Transform XML with XSLT

You have an XML document and an XSLT 1.0 stylesheet, and you want the transformed output. Parse both, then apply the stylesheet.

Apply a stylesheet

A stylesheet is itself XML, so read it with turbohtml.parse_xml(); the source is any parsed tree. Wrap the stylesheet in turbohtml.transform.Transform and call it with the 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"><xsl:sort select="."/></xsl:apply-templates>'
    "</ul></xsl:template>"
    '<xsl:template match="item"><li><xsl:value-of select="."/></li></xsl:template>'
    "</xsl:stylesheet>"
)
convert = Transform(style)
print(convert(parse_xml("<list><item>pear</item><item>apple</item></list>")))
<ul><li>apple</li><li>pear</li></ul>

Transform compiles the stylesheet once; call it on as many documents as you like. For a one-off, turbohtml.transform.transform() does both steps in a single call.

Pass parameters

A top-level xsl:param becomes a keyword argument. Each value is an XPath expression, so quote a string literal the way lxml does:

style = parse_xml(
    '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">'
    '<xsl:output method="text"/>'
    '<xsl:param name="greeting" select="\'Hello\'"/>'
    '<xsl:template match="/"><xsl:value-of select="$greeting"/>, <xsl:value-of select="//name"/></xsl:template>'
    "</xsl:stylesheet>"
)
doc = parse_xml("<r><name>World</name></r>")
print(Transform(style)(doc, greeting="'Hi'"))
Hi, World

Choose a method

The stylesheet’s xsl:output method picks the serialization: xml (the default, with a leading declaration unless omit-xml-declaration="yes"), html (void elements stay open), or text (character data only). A malformed stylesheet, a bad expression, or a reference to an undeclared key or template raises ValueError; an xsl:message with terminate="yes" raises RuntimeError.

Import a stylesheet

xsl:import composes stylesheets across files. Because a parsed tree carries no location, pass the importing stylesheet’s path as base_url so each href resolves against it; the imported templates join at lower import precedence, so the importer’s own rules win a conflict.

import tempfile
from pathlib import Path
from turbohtml import parse_xml
from turbohtml.transform import transform

with tempfile.TemporaryDirectory() as folder:
    base = Path(folder, "base.xsl")
    base.write_text(
        '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">'
        '<xsl:template match="a">[<xsl:value-of select="."/>]</xsl:template></xsl:stylesheet>',
        encoding="utf-8",
    )
    main = Path(folder, "main.xsl")
    main.write_text(
        '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">'
        '<xsl:output method="text"/><xsl:import href="base.xsl"/>'
        '<xsl:template match="/"><xsl:apply-templates select="r/a"/></xsl:template></xsl:stylesheet>',
        encoding="utf-8",
    )
    source = parse_xml("<r><a>x</a></r>")
    print(transform(parse_xml(main.read_text(encoding="utf-8")), source, base_url=str(main)))
[x]

The only features out of reach are locale-aware xsl:sort collation, id() over DTD-declared IDs, xsl:include, and document(). For the design, see XSLT; for a port from lxml, see From lxml.