1"""
2An :class:`python:html.parser.HTMLParser`-shaped adapter over the streaming tokenizer.
3
4A drop-in base class for the many ``HTMLParser`` subclasses in the wild: subclass it, override the ``handle_*`` methods,
5and call :meth:`~HTMLParser.feed`/:meth:`~HTMLParser.close` incrementally, exactly as with the standard library, but
6over turbohtml's WHATWG-conformant tokenizer. Migrating is changing the import and the base class.
7
8It is kept apart from the core API (which exposes the token stream and the tree directly) so the drop-in surface stays
9bounded. It differs from the standard library only where the standard library diverges from the WHATWG algorithm
10browsers run:
11
12- Character references are always resolved (as ``convert_charrefs=True``), so :meth:`~HTMLParser.handle_data` receives
13 decoded text and :meth:`~HTMLParser.handle_entityref`/:meth:`~HTMLParser.handle_charref` are never called. The
14 ``convert_charrefs`` argument is accepted for signature compatibility but has no effect.
15- A processing instruction reaches :meth:`~HTMLParser.handle_pi`. A CDATA section outside foreign content remains a
16 comment, so :meth:`~HTMLParser.unknown_decl` is never called.
17- A valueless attribute's value is the empty string rather than ``None`` (the tokenizer does not tell ``disabled``
18 apart from ``disabled=""``).
19"""
20
21from __future__ import annotations
22
23from turbohtml._html import Tokenizer
24
25__all__ = ["HTMLParser"]
26
27
28class HTMLParser:
29 """
30 A subclass-and-override callback parser over the streaming tokenizer.
31
32 :param convert_charrefs: accepted for compatibility but ignored; references are always resolved.
33 """
34
35 convert_charrefs: bool
36 """Accepted for compatibility; references are always resolved regardless of its value."""
37
38 def __init__(self, *, convert_charrefs: bool = True) -> None:
39 """Create a parser."""
40 self.convert_charrefs = convert_charrefs
41 self._tokenizer = Tokenizer()
42
43 def feed(self, data: str) -> None:
44 """
45 Feed a chunk of HTML, dispatching the tokens it completes to the handlers.
46
47 :param data: the next chunk of HTML.
48 """
49 self._tokenizer.feed(data)
50 self._tokenizer.dispatch(self)
51
52 def close(self) -> None:
53 """Signal end of input, dispatching any tokens still buffered."""
54 self._tokenizer.close()
55 self._tokenizer.dispatch(self)
56
57 def reset(self) -> None:
58 """Discard the buffered input and position, ready to parse a new document."""
59 self._tokenizer.reset()
60
61 def getpos(self) -> tuple[int, int]:
62 """
63 Report the source position of the last dispatched token.
64
65 :returns: the 1-based line and 0-based column of the token last dispatched.
66 """
67 return self._tokenizer.position()
68
69 def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
70 """
71 Handle a start tag; override to act on it.
72
73 :param tag: the lowercased tag name.
74 :param attrs: the (name, value) attribute pairs, a valueless attribute's value the empty string.
75 """
76
77 def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
78 """
79 Handle a self-closing tag; by default fire start then end, like the standard library.
80
81 :param tag: the lowercased tag name.
82 :param attrs: the (name, value) attribute pairs.
83 """
84 self.handle_starttag(tag, attrs)
85 self.handle_endtag(tag)
86
87 def handle_endtag(self, tag: str) -> None:
88 """
89 Handle an end tag; override to act on it.
90
91 :param tag: the lowercased tag name.
92 """
93
94 def handle_data(self, data: str) -> None:
95 """
96 Handle a run of text, with character references resolved.
97
98 :param data: the decoded text.
99 """
100
101 def handle_comment(self, data: str) -> None:
102 """
103 Handle a comment (also CDATA outside foreign content).
104
105 :param data: the comment text, without the ``<!--`` ``-->`` delimiters.
106 """
107
108 def handle_decl(self, decl: str) -> None:
109 """
110 Handle a ``<!DOCTYPE ...>`` declaration, with the leading ``<!`` and trailing ``>`` removed.
111
112 :param decl: the declaration text, such as ``DOCTYPE html``.
113 """
114
115 def handle_pi(self, data: str) -> None:
116 """
117 Handle an instruction with its target and data joined by one space.
118
119 :param data: the target followed by data when present.
120 """
121
122 def handle_entityref(self, name: str) -> None:
123 """
124 Handle a named reference; never called, since references are always resolved.
125
126 :param name: the entity name, without the ``&`` and ``;``.
127 """
128
129 def handle_charref(self, name: str) -> None:
130 """
131 Handle a numeric reference; never called, since references are always resolved.
132
133 :param name: the numeric reference body, without the ``&#`` and ``;``.
134 """
135
136 def unknown_decl(self, data: str) -> None:
137 """
138 Handle a CDATA section; never called, since it is a comment outside foreign content.
139
140 :param data: the section text.
141 """