СтатьиРегулярные выражения, Часть IIИспользование позиционных проверокМногим материал этой статьи покажется ненужным, так как большинство задач с использованием регулярных выражений решаются средствами, которые я описал в предыдущей. Сначала немного отвлечемся. Как вы опишите что-то неприметное, либо что вообще трудно описать? Думаю, что надо описать что-то приметное, либо то, что легко описать, а потом указать, где находится искомое "что-то неприметное" относительно описанного "приметного". Пример: мой директор спрашивает, как найти такой-то банк в Киеве. Я знаю, что объяснить местоположение банка голландсокму директору, используя транслитерированные русские названия, невозможно! Но я программист и объясняю, что нужный банк находится на центральной площади. В тоже время площадь описываю словами, что с одной стороны стоит женщина на огромной колонне, а с другой стороны Мак Дональдс. Даже некиевляне догадались, что я имею в виду Площадь Независимости. В синтаксисе регулярных выражений есть возможность описать, что стоит до, а что стоит после того участка строки, который нам нужно найти. Считайте, что есть возможность, не называя точного адреса, описать местоположение банка. Если сумеете описать, между чем и чем находится банк, а также сумеете описать, что это именно банк, а не кафе, то считайте, что вам не нужно давать точный адрес. Каждый раз при помощи регулярных выражений происходит поиск совпадений. Поиск проводится по строке, в строке ищется подстрока. Теперь научимся задавать условие, что должно совпасть перед подстрокой и после нее. Условие совпадения, которое идет перед подстрокой, называют ретроспективной проверкой, условие совпадения, которое идет после подстроки, называется опережающей проверкой. Можно объяснить также, что ретроспективная проверка совпадает слева, опережающая — справа. Позитивные и негативные проверкиХорошо, если можно описать, что следует перед искомой подстрокой, а что после. А что если нужно описать, что НЕ идет перед либо после искомой подстроки? Для этого опережающую и ретроспективную проверку делят еще каждую на две:
Опережающая позитивная проверка указывает, что условие описывает, что должно обязательно присутствовать после искомой подстроки. Опережающая негативная проверка описывает, что ни в коем случае не должно стоять после искомой строки. Аналогично с ретроспективной проверкой только условие ищется перед искомой подстроки. Ретроспективная проверка
Позитивная ретроспективная проверка либо проверка слева описывается специальной последовательностью символов:
Ретроспективная негативная проверка описывается подобной последовательностью символов:
Опережающая проверкаЕсли вы поняли описание ретроспективной проверки, то понять суть опережающей проверки вам будет несложно.
Для описания позитивной опережающей проверки используется последовательность символов:
Для описания негативной опережающей проверки используется последовательность символов: Пример №1Часто возникает проблема по парсингу интересующих программиста данных из HTML, который не всегда хорошего качества, все было бы терпимо, если бы еще не вставки на javascript'е, вот пример такого текста: <TD>20.02<BR>05:30 <TD class=l>Товар 1<BR>Товар 2 <TD><B>35</B> <TD><A href="http://ссылка/" id=sfsd32dfs onclick="return m(this)">26.92</A><BR><A href="http://ссылка/" id=r3_3143svsfd onclick="return m(this)">27.05</A> <TD><B>270.5</B> </TR>
Те цифры, которые написаны через точку, являются ценами. Задача состоит в том, чтобы собрать все цены, которые находятся между тегами
Видим, что помимо цен между заданными тегами, есть такие, которые идут сразу после тега
Путем таких размышлений мы пришли к выводу, что должно стоять справа, а что должно стоять слева искомой строки, которая описывается как цифры, разделенные точкой:
То, что должно совпасть слева, мы описали как символ
То, что должно совпасть справа описывается
Теперь опишем, что не должно стоять перед ценой:
При помощи негативной опережающей проверки опишем, что не должно стоять справа цены: Результирующее регулярное выражение, которое описывает все приведенные условия выглядит вот так:
preg_match_all("/(?<!<TD>)(?<=>)\d*\.\d*(?!<\/B>)(?=<\/A>)/", $string, $matches);
После рассмотрения первого примера стоит сделать замечания и пояснения по поводу использования позиционных проверок.
Механизм поиска совпадения в ретроспективной проверке реализован так, что при поиске механизму должна подаваться строка фиксированной длины, для того, чтобы в случае несовпадения, механизм мог вернуться назад на фиксированое количество символов и продолжить поиск совпадений в других позиционных проверках. Думаю, что сразу это понять сложно, но представьте себе как происходит поиск совпадения в части
С этого места происходит "заглядывание назад" (ретроспективные проверки на английском языке звучит как lookbhind assertions), т.е. все предыдущие 4 символа проверяются на совпадение со строкой <TD>, если механизм не нашел совпадения, то ему надо вернуться на 4 символа назад, выполнить тоже самое с проверкой Сразу возникает вопрос, а зачем идти вперед, чтобы потом "заглянуть" назад? Делается это для того, чтобы в случае совпадения всех проверок сразу же начать проверку тех символов, которые идут после позиционных проверок. Пример №2Как-то мне нужно было получить все изображения, которые использовались на сайте. Что для этого надо сделать? Правильно, надо в браузере нажать на "Сохранить как", указать куда сохранить страницу. Появится файл с исходным кодом страницы и папка с изображениями. Но вы никогда не сохраните в эту папку изображения, которые прописаны в стилях объектов по крайней мере в эксплорере: style="background-image:url(/editor/em/Unlink.gif);" Для проведения вышеописанной операции надо:
Делаем: в переменную Справа от относительного пути стоит закрывающаяся круглая скобка. Между этими последовательностями символов могут быть буквы латинского алфавита, цифры и слеши, а также точка перед расширением файла.
Начнем с простого. Символы латинсокого алфавита, цифры, точка и слеш описываются символьным классом:
Слева должны идти
Но обратите внимание на то, что скобка в регулярных выражениях является спецсимволом группировки, поэтому чтобы она стала символом, надо перед ней поставить другой спецсимвол — слеш.
Справа от относительного пути должна стоять закрывающаяся круглая скобка. Это условие описывается при помощи позитивной опережающей проверки: Как видите, перед одной из скобок стоит слеш, что означает, что она интепретируется не как спецсимвол, а как литерал. Ниже приведен полный код на PHP, который выполняет все действия, кроме вопроса о разрешении использовать контент:
preg_match_all("/(?<=url\()[a-z.\/]*(?=\))/i", $content, $matches);
Когда и почему не следует использовать регулярные выраженияЭта заметка написана по мотивам обсуждений на одном из форумов и призвана насторожить программистов, чтобы они не почувствовали всемогущество регулярных выражений при работе со строковыми данными. Я попытаюсь рассказать, в каком случае стоит поискать более приемлемое решение своей задачи, чем использование регулярных выражений.
Описание проблемы поставленной на форуме было таким: есть некая страница, созданная в результате работы шаблонизатора, в ней присутствуют элементы типа Задача программиста: ему надо по результатам отправленной формы запомнить выделенные чекбоксы и при последующем обращении этого пользователя к этой странице выводить его выбор с уже отмеченными элементами.
Что он делает: вместо того, чтобы взять шаблон, выбрать из базы данные и на основе их отметить заново чекбоксы, прогнав шаблон через шаблонизатор, программист берет сохраненную в кеше страницу и парсит ее при помощи регулярных выражений, в зависимости от данных, которые он выбрал для данного пользователя из базы, он либо удаляет данные из страницы либо добавляет новые Его аргументация: так быстрее работает, чем обработка первоначального шаблона. Попробую привести свои аргументы против: смысл кешировать, если потом данные из кеша надо менять при помощи регулярных выражений? Кеш как раз и нужен для того, чтобы лишний раз не гонять ресурсоемкие процессы, такие как например обработка ВСЕГО текста регулярными выражениями... по нескольку раз. Представим себе текст, в котором n символов, а также регулярное выражение, в котором
Приведу эти шаги одновременно с аргументами против подхода, который хотел применить программист, которые из них вытекают:
Надеюсь, я смог объяснить, почему регулярные выражения, являсь мощным средством работы со строками не подходят для решения подобных глобальных задач, надо научиться ставить себе задачи и уметь решать ее несколькими способами, тогда у вас появится выбор в сторону лучшего способа решения. Регулярные выражения — это всего лишь один из многих способов, но никак не панацея.
В заметке описан случай, когда человек, знал как пользоваться регулярными выражениями, а также знал, как использовать в своей работе шаблонизатор, т.е. у него был выбор, он просто не мог остановиться на одном из вариантов. Есть категория начинающих программистов, которая увидев, кучу значков, при помощи которых решаются много проблем со строками, воздвигают себе кумира в виде статуи этому "волшебному средству" и даже не догадываются, что их задача по замене слова на другое слово в строке решается простой функцией str_replace(). Не понимая, как работают регулярные выражения, они приходят на форум и спрашивают, как заменить abcd на asdf при помощи |
||