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

Александр Кичатов

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

Допустим, у нас есть следующий текст:

$text = '<p>Ищем <span>эту</span> и <span>может быть эту</span> строки в тексте.</p>';

И из него нужно достать текст, который находится между тегами <span> и </span>.

Проще всего это сделать с помощью регулярных выражений:

$text = '<p>Ищем <span>эту</span> и <span>может быть эту</span> строки в тексте.</p>';

if(preg_match_all('|<span>(.*)</span>|Uis', $text, $result))
{
    foreach($result[1] as $span_text)
        echo $span_text . '<br>';
}
else
    echo 'Совпадений нет';

Функция preg_match_all() принимает 3 параметра: шаблон поиска, сам текст и переменную, в которую эта функция сохранит результаты поиска.

Поскольку функция возвращает количество найденных строк (или false в случае ошибки), мы можем сразу подставить её в оператор if.

Массив с результатами поиска (в нашем случае $result) состоит из двух частей: в $result[0] будут найденные строки вместе с открывающим и закрывающим тегами span, а в $result[1] будут те же строки без тега span, т.е. тот текст, что находится в круглых скобках.

Маска регулярного выражения находится между вертикальными чертами |. В шаблоне (.*) точка означает любой символ, звёздочка - любое количество символов (т.е. суммарно получаем "любое количество любых символов").

Скобки говорят, что найденный текст нам нужно получить отдельно. Без скобок мы получим только $result[0], а $result[1] не будет существовать.

Чтобы найти только не пустые теги, можно заменить .* на .+. Плюсик означает любое количество символов, но не меньше одного.

Uis - модификаторы. U означает работу с UTF-8, i - регистронезависимый поиск, s - что символ точка включает в себя переносы строк, т.е. поиск будет по всем строкам, а не по одной.

Простая замена текста или тегов (preg_replace)

Заменить текст без замены тегов можно следующим образом:

$text = '<p>Строки <strong>один</strong> и <strong>два</strong> в тексте.</p>';

$new_text = preg_replace('|(<strong>).*(</strong>)|Uis', '$1три$2', $text);

echo $new_text;
// Выведет: <p>Строки <strong>три</strong> и <strong>три</strong> в тексте.</p>

$1 и $2 содержат открывающий и закрывающий теги соответственно, поскольку мы поместили их в скобки.

А в следующем примере меняются только теги, сам текст остаётся нетронутым:

<?php
$text = '<p>Строки <strong>один</strong> и <strong>два</strong> в тексте.</p>';

$new_text = preg_replace('|<strong>(.*)(</strong>)|Uis', '<span>$1</span>', $text);

echo $new_text;
// <p>Строки <span>один</span> и <span>два</span> в тексте.</p>

Замена текста собственной функцией (preg_replace_callback)

Самое вкусное. Допустим, мы хотим использовать на сайте что-то вроде BBCode, т.е. собственные теги, которые потом должны заменяться на обычный HTML код:

<div>
    {h1}Заголовок{/h1}
</div>

Заменить тег {h1} на обычный HTML тег <h1> можно так:

<?php
$text = '<div>{h1}Заголовок{/h1}</div>';

$new_text = preg_replace_callback('|{h1}(.*){/h1}|Uis', function($matches) {
    return '<h1>' . $matches[1] . '</h1>';
}, $text);

echo $new_text;

Функция preg_replace_callback передаёт каждую найденную строку в нашу безымянную функцию, затем заменяет найденный текст на то, что наша функция возвращает.

Не знаком с безымянными функциями? Тогда можно сделать так:

function replaceH1($matches) {
    return '<h1>' . $matches[1] . '</h1>';
}

$text = '<div>{h1}Заголовок{/h1}</div>';

$new_text = preg_replace_callback('|{h1}(.*){/h1}|Uis', 'replaceH1', $text);

echo $new_text;

Вторым параметром передаём название нашей функции. Код отработает точно также, как и предыдущий.

Вывод фрагментов исходного HTML и PHP кода

Частая проблема разработчиков, которым хочется вести свой блог. Есть HTML статья, внутри которой некоторые фрагменты кода нужно прогонять через htmlspecialchars(), чтобы они выводились как обычный текст:

$text ='
<h2>Пример кода</h2>
{code}<strong>Этот текст не должен быть жирным</strong>{/code}
<p>Какое-то описание кода выше.</p>
{code}<strong>Второй не жирный текст</strong>{/code}
';

Теперь в этом нет ничего сложного:

$text = '
<h2>Пример кода</h2>
{code}<strong>Этот текст не должен быть жирным</strong>{/code}
<p>Какое-то описание кода выше.</p>
{code}<strong>Второй не жирный текст</strong>{/code}
';

$new_text = preg_replace_callback('|{code}(.*){/code}|Uis', function($matches) {
    return htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8');
}, $text);

echo $new_text;

Продвинутый BBCode с атрибутами

Иногда описанного выше функционала бывает недостаточно, например если нужно передать в функцию какие-либо параметры:

{link url="/some_url" title="Заголовок ссылки"}текст ссылки{/link}

Вместо собственного велосипеда рекомендую использовать готовую библиотеку Shortcode.

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

use Thunder\Shortcode\ShortcodeFacade;

$text = 'текст с тегами';

$shortcode = new ShortcodeFacade();
$shortcode->addHandler('link', function($s) {
    $content = $s->getContent();
    $url = $s->getParameter('url');
    $title = $s->getParameter('title');

    if(!$title)
        $title = 'Стандартный заголовок';

    // Ещё какие-нибудь действия

    return $content;
});

$new_text = $shortcode->process($text);

Только нужно учесть, что в Shortcode по-умолчанию парсятся атрибуты в квадратных скобках []. Я и сам использую квадратные, но в примерах использовал фигурные, чтобы избежать возможных конфликтов тегов на этом сайте.

Комментарии