Implementing Custom Event Handlers
Relevant source files
The following files were used as context for generating this wiki page:
Custom event handlers are the primary mechanism for consuming data parsed by htmlparser2. Instead of building an entire Document Object Model (DOM) in memory, the Parser class provides a callback-based interface that triggers specific methods as it encounters different structures in the HTML or XML stream. This approach is highly efficient, allowing for low-memory processing of large documents by reacting to events in real-time.
Developers implement the Handler interface to capture events such as the start of a tag, attribute values, text content, and comments. These handlers can be passed directly to the Parser constructor, which coordinates between the low-level Tokenizer and the high-level event logic to ensure correct sequencing and context (such as XML mode or foreign SVG/MathML contexts).
Sources: README.md:38-40, src/Parser.ts:167-198
Handler Architecture and Interface
The Parser class relies on a structured callback interface defined by the Handler type. This interface abstracts the complexity of character-level tokenization into semantic events that represent the logical structure of a document.
The Handler Interface
The Handler interface includes several key lifecycle and structural events. All methods are optional when passing a handler to the Parser.
| Event Method | Parameters | Description |
|---|---|---|
onparserinit | parser: Parser | Triggered when the parser is initialized or reset. |
onreset | None | Signals the handler to return to its initial state. |
onopentagname | name: string | Emitted as soon as a tag name is identified, before attributes. |
onattribute | name, value, quote | Emitted for every attribute found in an opening tag. |
onopentag | name, attribs, isImplied | Emitted when a tag and all its attributes are fully parsed. |
ontext | data: string | Contains text content; may fire multiple times for one node. |
onclosetag | name, isImplied | Emitted when a tag is closed, either explicitly or implicitly. |
onerror | error: Error | Triggered when a parsing error occurs (e.g., write after end). |
Sources: src/Parser.ts:167-198, README.md:78-95
Component Interaction Flow
The Parser acts as a middleman. It receives chunks via .write(), passes them to a Tokenizer, and then interprets the tokenizer's low-level callbacks to emit the high-level Handler events.
The flow demonstrates how raw data is transformed through internal states before reaching the custom handler.
Sources: src/Parser.ts:217-227, src/Tokenizer.ts:251-260
Event Sequencing and Logic
The Parser handles complex HTML/XML rules to ensure handlers receive a consistent stream of events. This includes managing implied tags in HTML mode and handling foreign contexts like SVG.
Implicit Tag Management
In HTML mode, the parser automatically closes elements when certain other elements are opened (e.g., a <li> implicitly closes a previous <li>). This is managed via the openImpliesClose map.
This diagram illustrates the "open-implies-close" logic where the parser notifies the handler of a closure before the new opening tag is finalized.
Sources: src/Parser.ts:18-68, src/Parser.ts:333-356
Attribute Handling
Attributes are emitted piece-by-piece to the parser but aggregated for the onopentag event. The onattribute event provides the raw data, including the quote type used.
onattribute: Triggered immediately after each attribute is parsed.onopentag: Triggered once the opening tag's>is reached, providing a fullattribsobject.
Sources: src/Parser.ts:406-444, README.md:81-87
Implementation Example
Implementing a custom handler involves creating an object (or class) that satisfies the Partial<Handler> interface.
import { Parser } from "htmlparser2";
const myHandler = {
onopentag(name: string, attribs: Record<string, string>) {
if (name === "a") {
console.log("Found link to:", attribs.href);
}
},
ontext(text: string) {
console.log("Text content:", text);
},
onclosetag(name: string) {
console.log("Closed tag:", name);
}
};
const parser = new Parser(myHandler, { decodeEntities: true });
parser.write('<a href="https://example.com">Click here</a>');
parser.end();
Sources: README.md:42-74, src/Parser.spec.ts:8-15
Advanced Control and State
The Parser provides methods to control the execution flow, which affects when handler events are fired.
Pause and Resume
The pause() and resume() methods allow handlers to backpressure the parser. While paused, the Tokenizer stops processing, and any additional data provided via write() is buffered in the Parser.buffers array.
- Pause:
this.tokenizer.pause()is called, settingrunningtofalse. - Resume:
this.tokenizer.resume()is called, and the parser iterates throughwriteIndexto process buffered chunks.
Sources: src/Parser.ts:546-568, src/Parser.spec.ts:37-53
Parser Reset
The reset() method clears all internal state, including the tag stack and the tokenizer state, and triggers the onreset and onparserinit events on the handler.
Sources: src/Parser.ts:511-526, src/Tokenizer.spec.ts:213-228
Technical Summary
Implementing custom handlers requires understanding the transition from the Tokenizer's raw indices to the Parser's string-based events. The Parser maintains an internal stack to track open elements and a foreignContext stack to handle namespace-specific parsing rules for SVG and MathML. By implementing the Handler interface, developers gain precise control over the parsing lifecycle without the overhead of a full DOM.
Sources: src/Parser.ts:205-215, src/Parser.ts:384-394, src/index.ts:39-49