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


193

我正在尝试使用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>


谢谢。我检查了所有这些,没有任何帮助。我没有得到????,但是得到了一些其他奇怪的文字。我会尝试将其粘贴到此处,但不知道该网站将如何显示它。

尝试使用utf8_encode
Webnet

尝试没有成功。返回与以前相同的字符。

Answers:


511

DOMDocument::loadHTML除非另行说明,否则会将您的字符串视为ISO-8859-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表示(就像这些武士刀一样),所以这是最安全的选择。


1
是的,做到了。谢谢您的帮助。我尝试了saveHTML,saveXML,但认为加载期间可能没有问题。

4
mb_convert_encoding调用对我有用,而在编码声明前添加无效。可能是因为该文档已经有一个相互矛盾的声明。非常感谢-节省了我很多时间来解决这个问题。
Peter Bagnall

1
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);在PHP7中为我修复了该问题(因此仍然是一个问题)-这是一个非常烦人的问题,因为我在HTML文档中使用定义了utf8(带有<meta charset="UTF-8" />),但没有效果,它似乎需要<?xml部分,其中是完全不直观的。
iquito

11
还是在2017年,这个答案很有意义,对我也很有效。我将数据库,多字节,html元标记和DOM编码都设置为utf8,并且在将节点从一个DOC导入到另一个DOC时仍然编码不正确。修复了php.net/manual/en/function.mb-convert-encoding.php
Louis Loudog Trottier

6
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));很棒!谢谢你,
VEE

66

问题在于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 ”和“ MarcoAurélioDeleu ” 验证的那样,以下变体可能适用于您的情况:

$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分钟的时间,但是时间充裕。


5
使用此解决方案时,我必须进行utf8_decode。谢谢!
杰克M.14年

9
这必须成为utf8_decode($ dom-> saveHTML(dom-> documentElement))才能保留我的特殊字符。否则,它们就变成了别的东西。只是提及它以防其他人。
杰克M.

4
谢谢@MrJack。我也必须做同样的事情才能显示没有奇怪的字符$str = utf8_decode($dom->saveHTML($dom->documentElement));
Pamela

1
utf8_decode($dom->saveHTML($dom->documentElement));为我做了完美。
MarcoAurélioDeleu '16

2
你救了我一命。我到处都在寻找这个答案!谢谢!
Paulo Hgo

15

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

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

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

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


我知道您在说什么,但显示字符没有问题。如果我“回显$ profile;” 它工作正常。当DomDocument抓住它时,它开始失败。

2
您的meta会阻止saveHTML将ASCII以上的所有内容编码为实体。我一直在寻找解决方案:)
草皮

2
附带说明,较新的<meta charset="UTF-8">标记不适用于DOMDocument。
塔兰2015年

10

您可以在行强制utf-8编码之前添加前缀,如下所示:

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

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

$doc->saveXML()

10

这花了我一段时间才能弄清楚,但这是我的答案。

在使用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)= =假)。发布答案后,我发现我的第一个建议失败了,而第二个建议奏效了。
山姆

即使没有参数也可以为我工作DomDocument('1.0', 'UTF-8')。但是在我的情况下,只加载了部分html。
JKB

5

您必须向DOMDocument提供带有有意义标题的HTML版本。就像HTML5一样。

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

最好使您的html尽可能有效,因此,当您开始在...附近查询时,您不会遇到麻烦...-)并远离htmlentities!!!! 这是必要的来回浪费资源。保持您的代码疯狂!!!


5

我正在manjaro上使用php 7.3.8,并且正在使用波斯语内容。解决了我的问题:

$html = 'hi</b><p>سلام<div>の家庭に、9 ☆';
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
print $doc->saveHTML($doc->documentElement) . PHP_EOL . PHP_EOL;

山姆早些时候在同一页面上也给出了完全相同的建议。请不要发布多余的信息。
mickmackusa

4

作品为我找到了:

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

2
请注意,utf8_decode可能会丢失信息(用代替?
jwal

2

用它来获得正确的结果

$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的漏洞。


1

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

$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>返回其中一个标签。


0

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

我认为先前的答案对您有用,但是由于此解决方法对我不起作用,因此我添加了该答案以帮助可能属于我的个人。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.