Hi & bye
'): if token.type is TokenType.START_TAG: events.append(("start", token.tag, token.attrs)) elif token.type is TokenType.TEXT: events.append(("data", token.data)) elif token.type is TokenType.END_TAG: events.append(("end", token.tag)) print(events) .. testoutput:: [('start', 'p', [('class', 'x')]), ('data', 'Hi & bye'), ('end', 'p')] If you liked ``html.parser``'s callback shape and only want the WHATWG-correct tree behind it, :func:`turbohtml.saxparse.sax_parse` keeps that shape: subclass :class:`turbohtml.saxparse.SaxHandler`, override the events you need, and the parser fires them on the constructed tree rather than the raw tags. The ``handle_*`` methods map onto SAX methods one-to-one: .. list-table:: :header-rows: 1 :widths: 50 50 - - ``html.parser`` override - ``SaxHandler`` override - - ``handle_starttag(tag, attrs)`` - ``start_element(tag, attrs)`` - - ``handle_endtag(tag)`` - ``end_element(tag)`` - - ``handle_data(data)`` - ``characters(data)`` - - ``handle_comment(data)`` - ``comment(data)`` - - ``handle_decl(decl)`` - ``doctype(name, public_id, system_id)`` - - ``handle_pi(data)`` - ``processing_instruction(data)`` .. testcode:: from turbohtml.saxparse import SaxHandler, sax_parse class Collector(SaxHandler): def __init__(self): self.starts = [] def start_element(self, tag, attrs): self.starts.append(tag if not attrs else f"{tag} {dict(attrs)}") collector = Collector() sax_parse("| cell", collector)
print(collector.starts)
.. testoutput::
['html', 'head', 'body', 'table', 'tbody', 'tr', 'td']
``html.parser`` would report just ``table`` and ``td``; the SAX events carry the implied ``html``/``head``/``body`` and
the foster-parented ``tbody``/``tr`` the tree builder inserts. Unlike ``html.parser``, ``sax_parse`` builds the working
tree (freed at the end) rather than streaming in constant space, so it is the tool for a spec-correct one-pass
extraction, not for a document larger than memory. The :doc:`/how-to/sax` guide has more, and :doc:`/explanation/sax`
covers the memory model.
***********************
Unicode normalization
***********************
:func:`python:unicodedata.normalize` and :func:`python:unicodedata.is_normalized` move to
:func:`turbohtml.detect.normalize` and :func:`turbohtml.detect.is_normalized`: the form name comes first and the output
is identical, because turbohtml runs the four forms in C over tables generated from the interpreter's own
``unicodedata``. A quick check returns already-normalized text untouched.
.. list-table::
:header-rows: 1
:widths: 50 50
- - stdlib call
- turbohtml call
- - ``unicodedata.normalize("NFC", s)``
- ``turbohtml.detect.normalize("NFC", s)``
- - ``unicodedata.is_normalized("NFC", s)``
- ``turbohtml.detect.is_normalized("NFC", s)``
.. testcode::
import unicodedata
from turbohtml.detect import normalize
forms = ("NFC", "NFD", "NFKC", "NFKD")
text = "fi café ẛ̣"
print(all(normalize(form, text) == unicodedata.normalize(form, text) for form in forms))
.. testoutput::
True
**********************
Gotchas and pitfalls
**********************
- The token stream inverts ``html.parser``'s callback control flow: you loop over tokens and branch on :attr:`Token.type
|