Подтвердить что ты не робот

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>&acirc;&#152;&#134; Hello &acirc;&#152;&#134; World &acirc;&#152;&#134;</h1>
</body></html>

который отображается как:

â~ † Hello â~ † World â~ †


Что я могу делать неправильно? Насколько конкретнее я должен сказать, чтобы DomDocument правильно обрабатывал utf-8?

4b9b3361

Ответ 1

DOMDocument::loadHTML() ожидает строку HTML.

HTML использует кодировку ISO-8859-1 (ISO Latin Alphabet № 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:

  • Те символы, у которых есть именованные объекты, получат именованное имя. € -> &euro;
  • Другие получают свой цифровой (десятичный) объект, например. ☆ -> &#9734;

Ниже приведен пример кода, который делает прогресс более заметным с помощью функции обратного вызова:

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

Эти примерные выходы для вашей строки:

☆ -> &#9734;
☆ -> &#9734;
☆ -> &#9734;

В любом случае, это просто для того, чтобы глубже заглянуть в вашу строку. Вы хотите, чтобы он конвертировался в кодировку 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">

который является типом содержимого, определяющим кодировку. Это также наилучшая практика для строк HTML, которые недоступны через веб-сервер (например, сохранены на диске или внутри строки, как в вашем примере). Обычно веб-сервер устанавливает это как заголовок ответа.

Если вам не нравятся неулокальные предупреждения, вы можете просто добавить их перед строкой:

$dom = new DomDocument();
$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">'.$html);

В спецификациях HTML 2.0 элементы, которые могут отображаться только в разделе <head> документа, будут автоматически размещены там. Вот что здесь происходит и здесь. Выход (довольно-печатный):

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

Ответ 2

Там более быстрое исправление, после загрузки вашего html-документа в DOMDocument вы просто установили (или лучше сказали reset) исходную кодировку. Здесь пример кода:

$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

Ответ 3

<?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>&#9734; Hello &#9734; World &#9734;</h1>
</body></html>