Клас символів: [...], [^...]

Клас символів дає збіг з будь-яким символом, присутнім або відсутнім в заданому наборі символів. Коли ввімкнена позначка v, такі класи також можуть використовуватися для пошуку рядків скінченної довжини.

Синтаксис

[]
[abc]
[A-Z]

[^]
[^abc]
[^A-Z]

// Лише в режимі `v`
[operand1&&operand2]
[operand1--operand2]
[\q{substring}]

Параметри

operand1, operand2

Може бути одним символом, іще одним класом символів, загорнутим у квадратні дужки, екрануванням класу символів, екрануванням класу символів Unicode або рядком з використанням запису \q.

substring

Рядок, буквально.

Опис

Клас символів задає список символів між квадратними дужками та дає збіг з будь-яким символом зі списку. Позначка v різко змінює те, як розбираються та тлумачаться класи символів. Наступні види запису доступні як у режимі v, так і в режимі без v:

  • Єдиний символ: дає збіг з самим цим символом.
  • Діапазон символів: дає збіг з будь-яким символом у діапазоні, що включає свої краї. Цей діапазон задається двома символами, розділеними дефісом (-). Перший символ повинен бути меншим за символьним значенням, ніж другий. Оскільки кодові точки Unicode зазвичай присвоюють алфавітам послідовно, то [a-z] задає всі малі латинські літери, а [α-ω] задає всі малі грецькі літери. У режимі без урахування Unicode регулярні вирази тлумачаться як послідовність символів Базового багатомовного плану. Таким чином, сурогатні пари в класах символів представляють два символи, а не один; дивіться деталі нижче.
  • Послідовності екранування: \b, \-, екранування класів символів, екранування класів символів Unicode та інші екранування символів.

Ці записи можуть зустрічатися будь-яку кількість разів, і набори символів, котрі вони представляють, об'єднуються. Наприклад, /[a-zA-Z0-9]/ дає збіг з будь-якою літерою або цифрою.

Префікс ^ у класі символів створює доповняльний клас. Наприклад, [^abc] дає збіг з будь-яким символом, окрім a, b і c. Символ ^ є буквальним символом, коли зустрічається в середині класу символів – наприклад, [a^b] дає збіг з символами a, ^ і b.

Лексична граматика виконує дуже грубий розбір літералів регулярних виразів, тому що вона не завершує літерал регулярного виразу на символі /, котрий зустрічається всередині класу символів. Це означає, що /[/]/ є допустимим без необхідності екранування /.

Краї діапазону символів не повинні задавати більш ніж один символ, що відбувається, якщо використати екранування класу символів. Наприклад:

/[\s-9]/u; // SyntaxError: Invalid regular expression: Invalid character class

В режимі без урахування Unicode діапазони символів, у яких одна з меж є класом символів, змушують - стати буквальним символом. Це нерекомендований запис, необхідний для вебсумісності, і на нього не слід покладатися.

/[\s-9]/.test("-"); // true

В режимі без урахування Unicode регулярні вирази тлумачаться як послідовності символів Базового багатомовного плану. Таким чином, сурогатні пари в класах символів представляють два символи замість одного.

/[😄]/.test("\ud83d"); // true
/[😄]/u.test("\ud83d"); // false

/[😄-😛]/.test("😑"); // SyntaxError: Invalid regular expression: /[😄-😛]/: Вихід за межі допустимих значень в класі символів
/[😄-😛]/u.test("😑"); // true

Навіть якщо патерн ігнорує регістр, то регістр двох кінців діапазону є важливим при з'ясуванні того, які символи належать до цього діапазону. Наприклад, патерн /[E-F]/i дає збіг лише з E, F, e та f, а патерн /[E-f]/i дає збіг з усіма великими та малими літерами ASCII (тому, що простягається від E до Z та від a до f), а також [, \, ], ^, _ та `.

Клас символів у режимі без v

Класи символів у режимі без v тлумачать більшість символів буквально та мають менше обмежень щодо символів, котрі можуть вміщати. Наприклад, . - це буквальний символ крапки, а не джокер. Єдині символи, що не можуть зустрічатися буквально, – \, ] та -.

  • У класах символів підтримується більшість екранувальних послідовностей, окрім \b, \B та зворотних посилань. Послідовність \b позначає символ реверсу, а не межу слова, а два інших призводять до синтаксичних помилок. Щоб використовувати \ буквально, цей символ слід екранувати у вигляді \\.
  • Символ ] вказує на кінець класу символів. Щоб використовувати його буквально, його слід екранувати у вигляді \].
  • Символ дефіса (-), коли вжитий між двома символами, позначає діапазон. Коли він зустрічається на початку або в кінці класу символів, то є буквальним символом. Він також є буквальним символом, коли використовується як межа діапазону. Наприклад, [a-] збігається з символами a та -, [!--] збігається з символами від ! до -, а [--9] збігається з символами від - до 9. Також можна екранувати його як \-, якщо є потреба використовувати його буквально у будь-якому місці.

Клас символів у режимі з v

Базова ідея класів символів у режимі v залишається тією ж: так само можна використовувати більшість символів буквально, використовувати - для позначення діапазонів символів і використовувати послідовності екранування. Однією з найважливіших особливостей позначки v є запис множин у межах класів символів. Як вже згадувалося раніше, звичайні класи символів можуть виражати об'єднання, зчіплюючи докупи два діапазони, наприклад, використовуючи [A-Z0-9] для позначення "об'єднання множини [A-Z] та множини [0-9]". Однак немає простого способу виразити інші операції з множинами символів, такі як перетин та різниця.

За присутності позначки v перетин виражається за допомогою &&, а різниця — за допомогою --. Відсутність обох цих комбінацій позначає об'єднання. Два операнди && або -- можуть бути символами, екрануваннями символів, екрануванням класу символів або навіть іншим класом символів. Наприклад, щоб виразити "символ літери або цифри, але не підкреслення", можна використовувати [\w--_]. Не можна змішувати оператори на одному рівні. Наприклад, [\w&&[A-z]--_] є синтаксичною помилкою. Однак, оскільки можна вкладати класи символів один в одного, можна явно написати [\w&&[[A-z]--_]] або [[\w&&[A-z]]--_] (обидва ці записи означають [A-Za-z]). Аналогічно, [AB--C] є недійсним, і необхідно написати [A[B--C]] (що означає просто [AB]).

У режимі v екранування класу символів Unicode \p може давати збіг з рядками скінченної довжини, наприклад, емоджі. Задля симетрії, звичайні класи символів також можуть давати збіг більш ніж з одним символом. Щоб записати "рядковий літерал" у класі символів, такий рядок необхідно загорнути в \q{...}. Єдиний запис регулярних виразів, який підтримується тут, — це диз'юнкція — крім цього, \q повинен повністю оточувати літерали (включно з екранованими символами). Це забезпечує те, що класи символів можуть давати збіг лише з рядками скінченної довжини зі скінченною кількістю можливих варіантів.

У зв'язку з тим, що запис класів символів став складнішим, більше символів стали зарезервованими та забороненими для буквальної появи.

  • На додачу до ] і \, наступні символи повинні бути екрановані в класах символів, якщо вони представляють буквальні символи: (, ), [, {, }, /, -, |. Цей список дещо подібний до списку синтаксичних символів, за винятком того, що ^, $, *, + і ? не зарезервовані в межах класів символів, а / і - не зарезервовані поза межами класів символів (хоча / може відмежовувати літерал регулярного виразу і тому все ще потребує екранування). Усі ці символи також можуть бути екрановані за бажанням в класах символів у режимі u.
  • Наступні послідовності "подвійних розділових" також повинні бути екрановані (але вони все одно мають небагато змісту без позначки v): &&, !!, ##, $$, %%, **, ++, ,,, .., ::, ;;, <<, ==, >>, ??, @@, ^^, ``, ~~. У режимі u деякі з цих символів можуть з'являтися буквально лише в межах класів символів і викликають синтаксичну помилку при спробі їх екранувати. У режимі v вони повинні бути екрановані, коли з'являються парами, але можуть бути екрановані за бажанням, коли з'являються окремо. Наприклад, /[\!]/u є недійсним записом, оскільки це екранування ідентичності, але як /[\!]/v, так і /[!]/v є дійсними, але /[!!]/v є недійсним. Довідка буквальних символів містить докладну таблицю того, які символи можуть зустрічатися з екрануванням, і які без нього.

Доповняльні класи символів [^...] взагалі не можуть збігатися з рядками, довшими за один символ. Наприклад, [\q{ab|c}] є дійсним і дає збіг з рядком "ab", але [^\q{ab|c}] є недійсним, оскільки неясно, скільки символів потрібно спожити. Перевірка виконується шляхом перевірки того, чи всі \q містять один символ, і чи всі \p вказують властивості символів — для об'єднань всі операнди повинні бути чисто символами; для перетинів, принаймні один операнд повинен бути чисто символом; для віднімання, лівий операнд повинен бути чисто символом. Перевірка є синтаксичною без перегляду фактичного набору символів, який вказується, що означає, що хоча /[^\q{ab|c}--\q{ab}]/v еквівалентний /[^c]/v, він все ще відхиляється.

Доповняльні класи та режим ігнорування регістру

У режимі без v доповняльні класи символів [^...] реалізовані просто шляхом обертання результату збігу – тобто [^...] дає збіг щоразу, коли [...] збігу не дає, і навпаки. Проте інші доповняльні класи, як то \P{...} і \W, працюють шляхом негайної побудови набору, що складається з усіх символів без вказаної властивості. Вони, здається, дають той самий результат, але стають складнішими при поєднанні з ігноруванням регістру.

Для прикладу – такі два регулярні вирази:

const r1 = /\p{Lowercase_Letter}/iu;
const r2 = /[^\P{Lowercase_Letter}]/iu;

Вираз r2 – це подвійне заперечення і, здається, рівносильний r1. Але насправді r1 збігається з усіма символами нижнього і верхнього регістрів ASCII, тоді як r2 не збігається з жодним. Щоб проілюструвати, як це працює, удаймо, що маємо справу лише з символами ASCII, а не з усім набором символів Unicode, і r1 і r2 задані так:

const r1 = /[a-z]/iu;
const r2 = /[^A-Z]/iu;

Пригадайте, що пошук збігу з ігноруванням регістру відбувається шляхом зведення і патерну, і вихідного рядку до одного регістру (докладніше в ignoreCase). Для r1 клас символів a-z залишається таким самим після зведення регістру, тоді як обидва рядки ASCII верхнього і нижнього регістру зводяться до нижнього регістру, тому r1 може збігатися як із "A", так і з "a". Для r2 клас символів A-Z зводиться до a-z; проте ^ заперечує результат збігу, так що [^A-Z] насправді збігається лише з рядками верхнього регістру. Однак і рядки ASCII верхнього регістру, і рядки нижнього – однаково зводяться до нижнього регістру, що призводить до того, що r2 не збігається ні з чим.

У режимі v ця логіка виправлена: [^...] також негайно вибудовує доповняльний клас, а не заперечує результат збігу. Це робить [^\P{Lowercase_Letter}] і \p{Lowercase_Letter} строго рівносильними.

Приклади

Пошук шістнадцяткових цифр

Наступна функція з'ясовує, чи містить рядок дійсне шістнадцяткове число:

function isHexadecimal(str) {
  return /^[0-9A-F]+$/i.test(str);
}

isHexadecimal("2F3"); // true
isHexadecimal("beef"); // true
isHexadecimal("undefined"); // false

Застосування перетину

Наступна функція шукає збіги з грецькими літерами.

function greekLetters(str) {
  return str.match(/[\p{Script_Extensions=Greek}&&\p{Letter}]/gv);
}

// 𐆊 – це U+1018A GREEK ZERO SIGN (грецький нуль)
greekLetters("π𐆊P0零αAΣ"); // [ 'π', 'α', 'Σ' ]

Застосування різниці

Наступна функція шукає всі цифри, які не є символами ASCII.

function nonASCIINumbers(str) {
  return str.match(/[\p{Decimal_Number}--[0-9]]/gv);
}

// 𑜹 – це U+11739 AHOM DIGIT NINE (ахомська цифра дев'ять)
nonASCIINumbers("𐆊0零1𝟜𑜹a"); // [ '𝟜', '𑜹' ]

Пошук в рядках

Наступна функція шукає всі послідовності завершення рядка, включно з символами кінця рядка та послідовністю \r\n (CRLF).

function getLineTerminators(str) {
  return str.match(/[\r\n\u2028\u2029\q{\r\n}]/gv);
}

getLineTerminators(`
Вірш\r
Розбитий\r\n
На кілька
Строф
`); // [ '\r', '\r\n', '\n' ]

Цей приклад – строго рівносильний /(?:\r|\n|\u2028|\u2029|\r\n)/gu і /(?:[\r\n\u2028\u2029]|\r\n)/gu, але коротший.

Ситуації, в яких \q{} найбільш корисний, – це різниця та перетин. Раніше таке було можливо за допомогою кількох визирань. Функція нижче шукає прапори, які не є американським, китайським, російським, британським та французьким.

function notUNSCPermanentMember(flag) {
  return /^[\p{RGI_Emoji_Flag_Sequence}--\q{🇺🇸|🇨🇳|🇷🇺|🇬🇧|🇫🇷}]$/v.test(flag);
}

notUNSCPermanentMember("🇺🇸"); // false
notUNSCPermanentMember("🇩🇪"); // true

Цей приклад – здебільшого рівносильний /^(?!🇺🇸|🇨🇳|🇷🇺|🇬🇧|🇫🇷)\p{RGI_Emoji_Flag_Sequence}$/v, але, можливо, швидший.

Специфікації

Якщо ви це бачите — значить, щось трапилося з цією сторінкою.

Сумісність із браузерами

Якщо ви це бачите — значить, щось трапилося з цією сторінкою.

Дивіться також