Как заменить несколько экземпляров меньше чем <в строке php, которая также использует strip_tags? - PullRequest
2 голосов
/ 24 июня 2019

У меня есть следующая строка, хранящаяся в таблице базы данных, которая содержит HTML, который мне нужно вырезать перед рендерингом на веб-странице (это старое содержимое, которое я не мог контролировать).

<p>I am <30 years old and weight <12st</p>

Когда я использовал strip_tags, он показывает только I am.

Я понимаю, почему это делает strip_tags, поэтому мне нужно заменить2 экземпляра < с &lt;

Я нашел регулярное выражение, которое преобразует первый экземпляр, но не второй, но я не могу понять, как изменить это, чтобы заменить все экземпляры.

/<([^>]*)(<|$)/

, что приводит к I am currently &lt;30 years old and less than

У меня есть демо здесь https://eval.in/1117956

Ответы [ 4 ]

2 голосов
/ 25 июня 2019

Плохо пытаться анализировать html-контент с помощью строковых функций, включая функции регулярных выражений (есть много тем, которые объясняют это в SO, ищите их).html слишком сложен, чтобы сделать это.

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

  • Ничего не поделаешь: данные повреждены, поэтому информация теряется раз и навсегда, и вы не можете восстановить то, что исчезло, вот и все.Это совершенно приемлемая точка зрения.Может быть, вы можете найти другой источник для тех же данных где-нибудь или вы можете распечатать плохо отформатированный html как он есть.
  • Вы можете попытаться восстановить.В этом случае вы должны убедиться, что все проблемы с документами ограничены и могут быть решены (по крайней мере вручную).

Вместо прямого строкового подхода вы можете использовать реализацию PHP libxml черезDOMDocument.Даже если синтаксический анализатор libxml не даст лучших результатов, чем strip_tags, он предоставляет ошибки, которые вы можете использовать для определения типа ошибки и поиска проблемных позиций в строке html.

С вашей строкой, libxmlparser возвращает исправляемую ошибку XML_ERR_NAME_REQUIRED с кодом 68 на каждой проблемной угловой скобке открытия.Ошибки можно увидеть, используя libxml_get_errors().

Пример с вашей строкой:

$s = '<p>I am <30 years old and weight <12st</p>';

$libxmlErrorState = libxml_use_internal_errors(true);

function getLastErrorPos($code) {
    $errors = array_filter(libxml_get_errors(), function ($e) use ($code) {
        return $e->code === $code;
    });

    if ( !$errors )
        return false;

    $lastError = array_pop($errors);
    return ['line' => $lastError->line - 1, 'column' => $lastError->column - 2 ];
}

define('XML_ERR_NAME_REQUIRED', 68); // xmlParseEntityRef: no name

$patternTemplate = '~(?:.*\R){%d}.{%d}\K<~A';

$dom = new DOMDocument;
$dom->loadHTML($s, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);

while ( false !== $position = getLastErrorPos(XML_ERR_NAME_REQUIRED) ) {
    libxml_clear_errors();
    $pattern = vsprintf($patternTemplate, $position);

    $s = preg_replace($pattern, '&lt;', $s, 1);
    $dom = new DOMDocument;
    $dom->loadHTML($s, LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
}

echo $dom->saveHTML();

libxml_clear_errors();
libxml_use_internal_errors($libxmlErrorState);

demo

$patternTemplate - это отформатированная строка (см. sprintf в руководстве по php), в котором заполнители %d обозначают соответственно количество строк до и позицию от начала строки.(0 и 8 здесь)

Детали шаблона: Цель шаблона - достичь положения угловой скобки от начала строки.

~ # my favorite pattern delimiter
  (?:
      .* # all character until the end of the line
      \R # the newline sequence
  ){0} # reach the desired line

  .{8} # reach the desired column
  \K   # remove all on the left from the match result
  <    # the match result is only this character
~A # anchor the pattern at the start of the string

Другой связанный вопрос, в котором яиспользовал похожую технику: разобрать неверный XML вручную

0 голосов
/ 24 июня 2019

Было бы просто использовать str_replace().

  1. Заменить <p> and </p> на [p] and [/p]
  2. заменить < на &lt;
  3. верните p-теги назад, т.е. замените [p] and [/p] на <p> and </p>

Код

<?php
$description = "<p>I am <30 years old and weight <12st</p>";

$d = str_replace(['[p]','[/p]'],['<p>','</p>'], 
            str_replace('<', '&lt;', 
                str_replace(['<p>','</p>'], ['[p]','[/p]'], 
                    $description)));

echo $d;

RESULT

<p>I am &lt;30 years old and weight &lt;12st</p>
0 голосов
/ 24 июня 2019

Я предполагаю, что здесь мы могли бы разработать хорошую правую границу для захвата < в не тегах, возможно, в простом выражении, похожем на:

<(\s*[+-]?[0-9])

может сработать, поскольку у нас обычно должны быть цифры или знаки сразу после <. [+-]?[0-9] скорее всего изменится, если у нас будут другие экземпляры после <.

Демо

Test

$re = '/<(\s*[+-]?[0-9])/m';
$str = '<p>I am <30 years old and weight <12st I am <  30 years old and weight <  12st I am <30 years old and weight <  -12st I am <  +30 years old and weight <  12st</p>';
$subst = '&lt;$1';

$result = preg_replace($re, $subst, $str);

echo $result;
0 голосов
/ 24 июня 2019

попробуйте

$string = '<p>I am <30 years old and weight <12st</p>';
$html = preg_replace('/^\s*<[^>]+>\s*|\s*<\/[^>]+>\s*\z/', '', $string);// remove html tags
$final = preg_replace('/[^A-Za-z0-9 !@#$%^&*().]/u', '', $html); //remove special character

Live DEMO

...