Альтернация (или) |

Альтернация – термин в регулярных выражениях, которому в русском языке соответствует слово «ИЛИ».

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

Например, нам нужно найти языки программирования: HTML, PHP, Java и JavaScript.

Соответствующее регулярное выражение: html|php|java(script)?.

Пример использования:

let reg = /html|php|css|java(script)?/gi;

let str = "Сначала появился HTML, затем CSS, потом JavaScript";

alert( str.match(reg) ); // 'HTML', 'CSS', 'JavaScript'

Мы уже знакомы с подобным – квадратные скобки. Они позволяют выбирать между несколькими символами, например gr[ae]y найдёт gray, либо grey.

Квадратные скобки работают только с символами или наборами символов. Альтернация работает с любыми выражениями. Регулярное выражение A|B|C обозначает поиск одного из выражений: A, B или C.

Например:

  • gr(a|e)y означает точно то же, что и gr[ae]y.
  • gra|ey означает gra или ey.

Для отделения части паттерна с альтернацией мы обычно заключаем её в скобки, как здесь: before(XXX|YYY)after.

Регулярное выражение для времени

В предыдущих главах было задание написать регулярное выражение для поиска времени в формате чч:мм, например 12:00. Но паттерн \d\d:\d\d слишком неопределённый. Он принимает 25:99 за время (99 секунд подходят под паттерн, но так не должно быть).

Как сделать лучшее выражение?

Мы можем применить более тщательное сравнение. Во-первых, часы:

  • Если первая цифра 0 или 1, тогда следующая цифра может быть любой.
  • Или если первая цифра 2, тогда следующая должна быть [0-3].

В виде регулярного выражения: [01]\d|2[0-3].

Затем, минуты должны быть от 0 до 59. На языке регулярных выражений это означает [0-5]\d: первая цифра 0-5, а за ней любая.

Давайте соединим их в одно выражение: [01]\d|2[0-3]:[0-5]\d.

Почти готово, но есть проблема. Сейчас альтернация | выбирает между [01]\d и 2[0-3]:[0-5]\d.

Это неправильно, так как она должна примениться только к часам [01]\d ИЛИ 2[0-3]. Это частая ошибка в начале работы с регулярными выражениями.

Правильный вариант:

let reg = /([01]\d|2[0-3]):[0-5]\d/g;

alert("00:00 10:10 23:59 25:99 1:2".match(reg)); // 00:00,10:10,23:59

Задачи

Существует много языков программирования, например, Java, JavaScript, PHP, C, C++.

Напишите регулярное выражение, которое найдёт их все в строке Java JavaScript PHP C++ C:

let reg = /ваше регулярное выражение/флаги;

alert("Java JavaScript PHP C++ C".match(reg)); // Java JavaScript PHP C++ C

Первая идея, которая может прийти в голову – перечислить языки, разделив их |.

Но это не сработает, как надо:

let reg = /Java|JavaScript|PHP|C|C\+\+/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(reg) ); // Java,Java,PHP,C,C

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

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

То же самое – с языками C и C++.

Есть два решения проблемы:

  1. Поменять порядок, чтобы более длинное совпадение проверялось первым: JavaScript|Java|C\+\+|C|PHP.
  2. Соединить одинаково начинающиеся варианты: Java(Script)?|C(\+\+)?|PHP.

В действии:

let reg = /Java(Script)?|C(\+\+)?|PHP/g;

let str = "Java, JavaScript, PHP, C, C++";

alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++

BB-код имеет вид [tag]...[/tag], где tag– это один из: b, url или quote.

Например:

[b]текст[/b]
[url]http://ya.ru[/url]

BB-коды могут быть вложенными. Но сам в себя тег быть вложен не может, например:

Допустимо:
[url] [b]http://ya.ru[/b] [/url]
[quote] [b]текст[/b] [/quote]

Нельзя:
[b][b]текст[/b][/b]

Теги могут содержать переносы строк, это допустимо:

[quote]
  [b]текст[/b]
[/quote]

Создайте регулярное выражение для поиска всех BB-кодов и их содержимого.

Например:

let reg = /ваше регулярное выражение/флаги;

let str = "..[url]http://ya.ru[/url]..";
alert( str.match(reg) ); // [url]http://ya.ru[/url]

Если теги вложены, то нужно искать самый внешний тег (при желании можно продолжить поиск в его содержимом):

let reg = /ваше регулярное выражение/флаги;

let str = "..[url][b]http://ya.ru[/b][/url]..";
alert( str.match(reg) ); // [url][b]http://ya.ru[/b][/url]

Открывающий тег – это \[(b|url|quote)\].

Затем, чтобы найти всё до закрывающего тега – используем выражение .*? с флагом s: оно найдёт любые символы, включая новую строку, и затем добавим обратную ссылку на открывающий тег.

Полное выражение: \[(b|url|quote)\].*?\[/\1\].

В действии:

let reg = /[(b|url|quote)].*?[/\1]/gs;

let str = `
  [b]привет![/b]
  [quote]
    [url]http://ya.ru[/url]
  [/quote]
`;

alert( str.match(reg) ); // [b]привет![/b],[quote][url]http://ya.ru[/url][/quote]

Обратите внимание, что необходимо экранировать слеш [/\1], потому что обычно слеш завершает паттерн.

Создайте регулярное выражение для поиска строк в двойных кавычках "...".

Важно, что строки должны поддерживать экранирование с помощью обратного слеша, по аналогии со строками JavaScript. Например, кавычки могут быть вставлены как \", новая строка как \n, а сам обратный слеш как \\.

let str = "Как вот \"здесь\".";

В частности, обратите внимание: двойная кавычка после обратного слеша \" не оканчивает строку.

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

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

Примеры подходящих строк:

.. "test me" ..
.. "Скажи \"Привет\"!" ... (строка с экранированными кавычками)
.. "\\" ..  (внутри двойной слеш)
.. "\\ \"" ..  (внутри двойной слеш и экранированная кавычка)

В JavaScript приходится удваивать обратные слеши, чтобы добавлять их в строку, как здесь:

let str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\\\ \\"" .. ';

// эта строка в памяти:
alert(str); //  .. "test me" .. "Скажи \"Привет\"!" .. "\\ \"" ..

Решение: /"(\\.|[^"\\])*"/g.

Шаг за шагом:

  • Сначала ищем открывающую кавычку "
  • Затем, если есть обратный слеш \\ (удвоение обратного слеша – техническое, потому что это спец.символ, на самом деле там один обратный слеш), то после него также подойдёт любой символ (точка).
  • Иначе берём любой символ, кроме кавычек (которые будут означать конец строки) и обратного слеша (чтобы предотвратить одинокие обратные слеши, сам по себе единственный обратный слеш не нужен, он должен экранировать какой-то символ) [^"\\]
  • …И так далее, до закрывающей кавычки.

В действии:

let reg = /"(\\.|[^"\\])*"/g;
let str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\\\ \\"" .. ';

alert( str.match(reg) ); // "test me","Скажи \"Привет\"!","\\ \""

Напишите регулярное выражение, которое ищет тег <style...>. Оно должно искать весь тег: он может как не иметь атрибутов <style>, так и иметь несколько <style type="..." id="...">.

…Но регулярное выражение не должно находить <styler>!

Например:

let reg = /ваше регулярное выражение/g;

alert( '<style> <styler> <style test="...">'.match(reg) ); // <style>, <style test="...">

Начало шаблона очевидно: <style.

…А вот дальше… Мы не можем написать просто <style.*?>, потому что <styler> удовлетворяет этому выражению.

После <style должен быть либо пробел, после которого может быть что-то ещё, либо закрытие тега >.

На языке регулярных выражений: <style(>|\s.*?>).

В действии:

let reg = /<style(>|\s.*?>)/g;

alert( '<style> <styler> <style test="...">'.match(reg) ); // <style>, <style test="...">
Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.