diff --git a/src/nodes/html.ts b/src/nodes/html.ts index 6462024..c6b3534 100644 --- a/src/nodes/html.ts +++ b/src/nodes/html.ts @@ -158,11 +158,11 @@ export default class HTMLElement extends Node { return 'null'; } - return JSON.stringify(attr.replace(/"/g, '"')) - .replace(/([^\\])\\t/g, '$1\t') - .replace(/([^\\])\\n/g, '$1\n') - .replace(/([^\\])\\r/g, '$1\r') - .replace(/([^\\])\\/g, '$1'); + // Attribute values are literal text: only the double quote needs + // escaping (as "). The previous JSON.stringify-based approach + // doubled backslashes and then failed to undo runs of consecutive + // backslashes, corrupting values such as "C:\\Users\\me". + return `"${attr.replace(/"/g, '"')}"`; } /** diff --git a/test/tests/quoteattributes.js b/test/tests/quoteattributes.js index ae7572f..8c561ff 100644 --- a/test/tests/quoteattributes.js +++ b/test/tests/quoteattributes.js @@ -28,4 +28,12 @@ describe('quote attributes', function () { foo: '[{"bar":"baz"}]', }); }); + + it('preserves consecutive backslashes through a setAttribute round-trip', function () { + const root = parse('
'); + const div = root.firstChild; + const value = 'C:\\\\Users\\\\me'; // C:\\Users\\me (doubled backslashes) + div.setAttribute('path', value); + parse(div.toString()).firstChild.getAttribute('path').should.equal(value); + }); });