在PHP中使用htmlentities但保留HTML标签

59
我希望将字符串中的所有文本转换为HTML实体,但保留HTML标记,例如以下内容:
<p><font style="color:#FF0000">Camión español</font></p>

should be translated into this:

<p><font style="color:#FF0000">Cami&oacute;n espa&ntilde;ol</font></p>

有什么想法吗?


7
谢谢你的好问题,我也很想知道!+1 - Mr. Smith
3
实际上,我会说这是一个错误的问题。为什么你想要逃避那些人物呢? - Josh Davis
可能存在这样的需求(我喜欢Peter的回答),但是提出这个问题让我立刻怀疑OP是否存在字符编码不匹配的问题(通常是UTF-8与ISO-8859-1之间的区别),应该优先解决它,而不是试图隐藏在HTML中定义有实体引用的少量字符(相对于Unicode选择)。 - bobince
7个回答

69

你可以使用get_html_translation_table函数来获取被htmlentities使用的字符与实体对应关系列表;考虑下面的代码:

$list = get_html_translation_table(HTML_ENTITIES);
var_dump($list);

你可能需要在手册中检查该函数的第二个参数 - 也许你需要将其设为与默认值不同的值。

这将会得到类似于这样的结果:

array
  ' ' => string '&nbsp;' (length=6)
  '¡' => string '&iexcl;' (length=7)
  '¢' => string '&cent;' (length=6)
  '£' => string '&pound;' (length=7)
  '¤' => string '&curren;' (length=8)
  ....
  ....
  ....
  'ÿ' => string '&yuml;' (length=6)
  '"' => string '&quot;' (length=6)
  '<' => string '&lt;' (length=4)
  '>' => string '&gt;' (length=4)
  '&' => string '&amp;' (length=5)

现在,删除您不想要的对应项:

unset($list['"']);
unset($list['<']);
unset($list['>']);
unset($list['&']);

现在,您的列表包含htmlentites使用的所有字符=>实体的对应关系,除了您不想编码的几个字符。

现在,您只需要提取键和值的列表:

$search = array_keys($list);
$values = array_values($list);

最后,您可以使用str_replace进行替换:

$str_in = '<p><font style="color:#FF0000">Camión español</font></p>';
$str_out = str_replace($search, $values, $str_in);
var_dump($str_out);

你将获得:

string '<p><font style="color:#FF0000">Cami&Atilde;&sup3;n espa&Atilde;&plusmn;ol</font></p>' (length=84)

看起来这就是你想要的 ;-)


编辑: 好吧,除了编码问题(该死的UTF-8,我猜测--我正在尝试找到解决方法,并会再次编辑)

几分钟后第二次编辑:看起来你需要在调用str_replace之前,在$search列表上使用utf8_encode :-(

也就是说,要使用类似于以下的内容:

$search = array_map('utf8_encode', $search);

在调用 array_keys 和调用 str_replace 之间。

这一次,你应该真正得到你想要的:

string '<p><font style="color:#FF0000">Cami&oacute;n espa&ntilde;ol</font></p>' (length=70)


以下是完整的代码段:

$list = get_html_translation_table(HTML_ENTITIES);
unset($list['"']);
unset($list['<']);
unset($list['>']);
unset($list['&']);

$search = array_keys($list);
$values = array_values($list);
$search = array_map('utf8_encode', $search);

$str_in = '<p><font style="color:#FF0000">Camión español</font></p>';
$str_out = str_replace($search, $values, $str_in);
var_dump($str_in, $str_out);

完整的输出如下:

string '<p><font style="color:#FF0000">Camión español</font></p>' (length=58)
string '<p><font style="color:#FF0000">Cami&oacute;n espa&ntilde;ol</font></p>' (length=70)

这次应该没问题了 ^^
它可能不能适用于单行,也许不是最优化的解决方案; 但它应该可以正常工作,并且具有允许您添加/删除所需或不需要的任何对应字符=>实体的优点。

玩得开心!


4
哇,回答得很好呢,排版也很棒。如果可以的话我会给你+3分数的 ;) - casraf
+1 for the utf-8 part. 最初使用的是 strtr,但这破坏了编码。 - Horen
这被称为“令人敬畏的!” - Anand Singh
htmlspecialchars_decode( htmlentities( html_entity_decode( $string ) ) ); htmlspecialchars_decode( htmlentities( html_entity_decode( $string ) ) ); - aequalsb
这个在PHP 5.4中停止工作,因为get_html_translation_table现在默认返回UTF-8。如果需要,您可以指定不同的编码,但只需从此答案中删除utf8_encode 即可使其重新工作 - IMSoP

19

可能不是非常高效,但它能正常工作

$sample = '<p><font style="color:#FF0000">Camión español</font></p>';

echo htmlspecialchars_decode(
    htmlentities($sample, ENT_NOQUOTES, 'UTF-8', false)
  , ENT_NOQUOTES
);

7
这是被接受答案的优化版本。
$list = get_html_translation_table(HTML_ENTITIES);
unset($list['"']);
unset($list['<']);
unset($list['>']);
unset($list['&']);

$string = strtr($string, $list);

更加优化的代码:$list = get_html_translation_table(HTML_ENTITIES); unset($list['"'], $list['<'], $list['>'], $list['&']); echo strtr($val, $list); - Dmitri Zaitsev

5

除了解析器,没有其他方法能够适用于所有情况。您的情况很好:

<p><font style="color:#FF0000">Camión español</font></p>

但是您是否还想支持以下内容:
<p><font>true if 5 < a && name == "joe"</font></p>

您希望它输出的位置:

<p><font>true if 5 &lt; a &amp;&amp; name == &quot;joe&quot;</font></p>

问题:在构建HTML之前,您能否进行编码?换句话说,您可以像这样做:

"<p><font>" + htmlentities(inner) + "</font></p>"

如果您能够做到这一点,将会减少很多麻烦。如果不能,您需要找到一种方法来跳过编码 <, >, 和 "(如上所述),或者直接对其进行编码,然后撤销它(例如:replace('&lt;', '<')


5

无需翻译表或自定义函数的一行解决方案:

我知道这是一个老问题,但最近我不得不将一个静态网站导入到WordPress网站中,并且需要解决这个问题:

以下是我的解决方案,无需调整翻译表:

htmlspecialchars_decode( htmlentities( html_entity_decode( $string ) ) );

当应用于OP的字符串时:

<p><font style="color:#FF0000">Camión español</font></p>

输出:

<p><font style="color:#FF0000">Cami&oacute;n espa&ntilde;ol</font></p>

当应用于Luca的字符串时:

<b>Is 1 < 4?</b>è<br><i>"then"</i> <div style="some:style;"><p>gain some <strong>€</strong><img src="/some/path" /></p></div>

输出:

<b>Is 1 < 4?</b>&egrave;<br><i>"then"</i> <div style="some:style;"><p>gain some <strong>&euro;</strong><img src="/some/path" /></p></div>

2
我发现这个简单的代码确实可以正常运行。通常最好向下滚动以查看重新聚焦回答而不是所选答案。 - Arie
这应该只是 htmlspecialchars_decode( htmlentities( $string ) ); - 如果您不删除第三个函数调用,输入字符串 <p>The HTML you want is "1 &gt; 0"</p> 将变成 <p>The HTML you want is "1 > 0"</p>,这是不正确的,可能会存在安全漏洞。 - M Somerville
@M Somerville -- 不正确... 请先完整阅读OP的需求。重点是将可能已经编码的实体字符串转换为应该存在的HTML实体字符串,同时保持HTML标记--因此您必须使用html_entity_decode()--您能解释一下将HTML转换的安全风险吗?例如,没有提到公共表单提交...边缘情况不在本帖子的范围内--也许您可以开始一个解决您所指出问题的帖子。 - aequalsb

3
这是我刚写的一个函数,以非常优雅的方式解决了这个问题:
首先,从字符串中提取HTML标签,然后对每个剩余的子字符串执行htmlentities(),最后将原始HTML标签插入到它们的旧位置,从而不会改变HTML标签。:-)
玩得开心:
function htmlentitiesOutsideHTMLTags ($htmlText)
{
    $matches = Array();
    $sep = '###HTMLTAG###';

    preg_match_all("@<[^>]*>@", $htmlText, $matches);   
    $tmp = preg_replace("@(<[^>]*>)@", $sep, $htmlText);
    $tmp = explode($sep, $tmp);

    for ($i=0; $i<count($tmp); $i++)
        $tmp[$i] = htmlentities($tmp[$i]);

    $tmp = join($sep, $tmp);

    for ($i=0; $i<count($matches[0]); $i++)
        $tmp = preg_replace("@$sep@", $matches[0][$i], $tmp, 1);

    return $tmp;
}

感谢分享您的解决方案!如果您不介意,我对您的代码进行了一些更改,请查看我的答案。 - Luca Borrione

2

根据bflesch的答案,我对代码进行了一些更改以处理包含小于号大于号单引号双引号的字符串。

function htmlentitiesOutsideHTMLTags ($htmlText, $ent)
{
    $matches = Array();
    $sep = '###HTMLTAG###';

    preg_match_all(":</{0,1}[a-z]+[^>]*>:i", $htmlText, $matches);

    $tmp = preg_replace(":</{0,1}[a-z]+[^>]*>:i", $sep, $htmlText);
    $tmp = explode($sep, $tmp);

    for ($i=0; $i<count($tmp); $i++)
        $tmp[$i] = htmlentities($tmp[$i], $ent, 'UTF-8', false);

    $tmp = join($sep, $tmp);

    for ($i=0; $i<count($matches[0]); $i++)
        $tmp = preg_replace(":$sep:", $matches[0][$i], $tmp, 1);

    return $tmp;
}



使用示例:

$string = '<b>Is 1 < 4?</b>è<br><i>"then"</i> <div style="some:style;"><p>gain some <strong>€</strong><img src="/some/path" /></p></div>';
$string_entities = htmlentitiesOutsideHTMLTags($string, ENT_QUOTES | ENT_HTML401);
var_dump( $string_entities );

输出结果为:
string '<b>Is 1 &lt; 4?</b>&egrave;<br><i>&quot;then&quot;</i> <div style="some:style;"><p>gain some <strong>&euro;</strong><img src="/some/path" /></p></div>' (length=150)



您可以根据htmlentities手册传递任何ent标志


感谢您让我更接近解决方案,但是我使用了您的解决方案与以下字符串,这并不完全符合我的要求 -<a href="http://google.com">google</a><p>这是段落</p> <aeiou>指定的无效标记应按原样显示</aeiou> <b>粗体</b> 小于号 - aaaaa<pppppp 所有特殊字符 = `,./;'[]~!@#$%^&*()_+{}|:"<>? 所有<b>字符</strong> - Raj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接