// use parse5 to serialize html correctly import EntityAccessError from "@entity-access/entity-access/dist/common/EntityAccessError.js"; import { escapeText, escapeAttribute } from "entities"; type INodeToken = { target: XNode; token?: never; } | { token: string; target?: never; } export default class XNode { public static create( // eslint-disable-next-line @typescript-eslint/ban-types name: string | Function, attribs: Record, ... nodes: (XNode | string)[]): XNode { if (typeof name === "function") { return name(attribs ?? {}, ... nodes); } return new XNode(name, attribs, nodes); } protected constructor( public readonly name: string, public readonly attributes: Record, public readonly children: (XNode | string)[] ) { } public render(nest = "") { let text = ""; for (const element of this.readable(nest)) { text += element; } return text; } public * readable(nest = "") { const iterator = this.recursiveReadable(nest); const stack = []; let current = iterator; for(;;) { const { value, done } = current.next(); if (typeof value === "string") { yield value; continue; } if (done) { if (stack.length) { current = stack.pop(); continue; } break; } stack.push(current); current = value; } } protected *recursiveReadable(nest = ""): Generator { const { name, attributes, children } = this; if (nest) { yield nest; } yield `<${name}`; if (attributes) { for (const key in attributes) { if (Object.hasOwn(attributes, key)) { const element = attributes[key]; if (nest) { yield `\n${nest}\t`; } else { yield " "; } yield `${escapeAttribute(key)}="${escapeAttribute(element)}"`; } } } if (nest) { yield nest; } yield ">"; if (children) { for (const child of children) { if (child === null || child === undefined || (child as any) === false ) { continue; } switch(typeof child) { case "string": if (/^(script|style)$/i.test(name)) { /** script and style are not escaped */ yield child; } else { yield escapeText(child); } continue; case "object": yield `\n`; yield child.readable(nest + "\t"); continue; default: yield (child as any).toString(); continue; } } } if (nest) { yield nest; } yield ``; } }