В 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{…}. Для таких регулярных выражений обязательно использовать флаг 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.- И так далее.
Юникод поддерживает много различных свойств, их полное перечисление потребовало бы очень много места, поэтому вот ссылки:
- По символу посмотреть его свойства: https://unicode.org/cldr/utility/character.jsp.
- По свойству посмотреть символы с ним: https://unicode.org/cldr/utility/list-unicodeset.jsp.
- Короткие псевдонимы для свойств: https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt.
- Полная база Юникод-символов в текстовом формате вместе со всеми свойствами, находится здесь: https://www.unicode.org/Public/UCD/latest/ucd/.
Пример: шестнадцатеричные числа
Например, давайте поищем шестнадцатеричные числа, записанные в формате xFF, где вместо F может быть любая шестнадцатеричная цифра (0…9 или 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=<значение>, например для поиска кириллических букв: \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 включает поддержку Юникода в регулярных выражениях.
Конкретно, это означает, что:
- Символы из 4 байт воспринимаются как единое целое, а не как два символа по 2 байта.
- Работает поиск по Юникодным свойствам
\p{…}.
С помощью Юникодных свойств мы можем искать слова на нужных языках, специальные символы (кавычки, обозначения валюты) и так далее.
Комментарии
<code>, для нескольких строк кода — тег<pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)