Skip to content

Commit 97de564

Browse files
chore(xml-builder): single-pass XML escape for escapeElement and escapeAttribute (#7833)
Replace chained .replace() calls with a single regex + lookup map approach. - escapeElement: 9 sequential .replace() calls → 1 pass with combined regex - escapeAttribute: 4 sequential .replace() calls → 1 pass with combined regex - Fix missing /g flag on \u2028 replacement in escapeElement (only first occurrence was being escaped)
1 parent 7f54759 commit 97de564

File tree

3 files changed

+29
-11
lines changed

3 files changed

+29
-11
lines changed
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
const ATTR_ESCAPE_RE = /[&<>"]/g;
2+
3+
const ATTR_ESCAPE_MAP: Record<string, string> = {
4+
"&": "&amp;",
5+
"<": "&lt;",
6+
">": "&gt;",
7+
'"': "&quot;",
8+
};
9+
110
/**
211
* @internal
312
*
413
* Escapes characters that can not be in an XML attribute.
514
*/
615
export function escapeAttribute(value: string): string {
7-
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
16+
return value.replace(ATTR_ESCAPE_RE, (ch) => ATTR_ESCAPE_MAP[ch]);
817
}

packages-internal/xml-builder/src/escape-element.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ describe("escape-element", () => {
77
const value = "abc 123 &<>\"'%\n\r\u0085\u2028";
88
expect(escapeElement(value)).toBe("abc 123 &amp;&lt;&gt;&quot;&apos;%&#x0A;&#x0D;&#x85;&#x2028;");
99
});
10+
11+
it("escapes multiple \\u2028 occurrences", () => {
12+
expect(escapeElement("a\u2028b\u2028c")).toBe("a&#x2028;b&#x2028;c");
13+
});
1014
});
Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
const ELEMENT_ESCAPE_RE = /[&"'<>\r\n\u0085\u2028]/g;
2+
3+
const ELEMENT_ESCAPE_MAP: Record<string, string> = {
4+
"&": "&amp;",
5+
'"': "&quot;",
6+
"'": "&apos;",
7+
"<": "&lt;",
8+
">": "&gt;",
9+
"\r": "&#x0D;",
10+
"\n": "&#x0A;",
11+
"\u0085": "&#x85;",
12+
"\u2028": "&#x2028;",
13+
};
14+
115
/**
216
* @internal
317
*
418
* Escapes characters that can not be in an XML element.
519
*/
620
export function escapeElement(value: string): string {
7-
return value
8-
.replace(/&/g, "&amp;")
9-
.replace(/"/g, "&quot;")
10-
.replace(/'/g, "&apos;")
11-
.replace(/</g, "&lt;")
12-
.replace(/>/g, "&gt;")
13-
.replace(/\r/g, "&#x0D;")
14-
.replace(/\n/g, "&#x0A;")
15-
.replace(/\u0085/g, "&#x85;")
16-
.replace(/\u2028/, "&#x2028;");
21+
return value.replace(ELEMENT_ESCAPE_RE, (ch) => ELEMENT_ESCAPE_MAP[ch]);
1722
}

0 commit comments

Comments
 (0)