Parse XML into a tree

When your input is XML rather than HTML, use turbohtml.parse_xml(). It parses under XML 1.0 well-formedness instead of the WHATWG tree builder, so names stay case-sensitive, <x/> self-closes any element, and there is no HTML recovery – a malformed document raises. The result is the same navigable Document the HTML path returns, so you query, edit, and serialize it through the one node API.

Parse a well-formed document

Hand turbohtml.parse_xml() a string of XML. The document’s children are its prolog nodes (an optional doctype, comments, processing instructions) followed by the single root element:

import turbohtml

doc = turbohtml.parse_xml("<catalog><book id='b1'><title>One</title></book></catalog>")
catalog = doc.children[0]
book = catalog.children[0]
print(catalog.tag, book.attrs["id"])
print(book.children[0].tag, book.children[0].text)
catalog b1
title One

XML elements carry no HTML tag identity, so navigate the tree structurally through children, parent, and the sibling links, and read each tag verbatim.

Read namespaces, CDATA, and PIs

turbohtml keeps qualified names verbatim – an element declared under a prefix keeps its prefix:local tag, and every xmlns/xmlns:prefix declaration stays as an ordinary attribute. A CDATA section becomes a CData node and a processing instruction a ProcessingInstruction with a target and data:

doc = turbohtml.parse_xml(
    '<feed xmlns:dc="urn:dc">'
    "<?render mode=fast?>"
    "<dc:title>News</dc:title>"
    "<summary><![CDATA[<b>raw</b> & co]]></summary>"
    "</feed>"
)
feed = doc.children[0]
print(feed.attrs["xmlns:dc"])
print(feed.children[0].target, feed.children[0].data)
print(feed.children[1].tag)
print(feed.children[2].children[0].data)
urn:dc
render mode=fast
dc:title
<b>raw</b> & co

Only the five predefined entities (&amp;, &lt;, &gt;, &quot;, &apos;) and numeric character references such as &#233; resolve; a document that defines its own entities through a DTD is outside this mode.

Catch a well-formedness error

Ill-formed XML raises HTMLParseError at the first violation rather than recovering. Its error attribute is a ParseError carrying the code and the source position, so a linter or ingest pipeline can report exactly what and where:

try:
    turbohtml.parse_xml("<a><b></a>")
except turbohtml.HTMLParseError as exception:
    print(exception.error.code, exception.error.line, exception.error.col)
xml-mismatched-tag 1 8

The same exception surfaces an unclosed tag, an undeclared namespace prefix, an undefined entity, a duplicate attribute, content outside the root element, or a Namespaces in XML 1.0 violation – rebinding the reserved xml or xmlns prefix, a colon in a processing-instruction target, or two attributes that resolve to the same expanded name.