16 липня 2023 р.

Юнікод: прапорець "u" та клас \p{...}

У JavaScript для рядків використовується кодування Юнікод. Більшість символів кодуються 2 байтами, що дозволяє представити максимум 65536 символів.

Цей діапазон недостатньо великий для кодування всіх можливих символів, тому деякі рідкісні символи кодуються 4 байтами, наприклад 𝒳 (математичний X) або 😄 (смайл), деякі ієрогліфи тощо.

Ось значення Юнікодів для деяких символів:

Символ Юнікод Кількість байтів у Юнікоді
a 0x0061 2
0x2248 2
𝒳 0x1d4b3 4
𝒴 0x1d4b4 4
😄 0x1f604 4

Таким чином, такі символи, як a і , займають 2 байти, а коди для 𝒳, 𝒴 і 😄 довші, в них – 4 байти.

Коли створювалась мова JavaScript, кодування Юнікод було простіше: 4-байтових символів не існувало. Тому досі деякі функції мови все ще обробляють їх неправильно.

Наприклад, властивість length вважає, що тут два символи:

alert('😄'.length); // 2
alert('𝒳'.length); // 2

…Але ми бачимо, що лише один, правда ж? Річ у тому, що властивість length трактує 4 байти, як два символи по 2 байти. Це не правильно, адже їх необхідно розглядати тільки разом (так звана “сурогатна пара”, детальніше у розділі Рядки).

За замовчуванням регулярні вирази також розглядають 4-байтові “довгі символи” як пару 2-байтових. Як і у випадку з рядками, це може призвести до дивних результатів. Ми побачимо це трохи пізніше, у розділі Набори та діапазони [...].

На відміну від рядків, регулярні вирази мають прапорець u, який виправляє такі проблеми. З таким прапорцем регулярний вираз правильно обробляє 4-байтові символи. А також стає доступним пошук з використанням властивостей Юнікоду, який ми розглянемо далі.

Властивості Юнікоду \p{…}

Кожен символ в кодуванні Юнікод має багато властивостей. Вони описують, до якої “категорії” належить символ та містять різну інформацію про нього.

Наприклад, якщо символ містить властивість Letter, це означає, що він належить до алфавіту (будь-якої мови). А властивість Number означає, що це цифра: може бути арабська, китайська тощо.

Ми можемо шукати символи за властивістю, записаною як \p{…}. Щоб використовувати \p{…}, регулярний вираз повинен мати прапорець u.

Наприклад, \p{Letter} позначає літеру будь-якою мовою. Ми також можемо використовувати коротший запис \p{L}, оскільки L є псевдонімом Letter. Майже для кожної властивості існують варіанти коротшого запису.

У наведеному нижче прикладі буде знайдено три види літер: англійська, грузинська та корейська.

let str = "A ბ ㄱ";

alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ
alert( str.match(/\p{L}/g) ); // null (немає збігів, \p не працює без прапорця "u")

Ось основні категорії властивостей та їх підкатегорії:

  • Літери L:
    • мала літера Ll
    • модифікатор Lm,
    • регістр Lt,
    • велика літера Lu,
    • інші Lo.
  • Числа N:
    • десяткові цифри Nd,
    • числа позначені за допомогою літер Nl,
    • інші No.
  • Знаки пунктуації P:
    • з’єднувачі Pc,
    • тире Pd,
    • відкриваючі лапки Pi,
    • закриваючі лапки Pf,
    • відкриваючі дужки Ps,
    • закриваючі дужки Pe,
    • інші Po.
  • Знак M (акценти):
    • двокрапка Mc,
    • вкладення Me,
    • апострофи Mn.
  • Символи S:
    • валюти Sc,
    • модифікатори Sk,
    • математичні Sm,
    • інші So.
  • Розділювачі Z:
    • лінія Zl,
    • параграф Zp,
    • пробіл Zs.
  • Інші C:
    • контроль Cc,
    • форматування Cf,   – не призначенні Cn,
    • для приватного користування Co,
    • сурогат Cs.

Наприклад, якщо нам потрібно знайти маленькі літери, ми можемо написати \p{Ll}, знаки пунктуації \p{P} і так далі.

Існують також інші похідні категорії, наприклад:

  • Alphabetic (Alpha), містить в собі літери L, а також числа позначені за допомогою літер Nl (наприклад, Ⅻ – символ для римської цифри 12), і деякі інші символи Other_Alphabetic (OAlpha).
  • Hex_Digit містить шістнадцяткові числа: 0-9, a-f.
  • …І так далі.

Юнікод підтримує велику кількість властивостей, і їхній повний перелік вимагав би дуже багато місця, тому ось посилання:

Приклад: шістнадцяткові числа

Наприклад, пошукаймо шістнадцяткові числа, записані в форматі xFF, де замість F може бути будь-яка шістнадцяткова цифра (0…9 or A…F).

Шістнадцяткову цифру можна позначити як \p{Hex_Digit}:

let regexp = /x\p{Hex_Digit}\p{Hex_Digit}/u;

alert("число: xAF".match(regexp)); // xAF

Приклад: китайські ієрогліфи

Пошукаймо китайські ієрогліфи.

Нам допоможе властивість Юнікоду – Script (система письма), яка може мати значення: Cyrillic(Кирилиця), Greek (Грецька), Arabic (Арабська), Han (Китайська) та інші, тут повний перелік.

Для пошуку символів у певній системі письма ми повинні використати Script=<value>, наприклад для літер кирилиці: \p{sc=Cyrillic}, для китайських ієрогліфів: \p{sc=Han}, і так далі.

let regexp = /\p{sc=Han}/gu; // поверне китайські ієрогліфи

let str = `Hello Привіт 你好 123_456`;

alert( str.match(regexp) ); // 你,好

Приклад: валюти

Символи, які позначають валюту, такі як $, , ¥, мають властивість \p{Currency_Symbol}, короткий псевдонім: \p{Sc}.

Використаємо його для пошуку цін у форматі «валюта, за якою йде цифра»:

let regexp = /\p{Sc}\d/gu;

let str = `Ціни: $2, €1, ¥9`;

alert( str.match(regexp) ); // $2,€1,¥9

Пізніше у розділі Квантифікатори +, *, ? та {n} ми побачимо, як шукати числа, які містять багато цифр.

Підсумки

Прапорець u вмикає підтримку Юнікоду у регулярних виразах.

Це означає дві речі:

  1. Символи розміром 4 байти обробляються правильно: як один символ, а не як два 2-байтові символи.
  2. Працює пошук за допомогою властивостей Юнікоду: \p{…}.

За допомогою властивостей Юнікоду ми можемо шукати слова певними мовами, спеціальні символи (лапки, валюти) тощо.

Навчальна карта