Илья Лебедев, Debugger.ru
Примечание редакции. Существует много диалектов языка регулярных выражений. Выражения из этой статьи используют синтаксис, принятый в Perl, в том числе недоступные в других диалектах функции. Похожий диалект вне языка Perl известен как «Perl-совместимые регулярные выражения» (PCRE, Perl-Compatible Regular Expressions).
В большинстве текстовых редакторов можно провести поиск по слову, его части и учитывать регистр. Чаще всего, этих возможностей достаточно, поскольку приходится обрабатывать относительно простые тексты — мозг человека сразу оценивает полученное совпадение и принимает решения.
Рано или поздно наступает момент, когда человеческих ресурсов не хватает для обработки всей поступающей информации, или требуется ввести полностью автоматизированную обработку данных, например, для публикации новостей из разных источников на своём сайте. Здесь вступают в работу автоматизированные системы, в которых нашли широкое применение регулярные выражения. Их использование позволяет в десятки раз уменьшить объём кода, необходимого для обработки текстов. При первом знакомстве регулярные выражения вызывают самые различные реакции, но общее в них то, что человека отталкивает сложность понимания смысла этих «высказываний». Данная статья призвана объяснить, как прочитать выражение и понять его смысл.
Термины
Словарь операторов
Так выглядит перевод конструкций регулярных выражений на «человеческий» язык. В дальнейшем, при разборе примеров, я дам им развернутое описание.
Чтение и написание регулярных выражений
Проговаривание вслух и запись правил — основная проблема, возникающая при освоении техники разбора текста с помощью регулярных выражений. При отсутствии опыта трудно сформулировать словесную запись правил. Тем не менее — это наиболее эффективный путь составления регулярных выражений.
Основным правилом при составлении регулярных выражений является их запись в развёрнутом виде на листе бумаги или на экране. Так легче всего определить, насколько верно будет обработан текст. Другое правило заключается в составлении выражения от общего к частному. При его соблюдении значительно сокращается время написания выражения и количество ошибок.
Сформулируем условия для успешного составления регулярного выражения
Несмотря на трудоёмкость подобной записи, она повышает скорость разработки и отладки правил разбора текстов и эффективность их применения.
Пример составления выражения
Задача
Выделить в HTML разметке содержимое определённых блоков с установленными атрибутами:
<p>Абзац 1</p> <p>Абзац 2</p> <ul> <li>Элемент 1</li> <li>Элемент 2</li> </ul> <p>Абзац 2</p> <li>Элемент 2</li> Последовательность решения
Обычно начинают одновременно производить все 6 шагов что вызывает серьезные проблемы при отладке. Я предлагаю действовать постепенно. Шаг за шагом.
Приведу решение сразу:
#<(p|li)s+[^>]*?classs*=s*([‘»])content2[^>]*>((?:(?!</1>).)*)</1>#is
Согласитесь, выглядит оно почище «китайской грамоты». Тем не менее, следуя описанию, Вы увидите, что всё не так уж и сложно.
Итак, начнём:
Шаг 1. Выделение всех тегов
Запишем правила разбора по-русски:
Теперь, когда задача точно описана, можно приступить к записи её в виде регулярного выражения:
У нас получилось следующее выражение:
<(w+)[^>]*>([^<]*)
Оно имеет 2 недостатка:
Шаг 2. Выделение парных тегов
Запишем правила разбора формальным языком:
Теперь, когда задача точно описана, можно приступить к записи её в виде регулярного выражения:
Итак, у нас получилось следующее выражение:
<(w+)[^>]*>((?:(?!</1>).)*))</1>
Оно захватывает любые парные теги вместе с содержимым.
Шаг 3. Выделение требуемых тегов
Используя регулярное выражение, полученное на предыдущем шаге, мы можем выделить из текста сразу несколько типов тегов, используя конструкцию «альтернативная последовательность при отсутствии совпадения слева». В описании используем термин «альтернативная последовательность».
Добавим выделение из текста всего содержимого абзацев и пунктов списка:
Пункты 7 и 8 были добавлены для того, чтобы выражение не захватывало теги, начало которых совпадает с выделяемыми тегами. Например, чтобы при поиске тега <p> не были захвачены теги <param>.
Переводим её в операторы регулярного выражения:
Новое регулярное выражение:
<(p|li)(?=[s>])[^>w]*>((?:(?!</1>).)*))</1>
Теперь в тексте будут выделены только теги p и li и всё их содержимое.
Шаг 4. Выделение пар имя_атрибута = «значение» Требования к выражению
Опишем задачу формальным языком:
Переводим в операторы регулярного выражения:
Получается следующее регулярное выражение:
s+w+s*=s*([‘»])[^1]*1
Модифицируем его, чтобы выражение совпадало только с именем атрибута ‘class’ и его значением ‘content’:
s+classs*=s*([‘»])content1 Шаг 5. Добавим в основное выражение проверку на определённые атрибуты
Опишем задачу формальным языком:
Переводим её в операторы регулярного выражения:
Результирующее выражение:
<(p|li)s+[^>]*?classs*=s*([‘»])content2[^>]*>((?:(?!</1>).)*)</1> Шаг 6. Добавление модификаторов и ограничителей Ограничители
Практически во всех языках, где имеется поддержка регулярных выражений, возможно выбрать ограничители выражения. Самые распространённые это: / / и # #. В принципе, можно использовать практически любые пары символов, если это поддерживается интерпретатором. При выборе ограничителей лучше исходить из того, какие символы присутствуют в регулярном выражении. Выбирать лучше те, которых в выражении нет. В противном случае придётся экранировать эти символы, что сделает выражение более запутанным. В нашем случае стандартные / / не подходят, поскольку они есть внутри регулярного выражения. Поэтому я предлагаю использовать ограничители # #.
Модификаторы
Информацию по всем модификаторам поиска я советую смотреть в специальной справочной литературе, например, в документации по PHP и Perl. Здесь же мы используем i — поиск без учёта регистра и s — режим совпадения символа «.» с переводами строк.
Финальное выражение #<(p|li)s+[^>]*?classs*=s*([‘»])content2[^>]*>((?:(?!</1>).)*)</1>#is
Как видите, эта задача решается достаточно просто. При написании статьи я её выбрал потому, что на форумах очень часто задают вопрос «как выбрать содержимое определённого тега» и «как разобрать HTML разметку». Решение перед вами.
Полезные ссылки
Благодарности
Спасибо всем, кто помог мне в написании статьи:
Источник: http://citforum.ru/internet/articles/regexp_tutorial/
Источник: