JavaScript | Регулярные выражения | Утверждение (Assertion)

JavaScript | Регулярные выражения | Утверждение (Assertion)

 

Регулярные выражения по своей сути являются шаблонами для взаимодействия со строками. В этом разделе мы рассмотрим такое понятие как Утверждение (Assertion). Основное направление сопоставления строк движется слева направо.

Assertion[U, N] ::

Утверждение 1 ^
Утверждение 2$
Утверждение 3\b
Утверждение 4\B
Утверждение 5( ? = Disjunction[?U, ?N] )
Утверждение 6( ? ! Disjunction[?U, ?N] )
Утверждение 7( ? <= Disjunction[?U, ?N] )
Утверждение 8( ? <! Disjunction[?U, ?N] )

Регулярные выражения в JavaScript предлагают нам 8 вариантов оформления Утверждения (Assertion). Чтобы лучше понять как это работает нужно обратиться за помощью к примерам.

Утверждения в регулярных выражениях — это про ГРАНИЦЫ между символами. Основной акцент здесь именно в ГРАНИЦАХ — в положениях между символами.

 

Утверждение № 1 — ^

Знак ^ определяет начало ввода или начало строки. То есть он указывает на начало.

Знаком ^ мы проверяем начало ввода или начало строки. Мы проверяем начинается ли строка с нужной последовательности символов или нет. Мы воспользуемся методом exec(), чтобы передавать в него строку и получать нужную нам информацию.

Давайте потестируем на такой строке:

var stroka = "aabbxxss"
// проверяем любое начало
/^/.exec(stroka)
["", index: 0, input: "aabbxxss", groups: undefined]

// проверяем один начальный символ строки, содержащий букву "a"
/^a/.exec(stroka)
["a", index: 0, input: "aabbxxss", groups: undefined]

// проверяем два начальных символа строки, содержащие буквы "aa"
/^aa/.exec(stroka)
["aa", index: 0, input: "aabbxxss", groups: undefined]

// проверяем три начальных символа строки, содержащие буквы "aaa"
/^aaa/.exec(stroka)
null
//такое совпадение не найдено

// проверяем два начальных символа строки, содержащие буквы "ab"
/^ab/.exec(stroka)
null
//такое совпадение не найдено

Утверждение 1 - проверка последовательности начала строки - JavaScript
Утверждение 1 — проверка последовательности начала строки — JavaScript

Попытки отыскать другие символы из строки будут приводить к результату null т. к. строка не начинается с этих символов

/^b/.exec(stroka)
null
/^bb/.exec(stroka)
null

/^x/.exec(stroka)
null
/^xx/.exec(stroka)
null

/^s/.exec(stroka)
null
/^ss/.exec(stroka)
null

Логика работы Утверждения 1 — ^

Результат «Утверждение» (Assertion) :: ^ оценивается следующим образом:

1. Возвращает новый Matcher с параметрами (x, c), который ничего не захватывает и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть e будет x-овым endIndex.
   d. Если e = 0, или если Multiline является истиной true символ Input[e - 1] является одним из LineTerminator, тогда
      i. Вернуть c(x).
   e. Вернуть failure.
Примечание

Даже когда флаг y используется с шаблоном, знак ^ всегда соответствует только началу ввода Input или (если Multiline истинно true) началу строки.

 

Утверждение № 2 — $

Знак $ определяет конец ввода или конец строки. То есть он указывает на окончание.

Знаком $ мы проверяем конец ввода или конец строки. Мы проверяем заканчивается ли строка с нужной последовательности символов или нет. Мы воспользуемся методом exec(), чтобы передавать в него строку и получать нужную нам информацию.

Давайте потестируем на такой строке:

var stroka = "aabbxxss"
/$/.exec(stroka)
["", index: 8, input: "aabbxxss", groups: undefined]

/s$/.exec(stroka)
["s", index: 7, input: "aabbxxss", groups: undefined]

/ss$/.exec(stroka)
["ss", index: 6, input: "aabbxxss", groups: undefined]

/sss$/.exec(stroka)
null

/xs$/.exec(stroka)
null
Утверждение 2 - проверка последовательности конца строки - JavaScript
Утверждение 2 — проверка последовательности конца строки — JavaScript

Логика работы Утверждения 2 — $

Результат «Утверждение» (Assertion) :: $ оценивается следующим образом:

1. Возвращает новый Matcher с параметрами (x, c), который ничего не захватывает и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть e будет x-овым endIndex.
   d. Если e = InputLength, или если Multiline является истиной true и символ Input[e] является одним из LineTerminator, тогда
      i. Вернуть c(x).
   e. Вернуть failure.

 

Утверждение № 3 — \b

Сочетание символов \b определяет границу буквы и символа. Например с позицией между буквой и пробелом внутри строки, а также началом и концом строки. То есть оно указывают на границу. Нужно чётко понимать какие символы JavaScript считает «буквами«, а какие «не буквами«.

Дополнительная информация

WordCharacters — это математический набор, который представляет собой объединение всех шестидесяти трех символов в «ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_» (буквы, числа и U+005F (LOW LINE — НИЖНЯЯ ЛИНИЯ) в блоке Unicode Basic Latin), и все символы c, для которых c отсутствует в этом наборе, но есть Canonicalize(c).

WordCharacters не может содержать более шестидесяти трех символов, если Unicode и IgnoreCase не имеют значения true.

Давайте протестируем на такой строке:

var stroka = "aa bb xx ss"
/\b/.exec(stroka)
["", index: 0, input: "aa bb xx ss", groups: undefined]

/a\b/.exec(stroka)
["a", index: 1, input: "aa bb xx ss", groups: undefined]

/\ba/.exec(stroka)
["a", index: 0, input: "aa bb xx ss", groups: undefined]

/s\b/.exec(stroka)
["s", index: 10, input: "aa bb xx ss", groups: undefined]

/\bs/.exec(stroka)
["s", index: 9, input: "aa bb xx ss", groups: undefined]
Утверждение 3 - проверка границ с символами нулевой ширины - JavaScript
Утверждение 3 — проверка границ с символами нулевой ширины — JavaScript

Другой пример. Другая строка:

var stroka = "aabbxx;ss;"

/x\b/.exec(stroka)
["x", index: 5, input: "aabbxx;ss;", groups: undefined]
Утверждение 3 - нашли границу буквы и небуквы - JavaScript
Утверждение 3 — нашли границу буквы и небуквы — JavaScript

Теперь попробуем отыскать место строки, в котором символ буквы «x» находится справа от символа «небуквы».

/\bx/.exec(stroka)
null

Такого результата не встретится, потому что обе буквы «x» слева от себя имеют по букве:

  • первая буква «x» слева имеет букву «b«
  • вторая буква «x» слева имеет первую букву «x«

 

ВАЖНО!

По умолчанию это утверждение работает только с буквами английского алфавита и цифрами. Не работает с русскими символами!

Читай публикацию JavaScript | Регулярные выражения | Обозначение (Notation), чтобы лучше разобраться в вопросе букв.

Утверждение 3 не работает с русскими буквами - JavaScript
Утверждение 3 не работает с русскими буквами — JavaScript

Логика работы Утверждения 3 — \b

Результат «Утверждение» (Assertion) :: \b оценивается следующим образом:

1. Возвращает новый Matcher с параметрами (x, c), который ничего не захватывает и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть e будет x-овым endIndex.
   d. Пусть a будет ! IsWordChar(e - 1).
   e. Пусть b будет ! IsWordChar(e).
   f. Если a является true и b является false, или если a является false и b является true, вернуть c(x).
   g. Вернуть failure.

 

Утверждение № 4 — \B

Сочетание символов \B определяет границу буквы и буквы. То есть оно указывают на границу. Нужно чётко понимать какие символы JavaScript считает «буквами«, а какие «не буквами«.

Пример:

var stroka = "a;b;xs"

/\Ba/.exec(stroka)
null
/a\B/.exec(stroka)
null

В этом случае получаем null т. к. буква «a» не соседствует с другими буквами в этой строке.

/\Bx/.exec(stroka)
null

/x\B/.exec(stroka)
["x", index: 4, input: "a;b;xs", groups: undefined]

В этом случае получаем совпадение условия сопоставления. /\Bx/.exec(stroka) возвращает null т. к. буква «x» не соседствует слева с  другими буквами в этой строке. /x\B/.exec(stroka) возвращает объект (находит совпадение условия) т. к. буква «x» соседствует справа с  другой буквой в этой строке.

Дополнительная информация

WordCharacters — это математический набор, который представляет собой объединение всех шестидесяти трех символов в «ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_» (буквы, числа и U+005F (LOW LINE — НИЖНЯЯ ЛИНИЯ) в блоке Unicode Basic Latin), и все символы c, для которых c отсутствует в этом наборе, но есть Canonicalize(c).

WordCharacters не может содержать более шестидесяти трех символов, если Unicode и IgnoreCase не имеют значения true.

Логика работы Утверждения 4 — \B

Результат «Утверждение» (Assertion) :: \B оценивается следующим образом:

1. Возвращает новый Matcher с параметрами (x, c), который ничего не захватывает и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть e будет x-овым endIndex.
   d. Пусть a будет ! IsWordChar(e - 1).
   e. Пусть b будет ! IsWordChar(e).
   f. Если a является true и b является true, или если a является false и b является false, вернуть c(x).
   g. Вернуть failure.

 

Утверждение № 5 — ( ? = Disjunction[?U, ?N] )

Указывает на границу двух последовательностей символов. Слева от конструкции мы пишем последовательность «оканчивающуюся», а внутри конструкции пишем последовательность «начинающуюся».

Мы как бы сопоставляем начало с концом. То есть мы ищем границы, в которых за «началом» следует «конец». Сопоставление ведётся по левой части от круглых скобок.

Если мы хотим найти в строке места где стыкуются «ав» и «не«, то наше Утверждение будет иметь вид:

/ав(?=не)/

Пример работы утверждения:

var stroka = "aax;bbc;caabbx"

Мы хотим найти границы, где символ «а» слева, а символ «b» справа.

/a(?=b)/.exec(stroka)
["a", index: 10, input: "aax;bbc;caabbx", groups: undefined]
Утверждение 5 - соседство символов - JavaScript
Утверждение 5 — соседство символов — JavaScript

В ответ мы получаем объект, который указывает на 10 индекс в строке. То есть на границу 10 и 11 индексов. Значит такое совпадение было найдено.

Логика работы Утверждения 5 — ( ? = Disjunction[?U, ?N] )

1. Вычислите Disjunction с 1 в качестве аргумента направления direction, чтобы получить Matcher m.
2. Верните новый Matcher с параметрами (x, c), который захватывает m и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть d будет новым продолжением Continuation с параметрами (y), которое ничего не фиксирует и при вызове выполняет следующие шаги:
      i. Утверждено: y является состоянием State.
      ii. Вернуть y.
   d. Пусть r будет m(x, d).
   e. Если r является failure, вернуть failure.
   f. Пусть y будет r-ным состоянием State.
   g. Пусть cap будет y-ным captures Списка.
   h. Пусть xe будет x-ным endIndex.
   i. Пусть z будет состоянием State (xe, cap).
   j. Вернуть c(z).

 

Утверждение № 6 — ( ? ! Disjunction[?U, ?N] )

Указывает на границу двух последовательностей символов. Слева от конструкции мы пишем последовательность «оканчивающуюся», а внутри конструкции пишем последовательность «начинающуюся». Найдёт все сопоставления, которые не подпадают под это условие. Сопоставление ведётся по левой части.

Например, мы хотим получить все границы, где окончание — «ау«, а начало НЕ «ва«. То есть мы хотим найти те места в строке, где есть «ау«, но за ним НЕ следует «ва«.

Запись будет выглядеть так:

/ау(?!ва)/

Пример работы утверждения:

var stroka = "куауварыауси"
/ау(?!ва)/.exec(stroka)
Утверждение 6 - исключение сопоставления символов - JavaScript
Утверждение 6 — исключение сопоставления символов — JavaScript

В ответ нам приходит объект, который начинается с индекса 8. Именно в этом месте найдено подходящее Утверждению соответствие. Сочетание «ауси» подходит, а сочетание символов «аува» исключается из отбора.

Логика работы Утверждения 6 — ( ? ! Disjunction[?U, ?N] )

1. Вычислите Disjunction с 1 в качестве аргумента направления direction, чтобы получить Matcher m.
2. Верните новый Matcher с параметрами (x, c), который захватывает m и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть d будет новым продолжением Continuation с параметрами (y), которое ничего не фиксирует и при вызове выполняет следующие шаги:
      i. Утверждено: y является состоянием State.
      ii. Вернуть y.
   d. Пусть r будет m(x, d).
   e. Если r не является failure, вернуть failure. 
   f. Вернуть c(x).

 

Утверждение № 7 — ( ? <= Disjunction[?U, ?N] )

Утверждение 7 похоже на 5-ое. Только меняется направление сопоставления. Мы как бы сопоставляем конец с началом. То есть мы ищем ГРАНИЦЫ, в которых «концу» предшествует искомое «начало». Сопоставление ведётся по правой части от круглых скобок.

Пример:

var stroka = "agcdefg"

Мы хотим найти то место, где перед буквой «g» есть буква «a«. В этом примере такое место одно. Оно находится между НУЛЕВЫМ и ПЕРВЫМ индексами строки.

/(?<=a)g/.exec(stroka)
["g", index: 1, input: "agcdefg", groups: undefined]
Утверждение 7 - сопоставили право с лево - JavaScript
Утверждение 7 — сопоставили право с лево — JavaScript

Давайте немного изменим строку:

let stroka33 = «agcdefgagcdefgagcdefgagcdefg»

Я скопировал строку 3 раза и соединил в одну. Итого у нас предыдущая строка умноженная на 4.

 

Логика работы Утверждения 7 — ( ? <= Disjunction[?U, ?N] )

1. Вычислите Disjunction с -1 в качестве аргумента направления direction, чтобы получить Matcher m.
2. Верните новый Matcher с параметрами (x, c), который захватывает m и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть d будет новым продолжением Continuation с параметрами (y), которое ничего не фиксирует и при вызове выполняет следующие шаги:
      i. Утверждено: y является состоянием State.
      ii. Вернуть y.
   d. Пусть r будет m(x, d).
   e. Если r является failure, вернуть failure. 
   f. Пусть y будет r-ным состоянием State.
   g. Пусть cap будет y-ным captures Списка.
   h. Пусть xe будет x-ным endIndex.
   i. Пусть z будет состоянием State (xe, cap).
   j. Вернуть c(z).

 

Утверждение № 8 — ( ? <! Disjunction[?U, ?N] )

Утверждение 8 похоже на 6-ое. Только меняется направление сопоставления. Мы сопоставляем по условию в котором перед правой частью не стоит левая.

Пример:

var stroka = "agcdefg"

Мы хотим найти то место строки, где перед буквой «g» не стоит буква «a«. В нашем случае это последние два символа строки.

/(?<!a)g/.exec(stroka)
["g", index: 6, input: "agcdefg", groups: undefined]

В нашем случае нашлось сопоставление по условию. Нам вернулся объект с индексом 6. То есть такое соответствие нашлось между индексами 5 и 6. Мы как-бы сравнили справа налево.

Утверждение 8 - сопоставили право с лево - нашли исключения - JavaScript
Утверждение 8 — сопоставили право с лево — нашли исключения — JavaScript

Логика работы Утверждения 8 — ( ? <! Disjunction[?U, ?N] )

1. Вычислите Disjunction с -1 в качестве аргумента направления direction, чтобы получить Matcher m.
2. Верните новый Matcher с параметрами (x, c), который захватывает m и при вызове выполняет следующие шаги:
   a. Утверждено: x является состоянием State.
   b. Утверждено: c является продолжением Continuation.
   c. Пусть d будет новым продолжением Continuation с параметрами (y), которое ничего не фиксирует и при вызове выполняет следующие шаги:
      i. Утверждено: y является состоянием State.
      ii. Вернуть y.
   d. Пусть r будет m(x, d).
   e. Если r НЕ является failure, вернуть failure.
   f. Вернуть c(x).

 

IsWordChar ( e )

Абстрактная операция IsWordChar принимает аргумент e (целое число). При вызове он выполняет следующие шаги:

1. Если e = -1 или e является InputLength, вернуть false.
2. Пусть c будет символом Input[e].
3. Если c находится в WordCharacters, вернуть true.
4. Вернуть false.

 

Информационные ссылки

Стандарт ECMAScript — Раздел «22.2.2.6 Assertion» — https://tc39.es/ecma262/#sec-assertion