Клас символів: [...], [^...]
Клас символів дає збіг з будь-яким символом, присутнім або відсутнім в заданому наборі символів. Коли ввімкнена позначка 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
, але, можливо, швидший.