The strict XML parsing mode¶
turbohtml.parse() builds the tree a browser would: the WHATWG tree-construction algorithm implies missing tags,
special-cases void elements, foster-parents misnested table content, and recovers from almost anything. That is exactly
wrong for XML, where the grammar is small and a single violation makes the document not XML at all.
turbohtml.parse_xml() is a separate front end over the same arena, atom, and node infrastructure that applies XML
1.0 productions instead of HTML quirks.
What changes from HTML mode¶
The two modes differ wherever the HTML tree builder makes an assumption XML does not license:
No implied tags, no void elements. Every element opens and closes explicitly;
<br>is an ordinary element that must be written<br/>or<br></br>. Nothing is inserted that the source did not write.Self-closing is universal.
<x/>closes any element, not just the fixed HTML void set.Case is significant.
<Note>and<note>are different elements, and</Note>does not close<note>.CDATA, PIs, and the XML declaration are first-class. A
<![CDATA[...]]>section becomes aCDatanode, a<?target data?>becomes aProcessingInstruction, and a leading<?xml ...?>declaration is consumed without becoming a node.Entities are the XML set. Only
amp,lt,gt,quot,apos, and numeric character references resolve. An HTML entity like – or any DTD-declared entity – is an undefined-entity error, because the mode carries no entity table beyond the five the specification predefines.A violation raises, it never recovers. A mismatched or unclosed tag, an undeclared namespace prefix, an undefined entity, a duplicate attribute, or content outside the single root element stops the parse and raises
HTMLParseError, whoseerroris aParseErrorwith the code and source position. This mirrorslxml’s defaultrecover=Falseparser rather than the HTML path’s error collection onerrors.
Namespaces by name¶
XML namespaces are a well-formedness concern here, not a rewrite. The parser tracks the xmlns:prefix declarations in
scope on the open-element stack, pushes each element’s declarations before validating it, and reports an undeclared
prefix as an error; the reserved xml prefix is always in scope. It enforces the Namespaces in XML 1.0 constraints in
full: the xml prefix binds only to its own namespace and no other prefix may take that URI, the xmlns prefix is
never declarable, a prefix declaration is never empty (xmlns:=), a processing-instruction target is an NCName and
carries no colon, and two attributes may not resolve to one expanded name – the same local name reached through two
prefixes bound to a single URI is a duplicate. What it does not do is resolve a prefix to a URI. lxml stores a
namespaced tag in Clark notation ({urn:h}a) and exposes an nsmap; turbohtml keeps the qualified name exactly as
written (h:a) and leaves every xmlns/xmlns:prefix declaration as an ordinary attribute on the element.
This is a deliberate fit to turbohtml’s node model, whose element namespace is the fixed HTML/SVG/MathML enumeration the
HTML parser needs, not an open set of URIs. Keeping qualified names verbatim means an XML document round-trips through
the same serializer and navigates through the same tag/attrs API
as an HTML one, at the cost of not offering URI-keyed lookups. A pipeline that needs true namespace resolution,
DTD-declared entities, validation, or XSLT stays with lxml; the goal of the XML mode is a well-formed,
dependency-free tree under the one node API, not a second full XML toolchain.
Why a separate front end¶
The XML grammar shares nothing with the HTML tree-construction insertion modes, so threading an xml flag through the
WHATWG state machine would have meant a branch at every step for no shared logic. A standalone recursive-descent parser
that emits the same C node tree is smaller, keeps the HTML fast path untouched, and still reuses the arena allocator,
the interned tag and attribute atoms, and the zero-copy text spans – an XML text run with no entities or line-ending
fixups points straight into the source buffer, exactly as the HTML parser’s does.
Conformance¶
The mode is validated against the W3C XML Conformance Test Suite (the OASIS/NIST/Sun/IBM/James-Clark xmlconf
collection, 2585 cases), vendored as the tests/conformance/xml-conformance-suite submodule and driven by
tests/conformance/test_xml_conformance.py. Because parse_xml is a non-validating well-formedness checker, the
oracle is the well-formedness verdict: every not-wf case in the document instance or prolog must raise, and every
well-formed case (including DTD-invalid-but-well-formed ones) must parse. Cases that rely on a deliberately omitted
feature – DTD internal-subset grammar, DTD-declared or external entities, XML 1.1, or the byte-level encoding layer –
are recorded as expected deviations with a spec-grounded reason rather than asserted.