htmlparser2
Version:
Fast & forgiving HTML/XML parser
90 lines (76 loc) • 2.48 kB
text/typescript
import type { Parser, Handler } from "../Parser.js";
interface Event {
$event: string;
data: unknown[];
startIndex: number;
endIndex: number;
}
/**
* Creates a handler that calls the supplied callback with simplified events on
* completion.
*
* @internal
* @param callback Function to call with all events.
*/
export function getEventCollector(
callback: (error: Error | null, events?: Event[]) => void,
): Partial<Handler> {
const events: Event[] = [];
let parser: Parser;
function handle(event: string, data: unknown[]): void {
switch (event) {
case "onerror": {
callback(data[0] as Error);
break;
}
case "onend": {
callback(null, events);
break;
}
case "onreset": {
events.length = 0;
break;
}
case "onparserinit": {
parser = data[0] as Parser;
// Don't collect event
break;
}
default: {
// eslint-disable-next-line unicorn/prefer-at
const last = events[events.length - 1];
// Combine text nodes
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (event === "ontext" && last && last.$event === "text") {
(last.data[0] as string) += data[0];
last.endIndex = parser.endIndex;
break;
}
// Remove `undefined`s from attribute responses, as they cannot be represented in JSON.
if (event === "onattribute" && data[2] === undefined) {
data.pop();
}
if (!(parser.startIndex <= parser.endIndex)) {
throw new Error(
`Invalid start/end index ${parser.startIndex} > ${parser.endIndex}`,
);
}
events.push({
$event: event.slice(2),
startIndex: parser.startIndex,
endIndex: parser.endIndex,
data,
});
}
}
}
return new Proxy(
{},
{
get:
(_, event: string) =>
(...data: unknown[]) =>
handle(event, data),
},
);
}