speedy-entities 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,159 @@
1
+ import {} from "./createDoubleArrayEntityTrie.js";
2
+ /**
3
+ * Creates a decoder that uses a trie from {@linkcode createEntityTrie}, so decoders that differ only in options can
4
+ * share a trie.
5
+ */
6
+ export function createEntityTrieDecoder(entityTrie, options = {}) {
7
+ const { isNumericReferenceSemicolonRequired = false, areC1ControlsRemapped = true, isAttributeValue = false, } = options;
8
+ const { nodeBases, nodeChecks, nodeValueIndexes, values } = entityTrie;
9
+ return input => {
10
+ let output = '';
11
+ let textIndex = 0;
12
+ const inputLength = input.length;
13
+ // A reference is "&" followed by at least one char, so it can't start at the last char
14
+ let charIndex = inputLength > 1 ? input.indexOf('&') : -1;
15
+ while (charIndex !== -1 && charIndex < inputLength - 1) {
16
+ let startIndex = charIndex + 1;
17
+ let value;
18
+ let endIndex = startIndex;
19
+ // Both reads below must stay in bounds: reading past the end of the input makes V8 deoptimize this function,
20
+ // and it stays about twice as slow on short inputs afterwards
21
+ if (startIndex < inputLength - 1 && input.charCodeAt(startIndex) === /* # */ 35) {
22
+ // Numeric character reference
23
+ // All digits are consumed, out-of-range code points (Infinity included) are replaced with U+FFFD below
24
+ let codePoint = 0;
25
+ if ((input.charCodeAt(++startIndex) | 32) === /* x */ 120) {
26
+ endIndex = ++startIndex;
27
+ // parseInt of a hexadecimal number
28
+ while (endIndex < inputLength) {
29
+ const charCode = input.charCodeAt(endIndex);
30
+ // Convert alpha to lower case
31
+ const lowerCharCode = charCode | 32;
32
+ if (charCode >= /* 0 */ 48 && charCode <= /* 9 */ 57) {
33
+ codePoint = codePoint * 16 + charCode - 48;
34
+ }
35
+ else if (lowerCharCode >= /* a */ 97 && lowerCharCode <= /* f */ 102) {
36
+ codePoint = codePoint * 16 + lowerCharCode - 87;
37
+ }
38
+ else {
39
+ break;
40
+ }
41
+ ++endIndex;
42
+ }
43
+ }
44
+ else {
45
+ endIndex = startIndex;
46
+ // parseInt of a decimal number
47
+ while (endIndex < inputLength) {
48
+ const charCode = input.charCodeAt(endIndex);
49
+ if (charCode >= /* 0 */ 48 && charCode <= /* 9 */ 57) {
50
+ codePoint = codePoint * 10 + charCode - 48;
51
+ }
52
+ else {
53
+ break;
54
+ }
55
+ ++endIndex;
56
+ }
57
+ }
58
+ if (endIndex !== startIndex) {
59
+ // At least one digit must be present
60
+ const isTerminated = endIndex < inputLength && input.charCodeAt(endIndex) === /* ; */ 59;
61
+ if (isTerminated || !isNumericReferenceSemicolonRequired) {
62
+ // Convert a code point to a string
63
+ if (codePoint === 0 || (codePoint >= 0xd800 && codePoint <= 0xdfff) || codePoint > 0x10ffff) {
64
+ // Null char code, or character reference is outside the permissible Unicode range
65
+ value = '\uFFFD';
66
+ }
67
+ else if (areC1ControlsRemapped && codePoint >= 0x80 && codePoint <= 0x9f) {
68
+ value = c1ControlValues[codePoint - 0x80];
69
+ }
70
+ else {
71
+ value = String.fromCodePoint(codePoint);
72
+ }
73
+ }
74
+ if (isTerminated) {
75
+ ++endIndex;
76
+ }
77
+ }
78
+ }
79
+ else {
80
+ // Character entity reference
81
+ let index = startIndex;
82
+ let node = 0;
83
+ // The node where the longest entity name read so far ends, or 0. Its value is read once, after the loop, since
84
+ // reading the value at each node where a name ends makes decoding a few percent slower.
85
+ let valueNode = 0;
86
+ while (index < inputLength) {
87
+ const charCode = input.charCodeAt(index);
88
+ // Only ASCII chars have nodes, and bases keep base + charCode in bounds for them
89
+ if (charCode > 127) {
90
+ break;
91
+ }
92
+ const childNode = nodeBases[node] + charCode;
93
+ const childCheck = nodeChecks[childNode];
94
+ if (childCheck >> 1 !== node) {
95
+ // No entity name starts with chars read so far
96
+ break;
97
+ }
98
+ node = childNode;
99
+ ++index;
100
+ if ((childCheck & 1) === 1) {
101
+ // An entity name ends at the node
102
+ valueNode = node;
103
+ endIndex = index;
104
+ }
105
+ }
106
+ if (valueNode !== 0) {
107
+ value = values[nodeValueIndexes[valueNode]];
108
+ }
109
+ if (isAttributeValue &&
110
+ value !== undefined &&
111
+ endIndex < inputLength &&
112
+ input.charCodeAt(endIndex - 1) !== /* ; */ 59) {
113
+ const charCode = input.charCodeAt(endIndex);
114
+ // An unterminated reference followed by "=" or an alphanumeric char is likely a URL query parameter
115
+ if (charCode === /* = */ 61 || isAlphanumericCharCode(charCode)) {
116
+ value = undefined;
117
+ }
118
+ }
119
+ }
120
+ if (value !== undefined) {
121
+ // Appending the preceding text and the value separately avoids joining them into a new string first. Once
122
+ // the output is long enough to be a rope, each append only adds a node, even if the encodings differ.
123
+ if (textIndex !== charIndex) {
124
+ output += input.slice(textIndex, charIndex);
125
+ }
126
+ output += value;
127
+ textIndex = endIndex;
128
+ }
129
+ if (endIndex >= inputLength - 1) {
130
+ break;
131
+ }
132
+ // Entities often follow each other, and checking the next char is cheaper than calling indexOf
133
+ if (value === undefined || input.charCodeAt(endIndex) !== /* & */ 38) {
134
+ charIndex = input.indexOf('&', endIndex);
135
+ }
136
+ else {
137
+ charIndex = endIndex;
138
+ }
139
+ }
140
+ return textIndex === 0 ? input : textIndex === inputLength ? output : output + input.slice(textIndex);
141
+ };
142
+ }
143
+ /**
144
+ * Windows-1252 chars for U+0080 to U+009F. Code points that Windows-1252 leaves undefined map to themselves.
145
+ *
146
+ * @see https://github.com/mathiasbynens/he/blob/master/data/decode-map-overrides.json
147
+ */
148
+ // prettier-ignore
149
+ const c1ControlValues = [
150
+ '\u20AC', '\u0081', '\u201A', '\u0192', '\u201E', '\u2026', '\u2020', '\u2021',
151
+ '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u008D', '\u017D', '\u008F',
152
+ '\u0090', '\u2018', '\u2019', '\u201C', '\u201D', '\u2022', '\u2013', '\u2014',
153
+ '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u009D', '\u017E', '\u0178',
154
+ ];
155
+ function isAlphanumericCharCode(charCode) {
156
+ return ((charCode >= /* a */ 97 && charCode <= /* z */ 122) ||
157
+ (charCode >= /* A */ 65 && charCode <= /* Z */ 90) ||
158
+ (charCode >= /* 0 */ 48 && charCode <= /* 9 */ 57));
159
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Decodes HTML entities and numeric references in text.
3
+ */
4
+ export declare const decodeHTML: (input: string) => string;
5
+ /**
6
+ * Same as {@linkcode decodeHTML}, but for attribute values: like browsers, it doesn't decode a named reference without
7
+ * ";" if "=" or an ASCII letter or digit follows it, so "?a=1&copy=2" stays intact.
8
+ */
9
+ export declare const decodeHTMLAttribute: (input: string) => string;
package/decodeHTML.js ADDED
@@ -0,0 +1,20 @@
1
+ import { createDoubleArrayEntityTrie } from "./createDoubleArrayEntityTrie.js";
2
+ import { createEntityTrieDecoder } from "./createEntityTrieDecoder.js";
3
+ import entities from "./entities.js";
4
+ const entityTrie = createDoubleArrayEntityTrie(entities);
5
+ /**
6
+ * Decodes HTML entities and numeric references in text.
7
+ */
8
+ export const decodeHTML = createEntityTrieDecoder(entityTrie, {
9
+ isNumericReferenceSemicolonRequired: false,
10
+ areC1ControlsRemapped: true,
11
+ });
12
+ /**
13
+ * Same as {@linkcode decodeHTML}, but for attribute values: like browsers, it doesn't decode a named reference without
14
+ * ";" if "=" or an ASCII letter or digit follows it, so "?a=1&copy=2" stays intact.
15
+ */
16
+ export const decodeHTMLAttribute = createEntityTrieDecoder(entityTrie, {
17
+ isNumericReferenceSemicolonRequired: false,
18
+ areC1ControlsRemapped: true,
19
+ isAttributeValue: true,
20
+ });
package/decodeXML.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Decodes numeric references and the five predefined XML entities. References must end with ";", as XML requires.
3
+ */
4
+ export declare function decodeXML(input: string): string;
package/decodeXML.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Decodes numeric references and the five predefined XML entities. References must end with ";", as XML requires.
3
+ */
4
+ export function decodeXML(input) {
5
+ let charIndex = input.indexOf('&');
6
+ if (charIndex === -1) {
7
+ // Most text has no references
8
+ return input;
9
+ }
10
+ let output = '';
11
+ let textIndex = 0;
12
+ const inputLength = input.length;
13
+ while (charIndex !== -1 && charIndex < inputLength - 1) {
14
+ let startIndex = charIndex + 1;
15
+ let value;
16
+ let endIndex = startIndex;
17
+ const charCode = input.charCodeAt(startIndex);
18
+ if (charCode === /* # */ 35) {
19
+ // Numeric character reference
20
+ if (startIndex < inputLength - 1) {
21
+ // All digits are consumed, out-of-range code points (Infinity included) are replaced with U+FFFD below
22
+ let codePoint = 0;
23
+ // The char after the digits, or -1 at the end of the input
24
+ let terminatorCharCode = -1;
25
+ if ((input.charCodeAt(++startIndex) | 32) === /* x */ 120) {
26
+ endIndex = ++startIndex;
27
+ // parseInt of a hexadecimal number
28
+ while (endIndex < inputLength) {
29
+ const digitCharCode = input.charCodeAt(endIndex);
30
+ // Convert alpha to lower case
31
+ const lowerCharCode = digitCharCode | 32;
32
+ if (digitCharCode >= /* 0 */ 48 && digitCharCode <= /* 9 */ 57) {
33
+ codePoint = codePoint * 16 + digitCharCode - 48;
34
+ }
35
+ else if (lowerCharCode >= /* a */ 97 && lowerCharCode <= /* f */ 102) {
36
+ codePoint = codePoint * 16 + lowerCharCode - 87;
37
+ }
38
+ else {
39
+ terminatorCharCode = digitCharCode;
40
+ break;
41
+ }
42
+ ++endIndex;
43
+ }
44
+ }
45
+ else {
46
+ endIndex = startIndex;
47
+ // parseInt of a decimal number
48
+ while (endIndex < inputLength) {
49
+ const digitCharCode = input.charCodeAt(endIndex);
50
+ if (digitCharCode >= /* 0 */ 48 && digitCharCode <= /* 9 */ 57) {
51
+ codePoint = codePoint * 10 + digitCharCode - 48;
52
+ }
53
+ else {
54
+ terminatorCharCode = digitCharCode;
55
+ break;
56
+ }
57
+ ++endIndex;
58
+ }
59
+ }
60
+ // At least one digit and ";" must be present
61
+ if (endIndex !== startIndex && terminatorCharCode === /* ; */ 59) {
62
+ ++endIndex;
63
+ if (codePoint === 0 || (codePoint >= 0xd800 && codePoint <= 0xdfff) || codePoint > 0x10ffff) {
64
+ // Null char code, or character reference is outside the permissible Unicode range
65
+ value = '\uFFFD';
66
+ }
67
+ else {
68
+ value = String.fromCodePoint(codePoint);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ else if (charCode === /* l */ 108 || charCode === /* g */ 103) {
74
+ // "&lt;" or "&gt;"
75
+ if (startIndex + 2 < inputLength &&
76
+ input.charCodeAt(startIndex + 1) === /* t */ 116 &&
77
+ input.charCodeAt(startIndex + 2) === /* ; */ 59) {
78
+ value = charCode === /* l */ 108 ? '<' : '>';
79
+ endIndex = startIndex + 3;
80
+ }
81
+ }
82
+ else if (charCode === /* a */ 97) {
83
+ // "&amp;" or "&apos;"
84
+ if (startIndex + 3 < inputLength) {
85
+ const nextCharCode = input.charCodeAt(startIndex + 1);
86
+ if (nextCharCode === /* m */ 109 &&
87
+ input.charCodeAt(startIndex + 2) === /* p */ 112 &&
88
+ input.charCodeAt(startIndex + 3) === /* ; */ 59) {
89
+ value = '&';
90
+ endIndex = startIndex + 4;
91
+ }
92
+ else if (nextCharCode === /* p */ 112 &&
93
+ startIndex + 4 < inputLength &&
94
+ input.charCodeAt(startIndex + 2) === /* o */ 111 &&
95
+ input.charCodeAt(startIndex + 3) === /* s */ 115 &&
96
+ input.charCodeAt(startIndex + 4) === /* ; */ 59) {
97
+ value = "'";
98
+ endIndex = startIndex + 5;
99
+ }
100
+ }
101
+ }
102
+ else if (charCode === /* q */ 113) {
103
+ // "&quot;"
104
+ if (startIndex + 4 < inputLength &&
105
+ input.charCodeAt(startIndex + 1) === /* u */ 117 &&
106
+ input.charCodeAt(startIndex + 2) === /* o */ 111 &&
107
+ input.charCodeAt(startIndex + 3) === /* t */ 116 &&
108
+ input.charCodeAt(startIndex + 4) === /* ; */ 59) {
109
+ value = '"';
110
+ endIndex = startIndex + 5;
111
+ }
112
+ }
113
+ if (value === undefined) {
114
+ charIndex = endIndex < inputLength - 1 ? input.indexOf('&', endIndex) : -1;
115
+ continue;
116
+ }
117
+ // Appending the preceding text and the value separately avoids joining them into a new string first
118
+ if (textIndex !== charIndex) {
119
+ output += input.slice(textIndex, charIndex);
120
+ }
121
+ output += value;
122
+ textIndex = endIndex;
123
+ if (endIndex >= inputLength - 1) {
124
+ break;
125
+ }
126
+ // References often follow each other, and checking the next char is cheaper than calling indexOf
127
+ charIndex = input.charCodeAt(endIndex) === /* & */ 38 ? endIndex : input.indexOf('&', endIndex);
128
+ }
129
+ return textIndex === 0 ? input : textIndex === inputLength ? output : output + input.slice(textIndex);
130
+ }
package/encodeXML.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Same as {@linkcode escapeXML}, but also encodes non-ASCII chars as numeric references, so the output is ASCII and
3
+ * safe for any document encoding. A lone surrogate is encoded as `&#xfffd;`.
4
+ */
5
+ export declare function encodeXML(input: string): string;
package/encodeXML.js ADDED
@@ -0,0 +1,132 @@
1
+ const encodedCharRegExp = /["&'<>\u0080-\uFFFF]/g;
2
+ /**
3
+ * 1 for ASCII chars that are encoded.
4
+ */
5
+ const encodedAsciiFlags = new Uint8Array(128);
6
+ for (const char of '"&\'<>') {
7
+ encodedAsciiFlags[char.charCodeAt(0)] = 1;
8
+ }
9
+ /**
10
+ * References for U+0080 to U+07FF, formatted in advance, since text in Latin, Greek, Cyrillic, Hebrew or Arabic script
11
+ * reuses a few dozen of them.
12
+ */
13
+ const numericReferences = [];
14
+ for (let codePoint = 0x80; codePoint < 0x800; ++codePoint) {
15
+ numericReferences.push('&#x' + codePoint.toString(16) + ';');
16
+ }
17
+ const hexDigitCharCodes = Uint8Array.from('0123456789abcdef', char => char.charCodeAt(0));
18
+ /**
19
+ * Same as {@linkcode escapeXML}, but also encodes non-ASCII chars as numeric references, so the output is ASCII and
20
+ * safe for any document encoding. A lone surrogate is encoded as `&#xfffd;`.
21
+ */
22
+ export function encodeXML(input) {
23
+ const inputLength = input.length;
24
+ let output = '';
25
+ let textIndex = 0;
26
+ let index = 0;
27
+ while (index < inputLength) {
28
+ const charCode = input.charCodeAt(index);
29
+ if (!isEncodedCharCode(charCode)) {
30
+ // Encoded chars are often close to each other, so a short run of plain chars is read inline
31
+ const scanEndIndex = Math.min(index + 24, inputLength);
32
+ do {
33
+ ++index;
34
+ } while (index < scanEndIndex && !isEncodedCharCode(input.charCodeAt(index)));
35
+ if (index < scanEndIndex) {
36
+ continue;
37
+ }
38
+ if (index === inputLength) {
39
+ break;
40
+ }
41
+ // In a longer run, the regexp finds the next encoded char faster than reading chars one by one
42
+ encodedCharRegExp.lastIndex = index;
43
+ if (!encodedCharRegExp.test(input)) {
44
+ break;
45
+ }
46
+ index = encodedCharRegExp.lastIndex - 1;
47
+ continue;
48
+ }
49
+ let entity;
50
+ let nextIndex = index + 1;
51
+ if (charCode >= 0xd800 && charCode <= 0xdfff) {
52
+ const codePoint = input.codePointAt(index);
53
+ if (codePoint > 0xffff) {
54
+ entity = getNumericReference(codePoint);
55
+ nextIndex = index + 2;
56
+ }
57
+ else {
58
+ // A lone surrogate isn't a Unicode scalar value, so HTML parsers, decodeHTML and decodeXML decode a reference
59
+ // to it as U+FFFD anyway
60
+ entity = '&#xfffd;';
61
+ }
62
+ }
63
+ else if (charCode >= 0x80) {
64
+ entity = getNumericReference(charCode);
65
+ }
66
+ else if (charCode === /* " */ 34) {
67
+ entity = '&quot;';
68
+ }
69
+ else if (charCode === /* & */ 38) {
70
+ entity = '&amp;';
71
+ }
72
+ else if (charCode === /* ' */ 39) {
73
+ entity = '&apos;';
74
+ }
75
+ else if (charCode === /* < */ 60) {
76
+ entity = '&lt;';
77
+ }
78
+ else {
79
+ entity = '&gt;';
80
+ }
81
+ if (textIndex !== index) {
82
+ output += input.slice(textIndex, index);
83
+ }
84
+ output += entity;
85
+ textIndex = nextIndex;
86
+ index = nextIndex;
87
+ }
88
+ return textIndex === 0 ? input : textIndex === inputLength ? output : output + input.slice(textIndex);
89
+ }
90
+ function isEncodedCharCode(charCode) {
91
+ return charCode >= 0x80 || encodedAsciiFlags[charCode] === 1;
92
+ }
93
+ // The code point must be at least 0x80
94
+ function getNumericReference(codePoint) {
95
+ return codePoint < 0x800 ? numericReferences[codePoint - 0x80] : formatNumericReference(codePoint);
96
+ }
97
+ /**
98
+ * Formats a reference like "&#x1f600;". Building it from char codes in one call is about twice as fast as toString(16)
99
+ * and concatenation. The code point must be at least 0x100.
100
+ */
101
+ function formatNumericReference(codePoint) {
102
+ if (codePoint < 0x1000) {
103
+ return String.fromCharCode(
104
+ /* & */ 38,
105
+ /* # */ 35,
106
+ /* x */ 120, getHexDigitCharCode(codePoint, 8), getHexDigitCharCode(codePoint, 4), getHexDigitCharCode(codePoint, 0),
107
+ /* ; */ 59);
108
+ }
109
+ if (codePoint < 0x10000) {
110
+ return String.fromCharCode(
111
+ /* & */ 38,
112
+ /* # */ 35,
113
+ /* x */ 120, getHexDigitCharCode(codePoint, 12), getHexDigitCharCode(codePoint, 8), getHexDigitCharCode(codePoint, 4), getHexDigitCharCode(codePoint, 0),
114
+ /* ; */ 59);
115
+ }
116
+ if (codePoint < 0x100000) {
117
+ return String.fromCharCode(
118
+ /* & */ 38,
119
+ /* # */ 35,
120
+ /* x */ 120, getHexDigitCharCode(codePoint, 16), getHexDigitCharCode(codePoint, 12), getHexDigitCharCode(codePoint, 8), getHexDigitCharCode(codePoint, 4), getHexDigitCharCode(codePoint, 0),
121
+ /* ; */ 59);
122
+ }
123
+ return String.fromCharCode(
124
+ /* & */ 38,
125
+ /* # */ 35,
126
+ /* x */ 120, getHexDigitCharCode(codePoint, 20), getHexDigitCharCode(codePoint, 16), getHexDigitCharCode(codePoint, 12), getHexDigitCharCode(codePoint, 8), getHexDigitCharCode(codePoint, 4), getHexDigitCharCode(codePoint, 0),
127
+ /* ; */ 59);
128
+ }
129
+ // The char code of the hex digit at the given bit offset
130
+ function getHexDigitCharCode(codePoint, offset) {
131
+ return hexDigitCharCodes[(codePoint >> offset) & 15];
132
+ }
package/entities.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * HTML entity references without "&" and their values in turn, sorted by reference.
3
+ */
4
+ declare const _default: string[];
5
+ export default _default;