Skip to content

Commit

Permalink
fix: [#1661] Fixes problem with encoding and decoding attribute value…
Browse files Browse the repository at this point in the history
…s in HTML
  • Loading branch information
capricorn86 committed Jan 8, 2025
1 parent 4d338df commit 55110a1
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 13 deletions.
2 changes: 1 addition & 1 deletion packages/happy-dom/src/html-parser/HTMLParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions packages/happy-dom/src/html-serializer/HTMLSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 + '"';
Expand Down
31 changes: 29 additions & 2 deletions packages/happy-dom/src/utilities/XMLEncodeUtility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
}
Expand All @@ -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 '';
}
Expand All @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/xml-parser/XMLParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(':');
Expand Down
12 changes: 6 additions & 6 deletions packages/happy-dom/src/xml-serializer/XMLSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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])}"`;
}
}

Expand All @@ -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]
)}"`;
}
Expand Down
16 changes: 16 additions & 0 deletions packages/happy-dom/test/html-parser/HTMLParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2122,5 +2122,21 @@ describe('HTMLParser', () => {
'<html><head></head><body>Test</body></html>'
);
});

it('Handles line breaks in attributes for #1678', () => {
const result = new HTMLParser(window).parse(
` <div>
<button class="btn btn-secondary comment_reply" data-id="{{id}}" type="button">{{message_gui_reply}}</button> <button class="btn btn-secondary comment_collapse
visually-hidden" type="button">{{message_gui_replies}}</button>
</div>`
);

expect(new HTMLSerializer().serializeToString(result)).toBe(
` <div>
<button class="btn btn-secondary comment_reply" data-id="{{id}}" type="button">{{message_gui_reply}}</button> <button class="btn btn-secondary comment_collapse
visually-hidden" type="button">{{message_gui_replies}}</button>
</div>`
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ describe('HTMLSerializer', () => {
div.setAttribute('attr3', '');

expect(serializer.serializeToString(div)).toBe(
'<div attr1="Hello ⁨John⁩" attr2="&lt;span&gt; test" attr3=""></div>'
'<div attr1="Hello ⁨John⁩" attr2="<span> test" attr3=""></div>'
);
});

Expand Down

0 comments on commit 55110a1

Please sign in to comment.