// 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 `${name}>`;
}
}