Поиск и замена текста между тегами на PHP

Поиск и замена текста между тегами на PHP
Комментарии: 23

Рассмотрим примеры обработки и замены текста между HTML тегами, используя PHP функции для поиска и подмены по регулярным выражениям. Для примера возьмем абстрактный кусок кода, над которым будем проводить различные действия.

... <xx>наташа</xx> ... <xx>даша</xx> ... <xx>настя</xx> ...

На месте <xx> может быть любой тег, а троеточие обозначает любой произвольный текст.

Поиск текста функцией «preg_match_all»

Для поиска текста внутри тегов воспользуемся функцией «preg_match_all». Зададим маску поиска и посмотрим, что она возвращает в качестве результата.

$sContent = "... <xx>наташа</xx> ... <xx>даша</xx> ... <xx>настя</xx> ...";
if (preg_match_all('|<xx>(.+)</xx>|isU', $sContent, $arr)) { 
  echo $arr[0][0] . " " . $arr[0][1] . " " . $arr[0][2] . "<br />";
  echo $arr[1][0] . " " . $arr[1][1] . " " . $arr[1][2];
}

//на выходе получаем:
//<xx>наташа</xx> <xx>даша</xx> <xx>настя</xx>
//наташа даша настя

В нулевой разряд массива записались значения с тегами, а в первый — только текст между ними. Если требуется автоматизировать вывод всего найденного, то лучше использовать цикл foreach. Его рассмотрим ниже.

Функция preg_match_all возвращает «1» в случае нахождения в тексте соответствия с указанной маской или «0», если соответствий не найдено. В качестве параметров принимает маску, строку где ищем и переменную, в которую будут записаны найденные совпадения.

Маска поиска обрамляется символами «|». За ними идут директивы — «isU» обозначает регистронезависимый поиск в многострочном тексте с кодировкой «UTF-8»

|<xx>(.+)</xx>|isU

В самом правиле содержатся теги, между которыми требуется заменить текст — «(.+)». Точка символизирует любой символ, а плюс — что он может повторяться один или больше раз. Скобки говорят о том, что содержимое между ними нужно записать в переменную с результатом.

Перебор найденных результатов в цикле «foreach»

Для вывода результатов поиска можно воспользоваться циклом «foreach».

$sContent = "... <xx>Наташа</xx> ... <xx>Марина</xx> ... <xx>Настя</xx> ...";

if (preg_match_all('|<xx>(.+)</xx>|isU', $sContent, $arr)) { 

  foreach ($arr[0] as $value) echo $value." ";
  echo "<br/>";
  foreach ($arr[1] as $value) echo $value." ";

} 

На выходе получаем тоже самое что и в предыдущем примере, зато теперь мы автоматизировали перебор массива и сократили код.

Отличие «preg_match» от «preg_match_all»

Функция «preg_match» осуществляет поиск только до первого соответсвия с маской. Как только что-то найдено — поиск останавливается и возвращается одномерный массив.

if (preg_match('|<title>(.+)</title>|isU', $sContent, $arr))
return $arr[1]; else return false;

Здесь нулевой элемент массива «$arr» содержит найденное совпадение вместе с тегами «title», а первый элемент — «$arr[1]» только текст между этими тегами. Если в строке несколько тегов «title», это не значит что остальные значения будут записаны в «$arr[2]» и так далее. Элемент «$arr[2]» окажется не пуст если в маске указано несколько правил, но об этом в следующий раз.

Замена текста между тегами функцией «preg_replace»

Если требуется найти и произвести замену найденных элементов в строке, то на помощь приходит PHP функция «preg_replace».

Заменим в нашем примере все имена между тегами на какое-то конкретное, например — «Оля».

$sContent = "<xx>наташа</xx> ... <xx>даша</xx> ... <xx>настя</xx>";
$sContent = preg_replace('|(<xx>).+(</xx>)|isU', "$1"."Оля"."$2",$sContent);

Замена тегов, оставляя всё, что находится внутри

А теперь небольшой пример, показывающий как заменить определенные теги, сохранив содержимое между ними. Допустим, надо изменить в html коде все «strong» на CSS форматирование.

$sContent = "... <strong>Настя</strong> ...";

$sContent = preg_replace('|<strong(.*)strong>|isU', '<span style="font-weight: bold;" $1span>', $sContent);

//на выходе получаем $sContent:
//... <span style="font-weight: bold;" >Настя</span> ...

Обработка и замена при помощи «preg_replace_callback»

Переходим к самому интересному. Если нужно над найденным фрагметом произвести какие-то действия и только потом осуществить замену, то следует использовать «preg_replace_callback». Рассмотрим как с помощью этой функции в именах сделать первую букву заглавной.

<html>
<head> <meta charset="UTF-8"> </head>
<body>

<?php 
$sContent = "<xx>наташа</xx> ... <xx>даша</xx> ... <xx>настя</xx>";

echo htmlspecialchars($sContent); echo "<br />";

$sContent = preg_replace_callback('|(<xx>)(.+)(</xx>)|iU', function($matches){
	$matches[2] = mb_substr(mb_strtoupper($matches[2], 'UTF-8'),0,1,'UTF-8').substr($matches[2], 2);
	return $matches[1].$matches[2].$matches[3];
}
,$sContent);

echo htmlspecialchars($sContent);
?>

</body>
</html>

В качестве параметров передаём маску поиска, функцию с кодом обработки и строковую переменную в которой осуществляем поиск. Дополнительно могут ещё быть заданы два необязательных параметра. О них в следующем разделе статьи.

Переменная «$matches» это массив, содержащий элементы регулярного выражения. В нулевом элементе будет содержаться вся исходная строка, а в остальных — содержимое скобок.

Код обработки не описываю, но отмечу что для замены первой буквы на заглавную я использую PHP функции для работы со строками в UTF-8 кодировке. Если у Вас кодировка cp1251, то нужно отбросить префикс «mb_» и удалить последний параметр у функций.

ВНИМАНИЕ! Код в примере будет работать только при использовании PHP версии 5.3 и выше. Для более поздних версий требуется доработка.

Использование нумирации в заменах и другие продвинутые возможности

Теперь немного о продвинутых возможностях функции «preg_replace_callback». Ранее я упоминал что у неё есть два необязательных параметра. Первый (по умолчанию равен «-1») содержит максимальное количество замен, которое должна произвести функция. Второй — переменная, в которую будет записано количество произведенных замен.

$sContent = preg_replace_callback('|(<xx>)(.+)(</xx>)|iU', 
    function($matches){ //тут код }
,$sContent,2,$count);

Задав эти два параметра в предыдущем примере, замена главной буквы будет произведена только у первых двух имён. Соответственно, переменная «$count» будет содержать — 2. Если установить первый дополнительный параметр в «-1», то «$count» будет — 3.

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

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

<?php
$str = '<h2>Марина</h2> <b>Алёша</b> <h2>Наташа</h2> <h2>Катя</h2>';

$str = preg_replace_callback('|<h2>(.+)</h2>|iU', function($matches){
	static $id = 0;
	$id++;
	return '<h2 id="uniq-'.$id.'">'.$matches[1].'</h2>';
}, $str,-1,$count);

echo $str.' Количество замен: '.$count;
?>

Объявляя статическую переменную нужно помнить что она сохраняет своё значение между вызовами функции, поэтому идеально подходит для решения нашей задачи.

Темы:
PHP
Ещё интересное — 5
Комментарии —
  1. Анастасия
    Анастасия20 ноября 2015, 01:02#
    Здравствуйте, а можете помочь?
    Есть функция:
    function ingredients($ingr){  
    $ingr = Preg_Replace_Callback(
        '!<ul>(.*?)</ul>!si', 
        Create_Function(
            '$matches',
            'Return Str_Replace("<li>", "<li itemprop=\"ingredients\">", $matches[0]);'  
        ),
    	$ingr
    );
     return $ingr;      
    }
    add_filter('the_content', 'ingredients');
    заменяет внутри маркированного списка, надо переделать так, чтоб заменяла li внутри блока div class=«ingr», в который будет заключаться этот список
    1. Andy Si20 ноября 2015, 22:07#
      Вообще это какой-то костыль получается, но, видимо, надо вместо
      '!<ul>(.*?)</ul>!si',
      написать
      '!<div class="ingr"><ul>(.*?)</ul></div>!si',
      Хотя более точно можно сказать только увидев весь исходник
      1. Vetal
        Vetal20 апреля 2018, 00:21#
        Подскажите, нужно заменить все ссылки на
        1. Andy Si20 апреля 2018, 09:23#
          Для вставки html тегов в комментарии есть спец. поле. Вверху над окном с сообщением.
    2. Sim
      Sim01 ноября 2016, 03:33(был изменён)#
      В примере:
      $sContent = "<xx>наташа</xx> ... <xx>даша</xx> ... <xx>настя</xx>";
      $sContent = preg_replace('|(<xx>)(.+)(</xx>)|isU', "$1"."Оля"."$2",$sContent);
      В последняя строке вместо $2 должна быть $3 :)

      $sContent = preg_replace('|(<xx>)(.+)(</xx>)|isU', "$1"."Оля"."$3",$sContent);
      Если вместо ограничивающих символов || использовать //, то почему-то приходится экранировать слеш перед xx, т.е. строка будет такой:

      $sContent = preg_replace('/(<xx>)(.+)(<\/xx>)/isU', "$1"."Оля"."$3",$sContent);
      Интересно, почему? А, понял — чтобы дать понять, где заканчивается само регулярное выражение :) Тогда вопрос: а какие ограничивающие символы вообще можно использовать? Видел — используют ##, ||, //. Где бы почитать про них?
      1. Andy Si01 ноября 2016, 09:13(был изменён)#
        Да, действительно. Поправлю. По поводу #, /, |. Ещё есть знак %. И они ничем не отличаются, а много разных разделителей именно для того чтобы можно было выбирать.

        Если в регулярном выражении присутствуют проценты и слеши, то обрамляем решеткой или прямым слешем. Если решетки внутри, то обрамляем любым вариантом, кроме решетки. Или экранировать, как в Вашем случае.
      2. Sim
        Sim01 ноября 2016, 10:16#
        Спасибо за ответ! Я нашёл Вашу страничку через поисковик по запросу, как избавиться от предупреждения «preg_replace(): The /e modifier is deprecated». Я знаю, что нужно вместо preg_replace использовать preg_replace_callback. Но у меня, какой-то вообще очень тяжёлый случай, что боюсь сюда даже выкладывать. Не поможете разобраться? Уже вторые сутки бьюсь :), даже теорию пришлось выучить про preg_replace и preg_replace_callback :)
        1. Andy Si01 ноября 2016, 14:24#
          На почту пришли что есть и что надо, посмотрю.
        2. Sim
          Sim02 ноября 2016, 18:52#
          Хорошо, если сам не справлюсь… Кажется я нашёл ещё одну ошибку. В предложении: «Директива «isU» обозначает регистронезависимый поиск в многострочном тексте с кодировкой «UTF-8».»

          за UTF-8 отвечает маленькая u, а большая U — позволяет обрабатывать всю строку, а не только первое найденное выражение. Или по-научному — «Этот модификатор инвертирует жадность квантификаторов, таким образом они по умолчанию не жадные.»
          1. Sim
            Sim02 ноября 2016, 22:56#
            У меня вопрос. Можно ли для preg_replace_callback в качестве первого и второго аргументов использовать array по схеме, как для preg_replace?

            Похоже, что нельзя, т.к. call_back-функции через массив передавать нельзя — http://us2.php.net/manual/ru/language.types.callable.php
            1. Andy Si13 ноября 2016, 21:14#
              Что-то уведомлений не получал о новых сообщения. Но всё равно не помог бы, так как не знал)
            2. Sim
              Sim02 ноября 2016, 23:40(был изменён)#
              за UTF-8 отвечает маленькая u, а большая U — позволяет обрабатывать всю строку, а не только первое найденное выражение. Или по-научному — «Этот модификатор инвертирует жадность квантификаторов, таким образом они по умолчанию не жадные.»
              Точнее не так…

              Если стоит большая U, то в строке "<xx>наташа</xx> ... <xx>даша</xx> ... 
              <xx>настя</xx>" шаблону (<xx>)(.+)(</xx>) соответствует три варианта:
              1. <xx>наташа</xx>
              2. <xx>даша</xx>
              3. <xx>настя</xx>
              
              а если не стоит большая U, то  в строке "<xx>наташа</xx> ... <xx>даша</xx>
               ... <xx>настя</xx>" шаблону (<xx>)(.+)(</xx>) соответствует один вариант:
              1. <xx>наташа</xx> ... <xx>даша</xx> ... <xx>настя</xx>
              
              здесь:
              <xx> - это <xx>
              (.+) - это наташа</xx> ... <xx>даша</xx> ... <xx>настя
              </xx> - это </xx>
              1. Agata
                Agata25 августа 2017, 20:44#
                Помогите. span class:'tra la la' dir:'tra la la' >текст который нужно поменять< span. Буду ооочень благодарна,!#
                1. Andy Si27 августа 2017, 22:51#
                  Наверно скобки обрезались. Надо в комментариях заключать их в тег code. Могу предположить что так:
                  $str = preg_replace('|(<span class:".+" dir:".+">)([^<]+)(</span>)|iU','$1здесь на что меняем$3');
                2. Dimas
                  Dimas19 сентября 2019, 13:57#
                  Доброго времени суток, прошу помощи, а как составить регулярку если нужно выбрать все теги по маске? У вас например наташа, а мне нужно например <xx=1>Наташа? Все что вышло у меня — '|(<xx(.*?)}).+({/xx})|isU' — но он оставляет при замене "=1"
                  1. Andy Si19 сентября 2019, 14:24#
                    Я бы помог, но не понимаю что есть изначально и что должно получиться.
                  2. Ярослав
                    Ярослав26 января 2020, 13:40(был изменён)#
                    скажите, а можно как то с помощью preg_replace
                    <xx>даша</xx> заменить
                     на <xx>любые символы между тегами<title><title/></xx>
                    1. Andy Si26 января 2020, 14:10#
                      Можно, но я не совсем понял что нужно.
                      Просто между title меняется так:
                      preg_replace('|<title>(.+)</title>|isU', '<title>любые символы</title>',$sContent))
                      Ну а любые символы можно сначала получить, используя preg_match
                    2. Ярослав
                      Ярослав26 января 2020, 14:18(был изменён)#
                      Нет, у меня есть закрытый код который я править не могу,
                      $html = preg_replace( "/". $rep[0], $rep[1], $html )
                      только в preg_replace могу вставить данные. А мне надо все что находится в теге тайл перенести в
                      <xx>даша</xx>
                      .Это возможно вообще?
                      1. Денис
                        Денис29 января 2020, 16:03#
                        Подскажите, как удалить первый найденный тег h4 и его сожержимое?
                        1. Andy Si29 января 2020, 16:42#
                          Попробуй так:
                          $sContent = preg_replace_callback('|<h4>[^<]+</h4>|iU', function($matches){
                          	return '';
                          }
                          ,$sContent, 1, $count);
                          Это если между h4 только текст, без доп. тегов. Если есть другие теги, то просто .+ поставь.
                        2. болбес
                          болбес12 октября 2021, 18:04#
                          а можите помочь есть код $content  там теги
                           <td class="node_first">doctor.ru</td>
                          хочется видеть 
                          <td class="node_first"><img src="http://www.yandex.ru/cycounter?e-doctor.ru" border="0" width="88" height="31" alt="Анализ интернет сайта" />doctor.ru</td>
                          и тегов много всё поменять бы
                          1. Andy Si12 октября 2021, 19:40#
                            Наверно, достаточно обычную замену сделать без регулярных выражений:
                            $content = str_replace("doctor.ru", '<img src="http://www.yandex.ru/cycounter?e-doctor.ru" border="0" width="88" height="31" alt="Анализ интернет сайта" />doctor.ru', $content);
                          © REALADMIN.RU   2024 г.
                          Страница сгенерирована: 0,1944 s | 6 mb.
                          На каком уровне Вы играете в шахматы?
                          OPROS