web-dev-qa-db-fra.com

PHP DomDocument ne gère pas les caractères utf-8 (☆)

Le serveur Web fournit des réponses avec l'encodage utf-8, tous les fichiers sont enregistrés avec l'encodage utf-8, et tout ce que je sais du paramètre a été défini sur l'encodage utf-8.

Voici un programme rapide, pour tester si la sortie fonctionne:

<?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());

Le résultat du programme est:

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

Ce qui se traduit par:

☠† Bonjour â World † Monde ☠†


Que pourrais-je faire de mal? Dans quelle mesure dois-je être plus précis pour dire à DomDocument de gérer correctement utf-8?

51
Greg

DOMDocument::loadHTML() attend une chaîne HTML.

HTML utilise le ISO-8859-1 encodage (ISO Latin Alphabet No. 1) par défaut selon ses spécifications. C'est depuis plus long, voir 6.1. Le jeu de caractères du document HTML . En réalité, c'est plutôt le support par défaut pour Windows-1252 dans les navigateurs Web courants.

Je remonte aussi loin car le DOMDocument de PHP est basé sur libxml et cela apporte le HTMLparser qui est conçu pour HTML 4.0.

Je dirais qu'il est prudent de supposer que vous pouvez charger un ISO-8859-1 chaîne codée.

Votre chaîne est UTF-8 codé. Transformez tous les caractères supérieurs à 127/h7F en Entités HTML et tout va bien. Si vous ne voulez pas le faire vous-même, c'est ce que mb_convert_encoding avec le HTML-ENTITIES le codage cible:

  • Les personnages qui ont nommé des entités obtiendront le droit nommé. € -> &euro;
  • Les autres obtiennent leur entité numérique (décimale), par ex. ☆ -> &#9734;

Voici un exemple de code qui rend la progression un peu plus visible à l'aide d'une fonction de rappel:

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

Ces sorties exemplaires pour votre chaîne:

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

Quoi qu'il en soit, c'est juste pour approfondir votre chaîne. Vous voulez le convertir soit en un encodage que loadHTML peut gérer. Cela peut être fait en convertissant tout en dehors de US-ASCII en entités HTML:

$us_ascii = mb_convert_encoding($utf_8, 'HTML-ENTITIES', 'UTF-8');

Veillez à ce que votre entrée soit réellement encodée en UTF-8. Si vous avez même des encodages mixtes (cela peut arriver avec certaines entrées) mb_convert_encoding ne peut gérer qu'un seul encodage par chaîne. J'ai déjà expliqué ci-dessus comment effectuer plus spécifiquement les remplacements de chaînes à l'aide d'expressions régulières, donc je laisse plus de détails pour l'instant.

L'autre alternative est d'indiquer l'encodage. Cela peut être fait dans votre cas en modifiant le document et en ajoutant un

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

qui est un Content-Type spécifiant un jeu de caractères. C'est également la meilleure pratique pour les chaînes HTML qui ne sont pas disponibles via un serveur Web (par exemple enregistrées sur le disque ou à l'intérieur d'une chaîne comme dans votre exemple). Le serveur Web définit normalement cela comme en-tête de réponse.

Si vous ne vous souciez pas des avertissements égarés, vous pouvez simplement l'ajouter devant la chaîne:

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

Selon les spécifications HTML 2.0, les éléments qui ne peuvent apparaître que dans le <head> section d'un document, y sera automatiquement placée. C'est ce qui se passe ici aussi. La sortie (jolie impression):

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

Il existe une solution plus rapide pour cela, après avoir chargé votre document html dans DOMDocument, vous venez de définir (ou mieux dit réinitialiser) l'encodage d'origine. Voici un exemple de code:

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

Production:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Test!</title></head><body>
    <h1>&#9734; Hello &#9734; World &#9734;</h1>
</body></html>
11