diff --git a/src/nodes/html.ts b/src/nodes/html.ts index 78f283e..c8133c2 100644 --- a/src/nodes/html.ts +++ b/src/nodes/html.ts @@ -327,21 +327,41 @@ export default class HTMLElement extends Node { .replace(/\s+$/, ''); // trimRight; } - public toString() { - const tag = this.rawTagName; - if (tag) { - const attrs = this.rawAttrs ? ` ${this.rawAttrs}` : ''; - return this.voidTag.formatNode(tag, attrs, this.innerHTML); + public toString(): string { + return this.serialize([this]); + } + + public get innerHTML(): string { + return this.serialize(this.childNodes); + } + + // Iterative, so that deeply nested trees do not overflow the call stack + private serialize(nodes: Node[]): string { + const out: string[] = []; + const stack: (Node | string)[] = [...nodes].reverse(); + while (stack.length > 0) { + const item = stack.pop(); + if (typeof item === 'string') { + out.push(item); + } else if (item instanceof HTMLElement) { + const tag = item.rawTagName; + if (tag) { + const attrs = item.rawAttrs ? ` ${item.rawAttrs}` : ''; + if (item.voidTag.isVoidElement(tag.toLowerCase())) { + out.push(item.voidTag.formatNode(tag, attrs, '')); + continue; + } + out.push(`<${tag}${attrs}>`); + stack.push(`${tag}>`); + } + for (let i = item.childNodes.length - 1; i >= 0; i--) { + stack.push(item.childNodes[i]); + } + } else { + out.push(item.toString()); + } } - return this.innerHTML; - } - - public get innerHTML() { - return this.childNodes - .map((child) => { - return child.toString(); - }) - .join(''); + return out.join(''); } public set innerHTML(content: string) { diff --git a/test/tests/issues/324.js b/test/tests/issues/324.js new file mode 100644 index 0000000..2799a7e --- /dev/null +++ b/test/tests/issues/324.js @@ -0,0 +1,19 @@ +const { parse } = require('@test/test-target'); + +// https://github.com/taoqf/node-html-parser/issues/324 +describe('issue 324 toString of deeply nested html', function () { + it('serializes deep nesting without overflowing the stack', function () { + const depth = 10000; + const html = '
x
y
y');
+ });
+});