Parse with SAX callbacks

Process a document as a stream of events without building or keeping a tree, the way you would with html.parser.HTMLParser or expat – but over a spec-correct tree builder. Use turbohtml.saxparse when you want to pull a few facts out of a page and move on, not navigate it afterwards.

The callback form

Subclass SaxHandler, override only the events you care about, and pass an instance to sax_parse(). Every method defaults to a no-op, so a link collector overrides just start_element:

from turbohtml.saxparse import SaxHandler, sax_parse


class LinkCollector(SaxHandler):
    def __init__(self):
        self.links = []

    def start_element(self, tag, attrs):
        if tag == "a":
            self.links += [value for name, value in attrs if name == "href" and value]


collector = LinkCollector()
sax_parse('<ul><li><a href="/x">x</a><li><a href="/y">y</a></ul>', collector)
print(collector.links)
['/x', '/y']

attrs is a tuple of (name, value) pairs in source order; a valueless attribute (<input disabled>) has None for its value. Every element fires end_element when it closes, including empty and void ones.

The stream form

If you would rather drive the loop than invert control into callbacks, iterate iter_events(). It yields typed records – StartElement, Characters, and the rest – one at a time:

from turbohtml.saxparse import Characters, iter_events

text = "".join(event.data for event in iter_events("<p>Hello <b>there</b></p>") if isinstance(event, Characters))
print(text)
Hello there

The events are the built tree

Unlike html.parser, the stream reflects the tree the WHATWG algorithm constructs, not the raw tags. Implied elements appear, and misplaced content is foster-parented into place:

from turbohtml.saxparse import StartElement, iter_events

tags = [event.tag for event in iter_events("<table><td>cell") if isinstance(event, StartElement)]
print(tags)
['html', 'head', 'body', 'table', 'tbody', 'tr', 'td']

Nobody wrote <html>, <head>, <body>, <tbody>, or <tr>; the parser did, and the events say so.

Doctypes

A Doctype event carries the name and, when the source supplied them, the public_id and system_id (each None otherwise). A <?...> construct – a WHATWG bogus comment, since HTML has no processing instructions – arrives as a ProcessingInstruction, matching html.parser.HTMLParser.handle_pi().

For the memory model and why this is not a way to parse a document larger than memory, see The event-driven parse.