Skip to content

Commit

Permalink
Merge branch 'master' into capricorn86#976@minor
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed Jul 13, 2023
2 parents c3c9d80 + 2e1d66e commit 1052bbb
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 13 deletions.
37 changes: 24 additions & 13 deletions packages/happy-dom/src/xml-parser/XMLParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ import * as Entities from 'entities';
* Group 8: End of start tag (e.g. ">" in "<div>").
*/
const MARKUP_REGEXP =
/<([a-zA-Z0-9-]+)|<\/([a-zA-Z0-9-]+)>|<!--([^-]+)-->|<!--([^>]+)>|<!([^>]+)>|<\?([^>]+)>|(\/>)|(>)/gm;
/<([a-zA-Z0-9-]+)|<\/([a-zA-Z0-9-]+)\s*>|<!--([^-]+)-->|<!--([^>]+)>|<!([^>]+)>|<\?([^>]+)>|(\/>)|(>)/gm;

/**
* Attribute RegExp.
*
* Group 1: Attribute name when the attribute has a value using double apostrophe (e.g. "name" in "<div name="value">").
* Group 2: Attribute apostrophe value using double apostrophe (e.g. "value" in "<div name="value">").
* Group 3: Attribute value when the attribute has a value using double apostrophe (e.g. "value" in "<div name="value">").
* Group 4: Attribute apostrophe when the attribute has a value using double apostrophe (e.g. "value" in "<div name="value">").
* Group 5: Attribute name when the attribute has a value using single apostrophe (e.g. "name" in "<div name='value'>").
* Group 6: Attribute apostrophe when the attribute has a value using single apostrophe (e.g. "name" in "<div name='value'>").
* Group 2: Attribute value when the attribute has a value using double apostrophe (e.g. "value" in "<div name="value">").
* Group 3: Attribute name when the attribute has a value using double apostrophe (e.g. "name" in "<div name="value">").
* Group 4: Attribute value when the attribute has a value using double apostrophe (e.g. "value" in "<div name="value">").
* Group 5: Attribute end apostrophe when the attribute has a value using double apostrophe (e.g. '"' in "<div name="value">").
* Group 6: Attribute name when the attribute has a value using single apostrophe (e.g. "name" in "<div name='value'>").
* 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 8: Attribute end apostrophe when the attribute has a value using single apostrophe (e.g. "'" in "<div name='value'>").
* Group 9: Attribute name when the attribute has no value (e.g. "disabled" in "<div disabled>").
*/
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-_:.$@]+)/gm;
/\s*([a-zA-Z0-9-_:.$@?]+) *= *([a-zA-Z0-9-_:.$@?{}]+)|\s*([a-zA-Z0-9-_:.$@?]+) *= *"([^"]*)("{0,1})|\s*([a-zA-Z0-9-_:.$@?]+) *= *'([^']*)('{0,1})|\s*([a-zA-Z0-9-_:.$@?]+)/gm;

enum MarkupReadStateEnum {
startOrEndTag = 'startOrEndTag',
Expand Down Expand Up @@ -209,22 +209,33 @@ export default class XMLParser {

while ((attributeMatch = attributeRegexp.exec(attributeString))) {
if (
(attributeMatch[1] && attributeMatch[2] === attributeMatch[4]) ||
(attributeMatch[5] && attributeMatch[6] === attributeMatch[8]) ||
(attributeMatch[1] && attributeMatch[2]) ||
(attributeMatch[3] && attributeMatch[5] === '"') ||
(attributeMatch[6] && attributeMatch[8] === "'") ||
attributeMatch[9]
) {
// Valid attribute name and value.

const name = attributeMatch[1] || attributeMatch[5] || attributeMatch[9] || '';
const rawValue = attributeMatch[3] || attributeMatch[7] || '';
const name =
attributeMatch[1] ||
attributeMatch[3] ||
attributeMatch[6] ||
attributeMatch[9] ||
'';
const rawValue =
attributeMatch[2] || attributeMatch[4] || attributeMatch[7] || '';
const value = rawValue ? Entities.decodeHTMLAttribute(rawValue) : '';
const namespaceURI =
(<IElement>currentNode).tagName === 'SVG' && name === 'xmlns' ? value : null;

(<IElement>currentNode).setAttributeNS(namespaceURI, name, value);

startTagIndex += attributeMatch[0].length;
} else if (!attributeMatch[4] && !attributeMatch[8]) {
} else if (
!attributeMatch[1] &&
((attributeMatch[3] && !attributeMatch[5]) ||
(attributeMatch[6] && !attributeMatch[8]))
) {
// End attribute apostrophe is missing (e.g. "attr='value" or 'attr="value').

hasAttributeStringEnded = false;
Expand Down
16 changes: 16 additions & 0 deletions packages/happy-dom/test/query-selector/QuerySelector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,22 @@ describe('QuerySelector', () => {
expect(elements[2] === container.children[0].children[1].children[2]).toBe(true);
});

it('Returns only first child elements for selector "div>span"', () => {
const div = document.createElement('div');
div.innerHTML = `
<div>
<article></article>
<span><span></span></span>
<span><span></span></span>
<article></article>
</div>
`;
const elements = div.querySelectorAll('div>span');
expect(elements.length).toBe(2);
expect(elements[0] === div.children[0].children[1]).toBe(true);
expect(elements[1] === div.children[0].children[2]).toBe(true);
});

it('Returns all elements matching "div > div > .class1.class2".', () => {
const container = document.createElement('div');
container.innerHTML = QuerySelectorHTML;
Expand Down
38 changes: 38 additions & 0 deletions packages/happy-dom/test/xml-parser/XMLParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,12 +648,50 @@ describe('XMLParser', () => {
<img key1="value1"/>
<span .key$lit$="{{lit-11111}}"></span>
<div @event="{{lit-22222}}"></div>
<article ?checked="{{lit-33333}}"></div>
<img key1="value1" key2/>
`
);

expect(root.querySelector('span')?.getAttribute('.key$lit$')).toBe('{{lit-11111}}');
expect(root.querySelector('div')?.getAttribute('@event')).toBe('{{lit-22222}}');
expect(root.querySelector('article')?.getAttribute('?checked')).toBe('{{lit-33333}}');
});

it('Parses attributes without apostrophs.', () => {
const root = XMLParser.parse(
document,
`<div .theme$lit$={{lit-12345}} key1="value1">Test</div>`
);

expect(new XMLSerializer().serializeToString(root)).toBe(
'<div .theme$lit$="{{lit-12345}}" key1="value1">Test</div>'
);
});

it('Parses attributes with single apostrophs.', () => {
const root = XMLParser.parse(document, `<div key1='value1' key2='value2'>Test</div>`);

expect(new XMLSerializer().serializeToString(root)).toBe(
`<div key1="value1" key2="value2">Test</div>`
);
});

it('Parses HTML with end ">" on a new line.', () => {
const root = XMLParser.parse(
document,
`
<div key1="value1">
Test
</div
>
`
);

expect(new XMLSerializer().serializeToString(root).replace(/\s/gm, '')).toBe(
'<divkey1="value1">Test</div>'
);
expect(root.children[0].textContent.replace(/\s/gm, '')).toBe('Test');
});
});
});

0 comments on commit 1052bbb

Please sign in to comment.