diff --git a/packages/happy-dom/src/html-parser/HTMLParser.ts b/packages/happy-dom/src/html-parser/HTMLParser.ts index 6dc2ed98..c5f2ab46 100755 --- a/packages/happy-dom/src/html-parser/HTMLParser.ts +++ b/packages/happy-dom/src/html-parser/HTMLParser.ts @@ -402,7 +402,7 @@ export default class HTMLParser { const name = attributeMatch[1] || attributeMatch[3] || attributeMatch[6] || attributeMatch[9] || ''; const rawValue = attributeMatch[2] || attributeMatch[4] || attributeMatch[7] || ''; - const value = rawValue ? XMLEncodeUtility.decodeAttributeValue(rawValue) : ''; + const value = rawValue ? XMLEncodeUtility.decodeHTMLAttributeValue(rawValue) : ''; const attributes = this.nextElement[PropertySymbol.attributes]; if (this.nextElement[PropertySymbol.namespaceURI] === NamespaceURI.svg) { diff --git a/packages/happy-dom/src/html-serializer/HTMLSerializer.ts b/packages/happy-dom/src/html-serializer/HTMLSerializer.ts index 44b4f459..dede1814 100644 --- a/packages/happy-dom/src/html-serializer/HTMLSerializer.ts +++ b/packages/happy-dom/src/html-serializer/HTMLSerializer.ts @@ -147,11 +147,11 @@ export default class HTMLSerializer { if (!namedItems.has('is') && element[PropertySymbol.isValue]) { attributeString += - ' is="' + XMLEncodeUtility.encodeAttributeValue(element[PropertySymbol.isValue]) + '"'; + ' is="' + XMLEncodeUtility.encodeHTMLAttributeValue(element[PropertySymbol.isValue]) + '"'; } for (const attributes of namedItems.values()) { - const escapedValue = XMLEncodeUtility.encodeAttributeValue( + const escapedValue = XMLEncodeUtility.encodeHTMLAttributeValue( attributes[0][PropertySymbol.value] ); attributeString += ' ' + attributes[0][PropertySymbol.name] + '="' + escapedValue + '"'; diff --git a/packages/happy-dom/src/query-selector/SelectorItem.ts b/packages/happy-dom/src/query-selector/SelectorItem.ts index d27063ff..0bf5b4c5 100644 --- a/packages/happy-dom/src/query-selector/SelectorItem.ts +++ b/packages/happy-dom/src/query-selector/SelectorItem.ts @@ -7,6 +7,8 @@ import ISelectorAttribute from './ISelectorAttribute.js'; import ISelectorMatch from './ISelectorMatch.js'; import ISelectorPseudo from './ISelectorPseudo.js'; +const SPACE_REGEXP = /\s+/; + /** * Selector item. */ @@ -417,7 +419,7 @@ export default class SelectorItem { return null; } - const classList = element.className.split(' '); + const classList = element.className.split(SPACE_REGEXP); let priorityWeight = 0; for (const className of this.classNames) { diff --git a/packages/happy-dom/src/utilities/XMLEncodeUtility.ts b/packages/happy-dom/src/utilities/XMLEncodeUtility.ts index 67ad9c86..8aef0b06 100644 --- a/packages/happy-dom/src/utilities/XMLEncodeUtility.ts +++ b/packages/happy-dom/src/utilities/XMLEncodeUtility.ts @@ -8,7 +8,7 @@ export default class XMLEncodeUtility { * @param value Value. * @returns Escaped value. */ - public static encodeAttributeValue(value: string | null): string { + public static encodeXMLAttributeValue(value: string | null): string { if (value === null) { return ''; } @@ -28,7 +28,7 @@ export default class XMLEncodeUtility { * @param value Value. * @returns Decoded value. */ - public static decodeAttributeValue(value: string | null): string { + public static decodeXMLAttributeValue(value: string | null): string { if (value === null) { return ''; } @@ -43,6 +43,33 @@ export default class XMLEncodeUtility { .replace(/&/gu, '&'); } + /** + * Encodes attribute value. + * + * @param value Value. + * @returns Escaped value. + */ + public static encodeHTMLAttributeValue(value: string | null): string { + if (value === null) { + return ''; + } + return value.replace(/&/gu, '&').replace(/"/gu, '"'); + } + + /** + * Decodes attribute value. + * + * @param value Value. + * @returns Decoded value. + */ + public static decodeHTMLAttributeValue(value: string | null): string { + if (value === null) { + return ''; + } + + return value.replace(/"/gu, '"').replace(/&/gu, '&'); + } + /** * Encodes text content. * diff --git a/packages/happy-dom/src/xml-parser/XMLParser.ts b/packages/happy-dom/src/xml-parser/XMLParser.ts index ea051a38..39bba8d1 100755 --- a/packages/happy-dom/src/xml-parser/XMLParser.ts +++ b/packages/happy-dom/src/xml-parser/XMLParser.ts @@ -487,7 +487,7 @@ export default class XMLParser { // In XML, new line characters should be replaced with a space. const value = rawValue - ? XMLEncodeUtility.decodeAttributeValue(rawValue.replace(NEW_LINE_REGEXP, ' ')) + ? XMLEncodeUtility.decodeXMLAttributeValue(rawValue.replace(NEW_LINE_REGEXP, ' ')) : ''; const attributes = this.nextElement[PropertySymbol.attributes]; const nameParts = name.split(':'); diff --git a/packages/happy-dom/src/xml-serializer/XMLSerializer.ts b/packages/happy-dom/src/xml-serializer/XMLSerializer.ts index f568a5b9..75346e2e 100644 --- a/packages/happy-dom/src/xml-serializer/XMLSerializer.ts +++ b/packages/happy-dom/src/xml-serializer/XMLSerializer.ts @@ -229,7 +229,7 @@ export default class XMLSerializer { attribute[PropertySymbol.localName] === elementPrefix && element[PropertySymbol.namespaceURI] ) { - namespaceString += ` xmlns:${elementPrefix}="${XMLEncodeUtility.encodeAttributeValue( + namespaceString += ` xmlns:${elementPrefix}="${XMLEncodeUtility.encodeXMLAttributeValue( element[PropertySymbol.namespaceURI] )}"`; handledNamespaces.add(element[PropertySymbol.namespaceURI]); @@ -238,20 +238,20 @@ export default class XMLSerializer { attribute[PropertySymbol.name] === 'xmlns' && element[PropertySymbol.namespaceURI] ) { - namespaceString += ` xmlns="${XMLEncodeUtility.encodeAttributeValue( + namespaceString += ` xmlns="${XMLEncodeUtility.encodeXMLAttributeValue( element[PropertySymbol.namespaceURI] )}"`; handledNamespaces.add(element[PropertySymbol.namespaceURI]); } else { namespaceString += ` ${ attribute[PropertySymbol.name] - }="${XMLEncodeUtility.encodeAttributeValue(attribute[PropertySymbol.value])}"`; + }="${XMLEncodeUtility.encodeXMLAttributeValue(attribute[PropertySymbol.value])}"`; handledNamespaces.add(attribute[PropertySymbol.value]); } } else { attributeString += ` ${ attribute[PropertySymbol.name] - }="${XMLEncodeUtility.encodeAttributeValue(attribute[PropertySymbol.value])}"`; + }="${XMLEncodeUtility.encodeXMLAttributeValue(attribute[PropertySymbol.value])}"`; } } @@ -262,14 +262,14 @@ export default class XMLSerializer { !handledNamespaces.has(element[PropertySymbol.namespaceURI]) ) { if (elementPrefix && !inheritedNamespacePrefixes.has(element[PropertySymbol.namespaceURI])) { - namespaceString += ` xmlns:${elementPrefix}="${XMLEncodeUtility.encodeAttributeValue( + namespaceString += ` xmlns:${elementPrefix}="${XMLEncodeUtility.encodeXMLAttributeValue( element[PropertySymbol.namespaceURI] )}"`; } else if ( !elementPrefix && inheritedDefaultNamespace !== element[PropertySymbol.namespaceURI] ) { - namespaceString += ` xmlns="${XMLEncodeUtility.encodeAttributeValue( + namespaceString += ` xmlns="${XMLEncodeUtility.encodeXMLAttributeValue( element[PropertySymbol.namespaceURI] )}"`; } diff --git a/packages/happy-dom/test/html-parser/HTMLParser.test.ts b/packages/happy-dom/test/html-parser/HTMLParser.test.ts index 3c9806d8..a260b197 100644 --- a/packages/happy-dom/test/html-parser/HTMLParser.test.ts +++ b/packages/happy-dom/test/html-parser/HTMLParser.test.ts @@ -2122,5 +2122,25 @@ describe('HTMLParser', () => { '
Test' ); }); + + it('Handles line breaks in attributes for #1678', () => { + const result = new HTMLParser(window).parse( + `