Skip to content

Commit

Permalink
#875@trivial: Continues on implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed May 9, 2023
1 parent 2a78a72 commit da6edaa
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 13 deletions.
23 changes: 12 additions & 11 deletions packages/happy-dom/src/xml-parser/XMLParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ const MARKUP_REGEXP =
* Group 7: Attribute value when the attribute has a value using single apostrophe (e.g. "value" in "<div name='value'>").
* Group 8: Attribute apostrophe when the attribute has a value using single apostrophe (e.g. "name" in "<div name='value'>").
* Group 9: Attribute name when the attribute has no value (e.g. "disabled" in "<div disabled>").
* Group 10: Invalid characters to trim.
*/
const ATTRIBUTE_REGEXP =
/\s*([a-zA-Z0-9-_:]+) *= *("{0,1})([^"]*)("{0,1})|\s*([a-zA-Z0-9-_:]+) *= *('{0,1})([^']*)('{0,1})|\s*([a-zA-Z0-9-_:]+)|\s*([^a-zA-Z0-9-_:]+$)/gm;
/\s*([a-zA-Z0-9-_:]+) *= *("{0,1})([^"]*)("{0,1})|\s*([a-zA-Z0-9-_:]+) *= *('{0,1})([^']*)('{0,1})|\s*([a-zA-Z0-9-_:]+)/gm;

enum MarkupReadStateEnum {
startOrEndTag = 'startOrEndTag',
Expand Down Expand Up @@ -190,10 +189,11 @@ export default class XMLParser {
// Attribute name and value.

const attributeString = data.substring(startTagIndex, match.index);
if (attributeString) {
let hasAttributeStringEnded = true;

if (!!attributeString) {
const attributeRegexp = new RegExp(ATTRIBUTE_REGEXP, 'gm');
let attributeMatch: RegExpExecArray;
let attributeLatestIndex = 0;

while ((attributeMatch = attributeRegexp.exec(attributeString))) {
if (
Expand All @@ -212,20 +212,21 @@ export default class XMLParser {
(<IElement>currentNode).setAttributeNS(namespaceURI, name, value);

startTagIndex += attributeMatch[0].length;
attributeLatestIndex = attributeRegexp.lastIndex;
} else if (attributeMatch[10] && attributeLatestIndex === attributeMatch.index) {
// Invalid characters that should be trimmed.

startTagIndex += attributeMatch[0].length;
} else {
hasAttributeStringEnded = false;
break;
}
}
}

// We need to check if the attribute string is read completely.
// The attribute string can potentially contain "/>" or ">".
if (startTagIndex === match.index) {
if (hasAttributeStringEnded) {
// Non-self-closing tag
if (match[7] && !VoidElements.includes((<IElement>currentNode).tagName)) {
if (
(match[7] || (<IElement>currentNode).namespaceURI === NamespaceURI.html) &&
!VoidElements.includes((<IElement>currentNode).tagName)
) {
// Plain text elements such as <script> and <style> should only contain text.
plainTextTagName = PlainTextElements.includes((<IElement>currentNode).tagName)
? (<IElement>currentNode).tagName
Expand Down
93 changes: 92 additions & 1 deletion packages/happy-dom/test/xml-parser/XMLParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const GET_EXPECTED_HTML = (html: string): string =>
.replace('<?processing instruction?>', '<!--?processing instruction?-->')
.replace('<!Exclamation mark comment>', '<!--Exclamation mark comment-->')
.replace('<!DOCTYPE HTML', '<!DOCTYPE html')
.replace('<self-closing-custom-tag />', '<self-closing-custom-tag></self-closing-custom-tag>');
.replace('<img />', '<img>');

describe('XMLParser', () => {
let window: IWindow;
Expand Down Expand Up @@ -535,5 +535,96 @@ describe('XMLParser', () => {
'index > 1'
);
});

it('Doesn\'t close non-void elements when using "/>" when namespace is HTML.', () => {
const root = XMLParser.parse(
window.document,
`
<span key1="value1"/>
<span key1="value1" key2/>
<span key2/>
`
);

expect(new XMLSerializer().serializeToString(root).replace(/\s/gm, '')).toBe(
`
<span key1="value1">
<span key1="value1" key2="">
<span key2=""></span>
</span>
</span>`.replace(/\s/gm, '')
);
});

it('Parses malformed attributes.', () => {
const root = XMLParser.parse(
window.document,
`
<span key1="value1""></span>
<span key1="value1"" key2></span>
<span key1 key2 key3="value3""></span>
<img key1="value1"" key2/>
<img key1="value1""
/>
<img key1="value1""
key2/>
<span key1 key2 key3="value3"''" " "></span>
<span key1="value1 > value2"></span>
<img key1="value1 /> value2"/>
`
);

expect(root.children.length).toBe(8);

expect(root.children[0].attributes.length).toBe(1);
expect(root.children[0].attributes[0].name).toBe('key1');
expect(root.children[0].attributes[0].value).toBe('value1');

expect(root.children[1].attributes.length).toBe(2);
expect(root.children[1].attributes[0].name).toBe('key1');
expect(root.children[1].attributes[0].value).toBe('value1');
expect(root.children[1].attributes[1].name).toBe('key2');
expect(root.children[1].attributes[1].value).toBe('');

expect(root.children[2].attributes.length).toBe(3);
expect(root.children[2].attributes[0].name).toBe('key1');
expect(root.children[2].attributes[0].value).toBe('');
expect(root.children[2].attributes[1].name).toBe('key2');
expect(root.children[2].attributes[1].value).toBe('');
expect(root.children[2].attributes[2].name).toBe('key3');
expect(root.children[2].attributes[2].value).toBe('value3');

expect(root.children[3].attributes.length).toBe(2);
expect(root.children[3].attributes[0].name).toBe('key1');
expect(root.children[3].attributes[0].value).toBe('value1');
expect(root.children[3].attributes[1].name).toBe('key2');
expect(root.children[3].attributes[1].value).toBe('');

expect(root.children[4].attributes.length).toBe(1);
expect(root.children[4].attributes[0].name).toBe('key1');
expect(root.children[4].attributes[0].value).toBe('value1');

expect(root.children[5].attributes.length).toBe(2);
expect(root.children[5].attributes[0].name).toBe('key1');
expect(root.children[5].attributes[0].value).toBe('value1');
expect(root.children[5].attributes[1].name).toBe('key2');
expect(root.children[5].attributes[1].value).toBe('');

expect(root.children[6].attributes.length).toBe(3);
expect(root.children[6].attributes[0].name).toBe('key1');
expect(root.children[6].attributes[0].value).toBe('');
expect(root.children[6].attributes[1].name).toBe('key2');
expect(root.children[6].attributes[1].value).toBe('');
expect(root.children[6].attributes[2].name).toBe('key3');
expect(root.children[6].attributes[2].value).toBe('value3');

expect(root.children[7].attributes.length).toBe(1);
expect(root.children[7].attributes[0].name).toBe('key1');
expect(root.children[7].attributes[0].value).toBe('value1 > value2');

expect(root.children[8].attributes.length).toBe(1);
expect(root.children[8].attributes[0].name).toBe('key1');
expect(root.children[8].attributes[0].value).toBe('value1 /> value2');
});
});
});
2 changes: 1 addition & 1 deletion packages/happy-dom/test/xml-parser/data/XMLParserHTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default `
<!-- Comment 2 !-->
</article>
<img>
<self-closing-custom-tag />
<img />
</body>
</html>
`.trim();

0 comments on commit da6edaa

Please sign in to comment.