@oxyhq/core 16.0.0 → 16.1.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.
@@ -13,6 +13,7 @@ import {
13
13
  DISPLAY_NAME_ALLOWED_SCRIPTS,
14
14
  DISPLAY_NAME_DISALLOWED_SOURCE,
15
15
  DISPLAY_NAME_ORPHANED_MARK_SOURCE,
16
+ DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE,
16
17
  isValidUUID,
17
18
  isValidDate,
18
19
  isValidFileSize,
@@ -21,6 +22,16 @@ import {
21
22
  sanitizeHTML,
22
23
  validateAndSanitizeUserInput
23
24
  } from '../validationUtils';
25
+ // Imported from the generated module rather than re-exported through
26
+ // `validationUtils`: the broad letters class is an internal operand of the
27
+ // orphaned-mark lookbehind, and the denylist is a generation-time operand
28
+ // already subtracted from the allowlist — neither is part of the policy's
29
+ // public runtime surface.
30
+ import {
31
+ DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES,
32
+ DISPLAY_NAME_LETTERS_RANGES,
33
+ DISPLAY_NAME_NAME_SEPARATORS_RANGES,
34
+ } from '../displayNamePolicyRanges.generated';
24
35
 
25
36
  describe('Validation Utils', () => {
26
37
  describe('isRequiredString', () => {
@@ -214,6 +225,7 @@ describe('Validation Utils', () => {
214
225
  expect(PROPERTY_ESCAPE.test(DISPLAY_NAME_ALLOWED_SCRIPTS)).toBe(false);
215
226
  expect(PROPERTY_ESCAPE.test(DISPLAY_NAME_DISALLOWED_SOURCE)).toBe(false);
216
227
  expect(PROPERTY_ESCAPE.test(DISPLAY_NAME_ORPHANED_MARK_SOURCE)).toBe(false);
228
+ expect(PROPERTY_ESCAPE.test(DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE)).toBe(false);
217
229
  });
218
230
 
219
231
  it('only uses code-point escapes (\\x / \\u) in the class bodies', () => {
@@ -221,7 +233,11 @@ describe('Validation Utils', () => {
221
233
  // code-point escapes. Every backslash-escape in the runtime sources must
222
234
  // be one of those forms — never a property escape.
223
235
  const escapeLeads = new Set<string>();
224
- for (const src of [DISPLAY_NAME_DISALLOWED_SOURCE, DISPLAY_NAME_ORPHANED_MARK_SOURCE]) {
236
+ for (const src of [
237
+ DISPLAY_NAME_DISALLOWED_SOURCE,
238
+ DISPLAY_NAME_ORPHANED_MARK_SOURCE,
239
+ DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE,
240
+ ]) {
225
241
  for (const [, lead] of src.matchAll(/\\(.)/g)) {
226
242
  escapeLeads.add(lead);
227
243
  }
@@ -299,6 +315,281 @@ describe('Validation Utils', () => {
299
315
  });
300
316
  });
301
317
 
318
+ // A Unicode script is not only its letters: `scx=X` also carries script X's
319
+ // own digits, punctuation and symbols. The allowlist is therefore generated as
320
+ // `scripts ∩ General_Category L`. Before that intersection existed the class
321
+ // admitted 1831 non-letter code points, so the documented policy ("digits,
322
+ // hyphens, dots, symbols are removed") held for ASCII input only — a federated
323
+ // actor could keep Arabic-Indic digits, a Bengali currency sign, or the U+061C
324
+ // bidi control in their display name. This block is the regression guard.
325
+ describe('display-name allowlist admits ONLY letters', () => {
326
+ const allowed = new RegExp(`[${DISPLAY_NAME_ALLOWED_SCRIPTS}]`, 'u');
327
+ // Built from the same generated ranges the policy ships, so this assertion
328
+ // stays property-escape-free and cannot drift from the runtime regex.
329
+ const letter = new RegExp(`[${DISPLAY_NAME_LETTERS_RANGES}]`, 'u');
330
+
331
+ // Scanning the FULL code-point space, not a sample: one leaked invisible
332
+ // control is a spoofing vector, and a sample cannot prove its absence.
333
+ const leaks: number[] = [];
334
+ let admitted = 0;
335
+ for (let cp = 0; cp <= 0x10ffff; cp++) {
336
+ if (cp >= 0xd800 && cp <= 0xdfff) continue; // lone surrogates
337
+ const ch = String.fromCodePoint(cp);
338
+ if (!allowed.test(ch)) continue;
339
+ admitted++;
340
+ if (!letter.test(ch)) leaks.push(cp);
341
+ }
342
+
343
+ it('admits no non-letter code point anywhere in Unicode', () => {
344
+ const named = leaks
345
+ .slice(0, 16)
346
+ .map((cp) => `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`);
347
+ expect({ count: leaks.length, sample: named }).toEqual({ count: 0, sample: [] });
348
+ });
349
+
350
+ // Vacuity floor: an allowlist that collapsed to (near) nothing would satisfy
351
+ // the assertion above while silently rejecting every real name.
352
+ it('still admits the expected bulk of letters', () => {
353
+ expect(admitted).toBeGreaterThan(100_000);
354
+ });
355
+
356
+ it.each([
357
+ ['٥', 'Arabic-Indic digit (scx=Arabic, GC=Nd)'],
358
+ ['०', 'Devanagari digit (scx=Devanagari, GC=Nd)'],
359
+ ['۞', 'Arabic ornament (scx=Arabic, GC=So)'],
360
+ ['৳', 'Bengali rupee sign (scx=Bengali, GC=Sc)'],
361
+ ['।', 'Devanagari danda (scx=Devanagari, GC=Po)'],
362
+ ['،', 'Arabic comma (scx=Arabic, GC=Po)'],
363
+ ['؜', 'ARABIC LETTER MARK - invisible bidi control (scx=Arabic, GC=Cf)'],
364
+ ['᠎', 'MONGOLIAN VOWEL SEPARATOR - invisible (scx=Mongolian, GC=Cf)'],
365
+ ])('rejects %p, a non-letter from an allowlisted script (%s)', (ch) => {
366
+ expect(isValidDisplayName(ch)).toBe(false);
367
+ });
368
+
369
+ // The intersection narrows the allowlist, so every previously-accepted real
370
+ // name must still pass — these are the ones whose scripts own the code
371
+ // points removed above.
372
+ it.each([
373
+ ['مُحَمَد', 'Arabic letters + harakat survive'],
374
+ ['नमस्ते', 'Devanagari'],
375
+ ['ᠰᠣᠩᠭᠣᠯ', 'Mongolian'],
376
+ ['বাংলা', 'Bengali'],
377
+ ])('still accepts %p (%s)', (name) => {
378
+ expect(isValidDisplayName(name)).toBe(true);
379
+ });
380
+ });
381
+
382
+ // A character policy classifies FORM, never MEANING. `卐` U+5350 and `卍`
383
+ // U+534D are CJK Unified Ideographs (GC=Lo, scx=Han) — to Unicode they are the
384
+ // same kind of thing as `山` in `山田太郎`, so no script- or category-level rule
385
+ // can reject them without also rejecting every real Chinese, Japanese and
386
+ // Korean name. They are therefore subtracted from the allowlist by an explicit
387
+ // code-point denylist at generation time. This block is the regression guard,
388
+ // and asserts the division of labour: the denylist covers ONLY what the
389
+ // intersection cannot.
390
+ describe('display-name denylist — letters that function as symbols', () => {
391
+ // The glyphs below are visually confusable with each other and with ordinary
392
+ // ideographs, so pin them to their code points before relying on them.
393
+ it('the fixtures below really are U+5350 and U+534D', () => {
394
+ expect('卐'.codePointAt(0)).toBe(0x5350);
395
+ expect('卍'.codePointAt(0)).toBe(0x534d);
396
+ });
397
+
398
+ it.each([
399
+ ['卐', 'U+5350 right-facing swastika (GC=Lo, scx=Han)'],
400
+ ['卍', 'U+534D left-facing swastika (GC=Lo, scx=Han)'],
401
+ ['卐 Glowniggers 卐', 'the production display name that motivated the denylist'],
402
+ ['卍 卐', 'both, alone'],
403
+ ['山田卍太郎', 'embedded mid-name, between real Han letters'],
404
+ ])('rejects %p (%s)', (name) => {
405
+ expect(isValidDisplayName(name)).toBe(false);
406
+ });
407
+
408
+ // The whole point of a per-code-point denylist instead of a script rule.
409
+ it.each([
410
+ ['山田太郎', 'Han (Japanese)'],
411
+ ['김철수', 'Hangul (Korean)'],
412
+ ['王小明', 'Han (Chinese)'],
413
+ ['田中\u{20000}', 'Han incl. astral CJK Extension B'],
414
+ // The four immediate neighbours of the denied pair: the subtraction must
415
+ // punch out exactly two code points, not a range around them.
416
+ ['卌', 'U+534C, immediately below U+534D'],
417
+ ['华', 'U+534E, immediately above U+534D (as in 中华)'],
418
+ ['协', 'U+534F, immediately below U+5350'],
419
+ ['卑', 'U+5351, immediately above U+5350'],
420
+ ])('still accepts %p (%s)', (name) => {
421
+ expect(isValidDisplayName(name)).toBe(true);
422
+ });
423
+
424
+ // Enumerate what is ACTUALLY denied from the generated class rather than
425
+ // trusting the literals above, so a future entry that is added to the list
426
+ // but not enforced by the emitted allowlist fails here.
427
+ const denied = new RegExp(`[${DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES}]`, 'u');
428
+ const deniedCodePoints: number[] = [];
429
+ for (let cp = 0; cp <= 0x10ffff; cp++) {
430
+ if (cp >= 0xd800 && cp <= 0xdfff) continue; // lone surrogates
431
+ if (denied.test(String.fromCodePoint(cp))) deniedCodePoints.push(cp);
432
+ }
433
+
434
+ it('denies exactly the two swastika ideographs', () => {
435
+ expect(deniedCodePoints).toEqual([0x534d, 0x5350]);
436
+ });
437
+
438
+ it('rejects every code point on the denylist', () => {
439
+ const accepted = deniedCodePoints.filter((cp) =>
440
+ isValidDisplayName(String.fromCodePoint(cp))
441
+ );
442
+ expect(
443
+ accepted.map((cp) => `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`)
444
+ ).toEqual([]);
445
+ // Vacuity floor: an empty denylist would satisfy the assertion above while
446
+ // enforcing nothing.
447
+ expect(deniedCodePoints.length).toBeGreaterThanOrEqual(2);
448
+ });
449
+
450
+ // Hermes safety for the new class (see the property-escape block above).
451
+ it('the denylist ranges contain NO Unicode property escape', () => {
452
+ expect(/\\[pP]\{/.test(DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES)).toBe(false);
453
+ expect(() => new RegExp(`[${DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES}]`, 'u')).not.toThrow();
454
+ });
455
+
456
+ // Division of labour: the Tibetan svasti signs look like the entries above
457
+ // but are GC=So, so the `scripts ∩ General_Category L` intersection already
458
+ // excludes them. They are rejected — and deliberately NOT on the denylist,
459
+ // where they would be dead weight that reads as load-bearing. The generator
460
+ // fails the build if such a redundant entry is added.
461
+ it.each([
462
+ ['࿕', 'U+0FD5 RIGHT-FACING SVASTI SIGN'],
463
+ ['࿖', 'U+0FD6 LEFT-FACING SVASTI SIGN'],
464
+ ['࿗', 'U+0FD7 RIGHT-FACING SVASTI SIGN WITH DOTS'],
465
+ ['࿘', 'U+0FD8 LEFT-FACING SVASTI SIGN WITH DOTS'],
466
+ ])('rejects %p (%s) via the intersection, not the denylist', (ch) => {
467
+ expect(isValidDisplayName(ch)).toBe(false);
468
+ expect(denied.test(ch)).toBe(false);
469
+ });
470
+ });
471
+
472
+ // Four punctuation code points JOIN two letters inside one real name. All are
473
+ // General_Category P, so `scripts ∩ L` strips them by default and stripping
474
+ // SPLITS the name (`Codeur·euses` → `Codeur euses`). They are re-admitted, but
475
+ // ONLY between two letters — the same characters are also used as ornament
476
+ // (a trailing `Roberto ·`), which must keep being trimmed. The conditional is
477
+ // the whole rule, so it is tested from both sides.
478
+ describe('display-name name separators — allowed only between letters', () => {
479
+ // Visually confusable with each other and with ASCII punctuation.
480
+ it('the fixtures below really are U+00B7 U+05BE U+0F0B U+30FB', () => {
481
+ expect('·'.codePointAt(0)).toBe(0x00b7);
482
+ expect('־'.codePointAt(0)).toBe(0x05be);
483
+ expect('་'.codePointAt(0)).toBe(0x0f0b);
484
+ expect('・'.codePointAt(0)).toBe(0x30fb);
485
+ });
486
+
487
+ it.each([
488
+ ['Codeur·euses en Liberté', 'U+00B7 French inclusive writing (production value)'],
489
+ ['Codeur·euses', 'U+00B7 bare'],
490
+ ['Pouet·te', 'U+00B7 short form'],
491
+ ['お坐・エガード', 'U+30FB Japanese name separator (production value)'],
492
+ ['אייר אברמסקי־קרוננברג', 'U+05BE Hebrew maqaf compound surname (production value)'],
493
+ ['འོད་ཟེར', 'U+0F0B Tibetan intersyllabic tsheg (production value)'],
494
+ ])('accepts letter-flanked %p (%s)', (name) => {
495
+ expect(isValidDisplayName(name)).toBe(true);
496
+ });
497
+
498
+ // A base letter can carry a combining mark, and that mark sits between the
499
+ // letter and the separator. If the flanking test only looked for a letter,
500
+ // these would be misread as unflanked and the separator stripped. Both
501
+ // fixtures use marks that do NOT recompose under NFC, so the mark really
502
+ // reaches the flanking test instead of being folded into a precomposed
503
+ // letter — the Tibetan one is the ordinary shape of Tibetan text.
504
+ it.each([
505
+ ['ཀི་ཁ', 'Tibetan letter + vowel sign U+0F72 + tsheg + letter'],
506
+ ['مُ·م', 'Arabic letter + damma U+064F + middle dot + letter'],
507
+ ])('accepts %p where a combining mark precedes the separator (%s)', (name) => {
508
+ expect(isValidDisplayName(name)).toBe(true);
509
+ });
510
+
511
+ it.each([
512
+ ['Roberto ·', 'trailing ornament (production value)'],
513
+ ['Michał rysiek Woźniak ·', 'trailing ornament (production value)'],
514
+ ['·Roberto', 'leading'],
515
+ ['a··b', 'doubled — neither is letter-flanked on both sides'],
516
+ ['·', 'alone'],
517
+ ['お坐・', 'trailing katakana middle dot'],
518
+ ['・エガード', 'leading katakana middle dot'],
519
+ ['a ·b', 'space on the left'],
520
+ ['a· b', 'space on the right'],
521
+ ])('rejects unflanked %p (%s)', (name) => {
522
+ expect(isValidDisplayName(name)).toBe(false);
523
+ });
524
+
525
+ // Enumerate what is ACTUALLY admitted from the generated class rather than
526
+ // trusting the literals above, and exercise both sides of the rule for each.
527
+ const separator = new RegExp(`[${DISPLAY_NAME_NAME_SEPARATORS_RANGES}]`, 'u');
528
+ const separatorCodePoints: number[] = [];
529
+ for (let cp = 0; cp <= 0x10ffff; cp++) {
530
+ if (cp >= 0xd800 && cp <= 0xdfff) continue; // lone surrogates
531
+ if (separator.test(String.fromCodePoint(cp))) separatorCodePoints.push(cp);
532
+ }
533
+
534
+ it('admits exactly the four name separators', () => {
535
+ expect(separatorCodePoints).toEqual([0x00b7, 0x05be, 0x0f0b, 0x30fb]);
536
+ });
537
+
538
+ it('every separator is valid between letters and invalid unflanked', () => {
539
+ const wrongWhenFlanked: string[] = [];
540
+ const wrongWhenUnflanked: string[] = [];
541
+ for (const cp of separatorCodePoints) {
542
+ const ch = String.fromCodePoint(cp);
543
+ const label = `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;
544
+ if (!isValidDisplayName(`a${ch}b`)) wrongWhenFlanked.push(label);
545
+ if (isValidDisplayName(`a ${ch}`)) wrongWhenUnflanked.push(label);
546
+ }
547
+ expect({ wrongWhenFlanked, wrongWhenUnflanked }).toEqual({
548
+ wrongWhenFlanked: [],
549
+ wrongWhenUnflanked: [],
550
+ });
551
+ // Vacuity floor: an empty separator class would satisfy both loops above.
552
+ expect(separatorCodePoints.length).toBe(4);
553
+ });
554
+
555
+ // The ASCII hyphen decision is untouched: U+05BE is General_Category Pd like
556
+ // U+002D, but admitting one says nothing about the other.
557
+ it('does not admit the ASCII hyphen', () => {
558
+ expect(separator.test('-')).toBe(false);
559
+ expect(isValidDisplayName('Jean-Luc')).toBe(false);
560
+ expect(isValidDisplayName('a-b')).toBe(false);
561
+ });
562
+
563
+ // Hermes safety for the new pattern (see the property-escape block above).
564
+ it('the unflanked-separator source contains NO Unicode property escape', () => {
565
+ expect(/\\[pP]\{/.test(DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE)).toBe(false);
566
+ expect(/\\[pP]\{/.test(DISPLAY_NAME_NAME_SEPARATORS_RANGES)).toBe(false);
567
+ expect(() => new RegExp(DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE, 'u')).not.toThrow();
568
+ // Global variant is what @oxyhq/api compiles for the strip path.
569
+ expect(() => new RegExp(DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE, 'gu')).not.toThrow();
570
+ });
571
+
572
+ // Nothing the policy already rejected may become valid.
573
+ it.each([
574
+ ['卐', 'denied symbol letter'],
575
+ ['卍', 'denied symbol letter'],
576
+ ['Agent007', 'digit'],
577
+ ['J.R.', 'dot'],
578
+ ['ᯅ', 'non-allowlisted script'],
579
+ ['nixCraft \u{1f427}', 'emoji'],
580
+ ])('still rejects %p (%s)', (name) => {
581
+ expect(isValidDisplayName(name)).toBe(false);
582
+ });
583
+
584
+ it.each([
585
+ ['山田太郎', 'Han'],
586
+ ['김철수', 'Hangul'],
587
+ ["Renée O'Brien", 'Latin with accent + apostrophe'],
588
+ ])('still accepts %p (%s)', (name) => {
589
+ expect(isValidDisplayName(name)).toBe(true);
590
+ });
591
+ });
592
+
302
593
  describe('isValidUUID', () => {
303
594
  it('should return true for valid UUIDs', () => {
304
595
  expect(isValidUUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true);
@@ -17,7 +17,30 @@
17
17
  *
18
18
  * Classes captured (regexpu-core, u-mode):
19
19
  * - DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES: the 30-script Script_Extensions
20
- * allowlist (scx=Latin, scx=Greek, … scx=Han).
20
+ * allowlist (scx=Latin, scx=Greek, … scx=Han) INTERSECTED with
21
+ * General_Category L, MINUS an explicit code-point denylist. The
22
+ * intersection is load-bearing: `scx=X` also carries script X's digits,
23
+ * punctuation and symbols, so without it the class admitted 1831 non-letter
24
+ * code points — script digits, 1082 symbols, and 9 invisible format/control
25
+ * characters including the U+061C bidi control. The generator fails if any
26
+ * non-letter survives. The denylist covers the opposite case: code points
27
+ * that ARE letters of an allowlisted script yet function as hate symbols
28
+ * (卐 卍) — no script-level or category-level rule can exclude
29
+ * them without also rejecting every real Chinese, Japanese and Korean name.
30
+ * - DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES: that denylist on its own. NOT
31
+ * used to build any runtime regex — the code points are already subtracted
32
+ * from the allowlist above, so the policy enforces them with no extra probe.
33
+ * It is emitted so tests can enumerate what is denied and assert each entry
34
+ * is actually rejected.
35
+ * - DISPLAY_NAME_NAME_SEPARATORS_RANGES: the four punctuation code points that
36
+ * JOIN two letters inside one real name
37
+ * (· ־ ་ ・). All are General_Category P,
38
+ * so the letters intersection strips them by default; they are re-admitted
39
+ * ONLY between two letters. Unlike the denylist, this class DOES build a
40
+ * runtime regex — the conditional half lives in
41
+ * `DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE`, which strips any of them that
42
+ * is not letter-flanked on both sides, so decorative use
43
+ * (a trailing `Roberto ·`) keeps being trimmed.
21
44
  * - DISPLAY_NAME_COMBINING_MARKS_RANGES: General_Category M (combining marks).
22
45
  * - DISPLAY_NAME_SPACE_SEPARATORS_RANGES: General_Category Zs (space
23
46
  * separators).
@@ -28,7 +51,7 @@
28
51
  */
29
52
 
30
53
  export const DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES =
31
- 'A-Za-z\\xAA\\xB7\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02B8\\u02BC\\u02C7\\u02C9-\\u02CB\\u02CD\\u02D7\\u02D9\\u02E0-\\u02E4\\u02EA\\u02EB\\u0300-\\u030E\\u0310\\u0311\\u0313\\u0323-\\u0325\\u032D\\u032E\\u0330\\u0331\\u0342\\u0345\\u0358\\u035E\\u0363-\\u0377\\u037A-\\u037D\\u037F\\u0384\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03E1\\u03F0-\\u052F\\u0531-\\u0556\\u0559-\\u058A\\u058D-\\u058F\\u0591-\\u05C7\\u05D0-\\u05EA\\u05EF-\\u05F4\\u0600-\\u0604\\u0606-\\u06DC\\u06DE-\\u06FF\\u0750-\\u07B1\\u0870-\\u0891\\u0897-\\u08E1\\u08E3-\\u0952\\u0955-\\u0983\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BC-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CE\\u09D7\\u09DC\\u09DD\\u09DF-\\u09E3\\u09E6-\\u09FE\\u0A01-\\u0A03\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A59-\\u0A5C\\u0A5E\\u0A66-\\u0A76\\u0A81-\\u0A83\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABC-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AD0\\u0AE0-\\u0AE3\\u0AE6-\\u0AF1\\u0AF9-\\u0AFF\\u0B01-\\u0B03\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3C-\\u0B44\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B55-\\u0B57\\u0B5C\\u0B5D\\u0B5F-\\u0B63\\u0B66-\\u0B77\\u0B82\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD0\\u0BD7\\u0BE6-\\u0BFA\\u0C00-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3C-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C58-\\u0C5A\\u0C5C\\u0C5D\\u0C60-\\u0C63\\u0C66-\\u0C6F\\u0C77-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBC-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0CDC-\\u0CDE\\u0CE0-\\u0CE3\\u0CE6-\\u0CEF\\u0CF1-\\u0CF3\\u0D00-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4F\\u0D54-\\u0D63\\u0D66-\\u0D7F\\u0D81-\\u0D83\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DE6-\\u0DEF\\u0DF2-\\u0DF4\\u0E01-\\u0E3A\\u0E40-\\u0E5B\\u0E81\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EC8-\\u0ECE\\u0ED0-\\u0ED9\\u0EDC-\\u0EDF\\u0F00-\\u0F47\\u0F49-\\u0F6C\\u0F71-\\u0F97\\u0F99-\\u0FBC\\u0FBE-\\u0FCC\\u0FCE-\\u0FD4\\u0FD9\\u0FDA\\u1000-\\u10C5\\u10C7\\u10CD\\u10D0-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u135D-\\u137C\\u1380-\\u1399\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1780-\\u17DD\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1800-\\u1819\\u1820-\\u1878\\u1880-\\u18AA\\u19E0-\\u19FF\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CD0-\\u1CF9\\u1D00-\\u1DC1\\u1DF8\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FC4\\u1FC6-\\u1FD3\\u1FD6-\\u1FDB\\u1FDD-\\u1FEF\\u1FF2-\\u1FF4\\u1FF6-\\u1FFE\\u202F\\u204F\\u205A\\u205D\\u2071\\u207F\\u2090-\\u209C\\u20F0\\u2126\\u212A\\u212B\\u2132\\u214E\\u2160-\\u2188\\u2C60-\\u2C7F\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2DE0-\\u2DFF\\u2E17\\u2E31\\u2E41\\u2E43\\u2E80-\\u2E99\\u2E9B-\\u2EF3\\u2F00-\\u2FD5\\u2FF0-\\u2FFF\\u3001-\\u3003\\u3005-\\u3011\\u3013-\\u301F\\u302A-\\u3035\\u3021-\\u302D\\u3037-\\u303F\\u3041-\\u3096\\u3099-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u3190-\\u31E5\\u31EF-\\u321E\\u3220-\\u3247\\u3260-\\u327E\\u3280-\\u32B0\\u32C0-\\u32CB\\u32D0-\\u3370\\u337B-\\u337F\\u33E0-\\u33FE\\u3400-\\u4DBF\\u4E00-\\u9FFF\\uA640-\\uA69F\\uA700-\\uA707\\uA722-\\uA787\\uA78B-\\uA7DC\\uA7F1-\\uA7FF\\uA830-\\uA839\\uA8E0-\\uA8FF\\uA92E\\uA960-\\uA97C\\uA9E0-\\uA9FE\\uAA60-\\uAA7F\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABBF\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFDCF\\uFDF0-\\uFDFF\\uFE2E\\uFE2F\\uFE45\\uFE46\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF61-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10140}-\\u{1018E}\\u{101A0}\\u{102E0}-\\u{102FB}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10E60}-\\u{10E7E}\\u{10EC2}-\\u{10EC7}\\u{10ED0}-\\u{10ED8}\\u{10EFA}-\\u{10EFF}\\u{111E1}-\\u{111F4}\\u{11301}\\u{11303}\\u{1133B}\\u{1133C}\\u{11660}-\\u{1166C}\\u{116D0}-\\u{116E3}\\u{11B00}-\\u{11B09}\\u{11FC0}-\\u{11FF1}\\u{11FFF}\\u{16FE2}\\u{16FE3}\\u{16FF0}-\\u{16FF6}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1D200}-\\u{1D245}\\u{1D360}-\\u{1D371}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E08F}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{1EEF0}\\u{1EEF1}\\u{1F200}\\u{1F250}\\u{1F251}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}';
54
+ 'A-Za-z\\xAA\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02B8\\u02BC\\u02C7\\u02C9-\\u02CB\\u02CD\\u02E0-\\u02E4\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03E1\\u03F0-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0750-\\u07A5\\u07B1\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C\\u0C5D\\u0C60\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2126\\u212A\\u212B\\u2132\\u214E\\u2183\\u2184\\u2C60-\\u2C7F\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\u534C\\u534E\\u534F\\u5351-\\u9FFF\\uA640-\\uA66E\\uA67F-\\uA69D\\uA722-\\uA787\\uA78B-\\uA7DC\\uA7F1-\\uA7FF\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\uA8FE\\uA960-\\uA97C\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA60-\\uAA76\\uAA7A\\uAA7E\\uAA7F\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABBF\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10EC2}-\\u{10EC7}\\u{16FE3}\\u{16FF2}\\u{16FF3}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}';
32
55
 
33
56
  export const DISPLAY_NAME_COMBINING_MARKS_RANGES =
34
57
  '\\u0300-\\u036F\\u0483-\\u0489\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u07FD\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0859-\\u085B\\u0897-\\u089F\\u08CA-\\u08E1\\u08E3-\\u0903\\u093A-\\u093C\\u093E-\\u094F\\u0951-\\u0957\\u0962\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2\\u09E3\\u09FE\\u0A01-\\u0A03\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AE2\\u0AE3\\u0AFA-\\u0AFF\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B44\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B55-\\u0B57\\u0B62\\u0B63\\u0B82\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C00-\\u0C04\\u0C3C\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0C81-\\u0C83\\u0CBC\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0CE2\\u0CE3\\u0CF3\\u0D00-\\u0D03\\u0D3B\\u0D3C\\u0D3E-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0D62\\u0D63\\u0D81-\\u0D83\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EBC\\u0EC8-\\u0ECE\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E\\u0F3F\\u0F71-\\u0F84\\u0F86\\u0F87\\u0F8D-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102B-\\u103E\\u1056-\\u1059\\u105E-\\u1060\\u1062-\\u1064\\u1067-\\u106D\\u1071-\\u1074\\u1082-\\u108D\\u108F\\u109A-\\u109D\\u135D-\\u135F\\u1712-\\u1715\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B4-\\u17D3\\u17DD\\u180B-\\u180D\\u180F\\u1885\\u1886\\u18A9\\u1920-\\u192B\\u1930-\\u193B\\u1A17-\\u1A1B\\u1A55-\\u1A5E\\u1A60-\\u1A7C\\u1A7F\\u1AB0-\\u1ADD\\u1AE0-\\u1AEB\\u1B00-\\u1B04\\u1B34-\\u1B44\\u1B6B-\\u1B73\\u1B80-\\u1B82\\u1BA1-\\u1BAD\\u1BE6-\\u1BF3\\u1C24-\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE8\\u1CED\\u1CF4\\u1CF7-\\u1CF9\\u1DC0-\\u1DFF\\u20D0-\\u20F0\\u2CEF-\\u2CF1\\u2D7F\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F-\\uA672\\uA674-\\uA67D\\uA69E\\uA69F\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA823-\\uA827\\uA82C\\uA880\\uA881\\uA8B4-\\uA8C5\\uA8E0-\\uA8F1\\uA8FF\\uA926-\\uA92D\\uA947-\\uA953\\uA980-\\uA983\\uA9B3-\\uA9C0\\uA9E5\\uAA29-\\uAA36\\uAA43\\uAA4C\\uAA4D\\uAA7B-\\uAA7D\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uAAEB-\\uAAEF\\uAAF5\\uAAF6\\uABE3-\\uABEA\\uABEC\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE2F\\u{101FD}\\u{102E0}\\u{10376}-\\u{1037A}\\u{10A01}-\\u{10A03}\\u{10A05}\\u{10A06}\\u{10A0C}-\\u{10A0F}\\u{10A38}-\\u{10A3A}\\u{10A3F}\\u{10AE5}\\u{10AE6}\\u{10D24}-\\u{10D27}\\u{10D69}-\\u{10D6D}\\u{10EAB}\\u{10EAC}\\u{10EFA}-\\u{10EFF}\\u{10F46}-\\u{10F50}\\u{10F82}-\\u{10F85}\\u{11000}-\\u{11002}\\u{11038}-\\u{11046}\\u{11070}\\u{11073}\\u{11074}\\u{1107F}-\\u{11082}\\u{110B0}-\\u{110BA}\\u{110C2}\\u{11100}-\\u{11102}\\u{11127}-\\u{11134}\\u{11145}\\u{11146}\\u{11173}\\u{11180}-\\u{11182}\\u{111B3}-\\u{111C0}\\u{111C9}-\\u{111CC}\\u{111CE}\\u{111CF}\\u{1122C}-\\u{11237}\\u{1123E}\\u{11241}\\u{112DF}-\\u{112EA}\\u{11300}-\\u{11303}\\u{1133B}\\u{1133C}\\u{1133E}-\\u{11344}\\u{11347}\\u{11348}\\u{1134B}-\\u{1134D}\\u{11357}\\u{11362}\\u{11363}\\u{11366}-\\u{1136C}\\u{11370}-\\u{11374}\\u{113B8}-\\u{113C0}\\u{113C2}\\u{113C5}\\u{113C7}-\\u{113CA}\\u{113CC}-\\u{113D0}\\u{113D2}\\u{113E1}\\u{113E2}\\u{11435}-\\u{11446}\\u{1145E}\\u{114B0}-\\u{114C3}\\u{115AF}-\\u{115B5}\\u{115B8}-\\u{115C0}\\u{115DC}\\u{115DD}\\u{11630}-\\u{11640}\\u{116AB}-\\u{116B7}\\u{1171D}-\\u{1172B}\\u{1182C}-\\u{1183A}\\u{11930}-\\u{11935}\\u{11937}\\u{11938}\\u{1193B}-\\u{1193E}\\u{11940}\\u{11942}\\u{11943}\\u{119D1}-\\u{119D7}\\u{119DA}-\\u{119E0}\\u{119E4}\\u{11A01}-\\u{11A0A}\\u{11A33}-\\u{11A39}\\u{11A3B}-\\u{11A3E}\\u{11A47}\\u{11A51}-\\u{11A5B}\\u{11A8A}-\\u{11A99}\\u{11B60}-\\u{11B67}\\u{11C2F}-\\u{11C36}\\u{11C38}-\\u{11C3F}\\u{11C92}-\\u{11CA7}\\u{11CA9}-\\u{11CB6}\\u{11D31}-\\u{11D36}\\u{11D3A}\\u{11D3C}\\u{11D3D}\\u{11D3F}-\\u{11D45}\\u{11D47}\\u{11D8A}-\\u{11D8E}\\u{11D90}\\u{11D91}\\u{11D93}-\\u{11D97}\\u{11EF3}-\\u{11EF6}\\u{11F00}\\u{11F01}\\u{11F03}\\u{11F34}-\\u{11F3A}\\u{11F3E}-\\u{11F42}\\u{11F5A}\\u{13440}\\u{13447}-\\u{13455}\\u{1611E}-\\u{1612F}\\u{16AF0}-\\u{16AF4}\\u{16B30}-\\u{16B36}\\u{16F4F}\\u{16F51}-\\u{16F87}\\u{16F8F}-\\u{16F92}\\u{16FE4}\\u{16FF0}\\u{16FF1}\\u{1BC9D}\\u{1BC9E}\\u{1CF00}-\\u{1CF2D}\\u{1CF30}-\\u{1CF46}\\u{1D165}-\\u{1D169}\\u{1D16D}-\\u{1D172}\\u{1D17B}-\\u{1D182}\\u{1D185}-\\u{1D18B}\\u{1D1AA}-\\u{1D1AD}\\u{1D242}-\\u{1D244}\\u{1DA00}-\\u{1DA36}\\u{1DA3B}-\\u{1DA6C}\\u{1DA75}\\u{1DA84}\\u{1DA9B}-\\u{1DA9F}\\u{1DAA1}-\\u{1DAAF}\\u{1E000}-\\u{1E006}\\u{1E008}-\\u{1E018}\\u{1E01B}-\\u{1E021}\\u{1E023}\\u{1E024}\\u{1E026}-\\u{1E02A}\\u{1E08F}\\u{1E130}-\\u{1E136}\\u{1E2AE}\\u{1E2EC}-\\u{1E2EF}\\u{1E4EC}-\\u{1E4EF}\\u{1E5EE}\\u{1E5EF}\\u{1E6E3}\\u{1E6E6}\\u{1E6EE}\\u{1E6EF}\\u{1E6F5}\\u{1E8D0}-\\u{1E8D6}\\u{1E944}-\\u{1E94A}\\u{E0100}-\\u{E01EF}';
@@ -38,3 +61,9 @@ export const DISPLAY_NAME_SPACE_SEPARATORS_RANGES =
38
61
 
39
62
  export const DISPLAY_NAME_LETTERS_RANGES =
40
63
  'A-Za-z\\xAA\\xB5\\xBA\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C\\u0C5D\\u0C60\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005\\u3006\\u3031-\\u3035\\u303B\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}\\u{16FE1}\\u{16FE3}\\u{16FF2}\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}\\u{1D49F}\\u{1D4A2}\\u{1D4A5}\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}';
64
+
65
+ export const DISPLAY_NAME_DENIED_SYMBOL_LETTERS_RANGES =
66
+ '\\u{534D}\\u{5350}';
67
+
68
+ export const DISPLAY_NAME_NAME_SEPARATORS_RANGES =
69
+ '\\u{B7}\\u{5BE}\\u{F0B}\\u{30FB}';
@@ -7,8 +7,15 @@ import {
7
7
  DISPLAY_NAME_COMBINING_MARKS_RANGES,
8
8
  DISPLAY_NAME_SPACE_SEPARATORS_RANGES,
9
9
  DISPLAY_NAME_LETTERS_RANGES,
10
+ DISPLAY_NAME_NAME_SEPARATORS_RANGES,
10
11
  } from './displayNamePolicyRanges.generated';
11
12
 
13
+ /**
14
+ * Maximum stored length of a display name, in code units after cleaning.
15
+ * Shared by the API write path and client input surfaces.
16
+ */
17
+ export const MAX_DISPLAY_NAME_LENGTH = 80;
18
+
12
19
  /**
13
20
  * Email validation regex
14
21
  */
@@ -61,7 +68,16 @@ export function isValidPassword(password: string): boolean {
61
68
  * ideographic space, …) — but NOT control whitespace such as tab, newline,
62
69
  * or carriage return, which would break layout or enable multi-line
63
70
  * spoofing,
64
- * - the straight apostrophe (`'`, e.g. "O'Brien").
71
+ * - the straight apostrophe (`'`, e.g. "O'Brien"),
72
+ * - four name SEPARATORS — `·` U+00B7, `־` U+05BE, `་` U+0F0B, `・` U+30FB —
73
+ * but ONLY directly between two letters (`Codeur·euses`, `お坐・エガード`,
74
+ * `אברמסקי־קרוננברג`, `འོད་ཟེར`). Every one is General_Category P, so the
75
+ * letters intersection strips them by default; they are re-admitted because
76
+ * stripping them SPLITS a real name in two. The flanking condition is not a
77
+ * refinement but the whole point: the same characters are also used as
78
+ * ornament (a trailing `Roberto ·`), and unconditional admission would let
79
+ * that through. See {@link DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE}. The
80
+ * ASCII hyphen is NOT among them — `Jean-Luc` stays rejected.
65
81
  *
66
82
  * Everything else is rejected: emoji (🐧), symbols (⁂ ⏚), `:emoji:` shortcodes,
67
83
  * digits, hyphens, dots, control whitespace (tab/newline/CR), letters from
@@ -71,7 +87,15 @@ export function isValidPassword(password: string): boolean {
71
87
  *
72
88
  * The allowlist is expressed with Unicode Script_Extensions (scx=…) so a letter
73
89
  * shared by several scripts (e.g. a Han ideograph used in both Chinese and
74
- * Japanese) still matches. It is the set of scripts Unicode UTS #39 marks
90
+ * Japanese) still matches, INTERSECTED with General_Category L so only the
91
+ * scripts' LETTERS are admitted. That intersection is load-bearing, not a
92
+ * refinement: `scx=X` also carries script X's own digits, punctuation and
93
+ * symbols, so the un-intersected allowlist admitted 1831 non-letter code points
94
+ * — script digits (`٠١٢`, `०१२`), 1082 symbols (`֍ ۞ ৳`), 180 punctuation marks
95
+ * (`։ ، ؛ ।`), and 9 INVISIBLE format/control characters, among them U+061C
96
+ * ARABIC LETTER MARK (a bidi control that can visually reorder a name) and
97
+ * U+180E MONGOLIAN VOWEL SEPARATOR. Without it the rejections listed above held
98
+ * for ASCII input only. It is the set of scripts Unicode UTS #39 marks
75
99
  * "Recommended" for general interchange / identifiers, plus Cherokee and
76
100
  * Mongolian (both in real modern name use). "Common" script is deliberately
77
101
  * EXCLUDED — that is where ASCII digits and general punctuation live, and this
@@ -79,6 +103,29 @@ export function isValidPassword(password: string): boolean {
79
103
  * name needs are added back explicitly. Limited-use / excluded / historic
80
104
  * scripts (Batak, Runic, Deseret, Adlam, …) are simply absent.
81
105
  *
106
+ * CODE-POINT DENYLIST — a character policy classifies FORM, never MEANING, so a
107
+ * code point can be a perfectly ordinary letter to Unicode and a hate symbol to
108
+ * a reader. `卐` U+5350 and `卍` U+534D are the case in point: both are CJK
109
+ * Unified Ideographs (General_Category Lo, Script_Extensions Han), i.e. the same
110
+ * kind of thing as `山` in `山田太郎`, so NO script-level or category-level rule
111
+ * can separate them — every rule that would exclude these two also excludes Han
112
+ * itself, rejecting every real Chinese, Japanese and Korean name. They are
113
+ * therefore enumerated and SUBTRACTED from the allowlist at generation time (see
114
+ * `SYMBOL_LETTER_DENYLIST` in `scripts/generateDisplayNamePolicyRanges.mjs`).
115
+ * Because the subtraction happens inside the one emitted allowlist, every
116
+ * consumer enforces it with no extra probe and none can forget it; the generator
117
+ * additionally REFUSES an entry that an existing lever already excludes (e.g.
118
+ * the Tibetan svasti signs U+0FD5–U+0FD8, General_Category So, already dropped
119
+ * by the intersection above), so the list cannot silently accumulate dead
120
+ * weight.
121
+ *
122
+ * REMAINING LIMIT — that closes the "letter that reads as a symbol" gap, not the
123
+ * other one: slurs and abusive phrasing spelled in ordinary allowlisted letters
124
+ * pass by construction, because they are built from exactly the characters every
125
+ * real name needs. No character-level rule — allowlist, intersection, or
126
+ * denylist — can reject them; that needs a word-level moderation layer, which is
127
+ * deliberately not attempted here.
128
+ *
82
129
  * HERMES / RANGES: the class bodies below are built from explicit Unicode
83
130
  * code-point RANGES ({@link DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES} et al. from
84
131
  * `./displayNamePolicyRanges.generated`), NOT from `scx`/General_Category
@@ -100,21 +147,28 @@ export function isValidPassword(password: string): boolean {
100
147
 
101
148
  /**
102
149
  * The curated allowlist of Unicode scripts permitted in a display name, as a
103
- * character-class body of explicit code-point ranges (the compressed union of
104
- * the 30 allowlisted Script_Extensions, generated on V8). Interpolated into the
105
- * negated class below.
150
+ * character-class body of explicit code-point ranges: the union of the 30
151
+ * allowlisted Script_Extensions, INTERSECTED with General_Category L (so letters
152
+ * only), MINUS the symbol-letter denylist — 120823 code points. Interpolated
153
+ * into the negated class below, which is why both the reject gate here and the
154
+ * `@oxyhq/api` strip path inherit the denylist without a second pattern.
106
155
  */
107
156
  export const DISPLAY_NAME_ALLOWED_SCRIPTS = DISPLAY_NAME_ALLOWED_SCRIPTS_RANGES;
108
157
 
109
158
  /**
110
159
  * Source of the disallowed-character pattern: the negation of the full allowed
111
160
  * set (allowlisted scripts + combining marks + space separators + the straight
112
- * apostrophe). Consumers compile this with the `u` flag (and `g` for a global
113
- * strip). The whitespace class is space separators only (General_Category Zs),
114
- * NOT `\s` — the latter would admit tab/newline/carriage return, which break
115
- * layout and enable multi-line spoofing.
161
+ * apostrophe + the name separators). Consumers compile this with the `u` flag
162
+ * (and `g` for a global strip). The whitespace class is space separators only
163
+ * (General_Category Zs), NOT `\s` — the latter would admit tab/newline/carriage
164
+ * return, which break layout and enable multi-line spoofing.
165
+ *
166
+ * The name separators are admitted here only as CHARACTERS. Their position rule
167
+ * is a separate pattern ({@link DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE}), for
168
+ * the same reason combining marks are: a negated character class cannot express
169
+ * "allowed only in this context". Both halves must be applied together.
116
170
  */
117
- export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}${DISPLAY_NAME_COMBINING_MARKS_RANGES}${DISPLAY_NAME_SPACE_SEPARATORS_RANGES}']`;
171
+ export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}${DISPLAY_NAME_COMBINING_MARKS_RANGES}${DISPLAY_NAME_SPACE_SEPARATORS_RANGES}${DISPLAY_NAME_NAME_SEPARATORS_RANGES}']`;
118
172
 
119
173
  /**
120
174
  * Source of the orphaned combining-mark pattern: a run of combining marks NOT
@@ -129,29 +183,68 @@ export const DISPLAY_NAME_DISALLOWED_SOURCE = `[^${DISPLAY_NAME_ALLOWED_SCRIPTS}
129
183
  */
130
184
  export const DISPLAY_NAME_ORPHANED_MARK_SOURCE = `(?<![${DISPLAY_NAME_LETTERS_RANGES}${DISPLAY_NAME_COMBINING_MARKS_RANGES}])[${DISPLAY_NAME_COMBINING_MARKS_RANGES}]+`;
131
185
 
186
+ /**
187
+ * Source of the unflanked name-separator pattern — the positional half of the
188
+ * name-separator rule, and the exact counterpart of
189
+ * {@link DISPLAY_NAME_ORPHANED_MARK_SOURCE}: a combining mark is allowed only
190
+ * when it rides a base letter, and a name separator is allowed only when it JOINS
191
+ * two letters. Matches a separator that is missing a letter on either side, so
192
+ * consumers can reject it (non-global probe) or strip it (`g` flag).
193
+ *
194
+ * Two alternatives, one per side, because JS has no "not surrounded by" atom:
195
+ * - `(?<![letters][marks])[sep]` — nothing letter-like immediately BEFORE.
196
+ * - `[sep](?![letters])` — no letter immediately AFTER.
197
+ *
198
+ * The left-hand class deliberately includes combining marks as well as letters:
199
+ * a base letter can carry an accent (`Renée·euses`, or a decomposed `e`+◌́), and
200
+ * that mark sits between the letter and the separator. Treating a mark as
201
+ * letter-like is what stops an accent from defeating the flanking test — the
202
+ * same reason the orphaned-mark lookbehind uses the identical pair of classes.
203
+ * The right-hand class is letters only: a mark AFTER a separator has no base and
204
+ * is removed by the orphaned-mark rule, so the separator is genuinely unflanked.
205
+ *
206
+ * Both lookarounds are single-character (no variable-length lookbehind), and the
207
+ * classes are explicit code-point ranges, so the compiled regex is within what
208
+ * Hermes accepts.
209
+ *
210
+ * `Codeur·euses` and `お坐・エガード` match NOTHING here and survive. A leading
211
+ * `·Roberto`, a trailing `Roberto ·`, and a doubled `a··b` each match and are
212
+ * stripped — which is what keeps a decorative middle dot from riding in on the
213
+ * back of the orthographic one.
214
+ */
215
+ export const DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE = `(?<![${DISPLAY_NAME_LETTERS_RANGES}${DISPLAY_NAME_COMBINING_MARKS_RANGES}])[${DISPLAY_NAME_NAME_SEPARATORS_RANGES}]|[${DISPLAY_NAME_NAME_SEPARATORS_RANGES}](?![${DISPLAY_NAME_LETTERS_RANGES}])`;
216
+
132
217
  /** Non-global probe for the presence of a disallowed character. */
133
218
  const DISALLOWED_PROBE = new RegExp(DISPLAY_NAME_DISALLOWED_SOURCE, 'u');
134
219
 
135
220
  /** Non-global probe for the presence of an orphaned combining mark. */
136
221
  const ORPHANED_MARK_PROBE = new RegExp(DISPLAY_NAME_ORPHANED_MARK_SOURCE, 'u');
137
222
 
223
+ /** Non-global probe for the presence of an unflanked name separator. */
224
+ const UNFLANKED_SEPARATOR_PROBE = new RegExp(DISPLAY_NAME_UNFLANKED_SEPARATOR_SOURCE, 'u');
225
+
138
226
  /**
139
227
  * Whether `raw` already satisfies the display-name policy, i.e. it contains no
140
- * disallowed characters AND no orphaned combining marks. Used to REJECT native
141
- * (signup / profile edit) names with a 400 rather than silently stripping them,
142
- * and to validate inline in the client editor.
228
+ * disallowed characters, no orphaned combining marks, and no unflanked name
229
+ * separator. Used to REJECT native (signup / profile edit) names with a 400
230
+ * rather than silently stripping them, and to validate inline in the client
231
+ * editor.
143
232
  *
144
- * The orphaned-mark probe runs on the NFC-normalized form so a legitimate
145
- * decomposed accent (`e`+◌́) which normalization recomposes into `é` is NOT
146
- * rejected, while a lone, base-less mark (e.g. `"༘"`) IS.
233
+ * The two positional probes run on the NFC-normalized form, the character probe
234
+ * on the raw input. Normalization matters for both: a legitimate decomposed
235
+ * accent (`e`+◌́) recomposes into `é`, so it is NOT rejected as an orphaned mark,
236
+ * and a separator after that accent sees a base letter rather than a mark.
147
237
  *
148
- * The function only checks the character set; an empty or whitespace-only string
149
- * is considered valid (`true`). Call sites that require a non-empty name enforce
150
- * that separately.
238
+ * The function only checks the character set and separator placement; an empty
239
+ * or whitespace-only string is considered valid (`true`). Call sites that require
240
+ * a non-empty name enforce that separately.
151
241
  */
152
242
  export function isValidDisplayName(raw: string): boolean {
243
+ const normalized = raw.normalize('NFC');
153
244
  return (
154
- !DISALLOWED_PROBE.test(raw) && !ORPHANED_MARK_PROBE.test(raw.normalize('NFC'))
245
+ !DISALLOWED_PROBE.test(raw) &&
246
+ !ORPHANED_MARK_PROBE.test(normalized) &&
247
+ !UNFLANKED_SEPARATOR_PROBE.test(normalized)
155
248
  );
156
249
  }
157
250