首页 > 解决方案 > 如何在也使用strip_tags的php字符串中替换多个小于<的实例?

问题描述

我将以下字符串存储在包含 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

标签: phpregexpreg-replacepcre

解决方案


尝试使用字符串函数解析 html 内容是一个坏主意,包括正则表达式函数(有很多主题可以解释这一点,请搜索它们)。html 太复杂了,无法做到这一点。

问题是您无法控制的 html 格式不正确。有两种可能的态度:

  • 没有什么可做的:数据已损坏,因此信息一劳永逸地丢失,您无法检索已经消失的东西,仅此而已。这是一个完全可以接受的观点。可能您可以在某处找到相同数据的另一个来源,或者您可以选择打印格式不佳的 html。
  • 你可以尝试修复。在这种情况下,您必须确保所有文档问题都受到限制并且可以解决(至少可以手动解决)。

代替直接字符串方法,您可以通过DOMDocument. 即使 libxml 解析器不会给出比 更好的结果strip_tags,它也会提供错误,您可以使用它来识别错误类型并在 html 字符串中找到有问题的位置。

使用您的字符串,libxml 解析器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);

演示

$patternTemplate是一个格式化字符串(参见sprintfphp 手册),其中占位符%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


推荐阅读