Skip to content

Commit fe2e993

Browse files
authored
fix: enable client parser to retain carriage return characters (#902)
* fix: enable client parser to retain carriage return characters * fix: enable client parser to retain carriage return characters * chore: only find \r not \n * chore: add missing tests * chore: add missing tests
1 parent c4fc01e commit fe2e993

File tree

5 files changed

+117
-6
lines changed

5 files changed

+117
-6
lines changed

package-lock.json

Lines changed: 9 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/domparser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { escapeSpecialCharacters } from './utilities';
2+
13
// constants
24
const HTML = 'html';
35
const HEAD = 'head';
@@ -116,6 +118,9 @@ if (template && template.content) {
116118
* @returns - DOM nodes.
117119
*/
118120
export default function domparser(html: string): NodeList {
121+
// Escape special characters before parsing
122+
html = escapeSpecialCharacters(html);
123+
119124
const match = html.match(FIRST_TAG_REGEX);
120125
const firstTagName = match && match[1] ? match[1].toLowerCase() : '';
121126

src/client/utilities.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,26 @@ function formatTagName(tagName: string): string {
5151
return tagName;
5252
}
5353

54+
/**
55+
* Escapes special characters before parsing.
56+
*
57+
* @param html - The HTML string.
58+
* @returns - HTML string with escaped special characters.
59+
*/
60+
export function escapeSpecialCharacters(html: string): string {
61+
return html.replace(/\r/g, '\\r');
62+
}
63+
64+
/**
65+
* Reverts escaped special characters back to actual characters.
66+
*
67+
* @param text - The text with escaped characters.
68+
* @returns - Text with escaped characters reverted.
69+
*/
70+
export function revertEscapedCharacters(text: string): string {
71+
return text.replace(/\\r/g, '\r');
72+
}
73+
5474
/**
5575
* Transforms DOM nodes to `domhandler` nodes.
5676
*
@@ -95,7 +115,7 @@ export function formatDOM(
95115
}
96116

97117
case 3:
98-
current = new Text(node.nodeValue!);
118+
current = new Text(revertEscapedCharacters(node.nodeValue!));
99119
break;
100120

101121
case 8:

test/cases/html.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,28 @@ module.exports = [
305305
data: ' ',
306306
},
307307

308+
// text with special characters
309+
{
310+
name: 'text with carriage return',
311+
data: 'Hello\rWorld',
312+
},
313+
{
314+
name: 'text with multiple carriage returns',
315+
data: 'Hello\rDear\rWorld',
316+
},
317+
{
318+
name: 'text with mixed newlines and carriage returns',
319+
data: 'Hello\rWorld\nNew\rLine',
320+
},
321+
{
322+
name: 'text with carriage return in tag',
323+
data: '<div>Hello\rWorld</div>',
324+
},
325+
{
326+
name: 'nested tags with carriage returns',
327+
data: '<div>Hello\r<span>Beautiful\r</span>World</div>',
328+
},
329+
308330
// custom tag
309331
{
310332
name: 'custom tag',

test/server/client.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { expect } from 'chai';
22

33
import { formatDOM } from '../../src/client/utilities';
4+
import { revertEscapedCharacters } from '../../src/client/utilities';
5+
import { escapeSpecialCharacters } from '../../src/client/utilities';
46

57
describe('client utilities', () => {
68
describe('formatDOM', () => {
@@ -10,4 +12,62 @@ describe('client utilities', () => {
1012
).to.deep.equal([]);
1113
});
1214
});
15+
16+
describe('escapeSpecialCharacters', () => {
17+
it('escapes carriage return characters', () => {
18+
const input = 'Hello\rWorld';
19+
const expected = 'Hello\\rWorld';
20+
expect(escapeSpecialCharacters(input)).to.equal(expected);
21+
});
22+
23+
it('does not modify strings without special characters', () => {
24+
const input = 'Hello World';
25+
expect(escapeSpecialCharacters(input)).to.equal(input);
26+
});
27+
28+
it('handles empty strings', () => {
29+
expect(escapeSpecialCharacters('')).to.equal('');
30+
});
31+
32+
it('handles multiple carriage returns', () => {
33+
const input = 'Hello\rDear\rWorld';
34+
const expected = 'Hello\\rDear\\rWorld';
35+
expect(escapeSpecialCharacters(input)).to.equal(expected);
36+
});
37+
38+
it('only escapes carriage returns', () => {
39+
const input = 'Hello\rWorld\n'; // \n should not be affected
40+
const expected = 'Hello\\rWorld\n';
41+
expect(escapeSpecialCharacters(input)).to.equal(expected);
42+
});
43+
});
44+
45+
describe('revertEscapedCharacters', () => {
46+
it('reverts escaped carriage return characters', () => {
47+
const input = 'Hello\\rWorld';
48+
const expected = 'Hello\rWorld';
49+
expect(revertEscapedCharacters(input)).to.equal(expected);
50+
});
51+
52+
it('does not modify strings without escaped characters', () => {
53+
const input = 'Hello World';
54+
expect(revertEscapedCharacters(input)).to.equal(input);
55+
});
56+
57+
it('handles empty strings', () => {
58+
expect(revertEscapedCharacters('')).to.equal('');
59+
});
60+
61+
it('handles multiple escaped carriage returns', () => {
62+
const input = 'Hello\\rDear\\rWorld';
63+
const expected = 'Hello\rDear\rWorld';
64+
expect(revertEscapedCharacters(input)).to.equal(expected);
65+
});
66+
67+
it('only reverts escaped carriage returns', () => {
68+
const input = 'Hello\\rWorld\\n'; // \n should not be affected
69+
const expected = 'Hello\rWorld\\n';
70+
expect(revertEscapedCharacters(input)).to.equal(expected);
71+
});
72+
});
1373
});

0 commit comments

Comments
 (0)