The node and tree model

Every node in a parsed document is one of a small sealed set of types, all sharing the navigation defined on Node. The purple base carries the shared traversal; the green leaves are the concrete node types a tree is built from.

        graph TD
    Node["Node<br/>(shared navigation)"]
    Node --> Document[Document]
    Node --> Element[Element]
    Node --> Text[Text]
    Node --> Comment[Comment]
    Node --> CData[CData]
    Node --> PI[ProcessingInstruction]
    Node --> Doctype[Doctype]

    classDef base fill:#ede7f6,stroke:#4527a0,color:#311b92
    classDef leaf fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20

    class Node base
    class Document,Element,Text,Comment,CData,PI,Doctype leaf
    

The node types are a small sealed hierarchy (Document, Element, Text, Comment, Doctype, ProcessingInstruction, CData) sharing the navigation defined on Node. turbohtml models text as real child nodes (the WHATWG DOM shape) rather than the .text/.tail split lxml-style trees use, so a node’s children are its text runs and elements interleaved in document order. Each type sets __match_args__, so structural pattern matching unpacks a node’s defining field, and node equality is identity over the underlying arena node, so two wrappers for the same element compare equal and hash alike. An element’s attributes are a live mapping, with the space-separated token-list attributes (class, rel, …) surfacing as a list[str]; on top of that, has_class(), add_class(), remove_class(), and toggle_class() edit the class set without a read-split-rejoin dance. The token scan and the rewrite both run in C under the per-tree lock, so the read-modify-write of the attribute is one atomic step rather than the several Python list operations a hand-rolled version would take.