@weapp-tailwindcss/escape 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/README.zh-CN.md +23 -0
- package/dist/index.cjs +411 -0
- package/dist/index.d.cts +72 -0
- package/dist/index.d.mts +72 -0
- package/dist/index.mjs +398 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ice breaker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @weapp-tailwindcss/escape
|
|
2
|
+
|
|
3
|
+
> English | [简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Escape and unescape mini-program class names. This package is maintained in the weapp-tailwindcss monorepo and is published as `@weapp-tailwindcss/escape` with an independent version starting at 0.0.1. The implementation originates from `@weapp-core/escape@8.0.0`.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @weapp-tailwindcss/escape
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { escape, MappingChars2String, unescape } from '@weapp-tailwindcss/escape'
|
|
13
|
+
|
|
14
|
+
const options = { map: MappingChars2String }
|
|
15
|
+
const className = escape('hover:bg-red-500', options)
|
|
16
|
+
const original = unescape(className, options)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Both ESM imports and CommonJS require are supported. `EscapeOptions` and `UnescapeOptions` accept `map` and `ignoreHead`; use the same options on both sides. Existing mappings and conversion semantics are unchanged by the migration.
|
|
20
|
+
|
|
21
|
+
Exports include `escape`, `unescape`, `isAllowedClassName`, `isAsciiNumber`, `MappingChars2String`, `ComplexMappingChars2String`, their entry arrays, `SYMBOL_TABLE`, `MAX_ASCII_CHAR_CODE`, `toEscapeOptions`, `toUnescapeOptions`, and the corresponding TypeScript types.
|
|
22
|
+
|
|
23
|
+
See [source provenance and release handover](./MIGRATION.md) and [maintenance boundaries](./AGENTS.md).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @weapp-tailwindcss/escape
|
|
2
|
+
|
|
3
|
+
> [English](./README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
小程序类名的 escape/unescape 基础包,现由 weapp-tailwindcss monorepo 维护,以 `@weapp-tailwindcss/escape` 发布,独立版本从 0.0.1 开始;实现来自 `@weapp-core/escape@8.0.0`。
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @weapp-tailwindcss/escape
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { escape, MappingChars2String, unescape } from '@weapp-tailwindcss/escape'
|
|
13
|
+
|
|
14
|
+
const options = { map: MappingChars2String }
|
|
15
|
+
const className = escape('hover:bg-red-500', options)
|
|
16
|
+
const original = unescape(className, options)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
支持 ESM import 和 CommonJS require。`EscapeOptions`、`UnescapeOptions` 提供 `map` 和 `ignoreHead`,双向转换应使用一致配置。本次迁移保持已有映射和转换语义。
|
|
20
|
+
|
|
21
|
+
导出包含 `escape`、`unescape`、`isAllowedClassName`、`isAsciiNumber`、`MappingChars2String`、`ComplexMappingChars2String` 及其 entries、`SYMBOL_TABLE`、`MAX_ASCII_CHAR_CODE`、`toEscapeOptions`、`toUnescapeOptions` 和相关 TypeScript 类型。
|
|
22
|
+
|
|
23
|
+
源码来源、发布切换见[迁移记录](./MIGRATION.md),跨包调用关系见[维护规则](./AGENTS.md)。
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/constants.ts
|
|
3
|
+
const SYMBOL_TABLE = {
|
|
4
|
+
BACKQUOTE: "`",
|
|
5
|
+
TILDE: "~",
|
|
6
|
+
EXCLAM: "!",
|
|
7
|
+
AT: "@",
|
|
8
|
+
NUMBERSIGN: "#",
|
|
9
|
+
DOLLAR: "$",
|
|
10
|
+
PERCENT: "%",
|
|
11
|
+
CARET: "^",
|
|
12
|
+
AMPERSAND: "&",
|
|
13
|
+
ASTERISK: "*",
|
|
14
|
+
PARENLEFT: "(",
|
|
15
|
+
PARENRIGHT: ")",
|
|
16
|
+
MINUS: "-",
|
|
17
|
+
UNDERSCORE: "_",
|
|
18
|
+
EQUAL: "=",
|
|
19
|
+
PLUS: "+",
|
|
20
|
+
BRACKETLEFT: "[",
|
|
21
|
+
BRACELEFT: "{",
|
|
22
|
+
BRACKETRIGHT: "]",
|
|
23
|
+
BRACERIGHT: "}",
|
|
24
|
+
SEMICOLON: ";",
|
|
25
|
+
COLON: ":",
|
|
26
|
+
QUOTE: "'",
|
|
27
|
+
DOUBLEQUOTE: "\"",
|
|
28
|
+
BACKSLASH: "\\",
|
|
29
|
+
BAR: "|",
|
|
30
|
+
COMMA: ",",
|
|
31
|
+
LESS: "<",
|
|
32
|
+
PERIOD: ".",
|
|
33
|
+
GREATER: ">",
|
|
34
|
+
SLASH: "/",
|
|
35
|
+
QUESTION: "?",
|
|
36
|
+
SPACE: " ",
|
|
37
|
+
DOT: ".",
|
|
38
|
+
HASH: "#"
|
|
39
|
+
};
|
|
40
|
+
const ESCAPE_CHARACTER_CODES = [
|
|
41
|
+
["[", "b"],
|
|
42
|
+
["]", "B"],
|
|
43
|
+
["(", "p"],
|
|
44
|
+
[")", "P"],
|
|
45
|
+
["#", "h"],
|
|
46
|
+
["!", "e"],
|
|
47
|
+
["/", "f"],
|
|
48
|
+
["\\", "r"],
|
|
49
|
+
[".", "d"],
|
|
50
|
+
[":", "c"],
|
|
51
|
+
["%", "v"],
|
|
52
|
+
[",", "m"],
|
|
53
|
+
["'", "a"],
|
|
54
|
+
["\"", "q"],
|
|
55
|
+
["*", "x"],
|
|
56
|
+
["&", "n"],
|
|
57
|
+
["@", "t"],
|
|
58
|
+
["{", "k"],
|
|
59
|
+
["}", "K"],
|
|
60
|
+
["+", "u"],
|
|
61
|
+
[";", "j"],
|
|
62
|
+
["<", "l"],
|
|
63
|
+
["~", "w"],
|
|
64
|
+
["=", "z"],
|
|
65
|
+
[">", "g"],
|
|
66
|
+
["?", "Q"],
|
|
67
|
+
["^", "y"],
|
|
68
|
+
["`", "i"],
|
|
69
|
+
["|", "o"],
|
|
70
|
+
["$", "s"]
|
|
71
|
+
];
|
|
72
|
+
function buildMapping(prefix) {
|
|
73
|
+
return ESCAPE_CHARACTER_CODES.reduce((acc, [char, code]) => {
|
|
74
|
+
acc[char] = `${prefix}${code}`;
|
|
75
|
+
return acc;
|
|
76
|
+
}, {});
|
|
77
|
+
}
|
|
78
|
+
const SIMPLE_TOKEN_PREFIX = "_";
|
|
79
|
+
const ComplexMappingChars2String = buildMapping("_c");
|
|
80
|
+
const ComplexMappingChars2StringEntries = Object.entries(ComplexMappingChars2String);
|
|
81
|
+
const MappingChars2String = buildMapping(SIMPLE_TOKEN_PREFIX);
|
|
82
|
+
const MappingChars2StringEntries = Object.entries(MappingChars2String);
|
|
83
|
+
const MAX_ASCII_CHAR_CODE = 127;
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/mapping.ts
|
|
86
|
+
const DEFAULT_ESCAPE_MAPPING = MappingChars2String;
|
|
87
|
+
const COMPLEX_ESCAPE_MAPPING = ComplexMappingChars2String;
|
|
88
|
+
new Set(Object.keys(DEFAULT_ESCAPE_MAPPING));
|
|
89
|
+
const escapeMappingCache = /* @__PURE__ */ new WeakMap();
|
|
90
|
+
const tokenBucketCache = /* @__PURE__ */ new WeakMap();
|
|
91
|
+
const inverseMappingCache = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
function hasOwnKey(object, key) {
|
|
93
|
+
return Object.prototype.hasOwnProperty.call(object, key);
|
|
94
|
+
}
|
|
95
|
+
function createEscapeMapping(customMap) {
|
|
96
|
+
if (!customMap) return DEFAULT_ESCAPE_MAPPING;
|
|
97
|
+
if (customMap === DEFAULT_ESCAPE_MAPPING || customMap === COMPLEX_ESCAPE_MAPPING) return customMap;
|
|
98
|
+
const cached = escapeMappingCache.get(customMap);
|
|
99
|
+
if (cached) return cached;
|
|
100
|
+
if (Object.keys(customMap).length === 0) {
|
|
101
|
+
escapeMappingCache.set(customMap, DEFAULT_ESCAPE_MAPPING);
|
|
102
|
+
return DEFAULT_ESCAPE_MAPPING;
|
|
103
|
+
}
|
|
104
|
+
const merged = {
|
|
105
|
+
...DEFAULT_ESCAPE_MAPPING,
|
|
106
|
+
...customMap
|
|
107
|
+
};
|
|
108
|
+
escapeMappingCache.set(customMap, merged);
|
|
109
|
+
return merged;
|
|
110
|
+
}
|
|
111
|
+
function createUnescapeMapping(customMap) {
|
|
112
|
+
return customMap;
|
|
113
|
+
}
|
|
114
|
+
function buildInverseMapping(mapping) {
|
|
115
|
+
const inverse = {};
|
|
116
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
117
|
+
for (const [key, value] of Object.entries(mapping)) {
|
|
118
|
+
inverse[value] = key;
|
|
119
|
+
tokens.add(value);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
inverse,
|
|
123
|
+
tokens: Array.from(tokens).sort((a, b) => b.length - a.length)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function createInverseMapping(mapping) {
|
|
127
|
+
const cached = inverseMappingCache.get(mapping);
|
|
128
|
+
if (cached) return cached;
|
|
129
|
+
const built = buildInverseMapping(mapping);
|
|
130
|
+
inverseMappingCache.set(mapping, built);
|
|
131
|
+
return built;
|
|
132
|
+
}
|
|
133
|
+
function createTokenBuckets(mapping, tokens) {
|
|
134
|
+
if (tokens.length === 0) return;
|
|
135
|
+
const cached = tokenBucketCache.get(mapping);
|
|
136
|
+
if (cached) return cached;
|
|
137
|
+
const buckets = tokens.reduce((acc, token) => {
|
|
138
|
+
if (!token) return acc;
|
|
139
|
+
const first = token[0];
|
|
140
|
+
if (!first) return acc;
|
|
141
|
+
const bucket = acc[first];
|
|
142
|
+
if (bucket) bucket.push(token);
|
|
143
|
+
else acc[first] = [token];
|
|
144
|
+
return acc;
|
|
145
|
+
}, {});
|
|
146
|
+
tokenBucketCache.set(mapping, buckets);
|
|
147
|
+
return buckets;
|
|
148
|
+
}
|
|
149
|
+
function primeInverseCache(mapping) {
|
|
150
|
+
inverseMappingCache.set(mapping, buildInverseMapping(mapping));
|
|
151
|
+
}
|
|
152
|
+
primeInverseCache(DEFAULT_ESCAPE_MAPPING);
|
|
153
|
+
primeInverseCache(COMPLEX_ESCAPE_MAPPING);
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/predicates.ts
|
|
156
|
+
const DIGIT_MIN_CODE = 48;
|
|
157
|
+
const DIGIT_MAX_CODE = 57;
|
|
158
|
+
function isAsciiNumber(code) {
|
|
159
|
+
return code >= DIGIT_MIN_CODE && code <= DIGIT_MAX_CODE;
|
|
160
|
+
}
|
|
161
|
+
function isAllowedClassName(className) {
|
|
162
|
+
return /^[\w-]+$/.test(className);
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/escape.ts
|
|
166
|
+
const HIGH_SURROGATE_MIN = 55296;
|
|
167
|
+
const HIGH_SURROGATE_MAX = 56319;
|
|
168
|
+
const LOW_SURROGATE_MIN = 56320;
|
|
169
|
+
const LOW_SURROGATE_MAX = 57343;
|
|
170
|
+
const DEFAULT_ESCAPE_TABLE = (() => {
|
|
171
|
+
const table = Array.from({ length: 128 });
|
|
172
|
+
for (const [char, token] of Object.entries(DEFAULT_ESCAPE_MAPPING)) table[char.codePointAt(0)] = token;
|
|
173
|
+
return table;
|
|
174
|
+
})();
|
|
175
|
+
function readCodePoint(str, index) {
|
|
176
|
+
const first = str.charCodeAt(index);
|
|
177
|
+
if (first < HIGH_SURROGATE_MIN || first > HIGH_SURROGATE_MAX || index + 1 >= str.length) return {
|
|
178
|
+
codePoint: first,
|
|
179
|
+
size: 1
|
|
180
|
+
};
|
|
181
|
+
const second = str.charCodeAt(index + 1);
|
|
182
|
+
if (second < LOW_SURROGATE_MIN || second > LOW_SURROGATE_MAX) return {
|
|
183
|
+
codePoint: first,
|
|
184
|
+
size: 1
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
codePoint: (first - HIGH_SURROGATE_MIN << 10) + (second - LOW_SURROGATE_MIN) + 65536,
|
|
188
|
+
size: 2
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function escapeLeadingCharacter(char, nextChar, ignoreHead) {
|
|
192
|
+
if (ignoreHead) return char;
|
|
193
|
+
if (isAsciiNumber(char.codePointAt(0))) return `_${char}`;
|
|
194
|
+
if (char === "-") {
|
|
195
|
+
if (!nextChar) return `_${char}`;
|
|
196
|
+
if (isAsciiNumber(nextChar.codePointAt(0))) return `_${char}`;
|
|
197
|
+
}
|
|
198
|
+
return char;
|
|
199
|
+
}
|
|
200
|
+
function escape(selectors, options) {
|
|
201
|
+
const length = selectors.length;
|
|
202
|
+
if (length === 0) return "";
|
|
203
|
+
const map = createEscapeMapping(options?.map);
|
|
204
|
+
const ignoreHead = options?.ignoreHead ?? false;
|
|
205
|
+
const usingDefaultMap = map === DEFAULT_ESCAPE_MAPPING;
|
|
206
|
+
let cursor = 0;
|
|
207
|
+
let lastUnchangedIndex = 0;
|
|
208
|
+
let buffer;
|
|
209
|
+
while (cursor < length) {
|
|
210
|
+
const { codePoint, size } = readCodePoint(selectors, cursor);
|
|
211
|
+
const isHead = cursor === 0;
|
|
212
|
+
let replacement;
|
|
213
|
+
if (codePoint > 127) replacement = `u_x${codePoint.toString(16)}_`;
|
|
214
|
+
else if (usingDefaultMap) {
|
|
215
|
+
if (isHead) {
|
|
216
|
+
const mapped = DEFAULT_ESCAPE_TABLE[codePoint];
|
|
217
|
+
if (mapped !== void 0) replacement = mapped;
|
|
218
|
+
else {
|
|
219
|
+
const nextIndex = cursor + size;
|
|
220
|
+
const next = nextIndex < length ? readCodePoint(selectors, nextIndex) : void 0;
|
|
221
|
+
const char = selectors[cursor];
|
|
222
|
+
const escapedHead = escapeLeadingCharacter(char, next ? selectors.slice(nextIndex, nextIndex + next.size) : void 0, ignoreHead);
|
|
223
|
+
if (escapedHead !== char) replacement = escapedHead;
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
const mapped = DEFAULT_ESCAPE_TABLE[codePoint];
|
|
227
|
+
if (mapped !== void 0) replacement = mapped;
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
const char = selectors[cursor];
|
|
231
|
+
const mapped = hasOwnKey(map, char) ? map[char] : void 0;
|
|
232
|
+
if (mapped !== void 0) replacement = mapped;
|
|
233
|
+
else if (isHead) {
|
|
234
|
+
const nextIndex = cursor + size;
|
|
235
|
+
const next = nextIndex < length ? readCodePoint(selectors, nextIndex) : void 0;
|
|
236
|
+
const escapedHead = escapeLeadingCharacter(char, next ? selectors.slice(nextIndex, nextIndex + next.size) : void 0, ignoreHead);
|
|
237
|
+
if (escapedHead !== char) replacement = escapedHead;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (replacement !== void 0) {
|
|
241
|
+
if (!buffer) buffer = [];
|
|
242
|
+
if (lastUnchangedIndex !== cursor) buffer.push(selectors.slice(lastUnchangedIndex, cursor));
|
|
243
|
+
buffer.push(replacement);
|
|
244
|
+
lastUnchangedIndex = cursor + size;
|
|
245
|
+
}
|
|
246
|
+
cursor += size;
|
|
247
|
+
}
|
|
248
|
+
if (!buffer) return selectors;
|
|
249
|
+
if (lastUnchangedIndex < length) buffer.push(selectors.slice(lastUnchangedIndex));
|
|
250
|
+
return buffer.join("");
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/types.ts
|
|
254
|
+
function toEscapeOptions(options) {
|
|
255
|
+
return options ?? {};
|
|
256
|
+
}
|
|
257
|
+
function toUnescapeOptions(options) {
|
|
258
|
+
return options ?? {};
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/unicode.ts
|
|
262
|
+
const UNICODE_PREFIX = "u_x";
|
|
263
|
+
const UNICODE_SUFFIX = "_";
|
|
264
|
+
const HEX_RADIX = 16;
|
|
265
|
+
function isHexDigit(char) {
|
|
266
|
+
const code = char.codePointAt(0);
|
|
267
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
|
|
268
|
+
}
|
|
269
|
+
function decodeUnicodeSequence(value, index) {
|
|
270
|
+
if (!value.startsWith(UNICODE_PREFIX, index)) return;
|
|
271
|
+
let cursor = index + 3;
|
|
272
|
+
while (cursor < value.length) {
|
|
273
|
+
const nextChar = value[cursor];
|
|
274
|
+
if (!nextChar || !isHexDigit(nextChar)) break;
|
|
275
|
+
cursor += 1;
|
|
276
|
+
}
|
|
277
|
+
if (cursor === index + 3) return;
|
|
278
|
+
if (cursor >= value.length || value[cursor] !== UNICODE_SUFFIX) return;
|
|
279
|
+
const hex = value.slice(index + 3, cursor);
|
|
280
|
+
const codePoint = Number.parseInt(hex, HEX_RADIX);
|
|
281
|
+
if (Number.isNaN(codePoint) || codePoint <= 127 || codePoint > 1114111) return;
|
|
282
|
+
return {
|
|
283
|
+
char: String.fromCodePoint(codePoint),
|
|
284
|
+
length: cursor - index + 1
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/unescape.ts
|
|
289
|
+
const ESCAPE_PREFIX_CODE = "_".codePointAt(0);
|
|
290
|
+
const LOWER_U_CODE = "u".codePointAt(0);
|
|
291
|
+
const DEFAULT_UNESCAPE_TABLE = (() => {
|
|
292
|
+
const inverse = {};
|
|
293
|
+
for (const [char, token] of Object.entries(DEFAULT_ESCAPE_MAPPING)) inverse[token] = char;
|
|
294
|
+
return inverse;
|
|
295
|
+
})();
|
|
296
|
+
function unescapeDefault(value, ignoreHead) {
|
|
297
|
+
const length = value.length;
|
|
298
|
+
const hasLeadingEscape = !ignoreHead && value[0] === "_";
|
|
299
|
+
const hasUnicodeMarker = value.includes("u_x");
|
|
300
|
+
if (!hasLeadingEscape && !hasUnicodeMarker) return value;
|
|
301
|
+
let cursor = 0;
|
|
302
|
+
let result = "";
|
|
303
|
+
while (cursor < length) {
|
|
304
|
+
const currentChar = value[cursor];
|
|
305
|
+
if (value.codePointAt(cursor) === LOWER_U_CODE) {
|
|
306
|
+
const decodedUnicode = decodeUnicodeSequence(value, cursor);
|
|
307
|
+
if (decodedUnicode) {
|
|
308
|
+
result += decodedUnicode.char;
|
|
309
|
+
cursor += decodedUnicode.length;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
result += currentChar;
|
|
314
|
+
cursor++;
|
|
315
|
+
}
|
|
316
|
+
if (ignoreHead || result.length === 0 || !result.startsWith("_")) return result;
|
|
317
|
+
const nextChar = result[1];
|
|
318
|
+
if (!nextChar) return result.slice(1);
|
|
319
|
+
let shouldStrip = isAsciiNumber(nextChar.codePointAt(0));
|
|
320
|
+
if (!shouldStrip && nextChar === "-") {
|
|
321
|
+
const thirdChar = result[2];
|
|
322
|
+
shouldStrip = !thirdChar || isAsciiNumber(thirdChar.codePointAt(0));
|
|
323
|
+
}
|
|
324
|
+
return shouldStrip ? result.slice(1) : result;
|
|
325
|
+
}
|
|
326
|
+
function unescape(value, options) {
|
|
327
|
+
const length = value.length;
|
|
328
|
+
if (length === 0) return "";
|
|
329
|
+
const map = createUnescapeMapping(options?.map);
|
|
330
|
+
const ignoreHead = options?.ignoreHead ?? false;
|
|
331
|
+
if (!map) return unescapeDefault(value, ignoreHead);
|
|
332
|
+
const usingDefaultMap = map === DEFAULT_ESCAPE_MAPPING;
|
|
333
|
+
const { inverse, tokens } = usingDefaultMap ? {
|
|
334
|
+
inverse: {},
|
|
335
|
+
tokens: []
|
|
336
|
+
} : createInverseMapping(map);
|
|
337
|
+
const tokenBuckets = usingDefaultMap ? void 0 : createTokenBuckets(map, tokens);
|
|
338
|
+
let cursor = 0;
|
|
339
|
+
let lastUnchangedIndex = 0;
|
|
340
|
+
let buffer;
|
|
341
|
+
while (cursor < length) {
|
|
342
|
+
const currentCode = value.charCodeAt(cursor);
|
|
343
|
+
const currentChar = value[cursor];
|
|
344
|
+
let replacement;
|
|
345
|
+
let advance = 1;
|
|
346
|
+
if (replacement === void 0 && currentCode === LOWER_U_CODE) {
|
|
347
|
+
const decodedUnicode = decodeUnicodeSequence(value, cursor);
|
|
348
|
+
if (decodedUnicode) {
|
|
349
|
+
replacement = decodedUnicode.char;
|
|
350
|
+
advance = decodedUnicode.length;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (replacement === void 0) {
|
|
354
|
+
if (usingDefaultMap && currentCode === ESCAPE_PREFIX_CODE) {
|
|
355
|
+
const mapped = DEFAULT_UNESCAPE_TABLE[value.slice(cursor, cursor + 2)];
|
|
356
|
+
if (mapped !== void 0) {
|
|
357
|
+
replacement = mapped;
|
|
358
|
+
advance = 2;
|
|
359
|
+
}
|
|
360
|
+
} else if (tokenBuckets) {
|
|
361
|
+
const bucket = tokenBuckets[currentChar];
|
|
362
|
+
if (bucket) {
|
|
363
|
+
for (const token of bucket) if (value.startsWith(token, cursor)) {
|
|
364
|
+
replacement = inverse[token];
|
|
365
|
+
advance = token.length;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (replacement !== void 0) {
|
|
372
|
+
if (!buffer) buffer = [];
|
|
373
|
+
if (lastUnchangedIndex !== cursor) buffer.push(value.slice(lastUnchangedIndex, cursor));
|
|
374
|
+
buffer.push(replacement);
|
|
375
|
+
lastUnchangedIndex = cursor + advance;
|
|
376
|
+
cursor += advance;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
cursor += 1;
|
|
380
|
+
}
|
|
381
|
+
const result = buffer ? (() => {
|
|
382
|
+
if (lastUnchangedIndex < length) buffer.push(value.slice(lastUnchangedIndex));
|
|
383
|
+
return buffer.join("");
|
|
384
|
+
})() : value;
|
|
385
|
+
if (ignoreHead || result.length === 0 || !result.startsWith("_")) return result;
|
|
386
|
+
const nextChar = result[1];
|
|
387
|
+
if (!nextChar) return result.slice(1);
|
|
388
|
+
let shouldStrip = isAsciiNumber(nextChar.codePointAt(0));
|
|
389
|
+
if (!shouldStrip && nextChar === "-") {
|
|
390
|
+
const thirdChar = result[2];
|
|
391
|
+
shouldStrip = !thirdChar || isAsciiNumber(thirdChar.codePointAt(0));
|
|
392
|
+
}
|
|
393
|
+
return shouldStrip ? result.slice(1) : result;
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/index.ts
|
|
397
|
+
const __areExportsLoaded = true;
|
|
398
|
+
//#endregion
|
|
399
|
+
exports.ComplexMappingChars2String = ComplexMappingChars2String;
|
|
400
|
+
exports.ComplexMappingChars2StringEntries = ComplexMappingChars2StringEntries;
|
|
401
|
+
exports.MAX_ASCII_CHAR_CODE = MAX_ASCII_CHAR_CODE;
|
|
402
|
+
exports.MappingChars2String = MappingChars2String;
|
|
403
|
+
exports.MappingChars2StringEntries = MappingChars2StringEntries;
|
|
404
|
+
exports.SYMBOL_TABLE = SYMBOL_TABLE;
|
|
405
|
+
exports.__areExportsLoaded = __areExportsLoaded;
|
|
406
|
+
exports.escape = escape;
|
|
407
|
+
exports.isAllowedClassName = isAllowedClassName;
|
|
408
|
+
exports.isAsciiNumber = isAsciiNumber;
|
|
409
|
+
exports.toEscapeOptions = toEscapeOptions;
|
|
410
|
+
exports.toUnescapeOptions = toUnescapeOptions;
|
|
411
|
+
exports.unescape = unescape;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/constants.d.ts
|
|
2
|
+
export declare const SYMBOL_TABLE: {
|
|
3
|
+
readonly BACKQUOTE: "`";
|
|
4
|
+
readonly TILDE: "~";
|
|
5
|
+
readonly EXCLAM: "!";
|
|
6
|
+
readonly AT: "@";
|
|
7
|
+
readonly NUMBERSIGN: "#";
|
|
8
|
+
readonly DOLLAR: "$";
|
|
9
|
+
readonly PERCENT: "%";
|
|
10
|
+
readonly CARET: "^";
|
|
11
|
+
readonly AMPERSAND: "&";
|
|
12
|
+
readonly ASTERISK: "*";
|
|
13
|
+
readonly PARENLEFT: "(";
|
|
14
|
+
readonly PARENRIGHT: ")";
|
|
15
|
+
readonly MINUS: "-";
|
|
16
|
+
readonly UNDERSCORE: "_";
|
|
17
|
+
readonly EQUAL: "=";
|
|
18
|
+
readonly PLUS: "+";
|
|
19
|
+
readonly BRACKETLEFT: "[";
|
|
20
|
+
readonly BRACELEFT: "{";
|
|
21
|
+
readonly BRACKETRIGHT: "]";
|
|
22
|
+
readonly BRACERIGHT: "}";
|
|
23
|
+
readonly SEMICOLON: ";";
|
|
24
|
+
readonly COLON: ":";
|
|
25
|
+
readonly QUOTE: "'";
|
|
26
|
+
readonly DOUBLEQUOTE: "\"";
|
|
27
|
+
readonly BACKSLASH: "\\";
|
|
28
|
+
readonly BAR: "|";
|
|
29
|
+
readonly COMMA: ",";
|
|
30
|
+
readonly LESS: "<";
|
|
31
|
+
readonly PERIOD: ".";
|
|
32
|
+
readonly GREATER: ">";
|
|
33
|
+
readonly SLASH: "/";
|
|
34
|
+
readonly QUESTION: "?";
|
|
35
|
+
readonly SPACE: " ";
|
|
36
|
+
readonly DOT: ".";
|
|
37
|
+
readonly HASH: "#";
|
|
38
|
+
};
|
|
39
|
+
export type SYMBOL_TABLE_TYPE = typeof SYMBOL_TABLE;
|
|
40
|
+
export type SYMBOL_TABLE_TYPE_VALUES = SYMBOL_TABLE_TYPE[keyof SYMBOL_TABLE_TYPE];
|
|
41
|
+
export type MappingStringDictionary = Record<Exclude<SYMBOL_TABLE_TYPE_VALUES, '-' | '_' | ' '>, string>;
|
|
42
|
+
export declare const ComplexMappingChars2String: MappingStringDictionary;
|
|
43
|
+
export declare const ComplexMappingChars2StringEntries: [string, string][];
|
|
44
|
+
export declare const MappingChars2String: MappingStringDictionary;
|
|
45
|
+
export declare const MappingChars2StringEntries: [string, string][];
|
|
46
|
+
export declare const MAX_ASCII_CHAR_CODE = 127;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/types.d.ts
|
|
49
|
+
export interface EscapeOptions {
|
|
50
|
+
map?: Record<string, string>;
|
|
51
|
+
ignoreHead?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface UnescapeOptions {
|
|
54
|
+
map?: Record<string, string>;
|
|
55
|
+
ignoreHead?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export declare function toEscapeOptions(options?: EscapeOptions): EscapeOptions;
|
|
58
|
+
export declare function toUnescapeOptions(options?: UnescapeOptions): UnescapeOptions;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/escape.d.ts
|
|
61
|
+
export declare function escape(selectors: string, options?: EscapeOptions): string;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/predicates.d.ts
|
|
64
|
+
export declare function isAsciiNumber(code: number): boolean;
|
|
65
|
+
export declare function isAllowedClassName(className: string): boolean;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/unescape.d.ts
|
|
68
|
+
export declare function unescape(value: string, options?: UnescapeOptions): string;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/index.d.ts
|
|
71
|
+
export declare const __areExportsLoaded = true;
|
|
72
|
+
//#endregion
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/constants.d.ts
|
|
2
|
+
export declare const SYMBOL_TABLE: {
|
|
3
|
+
readonly BACKQUOTE: "`";
|
|
4
|
+
readonly TILDE: "~";
|
|
5
|
+
readonly EXCLAM: "!";
|
|
6
|
+
readonly AT: "@";
|
|
7
|
+
readonly NUMBERSIGN: "#";
|
|
8
|
+
readonly DOLLAR: "$";
|
|
9
|
+
readonly PERCENT: "%";
|
|
10
|
+
readonly CARET: "^";
|
|
11
|
+
readonly AMPERSAND: "&";
|
|
12
|
+
readonly ASTERISK: "*";
|
|
13
|
+
readonly PARENLEFT: "(";
|
|
14
|
+
readonly PARENRIGHT: ")";
|
|
15
|
+
readonly MINUS: "-";
|
|
16
|
+
readonly UNDERSCORE: "_";
|
|
17
|
+
readonly EQUAL: "=";
|
|
18
|
+
readonly PLUS: "+";
|
|
19
|
+
readonly BRACKETLEFT: "[";
|
|
20
|
+
readonly BRACELEFT: "{";
|
|
21
|
+
readonly BRACKETRIGHT: "]";
|
|
22
|
+
readonly BRACERIGHT: "}";
|
|
23
|
+
readonly SEMICOLON: ";";
|
|
24
|
+
readonly COLON: ":";
|
|
25
|
+
readonly QUOTE: "'";
|
|
26
|
+
readonly DOUBLEQUOTE: "\"";
|
|
27
|
+
readonly BACKSLASH: "\\";
|
|
28
|
+
readonly BAR: "|";
|
|
29
|
+
readonly COMMA: ",";
|
|
30
|
+
readonly LESS: "<";
|
|
31
|
+
readonly PERIOD: ".";
|
|
32
|
+
readonly GREATER: ">";
|
|
33
|
+
readonly SLASH: "/";
|
|
34
|
+
readonly QUESTION: "?";
|
|
35
|
+
readonly SPACE: " ";
|
|
36
|
+
readonly DOT: ".";
|
|
37
|
+
readonly HASH: "#";
|
|
38
|
+
};
|
|
39
|
+
export type SYMBOL_TABLE_TYPE = typeof SYMBOL_TABLE;
|
|
40
|
+
export type SYMBOL_TABLE_TYPE_VALUES = SYMBOL_TABLE_TYPE[keyof SYMBOL_TABLE_TYPE];
|
|
41
|
+
export type MappingStringDictionary = Record<Exclude<SYMBOL_TABLE_TYPE_VALUES, '-' | '_' | ' '>, string>;
|
|
42
|
+
export declare const ComplexMappingChars2String: MappingStringDictionary;
|
|
43
|
+
export declare const ComplexMappingChars2StringEntries: [string, string][];
|
|
44
|
+
export declare const MappingChars2String: MappingStringDictionary;
|
|
45
|
+
export declare const MappingChars2StringEntries: [string, string][];
|
|
46
|
+
export declare const MAX_ASCII_CHAR_CODE = 127;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/types.d.ts
|
|
49
|
+
export interface EscapeOptions {
|
|
50
|
+
map?: Record<string, string>;
|
|
51
|
+
ignoreHead?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface UnescapeOptions {
|
|
54
|
+
map?: Record<string, string>;
|
|
55
|
+
ignoreHead?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export declare function toEscapeOptions(options?: EscapeOptions): EscapeOptions;
|
|
58
|
+
export declare function toUnescapeOptions(options?: UnescapeOptions): UnescapeOptions;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/escape.d.ts
|
|
61
|
+
export declare function escape(selectors: string, options?: EscapeOptions): string;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/predicates.d.ts
|
|
64
|
+
export declare function isAsciiNumber(code: number): boolean;
|
|
65
|
+
export declare function isAllowedClassName(className: string): boolean;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/unescape.d.ts
|
|
68
|
+
export declare function unescape(value: string, options?: UnescapeOptions): string;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/index.d.ts
|
|
71
|
+
export declare const __areExportsLoaded = true;
|
|
72
|
+
//#endregion
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
//#region src/constants.ts
|
|
2
|
+
const SYMBOL_TABLE = {
|
|
3
|
+
BACKQUOTE: "`",
|
|
4
|
+
TILDE: "~",
|
|
5
|
+
EXCLAM: "!",
|
|
6
|
+
AT: "@",
|
|
7
|
+
NUMBERSIGN: "#",
|
|
8
|
+
DOLLAR: "$",
|
|
9
|
+
PERCENT: "%",
|
|
10
|
+
CARET: "^",
|
|
11
|
+
AMPERSAND: "&",
|
|
12
|
+
ASTERISK: "*",
|
|
13
|
+
PARENLEFT: "(",
|
|
14
|
+
PARENRIGHT: ")",
|
|
15
|
+
MINUS: "-",
|
|
16
|
+
UNDERSCORE: "_",
|
|
17
|
+
EQUAL: "=",
|
|
18
|
+
PLUS: "+",
|
|
19
|
+
BRACKETLEFT: "[",
|
|
20
|
+
BRACELEFT: "{",
|
|
21
|
+
BRACKETRIGHT: "]",
|
|
22
|
+
BRACERIGHT: "}",
|
|
23
|
+
SEMICOLON: ";",
|
|
24
|
+
COLON: ":",
|
|
25
|
+
QUOTE: "'",
|
|
26
|
+
DOUBLEQUOTE: "\"",
|
|
27
|
+
BACKSLASH: "\\",
|
|
28
|
+
BAR: "|",
|
|
29
|
+
COMMA: ",",
|
|
30
|
+
LESS: "<",
|
|
31
|
+
PERIOD: ".",
|
|
32
|
+
GREATER: ">",
|
|
33
|
+
SLASH: "/",
|
|
34
|
+
QUESTION: "?",
|
|
35
|
+
SPACE: " ",
|
|
36
|
+
DOT: ".",
|
|
37
|
+
HASH: "#"
|
|
38
|
+
};
|
|
39
|
+
const ESCAPE_CHARACTER_CODES = [
|
|
40
|
+
["[", "b"],
|
|
41
|
+
["]", "B"],
|
|
42
|
+
["(", "p"],
|
|
43
|
+
[")", "P"],
|
|
44
|
+
["#", "h"],
|
|
45
|
+
["!", "e"],
|
|
46
|
+
["/", "f"],
|
|
47
|
+
["\\", "r"],
|
|
48
|
+
[".", "d"],
|
|
49
|
+
[":", "c"],
|
|
50
|
+
["%", "v"],
|
|
51
|
+
[",", "m"],
|
|
52
|
+
["'", "a"],
|
|
53
|
+
["\"", "q"],
|
|
54
|
+
["*", "x"],
|
|
55
|
+
["&", "n"],
|
|
56
|
+
["@", "t"],
|
|
57
|
+
["{", "k"],
|
|
58
|
+
["}", "K"],
|
|
59
|
+
["+", "u"],
|
|
60
|
+
[";", "j"],
|
|
61
|
+
["<", "l"],
|
|
62
|
+
["~", "w"],
|
|
63
|
+
["=", "z"],
|
|
64
|
+
[">", "g"],
|
|
65
|
+
["?", "Q"],
|
|
66
|
+
["^", "y"],
|
|
67
|
+
["`", "i"],
|
|
68
|
+
["|", "o"],
|
|
69
|
+
["$", "s"]
|
|
70
|
+
];
|
|
71
|
+
function buildMapping(prefix) {
|
|
72
|
+
return ESCAPE_CHARACTER_CODES.reduce((acc, [char, code]) => {
|
|
73
|
+
acc[char] = `${prefix}${code}`;
|
|
74
|
+
return acc;
|
|
75
|
+
}, {});
|
|
76
|
+
}
|
|
77
|
+
const SIMPLE_TOKEN_PREFIX = "_";
|
|
78
|
+
const ComplexMappingChars2String = buildMapping("_c");
|
|
79
|
+
const ComplexMappingChars2StringEntries = Object.entries(ComplexMappingChars2String);
|
|
80
|
+
const MappingChars2String = buildMapping(SIMPLE_TOKEN_PREFIX);
|
|
81
|
+
const MappingChars2StringEntries = Object.entries(MappingChars2String);
|
|
82
|
+
const MAX_ASCII_CHAR_CODE = 127;
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/mapping.ts
|
|
85
|
+
const DEFAULT_ESCAPE_MAPPING = MappingChars2String;
|
|
86
|
+
const COMPLEX_ESCAPE_MAPPING = ComplexMappingChars2String;
|
|
87
|
+
new Set(Object.keys(DEFAULT_ESCAPE_MAPPING));
|
|
88
|
+
const escapeMappingCache = /* @__PURE__ */ new WeakMap();
|
|
89
|
+
const tokenBucketCache = /* @__PURE__ */ new WeakMap();
|
|
90
|
+
const inverseMappingCache = /* @__PURE__ */ new WeakMap();
|
|
91
|
+
function hasOwnKey(object, key) {
|
|
92
|
+
return Object.prototype.hasOwnProperty.call(object, key);
|
|
93
|
+
}
|
|
94
|
+
function createEscapeMapping(customMap) {
|
|
95
|
+
if (!customMap) return DEFAULT_ESCAPE_MAPPING;
|
|
96
|
+
if (customMap === DEFAULT_ESCAPE_MAPPING || customMap === COMPLEX_ESCAPE_MAPPING) return customMap;
|
|
97
|
+
const cached = escapeMappingCache.get(customMap);
|
|
98
|
+
if (cached) return cached;
|
|
99
|
+
if (Object.keys(customMap).length === 0) {
|
|
100
|
+
escapeMappingCache.set(customMap, DEFAULT_ESCAPE_MAPPING);
|
|
101
|
+
return DEFAULT_ESCAPE_MAPPING;
|
|
102
|
+
}
|
|
103
|
+
const merged = {
|
|
104
|
+
...DEFAULT_ESCAPE_MAPPING,
|
|
105
|
+
...customMap
|
|
106
|
+
};
|
|
107
|
+
escapeMappingCache.set(customMap, merged);
|
|
108
|
+
return merged;
|
|
109
|
+
}
|
|
110
|
+
function createUnescapeMapping(customMap) {
|
|
111
|
+
return customMap;
|
|
112
|
+
}
|
|
113
|
+
function buildInverseMapping(mapping) {
|
|
114
|
+
const inverse = {};
|
|
115
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const [key, value] of Object.entries(mapping)) {
|
|
117
|
+
inverse[value] = key;
|
|
118
|
+
tokens.add(value);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
inverse,
|
|
122
|
+
tokens: Array.from(tokens).sort((a, b) => b.length - a.length)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function createInverseMapping(mapping) {
|
|
126
|
+
const cached = inverseMappingCache.get(mapping);
|
|
127
|
+
if (cached) return cached;
|
|
128
|
+
const built = buildInverseMapping(mapping);
|
|
129
|
+
inverseMappingCache.set(mapping, built);
|
|
130
|
+
return built;
|
|
131
|
+
}
|
|
132
|
+
function createTokenBuckets(mapping, tokens) {
|
|
133
|
+
if (tokens.length === 0) return;
|
|
134
|
+
const cached = tokenBucketCache.get(mapping);
|
|
135
|
+
if (cached) return cached;
|
|
136
|
+
const buckets = tokens.reduce((acc, token) => {
|
|
137
|
+
if (!token) return acc;
|
|
138
|
+
const first = token[0];
|
|
139
|
+
if (!first) return acc;
|
|
140
|
+
const bucket = acc[first];
|
|
141
|
+
if (bucket) bucket.push(token);
|
|
142
|
+
else acc[first] = [token];
|
|
143
|
+
return acc;
|
|
144
|
+
}, {});
|
|
145
|
+
tokenBucketCache.set(mapping, buckets);
|
|
146
|
+
return buckets;
|
|
147
|
+
}
|
|
148
|
+
function primeInverseCache(mapping) {
|
|
149
|
+
inverseMappingCache.set(mapping, buildInverseMapping(mapping));
|
|
150
|
+
}
|
|
151
|
+
primeInverseCache(DEFAULT_ESCAPE_MAPPING);
|
|
152
|
+
primeInverseCache(COMPLEX_ESCAPE_MAPPING);
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/predicates.ts
|
|
155
|
+
const DIGIT_MIN_CODE = 48;
|
|
156
|
+
const DIGIT_MAX_CODE = 57;
|
|
157
|
+
function isAsciiNumber(code) {
|
|
158
|
+
return code >= DIGIT_MIN_CODE && code <= DIGIT_MAX_CODE;
|
|
159
|
+
}
|
|
160
|
+
function isAllowedClassName(className) {
|
|
161
|
+
return /^[\w-]+$/.test(className);
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/escape.ts
|
|
165
|
+
const HIGH_SURROGATE_MIN = 55296;
|
|
166
|
+
const HIGH_SURROGATE_MAX = 56319;
|
|
167
|
+
const LOW_SURROGATE_MIN = 56320;
|
|
168
|
+
const LOW_SURROGATE_MAX = 57343;
|
|
169
|
+
const DEFAULT_ESCAPE_TABLE = (() => {
|
|
170
|
+
const table = Array.from({ length: 128 });
|
|
171
|
+
for (const [char, token] of Object.entries(DEFAULT_ESCAPE_MAPPING)) table[char.codePointAt(0)] = token;
|
|
172
|
+
return table;
|
|
173
|
+
})();
|
|
174
|
+
function readCodePoint(str, index) {
|
|
175
|
+
const first = str.charCodeAt(index);
|
|
176
|
+
if (first < HIGH_SURROGATE_MIN || first > HIGH_SURROGATE_MAX || index + 1 >= str.length) return {
|
|
177
|
+
codePoint: first,
|
|
178
|
+
size: 1
|
|
179
|
+
};
|
|
180
|
+
const second = str.charCodeAt(index + 1);
|
|
181
|
+
if (second < LOW_SURROGATE_MIN || second > LOW_SURROGATE_MAX) return {
|
|
182
|
+
codePoint: first,
|
|
183
|
+
size: 1
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
codePoint: (first - HIGH_SURROGATE_MIN << 10) + (second - LOW_SURROGATE_MIN) + 65536,
|
|
187
|
+
size: 2
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function escapeLeadingCharacter(char, nextChar, ignoreHead) {
|
|
191
|
+
if (ignoreHead) return char;
|
|
192
|
+
if (isAsciiNumber(char.codePointAt(0))) return `_${char}`;
|
|
193
|
+
if (char === "-") {
|
|
194
|
+
if (!nextChar) return `_${char}`;
|
|
195
|
+
if (isAsciiNumber(nextChar.codePointAt(0))) return `_${char}`;
|
|
196
|
+
}
|
|
197
|
+
return char;
|
|
198
|
+
}
|
|
199
|
+
function escape(selectors, options) {
|
|
200
|
+
const length = selectors.length;
|
|
201
|
+
if (length === 0) return "";
|
|
202
|
+
const map = createEscapeMapping(options?.map);
|
|
203
|
+
const ignoreHead = options?.ignoreHead ?? false;
|
|
204
|
+
const usingDefaultMap = map === DEFAULT_ESCAPE_MAPPING;
|
|
205
|
+
let cursor = 0;
|
|
206
|
+
let lastUnchangedIndex = 0;
|
|
207
|
+
let buffer;
|
|
208
|
+
while (cursor < length) {
|
|
209
|
+
const { codePoint, size } = readCodePoint(selectors, cursor);
|
|
210
|
+
const isHead = cursor === 0;
|
|
211
|
+
let replacement;
|
|
212
|
+
if (codePoint > 127) replacement = `u_x${codePoint.toString(16)}_`;
|
|
213
|
+
else if (usingDefaultMap) {
|
|
214
|
+
if (isHead) {
|
|
215
|
+
const mapped = DEFAULT_ESCAPE_TABLE[codePoint];
|
|
216
|
+
if (mapped !== void 0) replacement = mapped;
|
|
217
|
+
else {
|
|
218
|
+
const nextIndex = cursor + size;
|
|
219
|
+
const next = nextIndex < length ? readCodePoint(selectors, nextIndex) : void 0;
|
|
220
|
+
const char = selectors[cursor];
|
|
221
|
+
const escapedHead = escapeLeadingCharacter(char, next ? selectors.slice(nextIndex, nextIndex + next.size) : void 0, ignoreHead);
|
|
222
|
+
if (escapedHead !== char) replacement = escapedHead;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
const mapped = DEFAULT_ESCAPE_TABLE[codePoint];
|
|
226
|
+
if (mapped !== void 0) replacement = mapped;
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const char = selectors[cursor];
|
|
230
|
+
const mapped = hasOwnKey(map, char) ? map[char] : void 0;
|
|
231
|
+
if (mapped !== void 0) replacement = mapped;
|
|
232
|
+
else if (isHead) {
|
|
233
|
+
const nextIndex = cursor + size;
|
|
234
|
+
const next = nextIndex < length ? readCodePoint(selectors, nextIndex) : void 0;
|
|
235
|
+
const escapedHead = escapeLeadingCharacter(char, next ? selectors.slice(nextIndex, nextIndex + next.size) : void 0, ignoreHead);
|
|
236
|
+
if (escapedHead !== char) replacement = escapedHead;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (replacement !== void 0) {
|
|
240
|
+
if (!buffer) buffer = [];
|
|
241
|
+
if (lastUnchangedIndex !== cursor) buffer.push(selectors.slice(lastUnchangedIndex, cursor));
|
|
242
|
+
buffer.push(replacement);
|
|
243
|
+
lastUnchangedIndex = cursor + size;
|
|
244
|
+
}
|
|
245
|
+
cursor += size;
|
|
246
|
+
}
|
|
247
|
+
if (!buffer) return selectors;
|
|
248
|
+
if (lastUnchangedIndex < length) buffer.push(selectors.slice(lastUnchangedIndex));
|
|
249
|
+
return buffer.join("");
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/types.ts
|
|
253
|
+
function toEscapeOptions(options) {
|
|
254
|
+
return options ?? {};
|
|
255
|
+
}
|
|
256
|
+
function toUnescapeOptions(options) {
|
|
257
|
+
return options ?? {};
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/unicode.ts
|
|
261
|
+
const UNICODE_PREFIX = "u_x";
|
|
262
|
+
const UNICODE_SUFFIX = "_";
|
|
263
|
+
const HEX_RADIX = 16;
|
|
264
|
+
function isHexDigit(char) {
|
|
265
|
+
const code = char.codePointAt(0);
|
|
266
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
|
|
267
|
+
}
|
|
268
|
+
function decodeUnicodeSequence(value, index) {
|
|
269
|
+
if (!value.startsWith(UNICODE_PREFIX, index)) return;
|
|
270
|
+
let cursor = index + 3;
|
|
271
|
+
while (cursor < value.length) {
|
|
272
|
+
const nextChar = value[cursor];
|
|
273
|
+
if (!nextChar || !isHexDigit(nextChar)) break;
|
|
274
|
+
cursor += 1;
|
|
275
|
+
}
|
|
276
|
+
if (cursor === index + 3) return;
|
|
277
|
+
if (cursor >= value.length || value[cursor] !== UNICODE_SUFFIX) return;
|
|
278
|
+
const hex = value.slice(index + 3, cursor);
|
|
279
|
+
const codePoint = Number.parseInt(hex, HEX_RADIX);
|
|
280
|
+
if (Number.isNaN(codePoint) || codePoint <= 127 || codePoint > 1114111) return;
|
|
281
|
+
return {
|
|
282
|
+
char: String.fromCodePoint(codePoint),
|
|
283
|
+
length: cursor - index + 1
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/unescape.ts
|
|
288
|
+
const ESCAPE_PREFIX_CODE = "_".codePointAt(0);
|
|
289
|
+
const LOWER_U_CODE = "u".codePointAt(0);
|
|
290
|
+
const DEFAULT_UNESCAPE_TABLE = (() => {
|
|
291
|
+
const inverse = {};
|
|
292
|
+
for (const [char, token] of Object.entries(DEFAULT_ESCAPE_MAPPING)) inverse[token] = char;
|
|
293
|
+
return inverse;
|
|
294
|
+
})();
|
|
295
|
+
function unescapeDefault(value, ignoreHead) {
|
|
296
|
+
const length = value.length;
|
|
297
|
+
const hasLeadingEscape = !ignoreHead && value[0] === "_";
|
|
298
|
+
const hasUnicodeMarker = value.includes("u_x");
|
|
299
|
+
if (!hasLeadingEscape && !hasUnicodeMarker) return value;
|
|
300
|
+
let cursor = 0;
|
|
301
|
+
let result = "";
|
|
302
|
+
while (cursor < length) {
|
|
303
|
+
const currentChar = value[cursor];
|
|
304
|
+
if (value.codePointAt(cursor) === LOWER_U_CODE) {
|
|
305
|
+
const decodedUnicode = decodeUnicodeSequence(value, cursor);
|
|
306
|
+
if (decodedUnicode) {
|
|
307
|
+
result += decodedUnicode.char;
|
|
308
|
+
cursor += decodedUnicode.length;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
result += currentChar;
|
|
313
|
+
cursor++;
|
|
314
|
+
}
|
|
315
|
+
if (ignoreHead || result.length === 0 || !result.startsWith("_")) return result;
|
|
316
|
+
const nextChar = result[1];
|
|
317
|
+
if (!nextChar) return result.slice(1);
|
|
318
|
+
let shouldStrip = isAsciiNumber(nextChar.codePointAt(0));
|
|
319
|
+
if (!shouldStrip && nextChar === "-") {
|
|
320
|
+
const thirdChar = result[2];
|
|
321
|
+
shouldStrip = !thirdChar || isAsciiNumber(thirdChar.codePointAt(0));
|
|
322
|
+
}
|
|
323
|
+
return shouldStrip ? result.slice(1) : result;
|
|
324
|
+
}
|
|
325
|
+
function unescape(value, options) {
|
|
326
|
+
const length = value.length;
|
|
327
|
+
if (length === 0) return "";
|
|
328
|
+
const map = createUnescapeMapping(options?.map);
|
|
329
|
+
const ignoreHead = options?.ignoreHead ?? false;
|
|
330
|
+
if (!map) return unescapeDefault(value, ignoreHead);
|
|
331
|
+
const usingDefaultMap = map === DEFAULT_ESCAPE_MAPPING;
|
|
332
|
+
const { inverse, tokens } = usingDefaultMap ? {
|
|
333
|
+
inverse: {},
|
|
334
|
+
tokens: []
|
|
335
|
+
} : createInverseMapping(map);
|
|
336
|
+
const tokenBuckets = usingDefaultMap ? void 0 : createTokenBuckets(map, tokens);
|
|
337
|
+
let cursor = 0;
|
|
338
|
+
let lastUnchangedIndex = 0;
|
|
339
|
+
let buffer;
|
|
340
|
+
while (cursor < length) {
|
|
341
|
+
const currentCode = value.charCodeAt(cursor);
|
|
342
|
+
const currentChar = value[cursor];
|
|
343
|
+
let replacement;
|
|
344
|
+
let advance = 1;
|
|
345
|
+
if (replacement === void 0 && currentCode === LOWER_U_CODE) {
|
|
346
|
+
const decodedUnicode = decodeUnicodeSequence(value, cursor);
|
|
347
|
+
if (decodedUnicode) {
|
|
348
|
+
replacement = decodedUnicode.char;
|
|
349
|
+
advance = decodedUnicode.length;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (replacement === void 0) {
|
|
353
|
+
if (usingDefaultMap && currentCode === ESCAPE_PREFIX_CODE) {
|
|
354
|
+
const mapped = DEFAULT_UNESCAPE_TABLE[value.slice(cursor, cursor + 2)];
|
|
355
|
+
if (mapped !== void 0) {
|
|
356
|
+
replacement = mapped;
|
|
357
|
+
advance = 2;
|
|
358
|
+
}
|
|
359
|
+
} else if (tokenBuckets) {
|
|
360
|
+
const bucket = tokenBuckets[currentChar];
|
|
361
|
+
if (bucket) {
|
|
362
|
+
for (const token of bucket) if (value.startsWith(token, cursor)) {
|
|
363
|
+
replacement = inverse[token];
|
|
364
|
+
advance = token.length;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (replacement !== void 0) {
|
|
371
|
+
if (!buffer) buffer = [];
|
|
372
|
+
if (lastUnchangedIndex !== cursor) buffer.push(value.slice(lastUnchangedIndex, cursor));
|
|
373
|
+
buffer.push(replacement);
|
|
374
|
+
lastUnchangedIndex = cursor + advance;
|
|
375
|
+
cursor += advance;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
cursor += 1;
|
|
379
|
+
}
|
|
380
|
+
const result = buffer ? (() => {
|
|
381
|
+
if (lastUnchangedIndex < length) buffer.push(value.slice(lastUnchangedIndex));
|
|
382
|
+
return buffer.join("");
|
|
383
|
+
})() : value;
|
|
384
|
+
if (ignoreHead || result.length === 0 || !result.startsWith("_")) return result;
|
|
385
|
+
const nextChar = result[1];
|
|
386
|
+
if (!nextChar) return result.slice(1);
|
|
387
|
+
let shouldStrip = isAsciiNumber(nextChar.codePointAt(0));
|
|
388
|
+
if (!shouldStrip && nextChar === "-") {
|
|
389
|
+
const thirdChar = result[2];
|
|
390
|
+
shouldStrip = !thirdChar || isAsciiNumber(thirdChar.codePointAt(0));
|
|
391
|
+
}
|
|
392
|
+
return shouldStrip ? result.slice(1) : result;
|
|
393
|
+
}
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/index.ts
|
|
396
|
+
const __areExportsLoaded = true;
|
|
397
|
+
//#endregion
|
|
398
|
+
export { ComplexMappingChars2String, ComplexMappingChars2StringEntries, MAX_ASCII_CHAR_CODE, MappingChars2String, MappingChars2StringEntries, SYMBOL_TABLE, __areExportsLoaded, escape, isAllowedClassName, isAsciiNumber, toEscapeOptions, toUnescapeOptions, unescape };
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@weapp-tailwindcss/escape",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "escape class name in weapp",
|
|
6
|
+
"author": "ice breaker <1324318532@qq.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/sonofmagic/weapp-tailwindcss/tree/main/packages/escape#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/sonofmagic/weapp-tailwindcss.git",
|
|
12
|
+
"directory": "packages/escape"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/sonofmagic/weapp-tailwindcss/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"weapp",
|
|
19
|
+
"escape",
|
|
20
|
+
"css"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/index.d.mts",
|
|
26
|
+
"default": "./dist/index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./dist/index.d.cts",
|
|
30
|
+
"default": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.cjs",
|
|
35
|
+
"module": "./dist/index.mjs",
|
|
36
|
+
"types": "./dist/index.d.mts",
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown",
|
|
42
|
+
"test": "vitest run --update=none",
|
|
43
|
+
"test:dev": "vitest",
|
|
44
|
+
"test:types": "tsdown && tsd --files test/types.test-d.ts",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit --pretty false",
|
|
46
|
+
"tsd": "tsd --files test/types.test-d.ts",
|
|
47
|
+
"lint": "eslint .",
|
|
48
|
+
"test:package": "node scripts/verify-package.mjs",
|
|
49
|
+
"bench": "vitest bench --run"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"registry": "https://registry.npmjs.org"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"escape-published": "npm:@weapp-core/escape@8.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|