Skip to content

Latest commit

 

History

History
138 lines (84 loc) · 9.3 KB

File metadata and controls

138 lines (84 loc) · 9.3 KB

Опережающие и ретроспективные проверки

В некоторых случаях нам нужно найти соответствия шаблону, но только те, за которыми или перед которыми следует другой шаблон.

Для этого в регулярных выражениях есть специальный синтаксис: опережающая (lookahead) и ретроспективная (lookbehind) проверка.

В качестве первого примера найдём стоимость из строки subject:1 индейка стоит 30€. То есть, найдём число, после которого есть знак валюты subject:€.

Опережающая проверка

Синтаксис опережающей проверки: pattern:X(?=Y).

Он означает: найди pattern:X при условии, что за ним следует pattern:Y. Вместо pattern:X и pattern:Y здесь может быть любой шаблон.

Для целого числа, за которым идёт знак subject:€, шаблон регулярного выражения будет pattern:\d+(?=€):

let str = "1 индейка стоит 30€";

alert( str.match(/\d+(?=)/) ); // 30, число 1 проигнорировано, так как за ним НЕ следует €

Обратим внимание, что проверка - это именно проверка, содержимое скобок pattern:(?=...) не включается в результат match:30.

При поиске pattern:X(?=Y) движок регулярных выражений, найдя pattern:X, проверяет, есть ли после него pattern:Y. Если это не так, то игнорирует совпадение и продолжает поиск дальше.

Возможны и более сложные проверки, например pattern:X(?=Y)(?=Z) означает:

  1. Найти pattern:X.
  2. Проверить, идёт ли pattern:Y сразу после pattern:X (если нет - не подходит).
  3. Проверить, идёт ли pattern:Z сразу после pattern:X (если нет - не подходит).
  4. Если обе проверки прошли - совпадение найдено.

То есть этот шаблон означает, что мы ищем pattern:X при условии, что за ним идут и pattern:Y, и pattern:Z.

Такое возможно только при условии, что шаблоны pattern:Y и pattern:Z не являются взаимно исключающими.

Например, pattern:\d+(?=\s)(?=.*30) ищет pattern:\d+ при условии, что за ним идёт пробел, и где-то далее есть 30:

let str = "1 индейка стоит 30€";

alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1

В нашей строке это как раз число 1.

Негативная опережающая проверка

Допустим, нам нужно узнать из этой же строки количество индеек, то есть число pattern:\d+, за которым НЕ следует знак subject:€.

Для этой задачи мы можем применить негативную опережающую проверку.

Синтаксис: pattern:X(?!Y)

Он означает: найди такой pattern:X, за которым НЕ следует pattern:Y.

let str = "2 индейки стоят 60€";

alert( str.match(/\d+(?!)/) ); // 2 (в этот раз проигнорирована цена)

Ретроспективная проверка

Обратите внимание: Lookbehind не поддерживается в браузерах построенных не на движке V8, таких как Safari и Internet Explorer.

Опережающие проверки позволяют задавать условия на то, что "идёт после".

Ретроспективная проверка выполняет такую же функцию, но с просмотром назад. Другими словами, она находит соответствие шаблону, только если перед ним есть что-то заранее определённое.

Синтаксис:

  • Позитивная ретроспективная проверка: pattern:(?<=Y)X, ищет совпадение с pattern:X при условии, что перед ним ЕСТЬ pattern:Y.
  • Негативная ретроспективная проверка: pattern:(?<!Y)X, ищет совпадение с pattern:X при условии, что перед ним НЕТ pattern:Y.

Чтобы протестировать ретроспективную проверку, давайте поменяем валюту на доллары США. Знак доллара обычно ставится перед суммой денег, поэтому для того чтобы найти $30, мы используем pattern:(?<=\$)\d+ - число, перед которым идёт subject:$:

let str = "1 индейка стоит $30";

// знак доллара экранируем \$, так как это специальный символ
alert( str.match(/(?<=\$)\d+/) ); // 30, одинокое число игнорируется

Если нам необходимо найти количество индеек -- число, перед которым не идёт subject:$, мы можем использовать негативную ретроспективную проверку pattern:(?<!\$)\d+:

let str = "2 индейки стоят $60";

alert( str.match(/(?<!\$)\d+/) ); // 2 (проигнорировалась цена)

Скобочные группы

Как правило, то что находится внутри скобок, задающих опережающую и ретроспективную проверку, не включается в результат совпадения.

Например, в шаблоне pattern:\d+(?=€) знак pattern:€ не будет включён в результат. Это логично, ведь мы ищем число pattern:\d+, а pattern:(?=€) - это всего лишь проверка, что за ним идёт знак subject:€.

Но в некоторых ситуациях нам может быть интересно захватить и то, что в проверке. Для этого нужно обернуть это в дополнительные скобки.

В следующем примере знак валюты pattern:(€|kr) будет включён в результат вместе с суммой:

let str = "1 индейка стоит 30€";
let regexp = /\d+(?=(|kr))/; // добавлены дополнительные скобки вокруг €|kr

alert( str.match(regexp) ); // 30, €

То же самое можно применить к ретроспективной проверке:

let str = "1 индейка стоит $30";
let regexp = /(?<=(\$|£))\d+/;

alert( str.match(regexp) ); // 30, $

Итого

Опережающая и ретроспективная проверки удобны, когда мы хотим искать шаблон по дополнительному условию на контекст, в котором он находится.

Для простых регулярных выражений мы можем сделать похожую вещь "вручную". То есть, найти все совпадения, независимо от контекста, а затем в цикле отфильтровать подходящие.

Как мы помним, regexp.match (без флага g) и str.matchAll (всегда) возвращают совпадения со свойством index, которое содержит позицию совпадения в строке, так что мы можем посмотреть на контекст.

Но обычно регулярные выражения удобнее.

Виды проверок:

Шаблон Тип Совпадение
X(?=Y) Позитивная опережающая pattern:X, если за ним следует pattern:Y
X(?!Y) Негативная опережающая pattern:X, если за ним НЕ следует pattern:Y
(?<=Y)X Позитивная ретроспективная pattern:X, если следует за pattern:Y
(?<!Y)X Негативная ретроспективная pattern:X, если НЕ следует за pattern:Y