PHP DomDocument не может обрабатывать символы utf-8 (☆)
Веб-сервер обслуживает ответы в кодировке 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());
Выход программы:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Test!</title></head><body>
<h1>☆ Hello ☆ World ☆</h1>
</body></html>
Который отображается как:
Привет, Мир!
Что я могу делать не так? Насколько более конкретно я должен сказать DomDocument для правильной обработки utf-8?
3 ответа
DOMDocument::loadHTML()
ожидает строку HTML.
HTML использует ISO-8859-1
кодировка (ISO Латинский алфавит № 1) по умолчанию согласно его спецификациям. Это дольше, см. 6.1. Набор символов документа HTML. На самом деле это больше поддержка по умолчанию для Windows-1252
в общих веб-браузерах.
Я возвращаюсь к этому далеко, потому что PHP DOMDocument основан на libxml, и это приносит HTMLparser, который разработан для HTML 4.0.
Я бы сказал, что можно с уверенностью предположить, что вы можете загрузить ISO-8859-1
закодированная строка.
Ваша строка UTF-8
закодирован. Превратите все символы выше 127 / h7F в объекты HTML, и все в порядке. Если вы не хотите делать это самостоятельно, вот что mb_convert_encoding
с HTML-ENTITIES
целевое кодирование делает:
- Те персонажи, которые имеют именованные сущности, получат именованные права.
€ -> €
- Остальные получают свою числовую (десятичную) сущность, например
☆ -> ☆
Ниже приведен пример кода, который делает прогресс более заметным с помощью функции обратного вызова:
$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);
Это примерный вывод для вашей строки:
☆ -> ☆
☆ -> ☆
☆ -> ☆
Во всяком случае, это просто для того, чтобы заглянуть глубже в вашу строку. Вы хотите, чтобы он либо был преобразован в кодировку loadHTML
может иметь дело с. Это может быть сделано путем преобразования всех вне US-ASCII
в HTML-объекты:
$us_ascii = mb_convert_encoding($utf_8, 'HTML-ENTITIES', 'UTF-8');
Позаботьтесь о том, чтобы ваш вход был в кодировке UTF-8. Если у вас есть даже смешанные кодировки (это может случиться с некоторыми входами) mb_convert_encoding
может обрабатывать только одну кодировку на строку. Я уже обрисовал в общих чертах выше, как более конкретно выполнять замену строк с помощью регулярных выражений, поэтому я оставляю дальнейшие подробности на данный момент.
Другой альтернативой является подсказка кодировки. Это можно сделать в вашем случае, изменив документ и добавив
<meta http-equiv="content-type" content="text/html; charset=utf-8">
который является Content-Type, определяющим набор символов. Это также рекомендуется для строк HTML, которые недоступны через веб-сервер (например, сохраняются на диске или внутри строки, как в вашем примере). Веб-сервер обычно устанавливает это как заголовок ответа.
Если вас не волнуют неуместные предупреждения, вы можете просто добавить их перед строкой:
$dom = new DomDocument();
$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">'.$html);
Согласно спецификациям HTML 2.0, элементы, которые могут появляться только в <head>
раздел документа, будет автоматически размещен там. Вот что и здесь происходит. Вывод (pretty-print):
<!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>
Есть более быстрое решение для этого, после загрузки вашего HTML-документа в DOMDocument, вы просто устанавливаете (или лучше сказать сброс) исходную кодировку. Вот пример кода:
$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
<?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());
Выход:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Test!</title></head><body>
<h1>☆ Hello ☆ World ☆</h1>
</body></html>