Javascript для начинающих: изучаем регулярные выражения

Замена текста REGEXP_REPLACE

Поиск и замена — одна из лучших областей применения регулярных выражений. Текст замены может включать ссылки на части исходного выражения (называемые обратными ссылками), открывающие чрезвычайно мощные возможности при работе с текстом. Допустим, имеется список имен, разделенный запятыми, и его содержимое необходимо вывести по два имени в строке. Одно из решений заключается в том, чтобы заменить каждую вторую запятую символом новой строки. Сделать это при помощи стандартной функции нелегко, но с функцией задача решается просто. Общий синтаксис ее вызова: 

REGEXP_REPLACE (исходная_строка, шаблон ]])

Здесь исходная_строка — строка, в которой выполняется поиск; шаблон — регулярное выражение, совпадение которого ищется в исходной_строке; начальная_позиция — позиция, с которой начинается поиск; модификаторы — один или несколько модификаторов, управляющих процессом поиска. Пример:

DECLARE
names VARCHAR2(60) := 'Anna,Matt,Joe,Nathan,Andrew,Jeff,Aaron';
names_adjusted VARCHAR2(61);
comma_delimited BOOLEAN;
extracted_name VARCHAR2(60);
name_counter NUMBER;
BEGIN
-- Искать совпадение шаблона
comma_delimited := REGEXP_LIKE(names,'^(*,)+(*){1}$', 'i');
-- Продолжать, только если мы действительно
-- работаем со списком, разделенным запятыми.
IF comma_delimited THEN
names := REGEXP_REPLACE(
names,
'(*),(*),',
'\1,\2' || chr(10) );
END IF;
DBMS_OUTPUT.PUT_LINE(names);
END;

Результат выглядит так:

Anna,Matt
Joe,Nathan
Andrew,Jeff
Aaron  

При вызове функции передаются три аргумента:

  • names — исходная строка;
  • ‘(*),(*),’ — выражение, описывающее заменяемый текст (см. ниже);
  • ‘\1,\2 ‘ || chr(10) — текст замены. \1 и \2 — обратные ссылки, заложенные в основу нашего решения. Подробные объяснения также приводятся ниже.

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

  • Совпадение должно начинаться с имени.
  • За именем должна следовать запятая.
  • Затем идет другое имя.
  • И снова одна запятая.

Наша цель — заменить каждую вторую запятую символом новой строки. Вот почему выражение написано так, чтобы оно совпадало с двумя именами и двумя запятыми. Также запятые не напрасно выведены за пределы подвыражений.

Первое совпадение для нашего выражения, которое будет найдено при вызове , выглядит так: 

Anna,Matt,

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

'\1,\2' || chr(10)     -- Текст замены
'Anna,\2' || chr(10)   -- Подстановка текста, совпавшего
                       -- с первым подвыражением
'Anna,Matt' || chr(10) -- Подстановка текста, совпавшего
                       -- со вторым подвыражением

Вероятно, вы уже видите, какие мощные инструменты оказались в вашем распоряжении. Запятые из исходного текста попросту не используются. Мы берем текст, совпавший с двумя подвыражениями (имена «Anna» и «Matt»), и вставляем их в новую строку с одной запятой и одним символом новой строки.

Но и это еще не все! Текст замены легко изменить так, чтобы вместо запятой в нем использовался символ табуляции (ASCII-код 9): 

names := REGEXP_REPLACE(
names,
'(*),(*),',
'\1' || chr(9) || '\2' || chr(10) );

Теперь результаты выводятся в два аккуратных столбца:

Anna Matt
Joe Nathan
Andrew Jeff
Aaron 

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

Строковые методы, поиск и замена

Следующие методы работают с регулярными выражениями из строк.

Все методы, кроме replace, можно вызывать как с объектами типа regexp в аргументах, так и со строками, которые автоматом преобразуются в объекты RegExp.

Так что вызовы эквивалентны:

var i = str.search(/\s/)
var i = str.search("\\s")

При использовании кавычек нужно дублировать \ и нет возможности указать флаги. Если регулярное выражение уже задано строкой, то бывает удобна и полная форма

var regText = "\\s"
var i = str.search(new RegExp(regText, "g"))

Возвращает индекс регулярного выражения в строке, или -1.

Если Вы хотите знать, подходит ли строка под регулярное выражение, используйте метод (аналогично RegExp-методы ). Чтобы получить больше информации, используйте более медленный метод (аналогичный методу ).

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

function testinput(re, str){
   if (str.search(re) != -1)
      midstring = " contains ";
   else
      midstring = " does not contain ";
   document.write (str + midstring + re.source);
}

Если в regexp нет флага , то возвращает тот же результат, что .

Если в regexp есть флаг , то возвращает массив со всеми совпадениями.

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

Если Вы хотите получить первый результат — попробуйте r.

В следующем примере используется, чтобы найти «Chapter», за которой следует 1 или более цифр, а затем цифры, разделенные точкой. В регулярном выражении есть флаг , так что регистр будет игнорироваться.

str = "For more information, see Chapter 3.4.5.1";
re = /chapter (\d+(\.\d)*)/i;
found = str.match(re);
alert(found);

Скрипт выдаст массив из совпадений:

  • Chapter 3.4.5.1 — полностью совпавшая строка
  • 3.4.5.1 — первая скобка
  • .1 — внутренняя скобка

Следующий пример демонстрирует использование флагов глобального и регистронезависимого поиска с . Будут найдены все буквы от А до Е и от а до е, каждая — в отдельном элементе массива.

var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var regexp = //gi;
var matches = str.match(regexp);
document.write(matches);

// matches = 

Метод replace может заменять вхождения регулярного выражения не только на строку, но и на результат выполнения функции. Его полный синтаксис — такой:

var newString = str.replace(regexp/substr, newSubStr/function)
Объект RegExp. Его вхождения будут заменены на значение, которое вернет параметр номер 2
Строка, которая будет заменена на .
Строка, которая заменяет подстроку из аргумента номер 1.
Функция, которая может быть вызвана для генерации новой подстроки (чтобы подставить ее вместо подстроки, полученной из аргумента 1).

Метод не меняет строку, на которой вызван, а просто возвращает новую, измененную строку.

Чтобы осуществить глобальную замену, включите в регулярное выражение флаг .

Если первый аргумент — строка, то она не преобразуется в регулярное выражение, так что, например,

var ab = "a b".replace("\\s","..") // = "a b"

Вызов replace оставил строку без изменения, т.к искал не регулярное выражение , а строку «\s».

В строке замены могут быть такие спецсимволы:

Pattern Inserts
Вставляет «$».
Вставляет найденную подстроку.
Вставляет часть строки, которая предшествует найденному вхождению.
Вставляет часть строки, которая идет после найденного вхождения.
or Где или — десятичные цифры, вставляет подстроку вхождения, запомненную -й вложенной скобкой, если первый аргумент — объект RegExp.

Если Вы указываете вторым параметром функцию, то она выполняется при каждом совпадении.

В функции можно динамически генерировать и возвращать строку подстановки.

Первый параметр функции — найденная подстрока. Если первым аргументом является объект , то следующие параметров содержат совпадения из вложенных скобок. Последние два параметра — позиция в строке, на которой произошло совпадение и сама строка.

Например, следующий вызов возвратит XXzzzz — XX , zzzz.

function replacer(str, p1, p2, offset, s)
{
return str + " - " + p1 + " , " + p2;
}
var newString = "XXzzzz".replace(/(X*)(z*)/, replacer)

Как видите, тут две скобки в регулярном выражении, и потому в функции два параметра , .
Если бы были три скобки, то в функцию пришлось бы добавить параметр .

Следующая функция заменяет слова типа на :

function styleHyphenFormat(propertyName)
{
  function upperToHyphenLower(match)
  {
    return '-' + match.toLowerCase();
  }
  return propertyName.replace(//, upperToHyphenLower);
}

Примеры

Разберём скобки на примерах.

Без скобок шаблон означает символ и идущий после него символ , который повторяется один или более раз. Например, или .

Скобки группируют символы вместе. Так что означает , , и т.п.

Сделаем что-то более сложное – регулярное выражение, которое соответствует домену сайта.

Например:

Как видно, домен состоит из повторяющихся слов, причём после каждого, кроме последнего, стоит точка.

На языке регулярных выражений :

Поиск работает, но такому шаблону не соответствует домен с дефисом, например, , так как дефис не входит в класс .

Можно исправить это, заменим на везде, кроме как в конце: .

Итоговый шаблон:

2 Практический раздел. Ссылки

Перед тем, как использовать регулярные выражения, стоит посмотреть в документацию по вашему языку программирования и используемой библиотеке, так как диалекты обладают особенностями. Например в Perl и некоторых версиях php можно описывать рекурсивные регулярные выражения, которые не поддерживаются большинством других реализаций; механизмом флагов отличается JavaScript и так далее. Незначительными отличиями могут обладать даже различные версии одной и той же библиотеки.

Отличаются регулярные выражения не только синтаксисом, но и реализацией. Регулярные выражения — это «не просто так». Строка, задающее выражение, преобразуется в автомат, от реализации которого зависит эффективность. Масштаб проблемы хорошо иллюстрирует график зависимости времени выполнения поиска от длины строки и реализации:

Картинка взята из статьи «Поиск с помощью регулярных выражений может быть простым и быстрым«. В ней можно прочитать про различные реализации выражений, а также о том, как написать выражение так, чтобы оно работало быстрее. Кстати, так как выражение преобразуется в автомат, то зачастую его удобно визуализировать — для этого есть специальные сервисы, например. Для последнего выражения статьи будет построен такой автомат:

Примеры использования регулярных выражений:

  • для валидации вводимых в поля данных: QValidator примеры использования. Ряд библиотек построения графического пользовательского интерфейса позволяют закреплять к полям ввода валидаторы, которые не позволяет ввести в формы некорректные данные. По приведенной выше ссылке можно найти валидацию номера банковской карты и номера телефона с помощью регулярных выражений библиотеки Qt. Аналогичные механизмы есть в других языках, например в Java для этого используется пакет ;
  • для парсинга сайтов: Парсер сайта на Qt, использование QRegExp. В примере с сайта-галереи выбираются и скачиваются картинки заданных категорий;
  • для валидации данных, передаваемых в формате JSON ряд библиотек позволяет задавать схему. При этом для строковых полей могут быть заданы регулярные выражения. В качестве упражнения можно попробовать составить выражение для пароля — проверить что строка содержит символы в разном регистре и цифры.

В сообществе Программирование и алгоритмы можно посмотреть дополнительную литературу по теме. Книгу Гойвертса и Левитана рекомендую посмотреть особенно, так как в ней по-полочкам разобраны десятки примеров, причем с учетом специфики реализации регулярных выражений в конкретных языках программирования.

str.match(regexp)

Метод ищет совпадения с в строке .

У него есть три режима работы:

  1. Если у регулярного выражения нет флага , то он возвращает первое совпадение в виде массива со скобочными группами и свойствами (позиция совпадения), (строка поиска, равна ):

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

  3. Если совпадений нет, то, вне зависимости от наличия флага , возвращается .

    Это очень важный нюанс. При отсутствии совпадений возвращается не пустой массив, а именно . Если об этом забыть, можно легко допустить ошибку, например:

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

Экранирование свойств Unicode

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

Экранирование свойств Unicode — это возможность ES2018, которая добавляет очень крутую функцию, расширяя эту концепцию на всех Unicode символы и добавляя  и .

У любого Unicode символа есть набор свойств. Например определяет семейство языков, — это логическое значение равное для ASCII символов и т.д. Вы можете положить это свойство в фигурные скобки и регулярное выражение будет проверять чтобы его значение было истинным:

 — это ещё одно логическое свойство, которое проверяет содержит ли строка тольк валидные шестнадцатеричные цифры:

Существует много других логических свойств, которые вы можете проверить просто добавив их имя в фигурные скобки, включая , , , , и другие:

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

Прочитать больше обо всех свойствах вы можете здесь.

Захват текста между двойными кавычками

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

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

Мы найдём то что нам нужно в :

Ещё примеры

Квантификаторы используются очень часто. Они служат основными «строительными блоками» сложных регулярных выражений, поэтому давайте рассмотрим ещё примеры.

Регулярное выражение для десятичных дробей (чисел с плавающей точкой):

В действии:

Регулярное выражение для «открывающего HTML-тега без атрибутов», например, или .

  1. Самое простое:

    Это регулярное выражение ищет символ , за которым идут одна или более букв латинского алфавита, а затем .

  2. Улучшенное:

    Здесь регулярное выражение расширено: в соответствие со стандартом, в названии HTML-тега цифра может быть на любой позиции, кроме первой, например .

Регулярное выражение для «открывающего или закрывающего HTML-тега без атрибутов»:

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

Чтобы регулярное выражение было точнее, нам часто приходится делать его сложнее

В этих примерах мы видим общее правило: чем точнее регулярное выражение – тем оно длиннее и сложнее.

Например, для HTML-тегов без атрибутов, скорее всего, подошло бы и более простое регулярное выражение: . Но стандарт HTML накладывает более жёсткие ограничения на имя тега, так что более точным будет шаблон .

Подойдёт ли нам или нужно использовать ? А, может быть, нужно ещё его усложнить, добавить атрибуты?

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

Первый тест

testtest()it()it()test()expect()Math.max(1, 5, 10)expect()toBe

  • toBe() — подходит, если нам надо сравнивать примитивные значения или является ли переданное значение ссылкой на тот же объект, что указан как ожидаемое значение. Сравниваются значения при помощи Object.is(). В отличие от === это дает возможность отличать 0 от -0, проверить равенство NaN c NaN.
  • toEqual() — подойдёт, если нам необходимо сравнить структуру более сложных типов. Он сравнит все поля переданного объекта с ожидаемым. Проверит каждый элемент массива. И сделает это рекурсивно по всей вложенности.
  • toContain() — проверят содержит массив или итерируемый объект значение. Для сравнения используется оператор ===.
  • toContainEqual() — проверяет или содержит массив элемент с ожидаемой структурой.
  • toHaveLength() — проверяет или свойство length у объекта соответствует ожидаемому.
  • toBeNull() — проверяет на равенство с null.
  • toBeUndefined() — проверяет на равенство с undefined.
  • toBeDefined() — противоположность toBeUndefined. Проверяет или значение !== undefined.
  • toBeTruthy() — проверяет или в булевом контексте значение соответствует true. Тоесть любые значения кроме false, null, undefined, 0, NaN и пустых строк.
  • toBeFalsy() — противоположность toBeTruthy(). Проверяет или в булевом контексте значение соответствует false.
  • toBeGreaterThan() и toBeGreaterThanOrEqual() — первый метод проверяет или переданное числовое значение больше, чем ожидаемое >, второй проверяет больше или равно ожидаемому >=.
  • toBeLessThan() и toBeLessThanOrEqual() — противоположность toBeGreaterThan() и toBeGreaterThanOrEqual()
  • toBeCloseTo() — удобно использовать для чисел с плавающей запятой, когда вам не важна точность и вы не хотите, чтобы тест зависел от незначительной разницы в дроби. Вторым аргументом можно передать до какого знака после запятой необходима точность при сравнении.
  • toMatch() — проверяет соответствие строки регулярному выражению.
  • toThrow() — используется в случаях, когда надо проверить исключение. Можно проверить как сам факт ошибки, так и проверить на выброс исключения определенного класса, либо по сообщению ошибки, либо по соответствию сообщения регулярному выражению.
  • not — это свойство позволяет сделать проверки на НЕравенство. Оно предоставляет объект, который имеет все методы перечисленные выше, но работать они будут наоборот.

areacircumferencetoBeCloseTotoBeNaN

byRangePricetoContainEqualtoContainEqualtoEqualtoBeGreaterThanOrEqualtoBeLessThanOrEqualnot.toContainEqual

Как исправить?

Есть два основных подхода, как это исправить.

Первый – уменьшить количество возможных комбинаций.

Перепишем регулярное выражение так: – то есть, будем искать любое количество слов с пробелом , после которых идёт (но не обязательно) обычное слово .

Это регулярное выражение эквивалентно предыдущему (ищет то же самое), и на этот раз всё работает:

Почему же проблема исчезла?

Теперь звёздочка стоит после вместо . Стало невозможно разбить одно слово на несколько разных . Исчезли и потери времени на перебор таких комбинаций.

Например, с предыдущим шаблоном слово могло быть представлено как два подряд :

Предыдущий шаблон из-за необязательности допускал варианты , , и т.п.

С переписанным шаблоном , такое невозможно: может быть или , но не . Так что общее количество комбинаций сильно уменьшается.

Выражения особых потребностей

  • Электронная почта:
    ^+\.+$
  • URL или доменное имя:
    ^(([^:/?#]+):)?(//([^/?#]*))?(*)(\?(*))?(#(.*))?
  • Дата (MM/DD/YYYY)/(MM-DD-YYYY)/(MM.DD.YYYY)/(MM DD YYYY):
    ^(0?|1)[- /.](0?||3)[- /.](19|20)?{2}$
  • 12 месяцев в году(01~09和1~12):
    ^(0?|1)$
  • 31 день в месяц(01~09和1~31):
    ^(0||3)$
  • Пароль:
    ^.*(?=.{6,})(?=.*d)(?=.*)(?=.*)(?=.*#$%^&*? ]).*$
  • Номера телефонов в США
    \b\d{3}?\d{3}?\d{4}\b
  • Индекс США
    ^{5}(?:-{4})?$
  • Слаг
    ^+$
  • Все специальные символы должны быть экранированы
    /\/\\\{\}\(\)\*\+\?\.\^\$\|]/
  • xml файл:
    ^(+-?)++\\.$
  • Регулярные выражения для китайских иероглифов:
  • Двухбайтовые символы:
  • Пустая строка:
    \n\s*\r (используется для удаления пустых строк)
  • HTML теги:
    <(\S*?)*>.*?|<.*? />
  • Ведущий и конечный символ пробела:
    ^\s*|\s*$ или (^\s*)|(\s*$)
  • IP-адрес:
    ((?:(?:25|2\d|((1\d{2})|(?\d)))\.){3}(?:25|2\d|((1\d{2})|(?\d))))

Итого

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

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

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

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

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

Шаблон Тип Совпадение
Позитивная опережающая , если за ним следует
Негативная опережающая , если за ним НЕ следует
Позитивная ретроспективная , если следует за
Негативная ретроспективная , если НЕ следует за
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *