From c14c79dd3e1a523d7044e4a62f8658f433168678 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 13 Mar 2024 20:24:22 +0100 Subject: [PATCH 1/4] chore: [#1039] Starts on implementation --- .../happy-dom/src/config/HTMLElementConfig.ts | 1294 +++++++++++++++++ .../src/config/HTMLElementLocalNameToClass.ts | 119 -- .../src/config/HTMLElementPlainText.ts | 4 - .../src/config/HTMLElementUnnestable.ts | 18 - .../happy-dom/src/config/HTMLElementVoid.ts | 16 - .../src/config/IHTMLElementConfigEntity.ts | 11 + .../happy-dom/src/nodes/document/Document.ts | 6 +- .../happy-dom/src/xml-parser/XMLParser.ts | 56 +- .../src/xml-serializer/XMLSerializer.ts | 4 +- .../test/xml-parser/XMLParser.test.ts | 29 + 10 files changed, 1378 insertions(+), 179 deletions(-) create mode 100644 packages/happy-dom/src/config/HTMLElementConfig.ts delete mode 100644 packages/happy-dom/src/config/HTMLElementLocalNameToClass.ts delete mode 100644 packages/happy-dom/src/config/HTMLElementPlainText.ts delete mode 100644 packages/happy-dom/src/config/HTMLElementUnnestable.ts delete mode 100644 packages/happy-dom/src/config/HTMLElementVoid.ts create mode 100644 packages/happy-dom/src/config/IHTMLElementConfigEntity.ts diff --git a/packages/happy-dom/src/config/HTMLElementConfig.ts b/packages/happy-dom/src/config/HTMLElementConfig.ts new file mode 100644 index 000000000..96699e729 --- /dev/null +++ b/packages/happy-dom/src/config/HTMLElementConfig.ts @@ -0,0 +1,1294 @@ +import IHTMLElementConfigEntity from './IHTMLElementConfigEntity.js'; + +/** + * @see https://html.spec.whatwg.org/multipage/indices.html + */ +export default <{ [key: string]: IHTMLElementConfigEntity }>{ + a: { + className: 'HTMLAnchorElement', + localName: 'a', + tagName: 'A', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: false + } + }, + abbr: { + className: 'HTMLElement', + localName: 'abbr', + tagName: 'ABBR', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + address: { + className: 'HTMLElement', + localName: 'address', + tagName: 'ADDRESS', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + area: { + className: 'HTMLElement', + localName: 'area', + tagName: 'AREA', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + article: { + className: 'HTMLElement', + localName: 'article', + tagName: 'ARTICLE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + aside: { + className: 'HTMLElement', + localName: 'aside', + tagName: 'ASIDE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + audio: { + className: 'HTMLAudioElement', + localName: 'audio', + tagName: 'AUDIO', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + b: { + className: 'HTMLElement', + localName: 'b', + tagName: 'B', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + base: { + className: 'HTMLBaseElement', + localName: 'base', + tagName: 'BASE', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + bdi: { + className: 'HTMLElement', + localName: 'bdi', + tagName: 'BDI', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + bdo: { + className: 'HTMLElement', + localName: 'bdo', + tagName: 'BDO', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + blockquaote: { + className: 'HTMLElement', + localName: 'blockquaote', + tagName: 'BLOCKQUAOTE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + body: { + className: 'HTMLElement', + localName: 'body', + tagName: 'BODY', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + template: { + className: 'HTMLTemplateElement', + localName: 'template', + tagName: 'TEMPLATE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + form: { + className: 'HTMLFormElement', + localName: 'form', + tagName: 'FORM', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + input: { + className: 'HTMLInputElement', + localName: 'input', + tagName: 'INPUT', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + textarea: { + className: 'HTMLTextAreaElement', + localName: 'textarea', + tagName: 'TEXTAREA', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + script: { + className: 'HTMLScriptElement', + localName: 'script', + tagName: 'SCRIPT', + contentModel: { + allowChildren: false, + isPlainText: true, + allowSelfAsDirectChild: false, + allowSelfAsChild: false + } + }, + img: { + className: 'HTMLImageElement', + localName: 'img', + tagName: 'IMG', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + link: { + className: 'HTMLLinkElement', + localName: 'link', + tagName: 'LINK', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + style: { + className: 'HTMLStyleElement', + localName: 'style', + tagName: 'STYLE', + contentModel: { + allowChildren: false, + isPlainText: true, + allowSelfAsDirectChild: false, + allowSelfAsChild: false + } + }, + label: { + className: 'HTMLLabelElement', + localName: 'label', + tagName: 'LABEL', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + slot: { + className: 'HTMLSlotElement', + localName: 'slot', + tagName: 'SLOT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + meta: { + className: 'HTMLMetaElement', + localName: 'meta', + tagName: 'META', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + blockquote: { + className: 'HTMLElement', + localName: 'blockquote', + tagName: 'BLOCKQUOTE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + br: { + className: 'HTMLElement', + localName: 'br', + tagName: 'BR', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + button: { + className: 'HTMLButtonElement', + localName: 'button', + tagName: 'BUTTON', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + canvas: { + className: 'HTMLElement', + localName: 'canvas', + tagName: 'CANVAS', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + caption: { + className: 'HTMLElement', + localName: 'caption', + tagName: 'CAPTION', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + cite: { + className: 'HTMLElement', + localName: 'cite', + tagName: 'CITE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + code: { + className: 'HTMLElement', + localName: 'code', + tagName: 'CODE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + col: { + className: 'HTMLElement', + localName: 'col', + tagName: 'COL', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + colgroup: { + className: 'HTMLElement', + localName: 'colgroup', + tagName: 'COLGROUP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + data: { + className: 'HTMLElement', + localName: 'data', + tagName: 'DATA', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + datalist: { + className: 'HTMLElement', + localName: 'datalist', + tagName: 'DATALIST', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + dd: { + className: 'HTMLElement', + localName: 'dd', + tagName: 'DD', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + del: { + className: 'HTMLElement', + localName: 'del', + tagName: 'DEL', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + details: { + className: 'HTMLElement', + localName: 'details', + tagName: 'DETAILS', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + dfn: { + className: 'HTMLElement', + localName: 'dfn', + tagName: 'DFN', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + dialog: { + className: 'HTMLDialogElement', + localName: 'dialog', + tagName: 'DIALOG', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + div: { + className: 'HTMLElement', + localName: 'div', + tagName: 'DIV', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + dl: { + className: 'HTMLElement', + localName: 'dl', + tagName: 'DL', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + dt: { + className: 'HTMLElement', + localName: 'dt', + tagName: 'DT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + em: { + className: 'HTMLElement', + localName: 'em', + tagName: 'EM', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + embed: { + className: 'HTMLElement', + localName: 'embed', + tagName: 'EMBED', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + fieldset: { + className: 'HTMLElement', + localName: 'fieldset', + tagName: 'FIELDSET', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + figcaption: { + className: 'HTMLElement', + localName: 'figcaption', + tagName: 'FIGCAPTION', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + figure: { + className: 'HTMLElement', + localName: 'figure', + tagName: 'FIGURE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + footer: { + className: 'HTMLElement', + localName: 'footer', + tagName: 'FOOTER', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + h1: { + className: 'HTMLElement', + localName: 'h1', + tagName: 'H1', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + h2: { + className: 'HTMLElement', + localName: 'h2', + tagName: 'H2', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + h3: { + className: 'HTMLElement', + localName: 'h3', + tagName: 'H3', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + h4: { + className: 'HTMLElement', + localName: 'h4', + tagName: 'H4', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + h5: { + className: 'HTMLElement', + localName: 'h5', + tagName: 'H5', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + h6: { + className: 'HTMLElement', + localName: 'h6', + tagName: 'H6', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + head: { + className: 'HTMLElement', + localName: 'head', + tagName: 'HEAD', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + header: { + className: 'HTMLElement', + localName: 'header', + tagName: 'HEADER', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + hgroup: { + className: 'HTMLElement', + localName: 'hgroup', + tagName: 'HGROUP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + hr: { + className: 'HTMLElement', + localName: 'hr', + tagName: 'HR', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + html: { + className: 'HTMLElement', + localName: 'html', + tagName: 'HTML', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + i: { + className: 'HTMLElement', + localName: 'i', + tagName: 'I', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + iframe: { + className: 'HTMLIFrameElement', + localName: 'iframe', + tagName: 'IFRAME', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + ins: { + className: 'HTMLElement', + localName: 'ins', + tagName: 'INS', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + kbd: { + className: 'HTMLElement', + localName: 'kbd', + tagName: 'KBD', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + legend: { + className: 'HTMLElement', + localName: 'legend', + tagName: 'LEGEND', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + li: { + className: 'HTMLElement', + localName: 'li', + tagName: 'LI', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + main: { + className: 'HTMLElement', + localName: 'main', + tagName: 'MAIN', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + map: { + className: 'HTMLElement', + localName: 'map', + tagName: 'MAP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + mark: { + className: 'HTMLElement', + localName: 'mark', + tagName: 'MARK', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + math: { + className: 'HTMLElement', + localName: 'math', + tagName: 'MATH', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + menu: { + className: 'HTMLElement', + localName: 'menu', + tagName: 'MENU', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + menuitem: { + className: 'HTMLElement', + localName: 'menuitem', + tagName: 'MENUITEM', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + meter: { + className: 'HTMLElement', + localName: 'meter', + tagName: 'METER', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + nav: { + className: 'HTMLElement', + localName: 'nav', + tagName: 'NAV', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + noscript: { + className: 'HTMLElement', + localName: 'noscript', + tagName: 'NOSCRIPT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + object: { + className: 'HTMLElement', + localName: 'object', + tagName: 'OBJECT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + ol: { + className: 'HTMLElement', + localName: 'ol', + tagName: 'OL', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + optgroup: { + className: 'HTMLOptGroupElement', + localName: 'optgroup', + tagName: 'OPTGROUP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + option: { + className: 'HTMLOptionElement', + localName: 'option', + tagName: 'OPTION', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + output: { + className: 'HTMLElement', + localName: 'output', + tagName: 'OUTPUT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + p: { + className: 'HTMLElement', + localName: 'p', + tagName: 'P', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + param: { + className: 'HTMLElement', + localName: 'param', + tagName: 'PARAM', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + picture: { + className: 'HTMLElement', + localName: 'picture', + tagName: 'PICTURE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + pre: { + className: 'HTMLElement', + localName: 'pre', + tagName: 'PRE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + progress: { + className: 'HTMLElement', + localName: 'progress', + tagName: 'PROGRESS', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + q: { + className: 'HTMLElement', + localName: 'q', + tagName: 'Q', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + rb: { + className: 'HTMLElement', + localName: 'rb', + tagName: 'RB', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + rp: { + className: 'HTMLElement', + localName: 'rp', + tagName: 'RP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + rt: { + className: 'HTMLElement', + localName: 'rt', + tagName: 'RT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + rtc: { + className: 'HTMLElement', + localName: 'rtc', + tagName: 'RTC', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + ruby: { + className: 'HTMLElement', + localName: 'ruby', + tagName: 'RUBY', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + s: { + className: 'HTMLElement', + localName: 's', + tagName: 'S', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + samp: { + className: 'HTMLElement', + localName: 'samp', + tagName: 'SAMP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + section: { + className: 'HTMLElement', + localName: 'section', + tagName: 'SECTION', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + select: { + className: 'HTMLSelectElement', + localName: 'select', + tagName: 'SELECT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + small: { + className: 'HTMLElement', + localName: 'small', + tagName: 'SMALL', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + source: { + className: 'HTMLElement', + localName: 'source', + tagName: 'SOURCE', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + span: { + className: 'HTMLElement', + localName: 'span', + tagName: 'SPAN', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + strong: { + className: 'HTMLElement', + localName: 'strong', + tagName: 'STRONG', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + sub: { + className: 'HTMLElement', + localName: 'sub', + tagName: 'SUB', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + summary: { + className: 'HTMLElement', + localName: 'summary', + tagName: 'SUMMARY', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + sup: { + className: 'HTMLElement', + localName: 'sup', + tagName: 'SUP', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + table: { + className: 'HTMLElement', + localName: 'table', + tagName: 'TABLE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: false, + allowSelfAsChild: true + } + }, + tbody: { + className: 'HTMLElement', + localName: 'tbody', + tagName: 'TBODY', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + td: { + className: 'HTMLElement', + localName: 'td', + tagName: 'TD', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + tfoot: { + className: 'HTMLElement', + localName: 'tfoot', + tagName: 'TFOOT', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + th: { + className: 'HTMLElement', + localName: 'th', + tagName: 'TH', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + thead: { + className: 'HTMLElement', + localName: 'thead', + tagName: 'THEAD', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + time: { + className: 'HTMLElement', + localName: 'time', + tagName: 'TIME', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + title: { + className: 'HTMLElement', + localName: 'title', + tagName: 'TITLE', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + tr: { + className: 'HTMLElement', + localName: 'tr', + tagName: 'TR', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + track: { + className: 'HTMLElement', + localName: 'track', + tagName: 'TRACK', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + u: { + className: 'HTMLElement', + localName: 'u', + tagName: 'U', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + ul: { + className: 'HTMLElement', + localName: 'ul', + tagName: 'UL', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + var: { + className: 'HTMLElement', + localName: 'var', + tagName: 'VAR', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + video: { + className: 'HTMLVideoElement', + localName: 'video', + tagName: 'VIDEO', + contentModel: { + allowChildren: true, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + }, + wbr: { + className: 'HTMLElement', + localName: 'wbr', + tagName: 'WBR', + contentModel: { + allowChildren: false, + isPlainText: false, + allowSelfAsDirectChild: true, + allowSelfAsChild: true + } + } +}; diff --git a/packages/happy-dom/src/config/HTMLElementLocalNameToClass.ts b/packages/happy-dom/src/config/HTMLElementLocalNameToClass.ts deleted file mode 100644 index fe5ab0b10..000000000 --- a/packages/happy-dom/src/config/HTMLElementLocalNameToClass.ts +++ /dev/null @@ -1,119 +0,0 @@ -export default <{ [key: string]: string }>{ - a: 'HTMLAnchorElement', - abbr: 'HTMLElement', - address: 'HTMLElement', - area: 'HTMLElement', - article: 'HTMLElement', - aside: 'HTMLElement', - audio: 'HTMLAudioElement', - b: 'HTMLElement', - base: 'HTMLBaseElement', - bdi: 'HTMLElement', - bdo: 'HTMLElement', - blockquaote: 'HTMLElement', - body: 'HTMLElement', - template: 'HTMLTemplateElement', - form: 'HTMLFormElement', - input: 'HTMLInputElement', - textarea: 'HTMLTextAreaElement', - script: 'HTMLScriptElement', - img: 'HTMLImageElement', - link: 'HTMLLinkElement', - style: 'HTMLStyleElement', - label: 'HTMLLabelElement', - slot: 'HTMLSlotElement', - meta: 'HTMLMetaElement', - blockquote: 'HTMLElement', - br: 'HTMLElement', - button: 'HTMLButtonElement', - canvas: 'HTMLElement', - caption: 'HTMLElement', - cite: 'HTMLElement', - code: 'HTMLElement', - col: 'HTMLElement', - colgroup: 'HTMLElement', - data: 'HTMLElement', - datalist: 'HTMLElement', - dd: 'HTMLElement', - del: 'HTMLElement', - details: 'HTMLElement', - dfn: 'HTMLElement', - dialog: 'HTMLDialogElement', - div: 'HTMLElement', - dl: 'HTMLElement', - dt: 'HTMLElement', - em: 'HTMLElement', - embed: 'HTMLElement', - fieldset: 'HTMLElement', - figcaption: 'HTMLElement', - figure: 'HTMLElement', - footer: 'HTMLElement', - h1: 'HTMLElement', - h2: 'HTMLElement', - h3: 'HTMLElement', - h4: 'HTMLElement', - h5: 'HTMLElement', - h6: 'HTMLElement', - head: 'HTMLElement', - header: 'HTMLElement', - hgroup: 'HTMLElement', - hr: 'HTMLElement', - html: 'HTMLElement', - i: 'HTMLElement', - iframe: 'HTMLIFrameElement', - ins: 'HTMLElement', - kbd: 'HTMLElement', - legend: 'HTMLElement', - li: 'HTMLElement', - main: 'HTMLElement', - map: 'HTMLElement', - mark: 'HTMLElement', - math: 'HTMLElement', - menu: 'HTMLElement', - menuitem: 'HTMLElement', - meter: 'HTMLElement', - nav: 'HTMLElement', - noscript: 'HTMLElement', - object: 'HTMLElement', - ol: 'HTMLElement', - optgroup: 'HTMLOptGroupElement', - option: 'HTMLOptionElement', - output: 'HTMLElement', - p: 'HTMLElement', - param: 'HTMLElement', - picture: 'HTMLElement', - pre: 'HTMLElement', - progress: 'HTMLElement', - q: 'HTMLElement', - rb: 'HTMLElement', - rp: 'HTMLElement', - rt: 'HTMLElement', - rtc: 'HTMLElement', - ruby: 'HTMLElement', - s: 'HTMLElement', - samp: 'HTMLElement', - section: 'HTMLElement', - select: 'HTMLSelectElement', - small: 'HTMLElement', - source: 'HTMLElement', - span: 'HTMLElement', - strong: 'HTMLElement', - sub: 'HTMLElement', - summary: 'HTMLElement', - sup: 'HTMLElement', - table: 'HTMLElement', - tbody: 'HTMLElement', - td: 'HTMLElement', - tfoot: 'HTMLElement', - th: 'HTMLElement', - thead: 'HTMLElement', - time: 'HTMLElement', - title: 'HTMLElement', - tr: 'HTMLElement', - track: 'HTMLElement', - u: 'HTMLElement', - ul: 'HTMLElement', - var: 'HTMLElement', - video: 'HTMLVideoElement', - wbr: 'HTMLElement' -}; diff --git a/packages/happy-dom/src/config/HTMLElementPlainText.ts b/packages/happy-dom/src/config/HTMLElementPlainText.ts deleted file mode 100644 index e0ac55c37..000000000 --- a/packages/happy-dom/src/config/HTMLElementPlainText.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - STYLE: true, - SCRIPT: true -}; diff --git a/packages/happy-dom/src/config/HTMLElementUnnestable.ts b/packages/happy-dom/src/config/HTMLElementUnnestable.ts deleted file mode 100644 index 1d2820a10..000000000 --- a/packages/happy-dom/src/config/HTMLElementUnnestable.ts +++ /dev/null @@ -1,18 +0,0 @@ -export default { - A: true, - BUTTON: true, - DD: true, - DT: true, - FORM: true, - H1: true, - H2: true, - H3: true, - H4: true, - H5: true, - H6: true, - LI: true, - OPTION: true, - P: true, - SELECT: true, - TABLE: true -}; diff --git a/packages/happy-dom/src/config/HTMLElementVoid.ts b/packages/happy-dom/src/config/HTMLElementVoid.ts deleted file mode 100644 index 93073f0d1..000000000 --- a/packages/happy-dom/src/config/HTMLElementVoid.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default { - AREA: true, - BASE: true, - BR: true, - COL: true, - EMBED: true, - HR: true, - IMG: true, - INPUT: true, - LINK: true, - META: true, - PARAM: true, - SOURCE: true, - TRACK: true, - WBR: true -}; diff --git a/packages/happy-dom/src/config/IHTMLElementConfigEntity.ts b/packages/happy-dom/src/config/IHTMLElementConfigEntity.ts new file mode 100644 index 000000000..cdcff9944 --- /dev/null +++ b/packages/happy-dom/src/config/IHTMLElementConfigEntity.ts @@ -0,0 +1,11 @@ +export default interface IHTMLElementConfigEntity { + className: string; + localName: string; + tagName: string; + contentModel: { + allowChildren: boolean; + isPlainText: boolean; + allowSelfAsDirectChild: boolean; + allowSelfAsChild: boolean; + }; +} diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index 784a4745e..e9ff1634a 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -8,7 +8,6 @@ import DocumentFragment from '../document-fragment/DocumentFragment.js'; import XMLParser from '../../xml-parser/XMLParser.js'; import Event from '../../event/Event.js'; import DOMImplementation from '../../dom-implementation/DOMImplementation.js'; -import HTMLElementLocalNameToClass from '../../config/HTMLElementLocalNameToClass.js'; import INodeFilter from '../../tree-walker/INodeFilter.js'; import NamespaceURI from '../../config/NamespaceURI.js'; import DocumentType from '../document-type/DocumentType.js'; @@ -51,6 +50,7 @@ import ISVGElementTagNameMap from '../../config/ISVGElementTagNameMap.js'; import ISVGElement from '../svg-element/ISVGElement.js'; import IHTMLFormElement from '../html-form-element/IHTMLFormElement.js'; import IHTMLAnchorElement from '../html-anchor-element/IHTMLAnchorElement.js'; +import HTMLElementConfig from '../../config/HTMLElementConfig.js'; const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/; @@ -1131,7 +1131,9 @@ export default class Document extends Node implements IDocument { } const localName = qualifiedName.toLowerCase(); - const elementClass = this[PropertySymbol.ownerWindow][HTMLElementLocalNameToClass[localName]]; + const elementClass = HTMLElementConfig[localName] + ? this[PropertySymbol.ownerWindow][HTMLElementConfig[localName].className] + : null; // Known HTML element if (elementClass) { diff --git a/packages/happy-dom/src/xml-parser/XMLParser.ts b/packages/happy-dom/src/xml-parser/XMLParser.ts index 5a5d7168b..1d092f9d3 100755 --- a/packages/happy-dom/src/xml-parser/XMLParser.ts +++ b/packages/happy-dom/src/xml-parser/XMLParser.ts @@ -1,15 +1,13 @@ import IDocument from '../nodes/document/IDocument.js'; import * as PropertySymbol from '../PropertySymbol.js'; -import HTMLElementVoid from '../config/HTMLElementVoid.js'; -import HTMLElementUnnestable from '../config/HTMLElementUnnestable.js'; import NamespaceURI from '../config/NamespaceURI.js'; import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement.js'; import IElement from '../nodes/element/IElement.js'; import HTMLLinkElement from '../nodes/html-link-element/HTMLLinkElement.js'; -import HTMLElementPlainText from '../config/HTMLElementPlainText.js'; import IDocumentType from '../nodes/document-type/IDocumentType.js'; import INode from '../nodes/node/INode.js'; import IDocumentFragment from '../nodes/document-fragment/IDocumentFragment.js'; +import HTMLElementConfig from '../config/HTMLElementConfig.js'; import * as Entities from 'entities'; /** @@ -58,6 +56,8 @@ const DOCUMENT_TYPE_ATTRIBUTE_REGEXP = /"([^"]+)"/gm; /** * XML parser. + * + * @see https://html.spec.whatwg.org/multipage/indices.html */ export default class XMLParser { /** @@ -79,7 +79,8 @@ export default class XMLParser { const stack: INode[] = [root]; const markupRegexp = new RegExp(MARKUP_REGEXP, 'gm'); const { evaluateScripts = false } = options || {}; - const unnestableTagNames: string[] = []; + const unallowedChildTagNames: string[] = []; + const unallowedDirectChildTagNames: string[] = []; let currentNode: INode | null = root; let match: RegExpExecArray; let plainTextTagName: string | null = null; @@ -111,9 +112,14 @@ export default class XMLParser { // Some elements are not allowed to be nested (e.g. "" is not allowed.). // Therefore we need to auto-close the tag, so that it become valid (e.g. ""). - const unnestableTagNameIndex = unnestableTagNames.indexOf(tagName); - if (unnestableTagNameIndex !== -1) { - unnestableTagNames.splice(unnestableTagNameIndex, 1); + const unallowedChildTagNameIndex = unallowedChildTagNames.indexOf(tagName); + if (unallowedChildTagNameIndex !== -1) { + const unallowedDirectChildTagNameIndex = + unallowedDirectChildTagNames.indexOf(tagName); + unallowedChildTagNames.splice(unallowedChildTagNameIndex, 1); + if (unallowedDirectChildTagNameIndex !== -1) { + unallowedDirectChildTagNames.splice(unallowedDirectChildTagNameIndex, 1); + } while (currentNode !== root) { if ((currentNode)[PropertySymbol.tagName].toUpperCase() === tagName) { stack.pop(); @@ -123,6 +129,8 @@ export default class XMLParser { stack.pop(); currentNode = stack[stack.length - 1] || root; } + } else if (unallowedDirectChildTagNames.length > 0) { + unallowedDirectChildTagNames.pop(); } // NamespaceURI is inherited from the parent element. @@ -147,11 +155,17 @@ export default class XMLParser { ) { // Some elements are not allowed to be nested (e.g. "" is not allowed.). // Therefore we need to auto-close the tag, so that it become valid (e.g. ""). - const unnestableTagNameIndex = unnestableTagNames.indexOf( + const unallowedChildTagNameIndex = unallowedChildTagNames.indexOf( (currentNode)[PropertySymbol.tagName].toUpperCase() ); - if (unnestableTagNameIndex !== -1) { - unnestableTagNames.splice(unnestableTagNameIndex, 1); + if (unallowedChildTagNameIndex !== -1) { + const unallowedDirectChildTagNameIndex = unallowedDirectChildTagNames.indexOf( + (currentNode)[PropertySymbol.tagName].toUpperCase() + ); + unallowedChildTagNames.splice(unallowedChildTagNameIndex, 1); + if (unallowedDirectChildTagNameIndex !== -1) { + unallowedDirectChildTagNames.splice(unallowedDirectChildTagNameIndex, 1); + } } stack.pop(); @@ -201,8 +215,6 @@ export default class XMLParser { case MarkupReadStateEnum.insideStartTag: // End of start tag if (match[7] || match[8]) { - // End of start tag. - // Attribute name and value. const attributeString = xml.substring(startTagIndex, match.index); @@ -257,12 +269,14 @@ export default class XMLParser { // We need to check if the attribute string is read completely. // The attribute string can potentially contain "/>" or ">". if (hasAttributeStringEnded) { + const config = HTMLElementConfig[(currentNode)[PropertySymbol.localName]]; + // Checks if the tag is a self closing tag (ends with "/>") or void element. // When it is a self closing tag or void element it should be closed immediately. // Self closing tags are not allowed in the HTML namespace, but the parser should still allow it for void elements. // Self closing tags is supported in the SVG namespace. if ( - HTMLElementVoid[(currentNode)[PropertySymbol.tagName]] || + !config?.contentModel.allowChildren || (match[7] && (currentNode)[PropertySymbol.namespaceURI] === NamespaceURI.svg) ) { @@ -271,9 +285,9 @@ export default class XMLParser { readState = MarkupReadStateEnum.startOrEndTag; } else { // Plain text elements such as