#############################
The strict XML parsing mode
#############################
:func:`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.
:func:`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; ``
`` is an ordinary element that
must be written ``
`` or ``
``. Nothing is inserted that the source did not write.
- **Self-closing is universal.** ```` closes any element, not just the fixed HTML void set.
- **Case is significant.** ```` and ```` are different elements, and ```` does not close ````.
- **CDATA, PIs, and the XML declaration are first-class.** A ```` section becomes a
:class:`~turbohtml.CData` node, a ```` becomes a :class:`~turbohtml.ProcessingInstruction`, and a
leading ```` 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
:exc:`~turbohtml.HTMLParseError`, whose ``error`` is a :class:`~turbohtml.ParseError` with the code and source
position. This mirrors ``lxml``'s default ``recover=False`` parser rather than the HTML path's error *collection* on
:attr:`~turbohtml.Document.errors`.
********************
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 :attr:`~turbohtml.Element.tag`/:attr:`~turbohtml.Element.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.