PHP DOMDocument 無法處理 utf-8 字符 (☆)你好†你好†世界†

2022-08-30 10:08:06

Web服务器使用utf-8编码提供响应,所有文件都使用utf-8编码保存,并且我所知道的设置的所有内容都已设置为utf-8编码。

下面是一个快速程序,用于测试输出是否正常工作:

<?php
$html = <<<HTML
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Test!</title>
</head>
<body>
    <h1>☆ Hello ☆ World ☆</h1>
</body>
</html>
HTML;

$dom = new DOMDocument("1.0", "utf-8");
$dom->loadHTML($html);

header("Content-Type: text/html; charset=utf-8");
echo($dom->saveHTML());

该程序的输出为:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Test!</title></head><body>
    <h1>&acirc;&#152;&#134; Hello &acirc;&#152;&#134; World &acirc;&#152;&#134;</h1>
</body></html>

其呈现为:

你好†你好†世界†


我做错了什么?我必须更具体多少才能告诉 DOMDocument 正确处理 utf-8?


答案 1

DOMDocument::loadHTML() 需要一个 HTML 字符串。

HTML根据其规范使用编码(ISO拉丁字母No. 1)作为默认值。这已经更长了,见6.1。HTML 文档字符集。实际上,这更像是普通Web浏览器的默认支持。ISO-8859-1Windows-1252

我之所以回到那么远,是因为PHP的DOMDocument是基于libxml的,这带来了为HTML 4.0设计的HTMLparser

我想说的是,可以安全地假设您可以加载编码的字符串。ISO-8859-1

您的字符串已编码。将所有高于127 / h7F的字符转换为HTML实体,就可以了。如果您不想自己执行此操作,则使用目标编码即可执行以下操作:UTF-8mb_convert_encodingHTML-ENTITIES

  • 那些具有命名实体的字符将获得命名实体。€ -> &euro;
  • 其他人获得他们的数字(十进制)实体,例如☆ -> &#9734;

下面是一个代码示例,它通过使用回调函数使进度更加明显:

$html = preg_replace_callback('/[\x{80}-\x{10FFFF}]/u', function($match) {
    list($utf8) = $match;
    $entity = mb_convert_encoding($utf8, 'HTML-ENTITIES', 'UTF-8');
    printf("%s -> %s\n", $utf8, $entity);
    return $entity;
}, $html);

字符串的此示例输出:

☆ -> &#9734;
☆ -> &#9734;
☆ -> &#9734;

无论如何,这只是为了更深入地研究你的字符串。您希望将其转换为可以处理的编码。这可以通过将 外部的所有内容转换为 HTML 实体来完成:loadHTMLUS-ASCII

$us_ascii = mb_convert_encoding($utf_8, 'HTML-ENTITIES', 'UTF-8');

请注意,您的输入实际上是 UTF-8 编码的。如果您甚至有混合编码(某些输入也可能发生这种情况)则每个字符串只能处理一种编码。我已经在上面概述了如何在正则表达式的帮助下更具体地进行字符串替换,因此我现在留下更多详细信息。mb_convert_encoding

另一种方法是提示编码。这可以通过修改文档并添加

<meta http-equiv="content-type" content="text/html; charset=utf-8">

这是指定字符集的内容类型。对于无法通过Web服务器获得的HTML字符串(例如,保存在磁盘上或字符串中,如示例中所示),这也是最佳实践。Web 服务器通常将其设置为响应标头。

如果您不关心放错位置的警告,则可以将其添加到字符串前面:

$dom = new DomDocument();
$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">'.$html);

根据HTML 2.0规范,只能出现在文档部分中的元素将自动放置在那里。这里也发生了这种情况。输出(漂亮打印):<head>

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta charset="utf-8">
    <title>Test!</title>
  </head>
  <body>
    <h1>☆ Hello ☆ World ☆</h1>    
  </body>
</html>

答案 2

有一个更快的修复方法,在DOMDocument中加载html文档后,您只需设置(或更好地说重置)原始编码即可。下面是一个示例代码:

$dom = new DOMDocument();
$dom->loadHTML('<?xml encoding="UTF-8">' . $html);

foreach ($dom->childNodes as $item)
    if ($item->nodeType == XML_PI_NODE)
        $dom->removeChild($item);
$dom->encoding = 'UTF-8'; // reset original encoding

推荐