Skip to content

Commit

Permalink
Merge pull request #480 from capricorn86/task/479-voidelements-instea…
Browse files Browse the repository at this point in the history
…d-of-selfclosingelements-and-unclosedelements

#479@major: Fixes issues with HTML parsing and serialization related …
  • Loading branch information
capricorn86 committed May 19, 2022
2 parents 126840e + 7cb40c6 commit c922f33
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 81 deletions.
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/happy-dom/src/config/UnclosedElements.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export default [
// Standard Elements
'area',
'base',
'br',
Expand All @@ -14,15 +13,5 @@ export default [
'source',
'track',
'wbr',

// SVG Elements
'circle',
'ellipse',
'line',
'path',
'polygon',
'polyline',
'rect',
'stop',
'use'
'meta'
];
47 changes: 11 additions & 36 deletions packages/happy-dom/src/xml-parser/XMLParser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Node from '../nodes/node/Node';
import Element from '../nodes/element/Element';
import IDocument from '../nodes/document/IDocument';
import SelfClosingElements from '../config/SelfClosingElements';
import VoidElements from '../config/VoidElements';
import UnnestableElements from '../config/UnnestableElements';
import ChildLessElements from '../config/ChildLessElements';
import { decode } from 'he';
Expand All @@ -15,7 +15,6 @@ const MARKUP_REGEXP = /<(\/?)([a-z][-.0-9_a-z]*)\s*([^>]*?)(\/?)>/gi;
const COMMENT_REGEXP = /<!--(.*?)-->|<([!?])([^>]*)>/gi;
const DOCUMENT_TYPE_ATTRIBUTE_REGEXP = /"([^"]+)"/gm;
const ATTRIBUTE_REGEXP = /([^\s=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))/gms;
const XMLNS_ATTRIBUTE_REGEXP = /xmlns[ ]*=[ ]*"([^"]+)"/;

/**
* XML parser.
Expand Down Expand Up @@ -48,8 +47,8 @@ export default class XMLParser {
}

if (isStartTag) {
const newElement = document.createElement(tagName);
const xmlnsAttribute = this.getXmlnsAttribute(match[3]);
const namespaceURI = tagName === 'svg' ? NamespaceURI.svg : parent.namespaceURI;
const newElement = document.createElementNS(namespaceURI, tagName);

// Scripts are not allowed to be executed when they are parsed using innerHTML, outerHTML, replaceWith() etc.
// However, they are allowed to be executed when document.write() is used.
Expand All @@ -63,17 +62,9 @@ export default class XMLParser {
(<HTMLLinkElement>newElement)._evaluateCSS = evaluateScripts;
}

// The HTML engine can guess that the namespace is SVG for SVG tags
// Even if "xmlns" is not set if the parent namespace is HTML.
if (tagName === 'svg' && parent.namespaceURI === NamespaceURI.html) {
(<string>newElement.namespaceURI) = xmlnsAttribute || NamespaceURI.svg;
} else {
(<string>newElement.namespaceURI) = xmlnsAttribute || parent.namespaceURI;
}

this.setAttributes(newElement, newElement.namespaceURI, match[3]);
this.setAttributes(newElement, match[3]);

if (!SelfClosingElements.includes(tagName)) {
if (!match[4] && !VoidElements.includes(tagName)) {
// Some elements are not allowed to be nested (e.g. "<a><a></a></a>" is not allowed.).
// Therefore we will auto-close the tag.
if (parentUnnestableTagName === tagName) {
Expand Down Expand Up @@ -203,28 +194,13 @@ export default class XMLParser {
return nodes;
}

/**
* Returns XMLNS attribute.
*
* @param attributesString Raw attributes.
*/
private static getXmlnsAttribute(attributesString: string): string {
const match = attributesString.match(XMLNS_ATTRIBUTE_REGEXP);
return match ? match[1] : null;
}

/**
* Sets raw attributes.
*
* @param element Element.
* @param namespaceURI Namespace URI.
* @param attributesString Raw attributes.
*/
private static setAttributes(
element: IElement,
namespaceURI: string,
attributesString: string
): void {
private static setAttributes(element: IElement, attributesString: string): void {
const attributes = attributesString.trim();
if (attributes) {
const regExp = new RegExp(ATTRIBUTE_REGEXP, 'gi');
Expand All @@ -233,18 +209,17 @@ export default class XMLParser {
// Attributes with value
while ((match = regExp.exec(attributes))) {
if (match[1]) {
element.setAttributeNS(
null,
this._getAttributeName(namespaceURI, match[1]),
decode(match[2] || match[3] || match[4] || '')
);
const value = decode(match[2] || match[3] || match[4] || '');
const name = this._getAttributeName(element.namespaceURI, match[1]);
const namespaceURI = element.tagName === 'SVG' && name === 'xmlns' ? value : null;
element.setAttributeNS(namespaceURI, name, value);
}
}

// Attributes with no value
for (const name of attributes.replace(ATTRIBUTE_REGEXP, '').trim().split(' ')) {
if (name) {
element.setAttributeNS(null, this._getAttributeName(namespaceURI, name), '');
element.setAttributeNS(null, this._getAttributeName(element.namespaceURI, name), '');
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions packages/happy-dom/src/xml-serializer/XMLSerializer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Element from '../nodes/element/Element';
import Node from '../nodes/node/Node';
import SelfClosingElements from '../config/SelfClosingElements';
import UnclosedElements from '../config/UnclosedElements';
import VoidElements from '../config/VoidElements';
import DocumentType from '../nodes/document-type/DocumentType';
import { escape } from 'he';
import INode from '../nodes/node/INode';
Expand All @@ -25,10 +24,8 @@ export default class XMLSerializer {
const element = <Element>root;
const tagName = element.tagName.toLowerCase();

if (UnclosedElements.includes(tagName)) {
if (VoidElements.includes(tagName)) {
return `<${tagName}${this._getAttributes(element)}>`;
} else if (SelfClosingElements.includes(tagName)) {
return `<${tagName}${this._getAttributes(element)}/>`;
}

let innerHTML = '';
Expand Down

0 comments on commit c922f33

Please sign in to comment.