cizgile 0.2.0 → 1.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.
- package/README.md +88 -35
- package/dist/idna.d.mts +60 -0
- package/dist/idna.mjs +313 -0
- package/dist/index.d.mts +20 -12
- package/dist/index.mjs +124 -53
- package/dist/shared/punycode-MAFJqHdd.mjs +180 -0
- package/dist/shared/{locales-latin-Bm94HAeL.mjs → registry-D787niCh.mjs} +162 -15
- package/dist/shared/{types-DbyhfPRc.d.mts → types-D5P1JMcX.d.mts} +2 -2
- package/dist/transliterate.d.mts +95 -2
- package/dist/transliterate.mjs +8771 -11
- package/dist/uri.d.mts +68 -17
- package/dist/uri.mjs +283 -298
- package/package.json +19 -14
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as applyCompat, N as compileTables, P as fold, j as DEFAULT_TABLES, n as registeredLocale, r as registryVersion, v as latinLocales, z as symbols } from "./shared/registry-D787niCh.mjs";
|
|
2
2
|
import { h as isSegmentNzNc, r as percentEncode } from "./shared/percent-DG-pZhJI.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/slug/bidi.ts
|
|
@@ -168,14 +168,15 @@ let cache$1;
|
|
|
168
168
|
function resolveLocale(locale) {
|
|
169
169
|
if (locale === void 0) return void 0;
|
|
170
170
|
if (typeof locale === "string") {
|
|
171
|
-
const found = latinLocales[locale];
|
|
172
|
-
if (found === void 0) throw new TypeError(`slugify: unknown locale "${locale}"; pass a Locale object exported by cizgile/transliterate`);
|
|
171
|
+
const found = latinLocales[locale] ?? registeredLocale(locale);
|
|
172
|
+
if (found === void 0) throw new TypeError(`slugify: unknown locale "${locale}"; pass a Locale object exported by cizgile/transliterate, or register it with registerLocale()`);
|
|
173
173
|
return found;
|
|
174
174
|
}
|
|
175
175
|
return locale;
|
|
176
176
|
}
|
|
177
177
|
function resolveTables(option, unicode, locale) {
|
|
178
178
|
if (unicode) return void 0;
|
|
179
|
+
if (option === "none") return (locale === void 0 ? [symbols] : [locale.table, symbols]).map(symbolEntries);
|
|
179
180
|
const out = [];
|
|
180
181
|
if (locale !== void 0) {
|
|
181
182
|
out.push(locale.table);
|
|
@@ -207,6 +208,15 @@ function build(options) {
|
|
|
207
208
|
}
|
|
208
209
|
const maxLength = options.maxLength;
|
|
209
210
|
if (maxLength !== void 0 && (!Number.isInteger(maxLength) || maxLength < 0)) throw new RangeError("slugify: maxLength must be a non-negative integer");
|
|
211
|
+
const maxLengthUnit = options.maxLengthUnit ?? "units";
|
|
212
|
+
if (![
|
|
213
|
+
"units",
|
|
214
|
+
"code-points",
|
|
215
|
+
"graphemes",
|
|
216
|
+
"bytes"
|
|
217
|
+
].includes(maxLengthUnit)) throw new TypeError(`slugify: unknown maxLengthUnit ${JSON.stringify(maxLengthUnit)}`);
|
|
218
|
+
const fallback = options.fallback;
|
|
219
|
+
if (fallback !== void 0 && typeof fallback !== "string" && typeof fallback !== "function") throw new TypeError("slugify: fallback must be a string or a function");
|
|
210
220
|
const wordExtra = preserveCharacters.map(escapeClassChar).join("");
|
|
211
221
|
const wordClass = unicode ? `\\p{L}\\p{N}\\p{M}${wordExtra}` : `a-z0-9${lowercase ? "" : "A-Z"}${wordExtra}`;
|
|
212
222
|
const allowedClass = wordClass + separatorChars.map(escapeClassChar).join("");
|
|
@@ -227,6 +237,8 @@ function build(options) {
|
|
|
227
237
|
preserveLeadingUnderscore: options.preserveLeadingUnderscore ?? false,
|
|
228
238
|
preserveTrailingSeparator: options.preserveTrailingSeparator ?? false,
|
|
229
239
|
maxLength,
|
|
240
|
+
maxLengthUnit,
|
|
241
|
+
fallback,
|
|
230
242
|
scripts: options.scripts ?? "any",
|
|
231
243
|
bidi: options.bidi ?? "allow",
|
|
232
244
|
allowedClass,
|
|
@@ -257,6 +269,7 @@ function optionsKey(options) {
|
|
|
257
269
|
const locale = options.locale;
|
|
258
270
|
const transliterate = options.transliterate;
|
|
259
271
|
const remove = options.remove;
|
|
272
|
+
const fallback = options.fallback;
|
|
260
273
|
return JSON.stringify([
|
|
261
274
|
options.separator,
|
|
262
275
|
options.lowercase,
|
|
@@ -270,60 +283,31 @@ function optionsKey(options) {
|
|
|
270
283
|
options.preserveLeadingUnderscore,
|
|
271
284
|
options.preserveTrailingSeparator,
|
|
272
285
|
options.maxLength,
|
|
286
|
+
options.maxLengthUnit,
|
|
287
|
+
typeof fallback === "function" ? `#${objectId(fallback)}` : fallback,
|
|
273
288
|
options.scripts,
|
|
274
289
|
options.bidi
|
|
275
290
|
]);
|
|
276
291
|
}
|
|
277
292
|
function resolveOptions(options = DEFAULT_OPTIONS$1) {
|
|
278
293
|
cache$1 ??= /* @__PURE__ */ new WeakMap();
|
|
279
|
-
const
|
|
280
|
-
if (
|
|
294
|
+
const registered = typeof options.locale === "string" && !(options.locale in latinLocales);
|
|
295
|
+
if (!registered) {
|
|
296
|
+
const cached = cache$1.get(options);
|
|
297
|
+
if (cached !== void 0) return cached;
|
|
298
|
+
}
|
|
281
299
|
byKey ??= /* @__PURE__ */ new Map();
|
|
282
|
-
const key = optionsKey(options);
|
|
300
|
+
const key = registered ? `${registryVersion()}:${optionsKey(options)}` : optionsKey(options);
|
|
283
301
|
let resolved = byKey.get(key);
|
|
284
302
|
if (resolved === void 0) {
|
|
285
303
|
resolved = build(options);
|
|
286
304
|
if (byKey.size > 512) byKey.clear();
|
|
287
305
|
byKey.set(key, resolved);
|
|
288
306
|
}
|
|
289
|
-
cache$1.set(options, resolved);
|
|
307
|
+
if (!registered) cache$1.set(options, resolved);
|
|
290
308
|
return resolved;
|
|
291
309
|
}
|
|
292
310
|
|
|
293
|
-
//#endregion
|
|
294
|
-
//#region src/slug/is-slug.ts
|
|
295
|
-
let cache;
|
|
296
|
-
function patternFor(options) {
|
|
297
|
-
cache ??= /* @__PURE__ */ new WeakMap();
|
|
298
|
-
const cached = cache.get(options);
|
|
299
|
-
if (cached !== void 0) return cached;
|
|
300
|
-
const o = resolveOptions(options);
|
|
301
|
-
const char = `[${o.wordClass}]`;
|
|
302
|
-
const separator = escapeRegExp$1(o.separator);
|
|
303
|
-
const lead = o.preserveLeadingUnderscore ? "_?" : "";
|
|
304
|
-
const word = o.unicode ? `(?!\\p{M})${char}+` : `${char}+`;
|
|
305
|
-
const body = o.separator === "" ? word : `${word}(?:${separator}${word})*`;
|
|
306
|
-
const tail = o.preserveTrailingSeparator && o.separator !== "" ? `(?:${separator})?` : "";
|
|
307
|
-
const pattern = new RegExp(`^${lead}${body}${tail}$`, "u");
|
|
308
|
-
cache.set(options, pattern);
|
|
309
|
-
return pattern;
|
|
310
|
-
}
|
|
311
|
-
const DEFAULT_OPTIONS = {};
|
|
312
|
-
/** Whether `input` is exactly what `slugify` would produce under the same options. */
|
|
313
|
-
function isSlug(input, options = DEFAULT_OPTIONS) {
|
|
314
|
-
if (typeof input !== "string" || input === "" || input === "." || input === "..") return false;
|
|
315
|
-
const o = resolveOptions(options);
|
|
316
|
-
if (o.maxLength !== void 0 && input.length > o.maxLength) return false;
|
|
317
|
-
if (o.separator !== "" && input.includes(o.separator + o.separator)) return false;
|
|
318
|
-
if (o.unicode) {
|
|
319
|
-
if (input !== input.normalize("NFKC")) return false;
|
|
320
|
-
if (o.lowercase && input !== lowercaseOf(input, o)) return false;
|
|
321
|
-
if (o.scripts !== "any" && !checkScripts(input, o.scripts).ok) return false;
|
|
322
|
-
if (o.bidi !== "allow" && !isBidiSafeComponent(input)) return false;
|
|
323
|
-
}
|
|
324
|
-
return patternFor(options).test(input);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
311
|
//#endregion
|
|
328
312
|
//#region src/slug/truncate.ts
|
|
329
313
|
let segmenter;
|
|
@@ -339,6 +323,53 @@ function isJoinerBefore(text, index) {
|
|
|
339
323
|
function isClusterExtender(cp) {
|
|
340
324
|
return cp >= 56320 && cp <= 57343 || cp === 8205 || cp >= 65024 && cp <= 65039 || cp >= 127995 && cp <= 127999 || cp >= 4448 && cp <= 4607 || cp >= 917536 && cp <= 917631 || /\p{M}/u.test(String.fromCodePoint(cp));
|
|
341
325
|
}
|
|
326
|
+
function utf8Length(cp) {
|
|
327
|
+
return cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
|
|
328
|
+
}
|
|
329
|
+
/** The length of `text` in the given unit. */
|
|
330
|
+
function measure(text, unit = "units") {
|
|
331
|
+
if (unit === "units") return text.length;
|
|
332
|
+
let count = 0;
|
|
333
|
+
if (unit === "graphemes") {
|
|
334
|
+
const seg = graphemeSegmenter();
|
|
335
|
+
if (seg !== void 0) {
|
|
336
|
+
for (const _ of seg.segment(text)) count += 1;
|
|
337
|
+
return count;
|
|
338
|
+
}
|
|
339
|
+
let index = 0;
|
|
340
|
+
for (const ch of text) {
|
|
341
|
+
if (!(isClusterExtender(ch.codePointAt(0) ?? 0) || isJoinerBefore(text, index)) || index === 0) count += 1;
|
|
342
|
+
index += ch.length;
|
|
343
|
+
}
|
|
344
|
+
return count;
|
|
345
|
+
}
|
|
346
|
+
for (const ch of text) count += unit === "bytes" ? utf8Length(ch.codePointAt(0) ?? 0) : 1;
|
|
347
|
+
return count;
|
|
348
|
+
}
|
|
349
|
+
function prefixEnd(text, limit, unit) {
|
|
350
|
+
if (unit === "units") return Math.min(limit, text.length);
|
|
351
|
+
let used = 0;
|
|
352
|
+
if (unit === "graphemes") {
|
|
353
|
+
const seg = graphemeSegmenter();
|
|
354
|
+
if (seg !== void 0) {
|
|
355
|
+
let end = 0;
|
|
356
|
+
for (const { index, segment } of seg.segment(text)) {
|
|
357
|
+
if (used === limit) break;
|
|
358
|
+
used += 1;
|
|
359
|
+
end = index + segment.length;
|
|
360
|
+
}
|
|
361
|
+
return end;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
let end = 0;
|
|
365
|
+
for (const ch of text) {
|
|
366
|
+
const cost = unit === "bytes" ? utf8Length(ch.codePointAt(0) ?? 0) : 1;
|
|
367
|
+
if (used + cost > limit) break;
|
|
368
|
+
used += cost;
|
|
369
|
+
end += ch.length;
|
|
370
|
+
}
|
|
371
|
+
return unit === "graphemes" ? graphemeBoundary(text, end, { fallback: true }) : end;
|
|
372
|
+
}
|
|
342
373
|
function graphemeBoundary(text, limit, options = {}) {
|
|
343
374
|
const seg = options.fallback === true ? void 0 : graphemeSegmenter();
|
|
344
375
|
if (seg !== void 0) {
|
|
@@ -361,13 +392,14 @@ function stripTrailingSeparator(text, separator) {
|
|
|
361
392
|
for (let k = separator.length - 1; k > 0; k--) if (out.endsWith(separator.slice(0, k))) return out.slice(0, -k);
|
|
362
393
|
return out;
|
|
363
394
|
}
|
|
364
|
-
/** Cuts `slug` to at most `maxLength` UTF-16 code units at a `separator` boundary, never inside a grapheme cluster. */
|
|
365
|
-
function truncateSlug(slug, maxLength, separator = "-") {
|
|
395
|
+
/** Cuts `slug` to at most `maxLength` in the given `unit` (UTF-16 code units by default) at a `separator` boundary, never inside a grapheme cluster. */
|
|
396
|
+
function truncateSlug(slug, maxLength, separator = "-", unit = "units") {
|
|
366
397
|
if (!Number.isInteger(maxLength) || maxLength < 0) throw new RangeError("truncateSlug: maxLength must be a non-negative integer");
|
|
367
|
-
if (slug
|
|
398
|
+
if (measure(slug, unit) <= maxLength) return slug;
|
|
368
399
|
if (maxLength === 0) return "";
|
|
369
|
-
|
|
370
|
-
|
|
400
|
+
const limit = prefixEnd(slug, maxLength, unit);
|
|
401
|
+
let cut = slug.slice(0, limit);
|
|
402
|
+
if (separator !== "" && !slug.startsWith(separator, limit)) {
|
|
371
403
|
const last = cut.lastIndexOf(separator);
|
|
372
404
|
if (last > 0) cut = cut.slice(0, last);
|
|
373
405
|
}
|
|
@@ -375,6 +407,40 @@ function truncateSlug(slug, maxLength, separator = "-") {
|
|
|
375
407
|
return stripTrailingSeparator(cut, separator);
|
|
376
408
|
}
|
|
377
409
|
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/slug/is-slug.ts
|
|
412
|
+
let cache;
|
|
413
|
+
function patternFor(options) {
|
|
414
|
+
cache ??= /* @__PURE__ */ new WeakMap();
|
|
415
|
+
const cached = cache.get(options);
|
|
416
|
+
if (cached !== void 0) return cached;
|
|
417
|
+
const o = resolveOptions(options);
|
|
418
|
+
const char = `[${o.wordClass}]`;
|
|
419
|
+
const separator = escapeRegExp$1(o.separator);
|
|
420
|
+
const lead = o.preserveLeadingUnderscore ? "_?" : "";
|
|
421
|
+
const word = o.unicode ? `(?!\\p{M})${char}+` : `${char}+`;
|
|
422
|
+
const body = o.separator === "" ? word : `${word}(?:${separator}${word})*`;
|
|
423
|
+
const tail = o.preserveTrailingSeparator && o.separator !== "" ? `(?:${separator})?` : "";
|
|
424
|
+
const pattern = new RegExp(`^${lead}${body}${tail}$`, "u");
|
|
425
|
+
cache.set(options, pattern);
|
|
426
|
+
return pattern;
|
|
427
|
+
}
|
|
428
|
+
const DEFAULT_OPTIONS = {};
|
|
429
|
+
/** Whether `input` is exactly what `slugify` would produce under the same options. */
|
|
430
|
+
function isSlug(input, options = DEFAULT_OPTIONS) {
|
|
431
|
+
if (typeof input !== "string" || input === "" || input === "." || input === "..") return false;
|
|
432
|
+
const o = resolveOptions(options);
|
|
433
|
+
if (o.maxLength !== void 0 && measure(input, o.maxLengthUnit) > o.maxLength) return false;
|
|
434
|
+
if (o.separator !== "" && input.includes(o.separator + o.separator)) return false;
|
|
435
|
+
if (o.unicode) {
|
|
436
|
+
if (input !== input.normalize("NFKC")) return false;
|
|
437
|
+
if (o.lowercase && input !== lowercaseOf(input, o)) return false;
|
|
438
|
+
if (o.scripts !== "any" && !checkScripts(input, o.scripts).ok) return false;
|
|
439
|
+
if (o.bidi !== "allow" && !isBidiSafeComponent(input)) return false;
|
|
440
|
+
}
|
|
441
|
+
return patternFor(options).test(input);
|
|
442
|
+
}
|
|
443
|
+
|
|
378
444
|
//#endregion
|
|
379
445
|
//#region src/slug/slugify.ts
|
|
380
446
|
const NON_ASCII = /[^\x00-\x7F]/;
|
|
@@ -414,10 +480,15 @@ function slugify(input, options) {
|
|
|
414
480
|
if (o.leadingMarks !== void 0 && !ascii) s = s.replace(o.leadingMarks, "$1");
|
|
415
481
|
const hadTrailingSeparator = o.separator !== "" && s.endsWith(o.separator);
|
|
416
482
|
s = collapse(s, o);
|
|
417
|
-
if (o.maxLength !== void 0) s = truncateSlug(s, o.maxLength, o.separator);
|
|
483
|
+
if (o.maxLength !== void 0) s = truncateSlug(s, o.maxLength, o.separator, o.maxLengthUnit);
|
|
418
484
|
if (s === "." || s === "..") s = "";
|
|
419
485
|
if (o.preserveLeadingUnderscore && hadLeadingUnderscore && s !== "" && !s.startsWith("_")) s = "_" + s;
|
|
420
486
|
if (o.preserveTrailingSeparator && hadTrailingSeparator && s !== "") s += o.separator;
|
|
487
|
+
if (s === "" && o.fallback !== void 0) {
|
|
488
|
+
const text = typeof o.fallback === "string" ? o.fallback : o.fallback(input);
|
|
489
|
+
const { fallback: _fallback, ...rest } = options ?? {};
|
|
490
|
+
return slugify(text, rest);
|
|
491
|
+
}
|
|
421
492
|
if (o.unicode && s !== "") {
|
|
422
493
|
if (o.scripts !== "any") {
|
|
423
494
|
const check = checkScripts(s, o.scripts);
|
|
@@ -433,11 +504,11 @@ function slugify(input, options) {
|
|
|
433
504
|
|
|
434
505
|
//#endregion
|
|
435
506
|
//#region src/slug/slugger.ts
|
|
436
|
-
function withSuffix(base, separator, n, maxLength) {
|
|
507
|
+
function withSuffix(base, separator, n, maxLength, unit) {
|
|
437
508
|
const suffix = separator + String(n);
|
|
438
|
-
if (maxLength === void 0 || base
|
|
439
|
-
const room = maxLength - suffix
|
|
440
|
-
const head = room > 0 ? truncateSlug(base, room, separator) : "";
|
|
509
|
+
if (maxLength === void 0 || measure(base + suffix, unit) <= maxLength) return base + suffix;
|
|
510
|
+
const room = maxLength - measure(suffix, unit);
|
|
511
|
+
const head = room > 0 ? truncateSlug(base, room, separator, unit) : "";
|
|
441
512
|
return head === "" ? String(n).slice(0, maxLength) : head + suffix;
|
|
442
513
|
}
|
|
443
514
|
/** A slugger that never repeats a slug: the second `"Hello"` becomes `"hello-2"`. `defaults` apply to every call. */
|
|
@@ -460,7 +531,7 @@ function createSlugger(defaults = {}) {
|
|
|
460
531
|
let candidate;
|
|
461
532
|
do {
|
|
462
533
|
n += 1;
|
|
463
|
-
candidate = withSuffix(base, separator, n, merged.maxLength);
|
|
534
|
+
candidate = withSuffix(base, separator, n, merged.maxLength, merged.maxLengthUnit ?? "units");
|
|
464
535
|
} while (issued.has(candidate));
|
|
465
536
|
counters.set(base, n);
|
|
466
537
|
issued.add(candidate);
|
|
@@ -483,4 +554,4 @@ function createSlugger(defaults = {}) {
|
|
|
483
554
|
const slugifyWithCounter = createSlugger;
|
|
484
555
|
|
|
485
556
|
//#endregion
|
|
486
|
-
export { checkScripts, createSlugger, decamelize, detectScripts, isBidiSafeComponent, isSlug, slugify, slugifyWithCounter, truncateSlug };
|
|
557
|
+
export { checkScripts, createSlugger, decamelize, detectScripts, isBidiSafeComponent, isSlug, measure, slugify, slugifyWithCounter, truncateSlug };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
//#region src/uri/punycode.ts
|
|
2
|
+
const BASE = 36;
|
|
3
|
+
const T_MIN = 1;
|
|
4
|
+
const T_MAX = 26;
|
|
5
|
+
const SKEW = 38;
|
|
6
|
+
const DAMP = 700;
|
|
7
|
+
const INITIAL_BIAS = 72;
|
|
8
|
+
const INITIAL_N = 128;
|
|
9
|
+
const DELIMITER = "-";
|
|
10
|
+
const PREFIX = "xn--";
|
|
11
|
+
const MAX_INT = 2147483647;
|
|
12
|
+
const LABEL_SEPARATORS = /[.\u3002\uFF0E\uFF61]/;
|
|
13
|
+
function adapt(delta, numPoints, firstTime) {
|
|
14
|
+
let d = firstTime ? Math.floor(delta / DAMP) : delta >> 1;
|
|
15
|
+
d += Math.floor(d / numPoints);
|
|
16
|
+
let k = 0;
|
|
17
|
+
while (d > 455) {
|
|
18
|
+
d = Math.floor(d / 35);
|
|
19
|
+
k += BASE;
|
|
20
|
+
}
|
|
21
|
+
return k + Math.floor(36 * d / (d + SKEW));
|
|
22
|
+
}
|
|
23
|
+
function digit(value) {
|
|
24
|
+
return String.fromCodePoint(value + (value < 26 ? 97 : 22));
|
|
25
|
+
}
|
|
26
|
+
function digitValue(cp) {
|
|
27
|
+
if (cp >= 48 && cp <= 57) return cp - 22;
|
|
28
|
+
if (cp >= 65 && cp <= 90) return cp - 65;
|
|
29
|
+
if (cp >= 97 && cp <= 122) return cp - 97;
|
|
30
|
+
return -1;
|
|
31
|
+
}
|
|
32
|
+
/** RFC 3492 Punycode encoding of one label, without the `xn--` prefix. */
|
|
33
|
+
function punycodeEncode(input) {
|
|
34
|
+
const points = Array.from(input, (ch) => ch.codePointAt(0) ?? 0);
|
|
35
|
+
let out = "";
|
|
36
|
+
for (const cp of points) if (cp < 128) out += String.fromCodePoint(cp);
|
|
37
|
+
const basicCount = out.length;
|
|
38
|
+
let handled = basicCount;
|
|
39
|
+
if (basicCount > 0) out += DELIMITER;
|
|
40
|
+
let n = INITIAL_N;
|
|
41
|
+
let delta = 0;
|
|
42
|
+
let bias = INITIAL_BIAS;
|
|
43
|
+
while (handled < points.length) {
|
|
44
|
+
let m = 1114112;
|
|
45
|
+
for (const cp of points) if (cp >= n && cp < m) m = cp;
|
|
46
|
+
delta += (m - n) * (handled + 1);
|
|
47
|
+
n = m;
|
|
48
|
+
for (const cp of points) {
|
|
49
|
+
if (cp < n) delta += 1;
|
|
50
|
+
if (cp === n) {
|
|
51
|
+
let q = delta;
|
|
52
|
+
for (let k = BASE;; k += BASE) {
|
|
53
|
+
const t = k <= bias ? T_MIN : k >= bias + T_MAX ? T_MAX : k - bias;
|
|
54
|
+
if (q < t) break;
|
|
55
|
+
out += digit(t + (q - t) % (BASE - t));
|
|
56
|
+
q = Math.floor((q - t) / (BASE - t));
|
|
57
|
+
}
|
|
58
|
+
out += digit(q);
|
|
59
|
+
bias = adapt(delta, handled + 1, handled === basicCount);
|
|
60
|
+
delta = 0;
|
|
61
|
+
handled += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
delta += 1;
|
|
65
|
+
n += 1;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* RFC 3492 Punycode decoding of one label, without the `xn--` prefix.
|
|
71
|
+
* @throws {RangeError} on malformed input.
|
|
72
|
+
*/
|
|
73
|
+
function punycodeDecode(input) {
|
|
74
|
+
const output = [];
|
|
75
|
+
const last = input.lastIndexOf(DELIMITER);
|
|
76
|
+
const basicEnd = last > 0 ? last : 0;
|
|
77
|
+
for (let i = 0; i < basicEnd; i++) {
|
|
78
|
+
const cp = input.codePointAt(i) ?? 0;
|
|
79
|
+
if (cp >= 128) throw new RangeError("punycodeDecode: invalid basic code point");
|
|
80
|
+
output.push(cp);
|
|
81
|
+
}
|
|
82
|
+
let n = INITIAL_N;
|
|
83
|
+
let bias = INITIAL_BIAS;
|
|
84
|
+
let i = 0;
|
|
85
|
+
let index = basicEnd > 0 ? basicEnd + 1 : 0;
|
|
86
|
+
while (index < input.length) {
|
|
87
|
+
const oldI = i;
|
|
88
|
+
let w = 1;
|
|
89
|
+
for (let k = BASE;; k += BASE) {
|
|
90
|
+
if (index >= input.length) throw new RangeError("punycodeDecode: truncated input");
|
|
91
|
+
const d = digitValue(input.codePointAt(index) ?? 0);
|
|
92
|
+
index += 1;
|
|
93
|
+
if (d < 0) throw new RangeError("punycodeDecode: invalid digit");
|
|
94
|
+
i += d * w;
|
|
95
|
+
if (i > MAX_INT) throw new RangeError("punycodeDecode: overflow");
|
|
96
|
+
const t = k <= bias ? T_MIN : k >= bias + T_MAX ? T_MAX : k - bias;
|
|
97
|
+
if (d < t) break;
|
|
98
|
+
w *= BASE - t;
|
|
99
|
+
}
|
|
100
|
+
const count = output.length + 1;
|
|
101
|
+
bias = adapt(i - oldI, count, oldI === 0);
|
|
102
|
+
n += Math.floor(i / count);
|
|
103
|
+
i %= count;
|
|
104
|
+
if (n > 1114111) throw new RangeError("punycodeDecode: code point out of range");
|
|
105
|
+
output.splice(i, 0, n);
|
|
106
|
+
i += 1;
|
|
107
|
+
}
|
|
108
|
+
return String.fromCodePoint(...output);
|
|
109
|
+
}
|
|
110
|
+
function isAsciiLabel(label) {
|
|
111
|
+
for (const ch of label) if ((ch.codePointAt(0) ?? 0) >= 128) return false;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function lowercaseCodePoints(text) {
|
|
115
|
+
let out = "";
|
|
116
|
+
for (const ch of text) out += ch.toLowerCase();
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
function mapLabel(label) {
|
|
120
|
+
return lowercaseCodePoints(label.replace(/\p{Cf}/gu, "").normalize("NFKC")).normalize("NFC");
|
|
121
|
+
}
|
|
122
|
+
function invalid(fn, label, reason) {
|
|
123
|
+
return /* @__PURE__ */ new RangeError(`${fn}: label ${JSON.stringify(label)} ${reason}`);
|
|
124
|
+
}
|
|
125
|
+
function decodeLabel(fn, label) {
|
|
126
|
+
let decoded;
|
|
127
|
+
try {
|
|
128
|
+
decoded = punycodeDecode(label.slice(4));
|
|
129
|
+
} catch {
|
|
130
|
+
throw invalid(fn, label, "is not valid Punycode");
|
|
131
|
+
}
|
|
132
|
+
if (decoded === "" || isAsciiLabel(decoded) || /[\p{C}\p{Z}]/u.test(decoded) || mapLabel(decoded) !== decoded || PREFIX + punycodeEncode(decoded) !== label) throw invalid(fn, label, "is not a valid A-label");
|
|
133
|
+
return decoded;
|
|
134
|
+
}
|
|
135
|
+
function checkAsciiLabel(fn, label, original) {
|
|
136
|
+
if (label.length > 63) throw invalid(fn, original, "exceeds 63 octets");
|
|
137
|
+
if (!/^[a-z0-9-]*$/.test(label)) throw invalid(fn, original, "contains characters outside letters, digits and hyphen");
|
|
138
|
+
if (label.startsWith("-") || label.endsWith("-")) throw invalid(fn, original, "may not start or end with a hyphen");
|
|
139
|
+
if (label.startsWith(PREFIX)) decodeLabel(fn, label);
|
|
140
|
+
else if (label.slice(2, 4) === "--") throw invalid(fn, original, "has hyphens in positions 3 and 4");
|
|
141
|
+
return label;
|
|
142
|
+
}
|
|
143
|
+
function checkDomain(fn, labels, host) {
|
|
144
|
+
if (labels.length > 1 && labels[labels.length - 1] === "") labels = labels.slice(0, -1);
|
|
145
|
+
for (const label of labels) if (label === "") throw invalid(fn, host, "contains an empty label");
|
|
146
|
+
const out = labels.join(".");
|
|
147
|
+
if (out.length > 253) throw new RangeError(`${fn}: ${JSON.stringify(host)} exceeds 253 octets`);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Converts a domain to its A-label form: maps and lowercases each label the way UTS #46 does, punycodes the non-ASCII ones, and validates lengths, hyphens and existing `xn--` labels. IP literals pass through.
|
|
152
|
+
* @throws {RangeError} when a label or the domain is not valid.
|
|
153
|
+
*/
|
|
154
|
+
function domainToAscii(host) {
|
|
155
|
+
if (host === "" || host.startsWith("[")) return host;
|
|
156
|
+
const out = checkDomain("domainToAscii", host.split(LABEL_SEPARATORS).flatMap((label) => {
|
|
157
|
+
if (isAsciiLabel(label)) return [checkAsciiLabel("domainToAscii", label.toLowerCase(), label)];
|
|
158
|
+
const mapped = mapLabel(label);
|
|
159
|
+
if (mapped.includes(".")) return domainToAscii(mapped).split(".");
|
|
160
|
+
if (isAsciiLabel(mapped)) return [checkAsciiLabel("domainToAscii", mapped, label)];
|
|
161
|
+
return [checkAsciiLabel("domainToAscii", PREFIX + punycodeEncode(mapped), label)];
|
|
162
|
+
}), host);
|
|
163
|
+
return host.endsWith(".") && out !== "" ? `${out}.` : out;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Converts the `xn--` labels of a domain back to Unicode and lowercases the rest.
|
|
167
|
+
* @throws {RangeError} when an `xn--` label does not decode to a valid U-label.
|
|
168
|
+
*/
|
|
169
|
+
function domainToUnicode(host) {
|
|
170
|
+
if (host === "" || host.startsWith("[")) return host;
|
|
171
|
+
const out = checkDomain("domainToUnicode", host.split(".").map((label) => {
|
|
172
|
+
const lower = label.toLowerCase();
|
|
173
|
+
if (!isAsciiLabel(lower)) return lower;
|
|
174
|
+
return lower.startsWith(PREFIX) ? decodeLabel("domainToUnicode", lower) : lower;
|
|
175
|
+
}), host);
|
|
176
|
+
return host.endsWith(".") && out !== "" ? `${out}.` : out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
export { punycodeEncode as i, domainToUnicode as n, punycodeDecode as r, domainToAscii as t };
|