PHP DOM文档加载HTML未正确编码UTF-8

PHP DOMDocument loadHTML not encoding UTF-8 correctly

提问人:Slightly A. 提问时间:11/22/2011 最后编辑:cmbuckleySlightly A. 更新时间:11/14/2023 访问量:144768

问:

我正在尝试使用 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>
php utf-8 字符编码

评论

0赞 frustratedtech 11/22/2011
这可能会对您有所帮助。stackoverflow.com/questions/1580543/......
1赞 Slightly A. 11/22/2011
谢谢。我检查了所有这些,没有任何帮助。我没有得到????,,但有一些其他奇怪的文字。我会尝试将其粘贴到此处,但不知道网站将如何显示它。
0赞 Ben 11/22/2011
尝试使用utf8_encode
0赞 Slightly A. 11/22/2011
尝试没有成功。返回与以前相同的字符。

答:

21赞 Hossein 11/22/2011 #1

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

此外,对于 HTML,请确保已使用标记声明了正确的编码:meta

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

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

评论

0赞 Slightly A. 11/22/2011
我明白你在说什么,但我显示字符没有问题。如果我做“回声$profile”,它工作正常。当 DomDocument 掌握它时,它才开始失败。
2赞 sod 6/28/2013
您的 meta 阻止 saveHTML 将 ASCII 以上的所有内容编码为实体。我一直在寻找的解决方案:)
3赞 Taylan 10/16/2015
顺便说一句,较新的标记不适用于 DOMDocument。<meta charset="UTF-8">
1赞 Casimir et Hippolyte 10/18/2020
@Taylan:完全没有问题:见 3v4l.org/AATjh<meta charset="UTF-8">
690赞 cmbuckley 11/22/2011 #2

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

DOMDocument 使用 HTML4 分析器。如果您要加载 HTML5,您可能需要查看替代解决方案

如果您正在处理 (X)HTML 的简单片段,则可以在前面附加 XML 编码声明或元字符集声明,以使字符串被视为 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赞 Slightly A. 11/22/2011
是的,做到了。谢谢你的帮助。我尝试了saveHTML,saveXML,没想到问题可能在加载过程中出现。
6赞 Peter Bagnall 7/4/2013
mb_convert_encoding调用对我有用,而在编码声明前面则不起作用。可能是因为该文件已经有了一个相互矛盾的声明。非常感谢 - 为我节省了很多时间来追逐这个。
4赞 iquito 4/20/2016
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);在 PHP7 中为我修复了它(所以它仍然是一个问题) - 这是一个非常烦人的问题,因为我在 HTML 文档中定义了 utf8(使用 )但这没有效果,它似乎需要 <?xml 部分,这完全不直观。<meta charset="UTF-8" />
13赞 Louis Loudog Trottier 3/7/2017
仍然在 2017 年,这个答案是相关的,也对我有用。我的数据库、多字节、html 元标记和 DOM 编码都设置为 utf8,但在将节点从一个 DOC 导入到另一个 DOC 时仍然编码错误。php.net/manual/en/function.mb-convert-encoding.php 是解决办法。
1赞 Free Radical 10/18/2017
使用“HTML-ENTITIES”是一个可怕的黑客,但在 2017 年 10 月,这是此页面上建议的唯一适用于 RHEL7 的技巧!
-1赞 copndz 5/16/2013 #3

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

我认为前面的答案对你有用,但由于这种解决方法对我不起作用,我添加该答案是为了帮助可能与我一样的人。

5赞 Lazaros Kosmidis 6/4/2013 #4

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

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

也许保持你的html尽可能有效是一个好主意,这样你就不会在开始查询时遇到问题......周围:-)并远离!!!这是一个必要的来回浪费资源。 让你的代码保持疯狂!!!htmlentities

评论

0赞 Dwza 9/6/2021
这或多或少是公认的答案的一部分......
95赞 Greeso 12/19/2013 #5

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

解决方法非常简单:

如果您尝试默认设置,您将收到您描述的错误

$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. 不带参数使用时,英文字符不会造成任何问题(因为英文字符在 UTF-8 中保存为单字节字符)saveHTML()

  2. 当您有多字节字符(例如中文、俄语、阿拉伯语、希伯来语......等)

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

评论

8赞 Jack M. 9/9/2014
我不得不在使用此解决方案时utf8_decode。谢谢!
14赞 Jack M. 9/10/2014
这必须变成 utf8_decode($dom->saveHTML(dom->documentElement)) 以保留我的特殊字符。否则,它们就变成了别的东西。只是提到它,以防它帮助别人。
6赞 Pamela 1/15/2016
谢谢@MrJack。我还必须做同样的事情,以使其显示时没有奇怪的字符$str = utf8_decode($dom->saveHTML($dom->documentElement));
3赞 Marco Aurélio Deleu 10/21/2016
utf8_decode($dom->saveHTML($dom->documentElement));对我来说做得很完美。
1赞 Greeso 1/31/2023
@Rounin-StandwithUkraine 好吧,哇,我写这个答案已经 10 年了,很高兴它仍然相关。
12赞 Ivan 11/22/2015 #6

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

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

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

$doc->saveXML()
5赞 Alexander Goncharov 6/15/2016 #7

使用它以获得正确的结果

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

评论

0赞 Motivated 11/8/2020
你能详细说明一下“他们在mb_convert_encoding后不会皈依两次”吗?
4赞 mMo 6/23/2016 #8

为我找到的作品:

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

评论

4赞 jwal 9/15/2017
请注意,utf8_decode可能会丢失信息(替换为?)
19赞 user8972079 11/20/2017 #9

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

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

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

评论

0赞 11/21/2017
只是想在我上面的答案中补充一下,解决这个问题的另一种方法是使用以下方法,在其他地方也建议:if ($dom->loadHTML('<?xml encoding=“UTF-8”>' . $str) == false)。在发布我的答案后,我发现我的第一个建议失败了,但第二个建议奏效了。
0赞 JKB 6/17/2020
即使没有 中的参数也对我有用。但在我的情况下,只加载了部分html。DomDocument('1.0', 'UTF-8')
0赞 Sagive 12/28/2021
非常感谢,为我处理希伯来语 👍
0赞 mickmackusa 9/22/2023
PHP8.2:Deprecated: mb_convert_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead
3赞 Luke Madhanga 11/8/2019 #10

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

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

14赞 Jan Turoň 4/5/2021 #11

对 UTF-8 使用正确的标头

不要满足于“它有效”。

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

我成功使用了 HTML5 声明:

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

如果您使用其他标准,请使用正确的标头,DOMDocument 非常迂腐地遵循标准,并且似乎也支持 HTML5(如果不是您的情况,请尝试更新 libxml 扩展)。

评论

2赞 miken32 12/24/2021
不幸的是,PHP 不支持 HTML5,因为 libxml 不支持它。你会得到相同的结果,即它只会输出你输入的任何内容。<!DOCTYPE alsfjaswrtoiufn>
0赞 MMJ 3/26/2022
我在 Windows 上运行 PHP 8.1.0 并且只添加标签<meta charset=“UTF-8”>对我来说效果很好。无需使用 <html><!文档类型...>