【问题标题】:PHP DOMDocument failing to handle utf-8 characters (☆)PHP DOMDocument 无法处理 utf-8 字符 (☆)
【发布时间】:2012-07-03 18:48:17
【问题描述】:

网络服务器正在使用 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,我需要更具体多少?

【问题讨论】:

标签: php utf-8 domdocument


【解决方案1】:

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

HTML 根据其规范默认使用ISO-8859-1 编码(ISO 拉丁字母编号 1)。那是因为更长,请参阅6.1. The HTML Document Character Set。实际上,这更多的是普通网络浏览器中对Windows-1252 的默认支持。

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

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

你的字符串是UTF-8 编码的。将所有高于 127 / h7F 的字符转换为 HTML Entities 就可以了。如果您不想自己做,那就是 mb_convert_encodingHTML-ENTITIES 目标编码所做的:

  • 那些具有命名实体的角色,将获得命名实体。 € -&gt; &amp;euro;
  • 其他人得到他们的数字(十进制)实体,例如☆ -&gt; &amp;#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;

无论如何,这只是为了更深入地了解您的字符串。你想让它要么转换成loadHTML 可以处理的编码。这可以通过将 US-ASCII 之外的所有内容转换为 HTML 实体来完成:

$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">

这是一个指定字符集的 Content-Type。对于无法通过网络服务器获得的 HTML 字符串(例如,保存在磁盘上或在您的示例中的字符串中),这也是最佳实践。网络服务器通常将其设置为响应标头。

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

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

根据 HTML 2.0 规范,只能出现在文档的 &lt;head&gt; 部分的元素将被自动放置在那里。这也是这里发生的事情。输出(漂亮的打印):

<!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>

【讨论】:

  • @hakre:太完美了!你解决了我的严重问题,现在我没有头疼了!
  • +1 很好的答案,但您推荐哪种方法 - 使用 mb_convert_encoding() 或在 loadHTML() 中添加元标记?
  • @Nate:我会说这取决于。我通常不推荐mb_convert_encoding(),但对于这种情况,我会以某种方式推荐。但是,这是个人喜好的细节。它仍然取决于您是想在自己的步骤中进行转换,还是只想将其粉碎到DOOMDocument::loadHTML() 中,这会将元元素泄漏到文档中。例如,如果该元素已经存在,我不知道会发生什么。我从未将其测试到保存点,但它通常“正常工作”(tm)。答案中的不同方式更多是为了解释。
  • 对于使用替代方法的任何人,我建议查看下面 DeZeA 的答案,因为它没有从 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

【讨论】:

  • 这比 hakre 添加元标记的版本更好,因为添加元从 html 标记中删除了类
  • 嗯,可能是.. 我在 txt 中有代码,里面有一堆有用的 sn-ps。尽管这是 DOMDocument 类的一些非常标准的用法,但我并不认为这是一些原创的东西。
【解决方案3】:
<?php
  header("Content-type: text/html; charset=utf-8");
  $html = <<<HTML
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Test!</title>
</head>
<body>
    <h1>☆ Hello ☆ World ☆</h1>
</body>
</html>
HTML;

  $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8");
  $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>&#9734; Hello &#9734; World &#9734;</h1>
</body></html>

【讨论】:

  • @powtac:这些变体实际上不需要header 行。所有不属于 us-ascii 的字符都是这里的实体。除非您指定不共享 us-ascii 的(错误)编码,否则地球上的任何浏览器都将始终正确显示此内容。但请注意,它也没有错。
猜你喜欢
  • 2017-01-02
  • 2015-12-28
  • 1970-01-01
  • 1970-01-01
  • 2020-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多