web-dev-qa-db-ja.com

PHP utf-8文字の処理に失敗したDomDocument(☆)

Webサーバーは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を適切に処理するよう指示するには、さらに具体的にする必要がありますか?

51
Greg

DOMDocument::loadHTML() HTML文字列が必要です。

HTMLは、仕様ごとにデフォルトとしてISO-8859-1エンコーディング(ISO Latin Alphabet No. 1)を使用します。それはもっと長いので、 6.1。HTMLドキュメントの文字セット をご覧ください。実際には、これは一般的なWebブラウザーでのWindows-1252のデフォルトのサポートです。

PHPのDOMDocumentはlibxmlに基づいており、HTML 4.0用に設計された HTMLparser をもたらすため、ここまで戻ります。

ISO-8859-1エンコードされた文字列をロードできると仮定するのは安全だと思います。

あなたの文字列はUTF-8エンコードされています。 127/h7F以上のすべての文字を HTMLエンティティ に変換すれば問題ありません。自分でそれを行いたくない場合、それはmb_convert_encodingターゲットエンコーディングでHTML-ENTITIESが行うことです:

  • 名前付きエンティティを持つこれらのキャラクターは、名前付きエンティティを取得します。 € -> &euro;
  • 他のユーザーは、数値(10進数)エンティティを取得します。 ☆ -> &#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は、文字列ごとに1つのエンコードしか処理できません。正規表現の助けを借りて、より具体的に文字列の置換を行う方法については既に上で説明したので、ここではさらに詳細を残します。

もう1つの方法は、エンコーディングをhintにすることです。これは、ドキュメントを変更し、

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

これは、文字セットを指定するContent-Typeです。これは、ウェブサーバー経由では利用できないHTML文字列のベストプラクティスでもあります(たとえば、ディスクに保存したり、例のように文字列の中に保存したりします)。 Webサーバーは通常、それを応答ヘッダーとして設定します。

置き忘れた警告を気にしない場合は、文字列の前に追加するだけです。

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

DOMDocumentにhtmlドキュメントを読み込んだ後、元のエンコードを設定する(またはリセットすることをお勧めします)ための、より速い修正があります。サンプルコードを次に示します。

$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
15
DeZeA
<?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>
11