Home Reference Source

src/adaptors/sax-event-adaptor.js

"use strict";

import EventEmitter from "node:events";

import { TokenisingStream } from "tokenising-stream";

import { assign2 } from "./utils.js";

/**
 * @typedef {Object} SAXEventAdaptor~AttributeToken
 * @property {'attribute'} type - Token type
 * @property {string} name - Attribute name
 * @property {string} value - Attribute value
 */

/**
 * @typedef {Object} SAXEventAdaptor~CDataToken
 * @property {'cdata'} type - Token type
 * @property {string} cdata - CDATA content
 */

/**
 * @typedef {Object} SAXEventAdaptor~CloseCDataToken
 * @property {'closecdata'} type - Token type
 */

/**
 * @typedef {Object} SAXEventAdaptor~CloseNamespaceToken
 * @property {'closenamespace'} type - Token type
 * @property {string} prefix - Namespace prefix
 * @property {string} uri - Namespace URI
 */

/**
 * @typedef {Object} SAXEventAdaptor~CloseTagToken
 * @property {'closetag'} type - Token type
 * @property {string} name - Tag name
 */

/**
 * @typedef {Object} SAXEventAdaptor~CommentToken
 * @property {'comment'} type - Token type
 * @property {string} comment - Comment content
 */

/**
 * @typedef {Object} SAXEventAdaptor~DoctypeToken
 * @property {'doctype'} type - Token type
 * @property {string} doctype - DOCTYPE declaration
 */

/**
 * @typedef {Object} SAXEventAdaptor~OpenCDataToken
 * @property {'opencdata'} type - Token type
 */

/**
 * @typedef {Object} SAXEventAdaptor~OpenTagToken
 * @property {'opentag'} type - Token type
 * @property {string} name - Tag name
 * @property {boolean} isSelfClosing - Whether the tag is self-closing
 */

/**
 * @typedef {Object} SAXEventAdaptor~OpenTagStartToken
 * @property {'opentagstart'} type - Token type
 * @property {string} name - Tag name
 */

/**
 * @typedef {Object} SAXEventAdaptor~OpenNamespaceToken
 * @property {'opennamespace'} type - Token type
 * @property {string} prefix - Namespace prefix
 * @property {string} uri - Namespace URI
 */

/**
 * @typedef {Object} SAXEventAdaptor~ProcessingInstructionToken
 * @property {'processinginstruction'} type - Token type
 * @property {string} name - Processing instruction name
 * @property {string} body - Processing instruction body
 */

/**
 * @typedef {Object} SAXEventAdaptor~ScriptToken
 * @property {'script'} type - Token type
 * @property {string} script - Script content
 */

/**
 * @typedef {Object} SAXEventAdaptor~SGMLDeclarationToken
 * @property {'sgmldeclaration'} type - Token type
 * @property {string} sgmldeclaration - Declaration content
 */

/**
 * @typedef {Object} SAXEventAdaptor~TextToken
 * @property {'text'} type - Token type
 * @property {string} text - Text content
 */

/**
 * @typedef {SAXEventAdaptor~AttributeToken|SAXEventAdaptor~CDataToken|SAXEventAdaptor~CloseCDataToken|SAXEventAdaptor~CloseNamespaceToken|SAXEventAdaptor~CloseTagToken|SAXEventAdaptor~CommentToken|SAXEventAdaptor~DoctypeToken|SAXEventAdaptor~OpenCDataToken|SAXEventAdaptor~OpenTagToken|SAXEventAdaptor~OpenTagStartToken|SAXEventAdaptor~OpenNamespaceToken|SAXEventAdaptor~ProcessingInstructionToken|SAXEventAdaptor~ScriptToken|SAXEventAdaptor~SGMLDeclarationToken|SAXEventAdaptor~TextToken} SAXEventAdaptor~SAXToken
 */

/**
 * EventAdaptor for SAX parser.
 *
 * The following events are piped through the stream as tokens
 *
 * <ul>
 *   <li><b>attribute</b> - {@link SAXEventAdaptor~AttributeToken}</li>
 *   <li><b>cdata</b> - {@link SAXEventAdaptor~CDataToken}</li>
 *   <li><b>closecdata</b> - {@link SAXEventAdaptor~CloseCDataToken}</li>
 *   <li><b>closenamespace</b> - {@link SAXEventAdaptor~CloseNamespaceToken}</li>
 *   <li><b>closetag</b> - {@link SAXEventAdaptor~CloseTagToken}</li>
 *   <li><b>comment</b> - {@link SAXEventAdaptor~CommentToken}</li>
 *   <li><b>doctype</b> - {@link SAXEventAdaptor~DoctypeToken}</li>
 *   <li><b>opencdata</b> - {@link SAXEventAdaptor~OpenCDataToken}</li>
 *   <li><b>opentag</b> - {@link SAXEventAdaptor~OpenTagToken}</li>
 *   <li><b>opentagstart</b> - {@link SAXEventAdaptor~OpenTagStartToken}</li>
 *   <li><b>opennamespace</b> - {@link SAXEventAdaptor~OpenNamespaceToken}</li>
 *   <li><b>processinginstruction</b> - {@link SAXEventAdaptor~ProcessingInstructionToken}</li>
 *   <li><b>script</b> - {@link SAXEventAdaptor~ScriptToken}</li>
 *   <li><b>sgmldeclaration</b> - {@link SAXEventAdaptor~SGMLDeclarationToken}</li>
 *   <li><b>text</b> - {@link SAXEventAdaptor~TextToken}</li>
 * </ul>
 *
 * @implements {EventAdaptor}
 * @see https://www.npmjs.com/package/sax
 */
export default class SAXEventAdaptor extends EventEmitter {
	/**
	 * @param {EventEmitter} delegate
	 */
	constructor(delegate) {
		super();

		delegate.on("attribute", (token) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, assign2(token, {
				type: "attribute"
			}));
		});

		delegate.on("cdata", (text) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "cdata",
				cdata: text
			});
		});

		delegate.on("closecdata", () => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "closecdata"
			});
		});

		delegate.on("closenamespace", (ns) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, assign2(ns, {
				type: "closenamespace",
			}));
		});

		delegate.on("closetag", (name) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "closetag",
				name
			});
		});

		delegate.on("comment", (comment) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "comment",
				comment: comment
			});
		});

		delegate.on("doctype", (doctype) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "doctype",
				doctype
			});
		});

		delegate.on("opencdata", () => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "opencdata"
			});
		});

		delegate.on("opennamespace", (ns) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, assign2(ns, {
				type: "opennamespace",
			}));
		});

		delegate.on("opentag", (node) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, assign2(node, {
				type: "opentag",
			}));
		});

		delegate.on("opentagstart", (node) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, assign2(node, {
				type: "opentagstart",
			}));
		});

		delegate.on("processinginstruction", (instruction) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, assign2(instruction, {
				type: "processinginstruction",
			}));
		});

		delegate.on("script", (script) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "script",
				script: script
			});
		});

		delegate.on("sgmldeclaration", (sgmldeclaration) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "sgmldeclaration",
				sgmldeclaration: sgmldeclaration
			});
		});

		delegate.on("text", (text) => {
			this.emit(TokenisingStream.TOKEN_EVENT_NAME, {
				type: "text",
				text: text
			});
		});

		delegate.on("error", (error) => {
			this.emit(TokenisingStream.ERROR_EVENT_NAME, error);
		})
	}
}