[Перевод] ECMAScript 6. Регулярные выражения с поддержкой Unicode

В ECMAScript 6 представлены два новых флага для регулярных выражений:


  1. y включает режим «липкого» сопоставления.
  2. u включает различные связанные с Unicode опции.


В данной статье объясняется влияние флага u. Эта статья будет Вам полезна, если Вы знакомы с Unicode-проблемами в Javascript.
560950c49f82435fa3e1076df84b5b6f.jpg

N.B.! Поскольку при попытке публикации статьи обнаружились проблемы с отображением некоторых используемых в статье Unicode-символов, часть кода была заменена изображениями со ссылками на JSFiddle.

Влияние на синтаксис


Установка флага u в регулярном выражении позволяет использовать escape-последовательности кодовых точек ES6 Unicode (\u{...}) в шаблоне.

0ff9a3422d174d278f89acf852344e5b.jpg

При отсутствии флага u такие вещи, как \u{1234}, технически могут по-прежнему возникать в шаблонах, но они не будут интерпретироваться как escape-последовательности кодовых точек Unicode. /\u{1234}/ эквивалентно записи /u{1234}/, которая соответствует 1234 последовательным символам u вместо символа, соответствующего escape-последовательности кодовых точек U+1234.

Движок Javascript делает так из соображений совместимости. Но с установленным флагом u и такие вещи как \a (где a не является escape-последовательностью) больше не будут эквивалентны a. Поэтому, даже если /\a/ обрабатывается как /a/, /\a/u выбрасывает ошибку, т.к. \a не является зарезервированной escape-последовательностью. Это позволяет расширить функционал флага u регулярных выражений в будущей версии ECMAScript. Например, /\p{Script=Greek}/u выбрасывает исключение для ES6, но может стать регулярным выражением, соответствующим всем символам греческого алфавита согласно базе данных Unicode, когда соответствующий синтаксис будет добавлен в спецификацию.

Влияние на оператор ».»


При отсутствии флага u, . соответствует любому символу BMP (базовая многоязыковая плоскость — Basic Multilingual Plane) за исключением разделителей строки. Когда установлен флаг ES6 u, . соответствует также астральным символам.

e18e4e822f294540812a36da32f6bd37.jpg

Влияние на квантификаторы


В регулярных выражениях Javascript доступны следующие квантификаторы (а также их вариации): *, +, ?, и {2}, {2,}, {2,4}. При отсутствии флага u, если квантификатор следует за астральным символом, он применяется только к низкому суррогату (low surrogate) этого символа.

ba0f0fa315744ab5a11d02ab9e731de5.jpg

С флагом ES6 u квантификаторы применяются к символам целиком, что справедливо даже для астральных символов.

1770fa1610794143a556f9c17902eaba.jpg

Влияние на символьные классы


При отсутствии флага u любой заданный символьный класс может соответствовать только символам BMP. Такие вещи, как [bcd] работают как мы того ожидаем:

const regex = /^[bcd]$/;
console.log(
	regex.test('a'), // false
	regex.test('b'), // true
	regex.test('c'), // true
	regex.test('d'), // true
	regex.test('e')  // false
);


Однако, когда в символьном классе используется астральный символ, движок Javascript обрабатывает его как два отдельных «символа»: по одному на каждую из его суррогатных половинок.

a03f3b24a0a54e28838010ddc54647aa.jpg

Флаг ES6 u позволяет использовать цельные астральные символы в символьных классах.

e320a52b407a4426a18a3cdd5af415fe.jpg

Следовательно, цельные астральные символы также могут использоваться в диапазонах символьных классов, и все будет работать как мы того ожидаем, пока установлен флаг u.

7aaae6ca7f4643a4b6be06496d65a57c.png

Флаг u также влияет на исключающие символьные классы. Например, /[^a]/ эквивалентно /[\0-\x60\x62-\uFFFF]/, что соответствует любому символу BMP, кроме a. Но с флагом u /[^a]/u соответствует гораздо большему набору всех символов Unicode, кроме a.

45c61e3eaf3c413bbfaec25454b08953.jpg

Влияние на escape-последовательности


Флаг u влияет на значение escape-последовательностей \D, \S, и \W. При отсутствии флага u, \D, \S, и \W соответствуют любым символам BMP, которые не соответствуют \d, \s и \w, соответственно.

ee25fc735870474b839125ee764f1f36.jpg

С флагом u, \D, \S, и \W также соответствуют астральным символам.

10972e08da9b4086bdae37814bc4cac8.png

Флаг u не обращается к их обратным аналогам \d, \s и \w. Было предложено сделать \d и \w (и \b) более Unicode-совместимыми, но это предложение было отклонено.

Влияние на флаг i


Когда установлены флаги i и u, все символы неявно приводятся к одному регистру с помощью простого преобразования, предоставляемого стандартом Unicode, непосредственно перед их сопоставлением.

const es5regex = /[a-z]/i;
const es6regex = /[a-z]/iu;
console.log(
	es5regex.test('s'),      es6regex.test('s'),      // true true
	es5regex.test('S'),      es6regex.test('S'),      // true true
	// Note: U+017F преобразуется в `S`.
	es5regex.test('\u017F'), es6regex.test('\u017F'), // false true
	// Note: U+212A преобразуется в `K`.
	es5regex.test('\u212A'), es6regex.test('\u212A')  // false true
);


Приведение к одному регистру (case-folding) применяется к символам в шаблоне регулярного выражения, а также к символам в сопоставляемой строке.

console.log(
	/\u212A/iu.test('K'), // true
	/\u212A/iu.test('k'), // true
	/\u017F/iu.test('S'), // true
	/\u017F/iu.test('s')  // true
);


Эта логика приведения к одному регистру применяется и к escape-последовательностям \w и \W, что также влияет на escape-последовательности \b и \B. /\w/iu соответствует [0-9A-Z_a-z], но также и U+017F, поскольку U+017F из сопоставляемой строки регулярного выражения преобразуется (canonicalizes) в S. То же самое касается U+212A и K. Таким образом, /\W/iu эквивалентно /[^0-9a-zA-Z_\u{017F}\u{212A}]/u.

console.log(
	/\w/iu.test('\u017F'), // true
	/\w/iu.test('\u212A'), // true
	/\W/iu.test('\u017F'), // false
	/\W/iu.test('\u212A'), // false
	/\W/iu.test('s'),      // false
	/\W/iu.test('S'),      // false
	/\W/iu.test('K'),      // false
	/\W/iu.test('k'),      // false
	/\b/iu.test('\u017F'), // true
	/\b/iu.test('\u212A'), // true
	/\b/iu.test('s'),      // true
	/\b/iu.test('S'),      // true
	/\B/iu.test('\u017F'), // false
	/\B/iu.test('\u212A'), // false
	/\B/iu.test('s'),      // false
	/\B/iu.test('S'),      // false
	/\B/iu.test('K'),      // false
	/\B/iu.test('k')       // false
);


Влияние на HTML-документы


Верьте или нет, но флаг u также влияет и на документы HTML.

Атрибут pattern для элементов input и textarea позволяет вам указать регулярное выражение для проверки ввода пользователя. Затем браузер обеспечивает вас стилями и скриптами для создания поведения, основанного на достоверности ввода.

f7430ec1bdfe4723a8e16b32f87b8346.jpg

Флаг u всегда включен для регулярных выражений, скомпилированных через HTML атрибут pattern. Вот демонстрационный пример.

Поддержка


На данный момент флаг ES6 u для регулярных выражений доступен в стабильных версиях всех основных браузеров, кроме Safari. Браузеры постепенно начинают использовать этот функционал для HTML атрибута pattern.

Browser (s) JavaScript engine u flag u flag for pattern attribute
Edge Chakra issue #1102227 + issue #517 + issue #1181 issue #7113940
Firefox Spidermonkey bug #1135377 + bug #1281739 bug #1227906
Chrome/Opera V8 V8 issue #2952 + issue #5080 issue #535441
WebKit JavaScriptCore bug #154842 + bug #151597 + bug #158505 bug #151598


Рекомендации для разработчиков


  • С этого момента используйте флаг u для каждого регулярного выражения, которое вы пишете.
  • …, но не добавляйте флаг u слепо в существующие регулярные выражения, поскольку это может неявным образом изменить их смысл.
  • Избегайте комбинирования флагов u и i. Лучше явно включать в регулярное выражение символы всех регистров, чем страдать от неявного программного приведения символов к одному регистру.
  • Используйте транспилер, чтобы убедиться, что ваш код работает везде, включая устаревшие окружения.

Преобразование (transpiling) Unicode ES6 регулярных выражений в ES5


Мной создан regexpu, транспилер, который преобразовывает регулярные выражения Unicode ES6 в эквивалентный код ES5, который работает уже сегодня. Это позволит вам играться с новым, развивающимся функционалом.

b9fb88ab1f76442db0d3cbefd47054e1.png

Полномасштабные транспилеры ES6/ES7, такие как Traceur и Babel, зависят от regexpu при транспиляции u. Дайте мне знать, если вам удастся это сломать.

© Habrahabr.ru