RealAdmin.ru

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

PHP
Категория: Кодинг
24 Окт 2013 г. в 23:14

Сегодня постараюсь объяснить как найти, обработать и заменить текст между тегами используя PHP функции. На первый взгляд простая задача, тем более в PHP есть специально предназначенные для этого функции, позволяющие использовать для поиска и замены регулярные выражения. Чтобы было проще разобраться, будем всё делать на примерах. За основу возьмем абстрактный html код.

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

Здесь <xx> — представляет какой-то конкретный html тег, а троеточие — другие произвольные теги. Предлагаю постепенно начинать разбираться с возможностями PHP по работе со строками. Все пункты будем рассматривать на примерах.

Поиск текста функцией «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];
  echo "<br />";
  echo $arr[1][0]." ".$arr[1][1]." ".$arr[1][2];

}

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

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

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

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

Маска поиска текста между тегов обрамляется символом «|». Таких правил в маске может быть несколько. Для нашей задачи достаточно одного.

В правиле содержатся теги, между которыми требуется заменить текст — «(.+)». Регулярное выражение указывает на то, что между ними может быть любое количество любых символов. Знак плюс означает что их должно быть больше нуля. То есть, если между тегами ничего нет, то результата никакого не получим. Если хотим найти даже те места, где между тегов нет текста, то вместо плюса ставим знак звёздочки — «*». Директива «isU» обозначает регистронезависимый поиск в многострочном тексте с кодировкой «UTF-8».

Перебор найденных результатов в цикле «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» ищет только до первого совпадения с маской поиска. Как только что-то найдено, поиск останавливается. Так же разница в выдаваемом результате. «preg_match» возвращает одномерный массив. Вот пример.

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

В этом случае нулевой элемент массива «$arr» содержит найденное совпадение вместе с тегами «title», а первый элемент — «$arr[1]» только текст между этими тегами. И не путайте, если в искомом коде несколько тегов «title», это не значит что остальные значения будут записаны в «$arr[2]» и так далее. В случае с «preg_match» поиск идет только до первого найденного совпадения, а элемент «$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;
?>

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

ВНИМАНИЕ! Дополнительные параметры в «preg_replace_callback» появились начиная с PHP версии 5.1

Теги:
PHP
Посмотрите похожее — 4
Комментарии — 10
  1. avatar Анастасия 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. avatar Simkin Andrew 20 ноября 2015, 22:07 #
      Вообще это какой-то костыль получается, но, видимо, надо вместо
      '!<ul>(.*?)</ul>!si',
      написать
      '!<div class="ingr"><ul>(.*?)</ul></div>!si',
      Хотя более точно можно сказать только увидев весь исходник
    2. avatar Sim 01 ноября 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. avatar Simkin Andrew 01 ноября 2016, 09:13(был изменён) #
        Да, действительно. Поправлю. По поводу #, /, |. Ещё есть знак %. И они ничем не отличаются, а много разных разделителей именно для того чтобы можно было выбирать.

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

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

            Похоже, что нельзя, т.к. call_back-функции через массив передавать нельзя — http://us2.php.net/manual/ru/language.types.callable.php
            1. avatar Simkin Andrew 13 ноября 2016, 21:14 #
              Что-то уведомлений не получал о новых сообщения. Но всё равно не помог бы, так как не знал)
            2. avatar Sim 02 ноября 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>
              © REALADMIN.RU   2016 г.
              Страница сгенерирована: 0,1170 s | 10 mb.
              На каком уровне Вы играете в шахматы?
              О П Р О С
              Home Question Top