PHP DOMDocument loadHTML n'encode pas correctement UTF-8

203

J'essaie d'analyser du HTML en utilisant DOMDocument, mais quand je le fais, je perds soudainement mon encodage (du moins c'est ainsi qu'il me semble).

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

Le résultat de ce code est que j'obtiens un tas de caractères qui ne sont pas japonais. Cependant, si je fais:

echo $profile;

il s'affiche correctement. J'ai essayé saveHTML et saveXML, et aucun des deux ne s'affiche correctement. J'utilise PHP 5.3.

Ce que je vois:

ã¤ãªãã¤å·ã·ã«ã´ã«ã¦ãã¢ã¤ã«ã©ã³ãç³»ã®å®¶åº­ã«ã9人åå¼ã®5çªç®ã¨ãã¦çã¾ãããå½¼ãå«ãã¦4人ã俳åªã«ãªã£ããç¶è¦ªã¯æ¨æã®ã»ã¼ã«ã¹ãã³ã§ãæ¯è¦ªã¯éµä¾¿å±ã®å®¢å®¤ä¿ã ã£ããé«æ ¡æ代ã¯ã­ã£ãã£ã®ã¢ã«ãã¤ãã«å¤ãã¿ãæè²è³éãåããªããã«ããªãã¯ç³»ã®é«æ ¡ã¸é²å­¦ã

Ce qui doit être montré:

イリノイ州シカゴにて、アイルランド系の家庭に、9人兄弟の5番目として生まれる。彼を含めて4人が俳優になった。父親は木材のセールスマンで、母親は郵便局の客室係だった。高校時代はキャディのアルバイトに勤しみ、教育資金を受けながらカトリック系の高校へ進学

EDIT: J'ai simplifié le code à cinq lignes pour que vous puissiez le tester vous-même.

$profile = "<div lang=ja><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>";
$dom = new DOMDocument();
$dom->loadHTML($profile);
echo $dom->saveHTML();
echo $profile;

Voici le html qui est retourné:

<div lang="ja"><p>イリノイ州シカゴã«ã¦ã€ã‚¢ã‚¤ãƒ«ãƒ©ãƒ³ãƒ‰ç³»ã®å®¶åº­ã«ã€</p></div>
<div lang="ja"><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>
Légèrement A.
la source
Cela peut vous aider. stackoverflow.com/questions/1580543/…
frustratedtech
Merci. J'ai vérifié tout cela et rien n'a aidé. Je ne comprends pas ????, mais un autre texte étrange. Je vais essayer de le coller ici, mais je ne sais pas comment le site l'affichera.
Légèrement A.
Essayez d'utiliser utf8_encode
Webnet
Essayé sans succès. Renvoyé les mêmes caractères qu'avant.
Légèrement A.

Réponses:

539

DOMDocument::loadHTMLtraitera votre chaîne comme étant ISO-8859-1, sauf indication contraire de votre part. Cela entraîne une interprétation incorrecte des chaînes UTF-8.

Si votre chaîne ne contient pas de déclaration de codage XML, vous pouvez en ajouter une pour que la chaîne soit traitée comme UTF-8:

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

Si vous ne pouvez pas savoir si la chaîne contiendra déjà une telle déclaration, il existe une solution de contournement dans SmartDOMDocument qui devrait vous aider:

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));
echo $dom->saveHTML();

Ce n'est pas une excellente solution de contournement, mais comme tous les caractères ne peuvent pas être représentés dans ISO-8859-1 (comme ces katana), c'est l'alternative la plus sûre.

cmbuckley
la source
1
Oui, ça l'a fait. Merci de votre aide. J'ai essayé saveHTML, saveXML, je ne pensais pas que le problème pouvait venir pendant le chargement.
Légèrement A.
4
L'appel mb_convert_encoding a fonctionné pour moi, alors que l'ajout de la déclaration d'encodage n'a pas fonctionné. Probablement parce que le document contenait déjà une déclaration contradictoire. Merci beaucoup - m'a fait gagner beaucoup de temps à poursuivre cela.
Peter Bagnall
1
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);corrigé pour moi dans PHP7 (donc c'est toujours un problème) - c'est un problème vraiment ennuyeux, car j'ai défini utf8 dans le document HTML (avec <meta charset="UTF-8" />) mais cela n'a aucun effet, il semble avoir besoin de la partie <? xml, qui est totalement peu intuitif.
iquito
12
Toujours en 2017, cette réponse est pertinente et a fonctionné pour moi aussi. J'avais ma base de données, multi-octets, balise meta html et encodage DOM tous réglés sur utf8 et j'avais toujours un mauvais encodage lors de l'importation d'un nœud d'un DOC à un autre. php.net/manual/en/function.mb-convert-encoding.php était le correctif.
Louis Loudog Trottier
8
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));fonctionne très bien! Merci,
vee
69

Le problème vient de saveHTML()et saveXML(), les deux ne fonctionnent pas correctement sous Unix. Ils n'enregistrent pas correctement les caractères UTF-8 lorsqu'ils sont utilisés sous Unix, mais ils fonctionnent sous Windows.

La solution de contournement est très simple:

Si vous essayez la valeur par défaut, vous obtiendrez l'erreur que vous avez décrite

$str = $dom->saveHTML(); // saves incorrectly

Tout ce que vous avez à faire est de sauvegarder comme suit:

$str = $dom->saveHTML($dom->documentElement); // saves correctly

Cette ligne de code permettra à vos caractères UTF-8 d'être enregistrés correctement. Utilisez la même solution de contournement si vous utilisez saveXML().


Mise à jour

Comme suggéré par " Jack M " dans la section commentaires ci-dessous, et vérifié par " Pamela " et " Marco Aurélio Deleu ", la variation suivante pourrait fonctionner dans votre cas:

$str = utf8_decode($dom->saveHTML($dom->documentElement));

Remarque

  1. Les caractères anglais ne posent aucun problème lorsque vous utilisez saveHTML()sans paramètres (car les caractères anglais sont enregistrés en tant que caractères à un octet en UTF-8)

  2. Le problème se produit lorsque vous avez des caractères multi-octets (tels que chinois, russe, arabe, hébreu, ... etc.)

Je recommande de lire cet article: http://coding.smashingmagazine.com/2012/06/06/all-about-unicode-utf8-character-sets/ . Vous comprendrez comment fonctionne UTF-8 et pourquoi vous rencontrez ce problème. Cela vous prendra environ 30 minutes, mais c'est du temps bien dépensé.

Greeso
la source
5
J'ai dû utf8_decode lors de l'utilisation de cette solution. Merci!
Jack M.
9
Cela devait devenir utf8_decode ($ dom-> saveHTML (dom-> documentElement)) pour conserver mes caractères spéciaux. Sinon, ils sont simplement devenus autre chose. Le mentionner simplement au cas où cela aiderait quelqu'un d'autre.
Jack M.
4
Merci @MrJack. J'ai également dû faire de même pour le faire afficher sans les personnages étranges$str = utf8_decode($dom->saveHTML($dom->documentElement));
Pamela
1
utf8_decode($dom->saveHTML($dom->documentElement));l'a fait parfaitement pour moi.
Marco Aurélio Deleu
2
Vous m'avez sauvé la vie avec ça. J'ai cherché cette réponse PARTOUT! Merci!
Paulo Hgo
15

Assurez-vous que le fichier source réel est enregistré au format UTF-8 (vous pouvez même essayer les caractères de nomenclature non recommandés avec UTF-8 pour vous en assurer).

Toujours dans le cas du HTML, assurez-vous d'avoir déclaré le bon encodage à l'aide de metabalises:

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

S'il s'agit d'un CMS (comme vous avez tagué votre question avec Joomla), vous devrez peut-être configurer les paramètres appropriés pour l'encodage.

Hossein
la source
Je comprends ce que vous dites, mais je n'ai aucun problème à afficher les caractères. si je fais "echo $ profile;" ça fonctionne bien. c'est quand le DomDocument s'en empare qu'il commence à échouer.
Légèrement A.
2
Votre méta empêche saveHTML de coder tout ce qui se trouve au-dessus de l'ASCII en entités. La solution que je cherchais :)
gazon
3
En remarque, la nouvelle <meta charset="UTF-8">balise ne fonctionne pas avec DOMDocument.
Taylan
@Taylan: pas de problème avec <meta charset="UTF-8">: voir 3v4l.org/AATjh
Casimir et Hippolyte
12

Cela m'a pris du temps à comprendre, mais voici ma réponse.

Avant d'utiliser DomDocument, j'utilisais file_get_contents pour récupérer les URL, puis les traiter avec des fonctions de chaîne. Peut-être pas le meilleur moyen mais rapide. Après avoir été convaincu que Dom était tout aussi rapide, j'ai d'abord essayé ce qui suit:

$dom = new DomDocument('1.0', 'UTF-8');
if ($dom->loadHTMLFile($url) == false) { // read the url
    // error message
}
else {
    // process
}

Cela a échoué de façon spectaculaire à préserver l'encodage UTF-8 malgré les balises méta, les paramètres php et tous les autres remèdes proposés ici et ailleurs. Voici ce qui fonctionne:

$dom = new DomDocument('1.0', 'UTF-8');
$str = file_get_contents($url);
if ($dom->loadHTML(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8')) == false) {
}

etc. Maintenant, tout va bien avec le monde. J'espère que cela t'aides.

Sam
la source
Je voulais juste ajouter à ma réponse ci-dessus qu'une autre façon de résoudre ce problème est la suivante, suggérée ailleurs également: if ($ dom-> loadHTML ('<? Xml encoding = "UTF-8">'. $ Str) = = faux). Après avoir publié ma réponse, j'ai trouvé une occasion où ma première suggestion a échoué mais la seconde a fonctionné.
Sam
Fonctionne pour moi même sans les paramètres DomDocument('1.0', 'UTF-8'). Mais dans mon cas, seul le HTML partiel est chargé.
JKB
10

Vous pouvez préfixer une ligne imposant le utf-8codage, comme ceci:

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

Et vous pouvez ensuite continuer avec le code que vous avez déjà, comme:

$doc->saveXML()
Ivan
la source
5

Vous devez alimenter le DOMDocument une version de votre HTML avec un en-tête qui a du sens. Tout comme HTML5.

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

C'est peut-être une bonne idée de garder votre html aussi valide que possible, afin de ne pas avoir de problèmes lorsque vous commencerez la requête ... autour de :-) et restez à l'écart htmlentities!!!! C'est un va-et-vient nécessaire qui gaspille des ressources. gardez votre code fou !!!!

Lazaros Kosmidis
la source
4

Les travaux me conviennent:

$dom = new \DOMDocument;
$dom->loadHTML(utf8_decode($html));
...
return  utf8_encode( $dom->saveHTML());
mMo
la source
2
Attention, utf8_decode peut perdre des informations (remplacé par a ?)
jwal
4

J'utilise php 7.3.8 sur un manjaro et je travaillais avec du contenu persan. Cela a résolu mon problème:

$html = 'hi</b><p>سلام<div>の家庭に、9 ☆';
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
print $doc->saveHTML($doc->documentElement) . PHP_EOL . PHP_EOL;
sajed zarrinpour
la source
Ce même conseil avait été donné par Sam des années plus tôt sur cette même page. Veuillez ne pas publier d'informations redondantes.
mickmackusa le
3

Utilisez-le pour un résultat correct

$dom = new DOMDocument();
$dom->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $profile);
echo $dom->saveHTML();
echo $profile;

Cette opération

mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8');

C'est une mauvaise façon, car des symboles spéciaux comme & lt; , & gt; peuvent être dans $ profile, et ils ne seront pas convertis deux fois après mb_convert_encoding. C'est le trou pour XSS et un HTML incorrect.

Alexandre Gontcharov
la source
1

La seule chose qui a fonctionné pour moi était la réponse acceptée de

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

POURTANT

Cela a entraîné de nouveaux problèmes, d'avoir <?xml encoding="utf-8" ?> dans la sortie du document.

La solution pour moi était alors de faire

foreach ($doc->childNodes as $xx) {
    if ($xx instanceof \DOMProcessingInstruction) {
        $xx->parentNode->removeChild($xx);
    }
}

Certaines solutions m'ont dit que pour supprimer l'en- xmltête, je devais effectuer

$dom->saveXML($dom->documentElement);

Cela n'a pas fonctionné pour moi comme pour un document partiel (par exemple un document avec deux <p>balises), une seule des <p>balises était retournée.

Luke Madhanga
la source
0

Peut également encoder comme ci-dessous .... recueilli à partir de https://davidwalsh.name/domdocument-utf8-problem

$profile = '<p>イリノイ州シカゴにて、アイルランド系の家庭に、9</p>';
$dom = new DOMDocument();
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));
echo $dom->saveHTML();
GQ.
la source
0

Cela a fonctionné pour moi.

Dans le php.inifichier, modifiez la propriété suivante.

Avant:

mbstring.encoding_transration = On

Après:

mbstring.encoding_transration = Off
Peter
la source
0

Le problème est que lorsque vous ajoutez un paramètre à une DOMDocument::saveHTML()fonction, vous perdez l'encodage. Dans quelques cas, vous devrez éviter l'utilisation du paramètre et utiliser l'ancienne fonction de chaîne pour trouver ce que vous recherchez.

Je pense que la réponse précédente fonctionne pour vous, mais comme cette solution de contournement n'a pas fonctionné pour moi, j'ajoute cette réponse pour aider les personnes qui pourraient être dans mon cas.

copndz
la source