- PVSM.RU - https://www.pvsm.ru -

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

Здравствуйте, уважаемые дамы и господа.

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

Рано или поздно вам придется иметь дело с регулярными выражениями. Притом, какой у них сложный синтаксис, путаная документация и жесткая кривая обучения, большинство разработчиков удовлетворяются следующим: копипастят выражение со StackOverflow и надеются, что оно будет работать. Но что если бы в самом деле могли расшифровывать регулярные выражения и пользоваться ими на всю катушку? В этой статье я расскажу, почему следует еще раз присмотреться к регулярным выражениям, и как они могут пригодиться на практике.

Зачем нужны регулярные выражения?

Зачем вообще возиться с регулярными выражениями? Чем они могут помочь именно вам?

  • Сравнение с шаблоном: Регулярные выражения отлично помогают определять, соответствует ли строка тому или иному формату – например, телефонному номеру, адресу электронной почты или номеру кредитной карты.
  • Замена: При помощи регулярных выражений легко находить и заменять шаблоны в строке. Так, выражение text.replace(/s+/g, " ") заменяет все пробелы в text, например, " nt ", одним пробелом.
  • Извлечение: При помощи регулярных выражений легко извлекать из шаблона фрагменты информации. Например, name.matches(/^(Mr|Ms|Mrs|Dr).?s/i)[1] извлекает из строки обращение к человеку, например, "Mr" из "Mr. Schropp".
  • Портируемость: Почти в любом распространенном языке программирования есть своя библиотека регулярных выражений. Синтаксис в основном стандартизирован, поэтому вам не придется переучиваться регулярным выражениям при переходе на новый язык.
  • Код: Когда пишете код, можно пользоваться регулярными выражениями для поиска информации в файлах; так, в Atom для этого предусмотрен find and replace, а в командной строке — ack.
  • Четкость и лаконичность: Если вы с регулярными выражениями на «ты», то сможете выполнять весьма нетривиальные операции, написав минимальный объем кода.

Как писать регулярные выражения

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

<input id="phone-number" type="text">
<label class="valid" for="phone-number"><img src="check.svg"></label>
<label class="invalid" for="phone-number"><img src="x.svg"></label>
input:not([data-validation="valid"]) ~ label.valid,
input:not([data-validation="invalid"]) ~ label.invalid {
  display: none;
}
$("input").on("input blur", function(event) {
  if (isPhoneNumber($(this).val())) {
    $(this).attr({ "data-validation": "valid" });
    return;
  }

  if (event.type == "blur") {
    $(this).attr({ "data-validation": "invalid" });
  }
  else {
    $(this).removeAttr("data-validation");
  }
});

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

function isPhoneNumber(string) {
  return /dddddddddd/.test(string);
}

В этой функции между символами / содержится регулярное выражение с десятью d', то есть, символами-цифрами. Метод test возвращает true, если регулярное выражение соответствует строке, в противном случае – false. Если выполнить isPhoneNumber("5558675309"), метод вернет true! Ура!

Однако, писать десять d – слегка муторная работа. К счастью, то же самое можно сделать и при помощи фигурных скобок.

function isPhoneNumber(string) {
  return /d{10}/.test(string);
}

Иногда, вводя телефонный номер, человек начинает с ведущей 1. Правда было бы неплохо, если бы ваше регулярное выражение обрабатывало и такие случаи? Это можно сделать при помощи символа?.

function isPhoneNumber(string) {
  return /1?d{10}/.test(string);
}

Символ ? означает «ноль или единица», поэтому теперь isPhoneNumber возвращает true как для «5558675309», так и для «15558675309»!

Пока isPhoneNumberвполне хороша, но мы упускаем одну ключевую деталь: регулярные выражения сплошь и рядом могут совпадать не со строкой, а с частью строки. Оказывается, isPhoneNumber("555555555555555555") возвращает true, поскольку в этой строке десять цифр. Проблему можно решить, воспользовавшись якорями ^ и $.

function isPhoneNumber(string) {
  return /^1?d{10}$/.test(string);
}

Грубо говоря, ^ соответствует началу строки, а $ — концу строки, поэтому теперь ваше регулярное выражение совпадет с целым телефонным номером.

Серьезный пример

Релиз страницы состоялся, она пользуется бешеным успехом, но есть существенная проблема. В США телефонный номер можно записать разными способами:

  • (234) 567-8901
  • 234-567-8901
  • 234.567.8901
  • 234/567-8901
  • 234 567 8901
  • +1 (234) 567-8901
  • 1-234-567-8901

Хотя пользователи и могут обойтись без пунктуации, им было бы гораздо проще вводить заранее отформатированный номер.

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

function isPhoneNumber(string) {
  return /^1?d{10}$/.test(string.replace(/D/g, ""));
}

Функция replace заменяет пустой строкой символ D, соответствующий любым символам кроме цифр. Глобальный флаг g приказывает функции заменить на регулярное выражение все совпадения, а не только первое.

Еще более серьезный пример

Ваша страница с телефонными номерами всем нравится, в офисе вы – король кулера. Однако, такие профессионалы как вы не останавливаются на достигнутом, поэтому вы хотите сделать страницу еще лучше.
North American Numbering Plan [1] – это стандарт по составлению телефонных номеров, используемый в США, Канаде и еще 23 странах. В этой системе есть несколько простых правил:

  1. Телефонный номер ((234) 567-8901) делится на три части: региональный код (234), код АТС (567) и номер абонента (8901).
  2. В региональном коде и коде АТС первая цифра может быть любой от 2 до 9, а вторая и третья цифры – от 0 до 9.
  3. В коде АТС 1 не может быть третьей цифрой, если вторая цифра – это 1.

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

/^1?<AREA CODE&gt&ltEXCHANGE CODE&gt&ltSUBSCRIBER NUMBER&gt$;/

Номер абонента прост, он состоит всего из четырех цифр

/^1?<AREA CODE&gt&ltEXCHANGE CODE>d{4}$/

Региональный код немного сложнее. Нас интересует цифра от 2 до 9, за которой идут еще две цифры. Для этого можно использовать символьное множество! Символьное множество позволяет задать группу символов, из которых затем можно выбирать.

/^1?[23456789]dd<EXCHANGE CODE>d{4}$/

Отлично, но мы устанем вручную вводить все символы от 2 до 9. Сделаем код еще чище при помощи символьного диапазона.

/^1?[2-9]dd<EXCHANGE CODE>d{4}$/

Уже лучше! Поскольку региональный код такой же, как и код АТС, можно просто продублировать регулярное выражение, чтобы довести этот шаблон до ума.

/^1?[2-9]dd[2-9]ddd{4}$/

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

/^1?([2-9]dd){2}d{4}$/

Итак, [2-9]dd содержится в группе, а {2} указывает, что эта группа должна фигурировать дважды.

Вот и все! Рассмотрим окончательный вариант функции
isPhoneNumber:

function isPhoneNumber(string) {
  return /^1?([2-9]dd){2}d{4}$/.test(string.replace(/D/g, ""));
}

Когда лучше обходиться без регулярных выражений

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

Не будьте слишком строги. Нет никакого смысла проявлять чрезмерную строгость, когда пишешь регулярные выражения. В случае с телефонными номерами, даже если мы учтем все правила из документа NANP, все равно невозможно определить, реален ли данный телефонный номер. Если я заделаю номер(555) 555-5555, то он совпадет с шаблоном, но ведь такого телефонного номера не существует.

Не пишите HTML-парсер. Хотя регулярные выражения отлично подходят для парсинга каких-то простых вещей, синтаксический анализатор для целого языка из них не сделаешь. Если вы не любите заморачиваться [2], то вам вряд ли понравится разбирать нерегулярные языки при помощи регулярных выражений.

Не используйте их с очень сложными строками. Полное регулярное выражение [3] для работы с электронной почтой состоит из 6 318 символов. Простое и приблизительное выглядит так: /^[^@]+@[^@]+.[^@.]+$/. Общее правило таково: если у вас получается регулярное выражение длиннее одной строки кода, то, возможно, стоит поискать другое решение.

Автор: Издательский дом «Питер»

Источник [4]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/120772

Ссылки в тексте:

[1] North American Numbering Plan: https://en.wikipedia.org/wiki/North_American_Numbering_Plan

[2] заморачиваться: https://en.wikipedia.org/wiki/Regular_expression#Patterns_for_non-regular_languages

[3] Полное регулярное выражение: http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html

[4] Источник: https://habrahabr.ru/post/300892/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best