| import { ElementType } from "domelementtype"; |
| import { Element, Text, Comment, CDATA, Document, ProcessingInstruction, } from "./node.js"; |
| export * from "./node.js"; |
| // Default options |
| const defaultOpts = { |
| withStartIndices: false, |
| withEndIndices: false, |
| xmlMode: false, |
| }; |
| export class DomHandler { |
| /** |
| * @param callback Called once parsing has completed. |
| * @param options Settings for the handler. |
| * @param elementCB Callback whenever a tag is closed. |
| */ |
| constructor(callback, options, elementCB) { |
| /** The elements of the DOM */ |
| this.dom = []; |
| /** The root element for the DOM */ |
| this.root = new Document(this.dom); |
| /** Indicated whether parsing has been completed. */ |
| this.done = false; |
| /** Stack of open tags. */ |
| this.tagStack = [this.root]; |
| /** A data node that is still being written to. */ |
| this.lastNode = null; |
| /** Reference to the parser instance. Used for location information. */ |
| this.parser = null; |
| // Make it possible to skip arguments, for backwards-compatibility |
| if (typeof options === "function") { |
| elementCB = options; |
| options = defaultOpts; |
| } |
| if (typeof callback === "object") { |
| options = callback; |
| callback = undefined; |
| } |
| this.callback = callback !== null && callback !== void 0 ? callback : null; |
| this.options = options !== null && options !== void 0 ? options : defaultOpts; |
| this.elementCB = elementCB !== null && elementCB !== void 0 ? elementCB : null; |
| } |
| onparserinit(parser) { |
| this.parser = parser; |
| } |
| // Resets the handler back to starting state |
| onreset() { |
| this.dom = []; |
| this.root = new Document(this.dom); |
| this.done = false; |
| this.tagStack = [this.root]; |
| this.lastNode = null; |
| this.parser = null; |
| } |
| // Signals the handler that parsing is done |
| onend() { |
| if (this.done) |
| return; |
| this.done = true; |
| this.parser = null; |
| this.handleCallback(null); |
| } |
| onerror(error) { |
| this.handleCallback(error); |
| } |
| onclosetag() { |
| this.lastNode = null; |
| const elem = this.tagStack.pop(); |
| if (this.options.withEndIndices) { |
| elem.endIndex = this.parser.endIndex; |
| } |
| if (this.elementCB) |
| this.elementCB(elem); |
| } |
| onopentag(name, attribs) { |
| const type = this.options.xmlMode ? ElementType.Tag : undefined; |
| const element = new Element(name, attribs, undefined, type); |
| this.addNode(element); |
| this.tagStack.push(element); |
| } |
| ontext(data) { |
| const { lastNode } = this; |
| if (lastNode && lastNode.type === ElementType.Text) { |
| lastNode.data += data; |
| if (this.options.withEndIndices) { |
| lastNode.endIndex = this.parser.endIndex; |
| } |
| } |
| else { |
| const node = new Text(data); |
| this.addNode(node); |
| this.lastNode = node; |
| } |
| } |
| oncomment(data) { |
| if (this.lastNode && this.lastNode.type === ElementType.Comment) { |
| this.lastNode.data += data; |
| return; |
| } |
| const node = new Comment(data); |
| this.addNode(node); |
| this.lastNode = node; |
| } |
| oncommentend() { |
| this.lastNode = null; |
| } |
| oncdatastart() { |
| const text = new Text(""); |
| const node = new CDATA([text]); |
| this.addNode(node); |
| text.parent = node; |
| this.lastNode = text; |
| } |
| oncdataend() { |
| this.lastNode = null; |
| } |
| onprocessinginstruction(name, data) { |
| const node = new ProcessingInstruction(name, data); |
| this.addNode(node); |
| } |
| handleCallback(error) { |
| if (typeof this.callback === "function") { |
| this.callback(error, this.dom); |
| } |
| else if (error) { |
| throw error; |
| } |
| } |
| addNode(node) { |
| const parent = this.tagStack[this.tagStack.length - 1]; |
| const previousSibling = parent.children[parent.children.length - 1]; |
| if (this.options.withStartIndices) { |
| node.startIndex = this.parser.startIndex; |
| } |
| if (this.options.withEndIndices) { |
| node.endIndex = this.parser.endIndex; |
| } |
| parent.children.push(node); |
| if (previousSibling) { |
| node.prev = previousSibling; |
| previousSibling.next = node; |
| } |
| node.parent = parent; |
| this.lastNode = null; |
| } |
| } |
| export default DomHandler; |