PHP DOMDocument loadHTML无法正确编码UTF-8

263

我正在尝试使用DOMDocument解析一些HTML,但是当我这样做时,我的编码突然丢失了(至少在我看来是这样的)。

$profile = "<div><p>various japanese characters</p></div>";
$dom = new DOMDocument();
$dom->loadHTML($profile); 

$divs = $dom->getElementsByTagName('div');

foreach ($divs as $div) {
    echo $dom->saveHTML($div);
}

这段代码的结果是我得到了一堆不是日语的字符。然而,如果我这样做:

echo $profile;

它能正确地显示。 我尝试过saveHTML和saveXML,但都无法正确显示。 我正在使用PHP 5.3。

我看到的:

ã¤ãªãã¤å·ã·ã«ã´ã«ã¦ãã¢ã¤ã«ã©ã³ãç³»ã®å®¶åº­ã«ã9人åå¼ã®5çªç®ã¨ãã¦çã¾ãããå½¼ãå«ãã¦4人ã俳åªã«ãªã£ããç¶è¦ªã¯æ¨æã®ã»ã¼ã«ã¹ãã³ã§ãæ¯è¦ªã¯éµä¾¿å±ã®å®¢å®¤ä¿ã ã£ããé«æ ¡æ代ã¯ã­ã£ãã£ã®ã¢ã«ãã¤ãã«å¤ãã¿ãæè²è³éãåããªããã«ããªãã¯ç³»ã®é«æ ¡ã¸é²å­¦ã

需要展示什么内容:

イリノイ州シカゴにて、アイルランド系の家庭に、9人兄弟の5番目として生まれる。彼を含めて4人が俳優になった。父親は木材のセールスマンで、母親は郵便局の客室係だった。高校時代はキャディのアルバイトに勤しみ、教育資金を受けながらカトリック系の高校へ進学

编辑:我已将代码简化为五行以便您进行测试。

$profile = "<div lang=ja><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>";
$dom = new DOMDocument();
$dom->loadHTML($profile);
echo $dom->saveHTML();
echo $profile;

这是返回的HTML代码:

<div lang="ja"><p>イリノイ州シカゴã«ã¦ã€ã‚¢ã‚¤ãƒ«ãƒ©ãƒ³ãƒ‰ç³»ã®å®¶åº­ã«ã€</p></div>
<div lang="ja"><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>

这可能会对你有所帮助。http://stackoverflow.com/questions/1580543/php-japanese-strings-getting-set-to - frustratedtech
1
谢谢。我检查了所有这些,但没有帮助。我不会得到????,但是会出现其他奇怪的文本。我会尝试将其粘贴在这里,但不知道网站会如何显示它。 - Slightly A.
尝试使用 utf8_encode - Ben
尝试过了,但没有成功。返回的字符与之前相同。 - Slightly A.
11个回答

690

DOMDocument::loadHTML 会将您的字符串视为 ISO-8859-1(HTTP/1.1 默认字符集),除非您告诉它其他。这会导致 UTF-8 字符串被错误解释。

DOMDocument 使用的是 HTML4 解析器。如果您加载的是 HTML5,您可能需要查看 其他解决方案

如果您处理的是简单的 (X)HTML 片段,您可以在字符串前面添加一个 XML 编码声明或一个 meta charset 声明,以使其被视为 UTF-8:

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();

// This version preserves the original characters
$contentType = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
$dom->loadHTML($contentType . $profile);
echo $dom->saveHTML();

// This version will HTML-encode high-ASCII bytes
$dom->loadHTML('<meta charset="utf8">' . $profile);
echo $dom->saveHTML();

// This version will also HTML-encode high-ASCII bytes,
// and won't work for LIBXML_DOTTED_VERSION >= 2.12.0
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $profile);
echo $dom->saveHTML();

如果你无法确定HTML是否已经包含声明,那么在SmartDOMDocument中有一个解决方法可以帮助你:
$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));
echo $dom->saveHTML();

在PHP 8.2+版本中,你会收到一个废弃警告,因此可以选择以下替代方案:
$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML(mb_encode_numericentity($profile, [0x80, 0x10FFFF, 0, ~0], 'UTF-8'));
echo $dom->saveHTML();

(对于那个相当晦涩的数组的更好解释,请参见这里。)
这不是一个很好的解决办法,但由于并非所有字符都可以用ISO-8859-1表示(比如这些武士刀),这是最安全的替代方案。

3
是的,就是这样。谢谢你的帮助。我尝试了saveHTML、saveXML,没想到问题可能出现在加载过程中。 - Slightly A.
6
mb_convert_encoding函数对我起了作用,而在编码声明前加上内容则不行。这很可能是因为文档已经有了冲突的声明。非常感谢-这让我省了很多时间来追踪问题。 - Peter Bagnall
4
在PHP7中,$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content)让我解决了这个问题(所以它仍然是一个问题)。这个问题真的很烦人,因为我在HTML文档中定义了utf8(使用<meta charset="UTF-8" />),但没有效果,似乎还需要<?xml> 部分,这是完全不直观的。 - iquito
13
即使在2017年,这个答案仍然是相关的并且对我也有效。我已经将我的数据库、多字节、HTML meta标记和DOM编码都设置为utf8,但在将节点从一个文档导入到另一个文档时,仍然存在错误的编码。 http://php.net/manual/en/function.mb-convert-encoding.php 是解决方法。 - Louis Loudog Trottier
12
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8')); 运行良好!谢谢。 - vee
显示剩余16条评论

95
问题出在saveHTML()saveXML()上,它们在Unix系统中无法正常工作。当在Unix系统中使用时,它们无法正确保存UTF-8字符,但在Windows系统中可以正常工作。
解决方法非常简单:
如果您尝试使用默认设置,您将会遇到您所描述的错误。
$str = $dom->saveHTML(); // saves incorrectly

只需要按照以下步骤保存即可:
$str = $dom->saveHTML($dom->documentElement); // saves correctly

这行代码将确保你的UTF-8字符能够正确保存。如果你使用saveXML(),也可以使用同样的解决方法。

更新

如下方评论区中所建议的 "Jack M",并由 "Pamela" 和 "Marco Aurélio Deleu" 验证,以下变化可能适用于您的情况:

$str = utf8_decode($dom->saveHTML($dom->documentElement));

更新2

utf8_decode现已弃用。一个替代方法是使用mb_convert_encoding()。您需要根据您的需求进行设置。


注意

  1. 当您在不使用参数的情况下使用saveHTML()时,英文字符不会引起任何问题(因为英文字符以UTF-8的单字节字符保存)。

  2. 问题出现在您使用多字节字符(如中文、俄文、阿拉伯文、希伯来文等)时。

我建议您阅读这篇文章:http://coding.smashingmagazine.com/2012/06/06/all-about-unicode-utf8-character-sets/。您将了解UTF-8的工作原理以及为什么会出现这个问题。阅读这篇文章大约需要30分钟,但这是值得花费时间的。


8
在使用这个解决方案时,我必须进行utf8_decode。谢谢! - Jack M.
14
为了保留我的特殊字符,这必须变成utf8_decode($dom->saveHTML(dom->documentElement))。否则,它们就会成为其他东西。只是提一下,以防对别人有帮助。 - Jack M.
6
谢谢@MrJack。我也必须这样做才能使它显示出来,而不出现奇怪的字符。 $str = utf8_decode($dom->saveHTML($dom->documentElement)); - Pamela
3
utf8_decode($dom->saveHTML($dom->documentElement)); 对我来说完美地完成了任务。 - Marco Aurélio Deleu
1
@Rounin-StandingwithUkraine 哇,距离我写下这个答案已经快10年了,很高兴它仍然有意义。 - Greeso
显示剩余10条评论

21

确保真实的源文件已保存为UTF-8(甚至可以尝试使用不推荐的BOM字符与UTF-8以确保)。

另外,在HTML的情况下,请确保您已经使用标签声明了正确的编码方式:

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

如果它是一个CMS(正如您在问题中标记了Joomla),您可能需要配置适当的编码设置。


我理解你的意思,但是我没有问题显示这些字符。如果我执行 "echo $profile;" 它可以正常工作。当 DomDocument 处理它时,它开始出现故障。 - Slightly A.
2
您的元标记防止saveHTML将ASCII之上的所有内容编码为实体。这正是我正在寻找的解决方案 :) - sod
3
顺便提一下,较新的<meta charset="UTF-8">标签无法与DOMDocument一起使用。 - Taylan
1
@Taylan:<meta charset="UTF-8">没有任何问题:请参见https://3v4l.org/AATjh。 - Casimir et Hippolyte

19
这花费了我一些时间才搞清楚,但这是我的答案。

在使用DomDocument之前,我会使用file_get_contents来获取URL,然后使用字符串函数进行处理。也许不是最好的方法,但很快。在被证明Dom与此一样快之后,我首先尝试了以下方法:

$dom = new DomDocument('1.0', 'UTF-8');
if ($dom->loadHTMLFile($url) == false) { // read the url
    // error message
}
else {
    // process
}

尽管使用了正确的标签,PHP设置和其他提供的解决方案,但在保留UTF-8编码方面这种方法表现极差。以下是有效的方法:

$dom = new DomDocument('1.0', 'UTF-8');
$str = file_get_contents($url);
if ($dom->loadHTML(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8')) == false) {
}

等等,现在世界各方面都很好。


只是想在上面的答案中补充一点,另一种解决方法是使用以下建议,其他地方也建议过:if ($dom->loadHTML('<?xml encoding="UTF-8">' . $str) == false)。在发布我的答案后,我发现有一个场合我的第一个建议失败了,但第二个建议起作用了。 - user8972079
即使在 DomDocument('1.0', 'UTF-8') 中没有参数,它对我仍然有效。但在我的情况下,只有部分 HTML 被加载。 - JKB
非常感谢,这对我处理希伯来语很有帮助。 - Sagive
PHP8.2:已弃用:mb_convert_encoding():通过mbstring处理HTML实体已弃用;请改用htmlspecialchars、htmlentities或mb_encode_numericentity/mb_decode_numericentity - undefined

14

使用正确的UTF-8标题

不要仅仅满足于“它工作了”。

@cmbuckley在他的回答中建议将<?xml encoding="utf-8" ?>设置为文档。然而,在HTML文档中使用XML声明有点奇怪。HTML不是XML(除非它是XHTML),这可能会使浏览器和其他客户端软件混淆(可能是其他人报告的故障源)。

我成功地使用了HTML5声明:

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML('<!DOCTYPE html><meta charset="UTF-8">' . $profile);
echo $dom->saveHTML();

如果您使用其他标准,请使用正确的标题,DOMDocument非常严谨地遵循标准,并似乎也支持HTML5(如果在您的情况下不支持,请尝试更新libxml扩展程序)。


2
很遗憾,PHP不支持HTML5,因为libxml不支持它。你可以使用<!DOCTYPE alsfjaswrtoiufn>,但是结果将会和你输入的一样。 - miken32
我在Windows上运行PHP 8.1.0,只添加标签<meta charset="UTF-8">对我来说就可以正常工作。不需要使用<html>和<!DOCTYPE...>。 - MMJ

12

您可以在一行代码前面加上utf-8编码,像这样:

@$doc->loadHTML('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $profile);

然后你可以继续使用你已经有的代码,例如:

$doc->saveXML()

5

使用它以获得正确的结果

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $profile);
echo $dom->saveHTML();
echo $profile;

这个操作
mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8');

这是一种不好的方法,因为像 &lt; ,&gt; 这样的特殊符号可能会出现在 $profile 中,而它们在经过 mb_convert_encoding 转换后不会被再次转换。这是 XSS 和错误 HTML 的漏洞。


你能详细说明一下“在 mb_convert_encoding 之后它们不会再次转换”吗? - Motivated

5

您必须将一个带有合适的头部信息的 HTML 版本提供给 DOMDocument,就像 HTML5 一样。

$profile ='<?xml version="1.0" encoding="'.$_encoding.'"?>'. $html;

也许保持你的HTML尽可能合法是个好主意,这样当你开始查询时就不会遇到问题...并且远离htmlentities!!!那是一种不必要的来回浪费资源的方法。保持你的代码疯狂!!!


这更多或少是被接受的答案的一部分。 - Dwza

4

对我来说运行良好:

$dom = new \DOMDocument;
$dom->loadHTML(utf8_decode($html));
...
return  utf8_encode( $dom->saveHTML());

4
注意,utf8_decode函数可能会丢失信息(并替换为“?”)。 - jwal

3
我成功的唯一方法是接受以下答案:
$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $profile);
echo $dom->saveHTML();

然而,这带来了新的问题,即在文档输出中出现了<?xml encoding="utf-8" ?>

我的解决方案是进行以下操作:

foreach ($doc->childNodes as $xx) {
    if ($xx instanceof \DOMProcessingInstruction) {
        $xx->parentNode->removeChild($xx);
    }
}

有些解决方案告诉我,要删除xml头,必须执行以下操作:

$dom->saveXML($dom->documentElement);

这对于部分文档(例如只有两个<p>标签的文档)对我没有用,只会返回其中一个<p>标签。

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