И так, не маловажная тема, которая, порой, может вставить палки в колёса, особенно по незнанию. В принципе, я не открою заново Америку этой статьёй, в Интернете достаточно много подобной информации, но вся она разрознена, поэтому в этой статье попытаюсь собрать всё воедино.
Регулярные выражения нужны для функций preg_, в частности мы рассмотрим это на функции preg_match, которую и рекомендуют использовать в таких случаях для поиска соответствий в силу её высокого быстродействия.
1. Вид функции для поиска соответствий
Формат функции такой:
preg_match (патерн, строка для поиска, массив результатов);
Патерн (регулярное выражение или regexp) — собственно то, что мы научимся составлять без труда и любой сложности по прочтению всей этой статьи до конца.
Строка для поиска — любые данные, в которых мы будем что-то искать.
Массив результатов — необязательный параметр, нужен только, если нам надо что-то выделить при поиске. Если не указывать его, то при соответствии функция возвращает TRUE, иначе FALSE.
2. Составление регулярных выражений
И так, собственно то, ради чего эта статья. Регулярное выражение имеет вид
‘/набор/модификаторы’
2.1 Модификаторы
Модификаторы расширяют возможность поиска по строке. В строке модификаторов игнорируются пробелы, неверный символ вызывает ошибку типа warning. Вот все известные модификаторы:
i – регистро-независимый поиск по строке. Регистр букв не учитывается, что строчные, что прописные становятся равнозначны.
примеры:
preg_match (‘/upper/’, ‘UPPER CASE’); – вернёт FALSE
preg_match (‘/upper/i’, ‘UPPER CASE’); – вернёт TRUE
m – достаточно эксцентричный модификатор, имеющий специфическое применение. По умолчанию установка в набор символов ^ или $ рассматривается как поиск ОТ начала или ДО конца ОДНОЙ СТРОКИ. Т.е. если строка для поиска форматированный текст с переносами и указаны признаки поиска от начала или до конца, то без этого модификатора поиск будет произведён только по первой строке данных, все остальные строки будут проигнорированы. Если в наборе нет конкретной привязки к началу или к концу строки, то использование этого модификатора не имеет смысла.
примеры:
preg_match (‘/^fird.*/’, ‘first line
second line
fird line
fourth line’); – вернёт FALSE
preg_match (‘/^fird.*/m’, ‘first line
second line
fird line
fourth line’); – вернёт TRUE
s – если установлен этот модификатор, то все точки в наборе соответствуют не только любому символу, но и символу новой строки.
примеры:
preg_match (‘/test.test/’, ‘test@test’); – вернёт TRUE
preg_match (‘/test.test/’, ‘test-test’); – вернёт TRUE
preg_match (‘/test.test/’, «test\ntest»); – вернёт FALSE
preg_match (‘/test.test/s’, ‘test@test’); – вернёт TRUE
preg_match (‘/test.test/s’, ‘test-test’); – вернёт TRUE
preg_match (‘/test.test/s’, «test\ntest»); – вернёт TRUE
x – этот модификатор принуждает полностью игнорировать все пробелы в наборе и не считать их за элемент набора, только если пробел не экранирован или не стоит внутри классов.
примеры:
preg_match (‘/t e s t/’, ‘t e s t’); – вернёт TRUE
preg_match (‘/t e s t/’, ‘test’); – вернёт FALSE
preg_match (‘/t e s t/x’, ‘t e s t’); – вернёт FALSE
preg_match (‘/t e s t/x’, ‘test’); – вернёт TRUE
preg_match (‘/t\ e\ s\ t/x’, ‘test’); – вернёт FALSE
preg_match (‘/t\ e\ s\ t/x’, ‘t e s t’); – вернёт TRUE
e – применим только к функции preg_replace() и заставляет эту функцию обрабатывать полученное значение как php скрипт, экранируя любые кавычки обратной косой чертой.
примеры:
Преобразование всех тегов в HTML документе в верхний регистр:
$html = ‘<select><option value=»all»>Everything</option></select>’;
preg_replace (‘/(<\/?)(\w+)([^>]*>)/e’, «‘\\1’.strtoupper(‘\\2’).’\\3′», $html);
вернёт:
<SELECT><OPTION value=\»all\»>Everything</OPTION></SELECT>
S – этот модификатор следует использовать для наборов, которые применимы многократно и ускоряет процесс поиска.
примеры:
preg_match_all (‘/[\w]/’, $string, $matches); — выполняется за 0.17 секунд
preg_match_all (‘/[\w]/S’, $string, $matches); — выполняется за 0.15 секунд
U – интересный модификатор для поиска неопределённых значений. Если в наборе задан поиск *, чего угодно, то даже при первом нахождении такого условия php всё равно продолжит поиск до конца переменной для нахождения наилучшего, по его мнению, соответствия. Это ненужная работа, и дополнительная ненужная нагрузка, которая побеждается этим модификатором.
примеры:
preg_match (‘/foo(.*)bar/’, ‘foobar foo—bar fubar’); — успокоится только по нахождению «bar foo—bar fu»
preg_match (‘/foo(.*)bar/U’, ‘foobar foo—bar fubar’); — будет достаточно и первого слова
Все ненужные, на мой взгляд, модификаторы не описаны, ненужные – те которых смысл кроится где-то в самом укромном уголку мозга их создателей и даже придумать какой-то пример не хватает интеллекта.
2.2 Принципы составления регулярных выражений.
Я не просто так сначала написал о модификаторах, теперь зная, как можно расширить набор для поиска вы сможете найти оптимальный.
И так, приступим. На самом деле в составлении регулярного выражения нет ничего сложного, главное понять, что к чему. Регулярное выражение указывается в кавычках, сам набор для поиска указывается между двумя косыми, после второй из которых идут модификаторы, ‘/набор условий/ модификаторы’. Поехали.
^ – этот символ имеет 2 значения, в зависимости от того, где он стоит. Если этот символ стоит в начале набора, он символизирует, что поиск соответствия нужно начинать с самого начала СТРОКИ (помните пример с модификатором m?), многие ошибочно считают, что с начала данных для поиска.
примеры:
‘/^test/’ найдёт слово test в ”test it”, но не найдёт его в ”for test”, потому что первое слово явно не test.
‘/test/’ найдёт слово test в обоих случаях, так как нет привязки к началу строки.
Если этот символ стоит внутри класса, то означает отрицание
примеры:
‘/[^test]/’ будет истинно для всего чего угодно, но только не test.
‘/^[^test]/’ а это означает, всё что угодно с начала строки, но только не test.
$ – этот символ (доллар) признак конца документа, если стоит в самом конце набора и не экранирован.
примеры:
‘/^test$/’ не найдёт слово test ни в ”test it”, ни в ”for test”, потому что стоит жёсткая привязка к концу и к началу строки. По этому выражению поиск будет удачным, если строка состоит из единственного слова test.
‘/test$/’ найдёт слово test в ”for test”, это конец документа и соблюдение нашего условия.
Поиграли со словами, поискали соответствия и несоответствия в начале и в конце документа, захотелось больше информации. Пожалуйста.
Теперь переходим к классам. Класс – это сложное условие, заключённое в квадратные скобки, которые вы уже видели раньше.
[] – обозначение сложного условия. Мы применяли ранее его для обозначения отрицания, но возможности его велики.
примеры:
[ABCD] будет истинно, если есть хоть одна их этих букв. Классы можно делать сквозными, то есть это же условие эквивалентно [A-D]. Заметьте, что знак «минус» внутри класса обозначает промежуток, если он стоит вне класса, он расценивается как элемент для поиска.
preg_match (‘/[A-D]/’, ‘testB’); – вернёт TRUE, так же как и для testA, testC, testD или в любой другой строке, где есть A, B, C, D независимо от места их расположения в строке. Если добавить модификатор i, то мы так же сможем успешно искать и a, b, c, d.
[a-zA-Zа-яА-Я] класс, который будет истинной ко всем буквам.
[0-9] класс для всех цифр.
Но есть уже заранее предопределённые классы в php:
\w – все буквы
\d – все цифры. Ещё иногда обозначается [[:digit:]], но \d однозначнее приятнее выглядит
\W – отрицание класса букв, то есть все, что не буквы
\D – отрицание класса цифр
\s – пробел, знак табуляции, символ новой строки, в общем любой разделитель.
\S – под этот класс подходят все «видимые» символы.
Вы обратили внимание на обратную косую черту? А зря. Этот знак в начале чего-либо вызывает экранирование, то есть чтобы обозначить, что дальше после него что-то будет… как вы уже поняли, буквы экранировать не надо, а надо экранировать так называемые мета-символы. Неэкранированные мета-символы являются системными, будьте внимательны! То есть если мы напишем \[ это уже не будет означать начало класса, а просто поиск [.
Запомните, все классы работают по условию ИЛИ.
. – точка. Вы уже читали ранее в модификаторах, что точка – это любой символ, кроме символа новой строки без соответствующего модификатора.
примеры:
preg_match (‘/a.cd/’, ‘azcd’); – вернёт TRUE, как и для любого ”a*cd”
{} – это признак количества или квантатор. Квантатор символизирует количество предшествующего элемента. Может указывать на чёткое количество искомых элементов, либо может состоять из двух чисел, разделённых запятой.
примеры:
preg_match (‘/a{4}/’, ‘a’); – вернёт FALSE
preg_match (‘/a{4}/’, ‘aaaa’); – вернёт TRUE, но и для любого ”aaaaaaaaaaaaaaaa”, пока нет конкретной привязки к чему либо
preg_match (‘/b{2,4}/’, ‘ab’); – вернёт FALSE, b должно быть от 2 до 4 по количеству
preg_match (‘/^b{2,4}$/’, ‘bbb’); – вернёт TRUE, так как уже есть привязка к началу и концу строки, то “bbbbbbbbbbb” вернёт FALSE, и будет только истинно для строки с b в количестве от 2 до 4.
preg_match (‘/^b{2,}$/’, ‘bbbbbbbbbbbbb’); – вернёт TRUE, так как не указано конечное количество и такой квантатор читается как не менее.
* – квантатор, который символизирует любое количество, вплоть до полного отсутствия элемента.
примеры:
preg_match (‘/abc*d/’, ‘abcd’); – вернёт TRUE
preg_match (‘/abc*d/’, ‘abcccccccccccccccccd’); – вернёт TRUE
preg_match (‘/abc*d/’, ‘abd’); – вернёт TRUE, потому что “c” как бы присутствует в количестве 0.
+ – квантатор, который символизирует количество, не менее одного.
примеры:
preg_match (‘/abc+d/’, ‘abcd’); – вернёт TRUE
preg_match (‘/abc+d/’, ‘abcccccccccccccccccd’); – вернёт TRUE
preg_match (‘/abc+d/’, ‘abd’); – вернёт FALSE, потому что “c” отсутствует.
Поиграли с количеством и букв и цифр и даже слов, понравилось что стало наконец всё получаться. Теперь настало время перейти к изучению альтернатив.
| – символ, символизирующий о наличии нескольких возможных вариантов.
примеры:
preg_match (‘/very nice|good/’, ‘very nice’); – вернёт TRUE
preg_match (‘/very nice|good/’, ‘very good’); – вернёт TRUE
preg_match (‘/very nice|good/’, ‘very cool’); – вернёт FALSE, потому что слово “cool” в альтернативах нет
? – символизирует о необязательном присутствии предшествующего элемента.
примеры:
preg_match (‘/colou?r/’, ‘color’); – вернёт TRUE
preg_match (‘/colou?r/’, ‘colour’); – вернёт TRUE
preg_match (‘/colouu?r/’, ‘color’); – вернёт FALSE
preg_match (‘/colouu?r/’, ‘colour’); – вернёт TRUE
() – Буферизация. Выражение, которое заключено в круглые скобки, буферизируется в дополнительную переменную, если нужно собрать какие-то данные. По выполнению функции на выходе мы имеем массив, первое (с индексом 0) значение – это соответствие условию, последующие – те значения, которые мы отметили для буферизации.
примеры:
preg_match (‘/colou?r\s(\w+)/’, ‘color black’, $matches);
$matches[1]; – вернёт “black”
preg_match (‘/\S+\s(\S+)\s\S+\s(\S+)/’, ‘name Vasya from Muhosransk’, $matches);
$matches[1]; – вернёт “Vasya”
$matches[2]; – вернёт “Muhosransk”
(?:) — Чтобы скобки нужны для выделения альтернатив или объединения проверки на существование группы символов и если в если эти данные не нужны в результатах буферизации, используется такой вид.
примеры:
preg_match_all (‘/^(?:https?\:\/\/)?(?:www\.)?(.*)/m’, «https://www.ptipti.ru\nhttp://ptipti.ru\nhttps://ptipti.ru\nwww.ptipti.ru\nptipti.ru», $matches);
Результатом, как и ожидалось, будет массив:
Array
(
[0] => Array
(
[0] => https://www.ptipti.ru
[1] => http://ptipti.ru
[2] => https://ptipti.ru
[3] => www.ptipti.ru
[4] => ptipti.ru
)
[1] => Array
(
[0] => ptipti.ru
[1] => ptipti.ru
[2] => ptipti.ru
[3] => ptipti.ru
[4] => ptipti.ru
)
)
Рассмотрим сложную конструкцию (?:https?\:\/\/)? детальнее. вопросик после скобок ()? говорит о том, что эта часть может как присутствовать, так и отсутствовать. Начало в скобках (?: говорит о том, что эту часть не надо будет буферизировать в результаты. Ну и самое простое s? означает, что этой буквы может и не быть. Очень важно самому не запутаться 🙂
(?!) и (?=) — Отрицательный и положительный вперёдсмотрящие.
Эта конструкция иногда необходима, для определения части обязательного текста или выражения.
примеры:
preg_match_all(‘/href\=(?:\’|\»)?((?:\/|[^\s\’\»]*ptipti.ru\/)(?!wp\-content|wp\-includes)[^\s\’\»]*)/i’, file_get_contents(‘http://ptipti.ru’), $matches);
Этот пример демонстрирует работу отрицательного вперёдсмотрящего (?!wp\-content|wp\-includes) и найдёт все ссылки с моей главной страницы за исключением тех, которые содержат wp-content или wp-includes. Обратите внимание, что вперёдсмотрящий стоит именно в месте, где предполагается наличие wp-content или wp-includes в ссылке.
Теперь пример с положительным вперёдсмотрящим (?=wp\-content|wp\-includes):
preg_match_all(‘/href\=(?:\’|\»)?((?:\/|[^\s\’\»]*ptipti.ru\/)(?=wp\-content|wp\-includes)[^\s\’\»]*)/i’, file_get_contents(‘http://ptipti.ru’), $matches);
Этот пример наоборот найдёт ссылки, которые будут иметь wp-content или wp-includes в ожидаемом месте ссылки.
(?<!) и (?<=) — Отрицательный и положительный назадсмотрящий
Эта конструкция схожа с предыдущей только с той разницей, что производит поиск перед найденной частью.
примеры:
preg_match_all(‘/^\w+(?<!John|Johny)\sBrown/mi’, «Eve Brown\nKate Brown\nJessie Brown\nJohn Brown\nLeslie Brown\nJohny Brown», $matches);
Из семейки Браунов это выражение найдёт всех, кто не John или не Johny. Дополнительный модификатор i исключает возможность сим товарищам прорваться, если они напишут себя с маленькой буквы.
Кстати, очень важное замечание. Вперёд- и назадсмотрящие могут содержать только фиксированный текст, максимум альтернативы, поэтому мы и использовали John|Johny, так просящаяся конструкция Johny? вызовет ошибку и работать не будет.
3. Примеры использования
Теперь знания закрепим практикой и рассмотрим некоторые «боевые» примеры использования регулярных выражений:
1. Считаем ссылки:
preg_match (‘/<a href=»([^»]+)»>([^<]+)<\/a>/’, ‘<a href=»http://ptipti.ru»>Сайт Pti_the_Leader</a>’, $matches);
результат выполнения получим
Array (
[0] => <a href=»http://ptipti.ru»>Сайт Pti_the_Leader</a>
[1] => http://ptipti.ru
[2] => Сайт Pti_the_Leader
)
Как мы описали ссылку:
/<a href=»([^»]+)»>([^<]+)<\/a>/’
Теперь давайте разберём эту паттерну. Понятно, что ссылка – это тег и формат его мы тоже знаем
<a href=»адрес»>анкор</a>
Нам надо будет запомнить отдельно адрес, отдельно анкор, выделяем их в скобки:
<a href=»(адрес)»>(анкор)</a>
Уже есть начало. Как же нам описать адрес? Ведь в нём может быть http и косые, а может и не быть… В общем можно используя все полученные знания привлечь, чтобы составить огромный паттерн… но мы пойдём другим путём. Посмотрим на атрибут ссылки href и повнимательнее. Он заключён в кавычки, это всегда так… первую кавычку мы не трогаем, тоже понятно, и надо найти всё, что угодно до второй кавычки… то есть вторая кавычка – это стоп, говорящий, что всё значение найдено. Вот давайте и обозначим ссылку, что это всё, что угодно, кроме кавычки:
<a href=»([^»]+)»>(анкор)</a>
Вот мы создали класс [], в котором минимум один символ + и всё что угодно, кроме кавычки ^”
Как теперь описать анкор? \w+? А пробелы? Сделаем опять точно так же, анкор стоит между >< значит первый > мы от него и начинаем поиск и до <,
<a href=»([^»]+)»>([^<]+)</a>
В итоге и получаем очень красивое регулярное выражение.
2. Считывание данных.
Вернёмся к примеру из буферизации:
preg_match (‘/\S+\s(\S+)\s\S+\s(\S+)/’, , $matches);
рассмотрим наш паттерн ‘/\S+\s(\S+)\s\S+\s(\S+)/’
Не разделитель больше одного, разделитель, (не разделитель больше одного), то есть Vasya, разделитель, не разделитель больше одного, разделитель, (не разделитель больше одного), то есть Muhosransk. Этот паттерн не универсален и будет работать только максимум со строкой, в которой разделители будут ещё или знаки табуляции или знаки новой строки. А если к тому же разделителей будет несколько, и нужные нам данные будут стоять, к примеру, через двоеточие? Понятное дело, что этой паттерной мы уже данные не сможем считать. Давайте опять подумаем, нам нужно считать именно слова, значит будем использовать \w класс и его отрицание.
‘/\w+\W+(\w+)\W+\w+\W+(\w+)/i’
Это более улучшенный вариант, который найдёт и Васю и его город проживания и в ”name Vasya from Muhosransk”, и в ”name : Vasya from — Muhosransk”, и даже в ”name : $+Vasya+$ from — —==Muhosransk==—”
4. Заключение
Как вы поняли, нет ничего сложного в составлении регулярных выражений. Зная все основы вы, теперь сможете составлять паттерны любой сложности. Был рад, если эта статья вам помогла.