PHP DomDocument无法处理utf-8字符(☆)

Gre*_*reg 52 php utf-8 domdocument

网络服务器使用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());
Run Code Online (Sandbox Code Playgroud)

该计划的输出是:

<!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>
Run Code Online (Sandbox Code Playgroud)

其呈现为:

Hello你好 世界††


我能做错什么?我需要告诉DomDocument正确处理utf-8还有多少具体内容?

hak*_*kre 112

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

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

我回到目前为止,因为PHP的DOMDocument基于libxml并且带来了为HTML 4.0设计的HTMLparser.

我会说可以安全地假设你可以加载一个ISO-8859-1编码的字符串.

您的字符串已UTF-8编码.将所有高于127/h7F的字符转换为HTML实体,你没事.如果你不想这样做你自己,这就是mb_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);
Run Code Online (Sandbox Code Playgroud)

此字符串的示例输出:

? -> &#9734;
? -> &#9734;
? -> &#9734;
Run Code Online (Sandbox Code Playgroud)

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

$us_ascii = mb_convert_encoding($utf_8, 'HTML-ENTITIES', 'UTF-8');
Run Code Online (Sandbox Code Playgroud)

请注意您的输入实际上是UTF-8编码的.如果你有甚至混合编码(可能会发生一些输入)mb_convert_encoding只能处理每个字符串一个编码.我已经在上面概述了如何在正则表达式的帮助下更具体地进行字符串替换,所以我现在留下更多细节.

另一种方法是提示编码.这可以通过修改文档并添加一个来完成

<meta http-equiv="content-type" content="text/html; charset=utf-8">
Run Code Online (Sandbox Code Playgroud)

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

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

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

根据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>
Run Code Online (Sandbox Code Playgroud)

  • @hakre:那太棒了!你解决了我的严重问题,现在我没有头痛! (2认同)

小智 15

有一个更快的解决方案,在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
Run Code Online (Sandbox Code Playgroud)

  • 嗯,这个答案就像一个似曾相识 - http://stackoverflow.com/a/10834989/367456 (6认同)

Vla*_*ili 11

<?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());
Run Code Online (Sandbox Code Playgroud)

输出:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Test!</title></head><body>
    <h1>&#9734; Hello &#9734; World &#9734;</h1>
</body></html>
Run Code Online (Sandbox Code Playgroud)

  • @powtac:这些变体实际上不需要`header`行.所有不属于us-ascii的字符都是这里的实体.除非您指定(错误的)编码不共享us-ascii,否则地球上的任何浏览器都将始终正确显示.但只是注意到,它也没有错. (2认同)