PHP 7: простая замена с помощью регулярного выражения
В интернете много курсов по регулярным выражениям, но все они написаны слишком сложно даже для человека, имеющего большой опыт в разработке сайтов ) Попробую исправить эту ситуацию, возьму живой пример из своей работы и напишу просто — о сложном.
Самый частый случай использования регулярок в моих проектах — какое-то преобразование контента на сервере перед передачей клиенту. Например, есть картинка с подписью alt:
<img src="file.jpg" alt="подпись">
Нужно вытащить подпись из тега alt, поставить её под картинкой, и обернуть всё это дело в div.
Вот, что должно получиться на выходе:
<div><img alt="подпись" src="file.jpg"><span>подпись</span></div>
Цветом помечены те кусочки, которые мы взяли из первой фразы в неизменном виде. Как видим, в выходной конструкции «подпись» использована два раза, а свойства поменяны местами — это значит, что обычной заменой str_replace данная задача никак не решается )
Если вам сразу нужна готовая функция из моего блога, которая это делает, то вот она. А далее разберём, как она работает.
function regrepl_img($in) {
$out=preg_replace_callback(
"/(<image src=")(.*?)(" alt=")(.*?)(">)/",
function($m) {
return '<div><img alt="'.$m[4].'" src="'.$m[2].'"><span>'.$m[4].'</span></div>';
},$in);
return $out;
}
Функция preg_replace_callback — стандартная функция языка PHP для работы с регулярными выражениями. Она работает аналогично str_replace, но имеет более широкие возможности. Однако суть у неё та же: на входе — то, что мы ищем, а на выходе — то, на что нужно заменить. Самый сложный этап — составление шаблона для поиска, его мы разберём максимально подробно. А вот замена — это просто, и о ней будет только пара слов в конце статьи.
Как составить шаблон поиска?
Представим, что мы сами решаем данную задачу, и опишем наши действия простыми словами. Но сначала ещё раз повторим исходный текст:
<img src="file.jpg" alt="подпись">
Итак, нам нужно:
1) найти в тексте все подобные конструкции
2) вытащить имя файла
3) вытащить подпись из тега alt
Имя файла находится между двумя кусками текста:
текст слева | имя файла | текст справа |
file.jpg |
Чтобы найти имя файла, нужно найти один из этих кусков текста, например, левый:
<img src=" |
На языке регулярных выражений это выглядит как тот же самый текст, заключённый в скобки:
(<image src=") |
Затем найти правый кусок:
" alt=" |
Или на языке регулярных выражений:
(" alt=") |
А то, что окажется между этими кусками — и будет искомым именем файла. Мы не знаем, какая длина имени файла, и какие могут быть символы в имени файла. На языке регулярных выражений это записывается вот так:
(.*?) |
— это означает «любое количество любых символов».
Вот такой шаблон пока что получается:
(<image src=") | (.*?) | (" alt=") |
Буквально это означает «найти в тексте левый кусок, затем двигаться вправо, пропуская любое количество любых символов, пока мы не встретим правый кусок, и в итоге запомнить оба этих куска и то, что находится между ними». Нам доподлинно неизвестно, как именно машина будет искать текст — слева направо или справа налево, или прыгать из одного места в другое. Но при составлении и проверке условий проще всего считать, что поиск идёт слева направо. На самом деле есть и прямые указания, в каком направлении искать, строго или нестрого, но это выходит за рамки данной статьи.
Текст из тега alt ищется аналогично — по двум соседним известным кускам текста. В итоге получается вот такой шаблон поиска, который я разбил на 5 частей для удобства понимания:
(<image src=") | (.*?) | (" alt=") | (.*?) | (">) |
А ниже - то, что находит функция по данным условиям: | ||||
file.jpg | подпись | "> |
Пять конструкций в круглых скобках — это ключевые фразы, которые машина пытается найти в исходном тексте. У каждой фразы есть начало и конец, или какие-то иные признаки, которые мы задаём в условиях. Если мы не зададим каких-то конкретных условий, то поиск работать не будет.
Грубо говоря, нам нужно разрезать текст на пять частей.
Где резать? — Определяют условия поиска, заданные в шаблоне.
Ключевых фраз может быть сколько угодно, но важно уметь их правильно составить, чтобы однозначно указать машине на нужные нам фрагменты текста, и при этом чтобы условий не получилось слишком много. Работа с регулярными выражениями — не самый экономичный процесс, и это может ухудшить производительность системы.
В дополнение я составил табличку для тех людей, которым удобнее воспринимать табличные данные:
Разъясняющая табличка
Условие | (<img src=") | (.*?) | (" alt=") | (.*?) | (">) |
Объяснение | Ищем ключевую фразу | Если нашли, далее движемся вправо по тексту, и пропускаем любое количество любых символов, пока не встретим следующую ключевую фразу | Вот эту: | Далее опять пропускаем любое кол-во символов до следующей ключевой фразы | Вот этой: "> |
Результат | file.jpg | подпись | "> |
Итак, верхняя строка таблицы - это условие поиска (<image src=")(.*?)(" alt=")(.*?)(">), разбитое на пять столбиков.
А нижняя строка — исходный текст, разбитый на пять частей, в соответствии с условиями, которые заданы в первой строке. Это промежуточный этап работы функции preg_replace_callback. Эти пять значений записаны в массив, и можно использовать их на своё усмотрение.
Массив внутри функции preg_replace_callback можно обозначить как угодно. Пусть будет $m. Вот, что в него попало (та самая нижняя строчка таблицы):
$m[1] = <img src=";
$m[2] = file.jpg;
$m[3] = " alt=";
$m[4] = подпись;
$m[5] = ">;
Больше всего нас интересуют элементы $m[2] и $m[4], в которых хранится имя файла и подпись картинки.
Подытожим: на входе функции — пять ключевых фраз, объясняющих и задающих параметры поиска, а на выходе — пять соответствующих найденных элементов (кусков исходного текста). У вас их может быть не пять, а любое другое количество.
Замена
Далее нужно составить конструкцию, которую будет возвращать функция замены.
function($m) {
return '<div><img alt="'.$m[4].'" src="'.$m[2].'"><span>'.$m[4].'</span></div>';
}
И мы получаем то, что нужно:
<div><img alt="подпись" src="file.jpg"><span>подпись</span></div>
При составлении подобных замен важно учитывать, что поиск может зациклиться. Чтобы надёжно избежать зацикливания, я всегда делаю так, чтобы повторная замена точно не сработала. Например, можно изменить порядок свойств — было АБВ, стало АВБ, — и тогда строгое соответствие при потенциально повторяющемся поиске уже не сработает.
Спасибо за внимание )
P.S. Прошу прощения у true программистов, которым данная статья показалась нестрогой или неправильной. Написана она, в первую очередь, для новичков и для таких же лентяев, как я )
Смотрите также
Комментарии
Дальнейшие комментарии закрыты. По всем вопросам пишите или звоните мне лично, см. раздел Контакты