HTML describes a document with elements, attributes and text. In <p class="note">Hi <em>Sam</em></p>, the paragraph contains a text node and an emphasis element. The class attribute belongs to the paragraph; it is not a child element. A browser builds a document object model (DOM) and applies rendering rules. The text file and the resulting tree are related representations, not interchangeable objects.
A normal element often has a start tag, content and an end tag. A void element such as img or br has no end tag in HTML. A trailing slash on <div/> does not make a div a void element in browser HTML. Python’s HTMLParser nevertheless reports that lexical form through handle_startendtag. Our inventory counts source start events; it does not certify conformance or reconstruct the browser’s repaired DOM.
Subclassing gives the parser a small set of callbacks. class Trace(HTMLParser) reuses the standard parser. self is the particular instance receiving an event, and super().__init__() initializes its inherited machinery. Override only the handlers needed for the result. A fresh instance keeps one document’s counters from leaking into another. This is the limited class pattern used here; a larger class-design course is optional preparation for larger projects.
from html.parser import HTMLParser
class Trace(HTMLParser):
def handle_starttag(self, tag, attrs):
print("start", tag)
def handle_data(self, data):
print("data", repr(data))
parser = Trace(convert_charrefs=True)
parser.feed('<P>Fish & chips<br></P>')
parser.close()Names passed to the start handler are lowercase. Data contains decoded character references such as &. Attribute values are also decoded. Comments use their own handler, so <!-- <a> --> must not add an anchor to the inventory. A doctype is a declaration, not an element. Splitting the input into multiple feed calls can split text callbacks differently, so collect chunks and join them instead of treating a callback as a whole paragraph.
close() processes buffered input at end of source. It does not invent missing end tags, validate nesting or execute scripts. Script and style contents can arrive as data; they are source text, not evidence of what a visitor sees. The build intentionally retains every data chunk, including whitespace and raw-text content, and ignores end-tag events when counting elements.
Implement describe_html(source) with three independent outputs: a mapping of element-name counts, a comment count and concatenated data. Test an empty document, uppercase names, a void tag, an XHTML-style empty tag and a comment containing apparent markup. Before coding, predict which callbacks each fragment triggers. Use structured parser events for HTML; a regular expression searching for angle brackets cannot reliably handle quoted attributes, comments, nesting and raw-text elements.
The result is an offline source description. It makes no requests, checks no links and claims no accessibility, browser-layout or HTML-validity audit. Those limits make its small contract useful and testable.