Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#639@patch: Element.innerHTML should be able to handle other types th… #640

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
120 changes: 62 additions & 58 deletions packages/happy-dom/src/xml-parser/XMLParser.ts
Expand Up @@ -42,76 +42,80 @@ export default class XMLParser {
let lastTextIndex = 0;
let match: RegExpExecArray;

while ((match = markupRegexp.exec(data))) {
const tagName = match[2].toLowerCase();
const isStartTag = !match[1];
if (data !== null && data !== undefined) {
data = String(data);

if (parent && match.index !== lastTextIndex) {
const text = data.substring(lastTextIndex, match.index);
this.appendTextAndCommentNodes(document, parent, text);
}

if (isStartTag) {
const namespaceURI =
tagName === 'svg'
? NamespaceURI.svg
: (<IElement>parent).namespaceURI || NamespaceURI.html;
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.
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement
if (tagName === 'script') {
(<HTMLScriptElement>newElement)._evaluateScript = evaluateScripts;
}
while ((match = markupRegexp.exec(data))) {
const tagName = match[2].toLowerCase();
const isStartTag = !match[1];

// An assumption that the same rule should be applied for the HTMLLinkElement is made here.
if (tagName === 'link') {
(<HTMLLinkElement>newElement)._evaluateCSS = evaluateScripts;
if (parent && match.index !== lastTextIndex) {
const text = data.substring(lastTextIndex, match.index);
this.appendTextAndCommentNodes(document, parent, text);
}

this.setAttributes(newElement, match[3]);
if (isStartTag) {
const namespaceURI =
tagName === 'svg'
? NamespaceURI.svg
: (<IElement>parent).namespaceURI || NamespaceURI.html;
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.
// See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement
if (tagName === 'script') {
(<HTMLScriptElement>newElement)._evaluateScript = evaluateScripts;
}

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) {
stack.pop();
parent = <Element>parent.parentNode || root;
// An assumption that the same rule should be applied for the HTMLLinkElement is made here.
if (tagName === 'link') {
(<HTMLLinkElement>newElement)._evaluateCSS = evaluateScripts;
}

parent = <Element>parent.appendChild(newElement);
parentUnnestableTagName = this.getUnnestableTagName(parent);
stack.push(parent);
} else {
parent.appendChild(newElement);
}
lastTextIndex = markupRegexp.lastIndex;

// Tags which contain non-parsed content
// For example: <script> JavaScript should not be parsed
if (ChildLessElements.includes(tagName)) {
let childLessMatch = null;
while ((childLessMatch = markupRegexp.exec(data))) {
if (childLessMatch[2].toLowerCase() === tagName && childLessMatch[1]) {
markupRegexp.lastIndex -= childLessMatch[0].length;
break;
this.setAttributes(newElement, match[3]);

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) {
stack.pop();
parent = <Element>parent.parentNode || root;
}

parent = <Element>parent.appendChild(newElement);
parentUnnestableTagName = this.getUnnestableTagName(parent);
stack.push(parent);
} else {
parent.appendChild(newElement);
}
lastTextIndex = markupRegexp.lastIndex;

// Tags which contain non-parsed content
// For example: <script> JavaScript should not be parsed
if (ChildLessElements.includes(tagName)) {
let childLessMatch = null;
while ((childLessMatch = markupRegexp.exec(data))) {
if (childLessMatch[2].toLowerCase() === tagName && childLessMatch[1]) {
markupRegexp.lastIndex -= childLessMatch[0].length;
break;
}
}
}
}
} else {
stack.pop();
parent = stack[stack.length - 1] || root;
parentUnnestableTagName = this.getUnnestableTagName(parent);
} else {
stack.pop();
parent = stack[stack.length - 1] || root;
parentUnnestableTagName = this.getUnnestableTagName(parent);

lastTextIndex = markupRegexp.lastIndex;
lastTextIndex = markupRegexp.lastIndex;
}
}
}

// Text after last element
if ((!match && data.length > 0) || (match && lastTextIndex !== match.index)) {
const text = data.substring(lastTextIndex);
this.appendTextAndCommentNodes(document, parent || root, text);
// Text after last element
if ((!match && data.length > 0) || (match && lastTextIndex !== match.index)) {
const text = data.substring(lastTextIndex);
this.appendTextAndCommentNodes(document, parent || root, text);
}
}

return root;
Expand Down
14 changes: 14 additions & 0 deletions packages/happy-dom/test/xml-parser/XMLParser.test.ts
Expand Up @@ -396,5 +396,19 @@ describe('XMLParser', () => {

expect((<IHTMLElement>root.children[0]).innerText).toBe(`console.log('hello')`);
});

it('Handles different value types.', () => {
const root1 = XMLParser.parse(window.document, null);
expect(new XMLSerializer().serializeToString(root1)).toBe('');

const root2 = XMLParser.parse(window.document, undefined);
expect(new XMLSerializer().serializeToString(root2)).toBe('');

const root3 = XMLParser.parse(window.document, <string>(<unknown>1000));
expect(new XMLSerializer().serializeToString(root3)).toBe('1000');

const root4 = XMLParser.parse(window.document, <string>(<unknown>false));
expect(new XMLSerializer().serializeToString(root4)).toBe('false');
});
});
});