PHP DOMDocument loadHTML没有正确编码UTF-8

我试图使用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>
133808 次浏览

确保真正的源文件保存为UTF-8(您甚至可能想尝试不推荐的UTF-8 BOM字符以确保)。

同样对于HTML,确保使用meta标记声明了正确的编码:

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

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

DOMDocument::loadHTML将把您的字符串视为ISO-8859-1 (HTTP/1.1默认字符集),除非您另有说明。这将导致UTF-8字符串被错误地解释。

如果你的字符串不包含XML编码声明,你可以在前面加上一个,使字符串被视为UTF-8:

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

如果你不知道字符串是否已经包含这样的声明,在SmartDOMDocument中有一个变通方法,它应该会帮助你:

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));
echo $dom->saveHTML();

这不是一个很好的解决办法,但由于不是所有字符都可以在ISO-8859-1中表示(比如这些武士刀),这是最安全的替代方案。

问题是,当你向DOMDocument::saveHTML()函数添加参数时,你会丢失编码。在少数情况下,您需要避免使用形参,并使用旧的字符串函数来查找您要查找的内容。

我认为前面的答案对你有用,但因为这个方法对我没用,所以我加上这个答案来帮助那些可能和我有同样情况的人。

你必须给DOMDocument提供一个带有合理头的HTML版本。 就像HTML5一样。< / p >
$profile ='<?xml version="1.0" encoding="'.$_encoding.'"?>'. $html;

可能是一个好主意,让你的HTML尽可能有效,这样你就不会陷入问题,当你开始查询…around:-)和远离htmlentities!!!!这是一个必要的来回浪费资源。 保持你的代码疯狂!!!!< / p >

问题在于saveHTML()saveXML(),它们在Unix中都不能正常工作。它们在Unix中使用时不能正确保存UTF-8字符,但在Windows中可以使用。

变通方法非常简单:

如果尝试默认设置,则会得到您所描述的错误

$str = $dom->saveHTML(); // saves incorrectly

你所要做的就是保存如下:

$str = $dom->saveHTML($dom->documentElement); // saves correctly

这行代码将正确保存UTF-8字符。如果使用saveXML(),使用相同的解决方法。


更新

正如下面评论部分的“杰克米”所建议的,并通过“帕梅拉”和“Marco Aurélio Deleu”验证,以下变化可能适用于您的情况:

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

请注意

  1. 当你使用不带参数的saveHTML()时,英文字符不会引起任何问题(因为英文字符在UTF-8中保存为单字节字符)

  2. 当您使用多字节字符(如中文、俄语、阿拉伯语、希伯来语等)时,就会出现这个问题。

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

你可以在行前加上前缀,强制执行utf-8编码,如下所示:

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

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

$doc->saveXML()

使用它可以得到正确的结果

$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');

这是不好的方式,因为像<, >可以在$profile中,并且它们不会在mb_convert_encoding之后进行两次转换。它是XSS和错误HTML的漏洞。

为我找到的作品:

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

我花了一段时间才想明白,下面是我的答案。

在使用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) {
}

等。现在世界一切都好了。

唯一对我有用的是公认的答案

$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>标记被返回。

使用正确的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扩展)。