PHP DOMDocument loadHTML no codifica UTF-8 correctamente

194

Estoy tratando de analizar algo de HTML usando DOMDocument, pero cuando lo hago, de repente pierdo mi codificación (al menos así es como me parece).

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

El resultado de este código es que obtengo un montón de caracteres que no son japoneses. Sin embargo, si lo hago:

echo $profile;

Se muestra correctamente. He intentado saveHTML y saveXML, y ninguno se muestra correctamente. Estoy usando PHP 5.3.

Lo que veo:

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

Lo que se debe mostrar:

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

EDITAR: he simplificado el código a cinco líneas para que pueda probarlo usted mismo.

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

Aquí está el html que se devuelve:

<div lang="ja"><p>イリノイ州シカゴã«ã¦ã€ã‚¢ã‚¤ãƒ«ãƒ©ãƒ³ãƒ‰ç³»ã®å®¶åº­ã«ã€</p></div>
<div lang="ja"><p>イリノイ州シカゴにて、アイルランド系の家庭に、</p></div>
Ligeramente A.
fuente
Esto puede ayudarte. stackoverflow.com/questions/1580543/…
frustratedtech
Gracias. Revisé todo eso y nada ayudó. No entiendo ????, pero algún otro texto extraño. Intentaré pegarlo aquí, pero no sé cómo lo mostrará el sitio.
Ligeramente A.
Intente usar utf8_encode
Webnet
Probado sin éxito. Devuelve los mismos personajes que antes.
Ligeramente A.

Respuestas:

513

DOMDocument::loadHTMLtratará su cadena como si estuviera en ISO-8859-1 a menos que le indique lo contrario. Esto da como resultado que las cadenas UTF-8 se interpreten incorrectamente.

Si su cadena no contiene una declaración de codificación XML, puede anteponer una para que la cadena sea tratada como UTF-8:

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

Si no puede saber si la cadena ya contendrá dicha declaración, hay una solución en SmartDOMDocument que debería ayudarlo:

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

Esta no es una gran solución, pero dado que no todos los caracteres se pueden representar en ISO-8859-1 (como estas katana), es la alternativa más segura.

cmbuckley
fuente
1
Si, eso lo hizo. Gracias por tu ayuda. Intenté saveHTML, saveXML, no pensé que el problema podría haber surgido durante la carga.
Ligeramente A.
44
La llamada mb_convert_encoding funcionó para mí, mientras que antes de la declaración de codificación no. Probablemente porque el documento ya tenía una declaración contradictoria. Muchas gracias, me ahorró mucho tiempo persiguiendo esto.
Peter Bagnall el
1
$dom->loadHTML('<?xml encoding="utf-8" ?>' . $content);lo arregló para mí en PHP7 (por lo que sigue siendo un problema): este es un problema realmente molesto, porque definí utf8 en el documento HTML (con <meta charset="UTF-8" />) pero eso no tiene ningún efecto, parece necesitar la parte <? xml, que Es totalmente poco intuitivo.
iquito
11
Aún en 2017, esta respuesta es relevante y también funcionó para mí. Tenía mi base de datos, multibyte, metaetiqueta html y codificación DOM configurada en utf8 y todavía tenía una codificación incorrecta al importar un nodo de un DOC a otro. php.net/manual/en/function.mb-convert-encoding.php fue la solución.
Louis Loudog Trottier
66
$dom->loadHTML(mb_convert_encoding($profile, 'HTML-ENTITIES', 'UTF-8'));¡Funciona genial! Gracias,
vee
66

El problema es con saveHTML()y saveXML(), ambos no funcionan correctamente en Unix. No guardan los caracteres UTF-8 correctamente cuando se usan en Unix, pero funcionan en Windows.

La solución es muy simple:

Si prueba el valor predeterminado, obtendrá el error que describió

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

Todo lo que tiene que hacer es guardar de la siguiente manera:

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

Esta línea de código hará que sus caracteres UTF-8 se guarden correctamente. Use la misma solución si la está usando saveXML().


Actualizar

Según lo sugerido por " Jack M " en la sección de comentarios a continuación, y verificado por " Pamela " y " Marco Aurélio Deleu ", la siguiente variación podría funcionar en su caso:

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

Nota

  1. Los caracteres en inglés no causan ningún problema cuando se usa saveHTML()sin parámetros (porque los caracteres en inglés se guardan como caracteres de un solo byte en UTF-8)

  2. El problema ocurre cuando tiene caracteres de varios bytes (como chino, ruso, árabe, hebreo, etc.).

Recomiendo leer este artículo: http://coding.smashingmagazine.com/2012/06/06/all-about-unicode-utf8-character-sets/ . Comprenderá cómo funciona UTF-8 y por qué tiene este problema. Le llevará unos 30 minutos, pero es un tiempo bien empleado.

Greeso
fuente
55
Tuve que utf8_decode mientras usaba esta solución. ¡Gracias!
Jack M.
9
Esto tenía que convertirse en utf8_decode ($ dom-> saveHTML (dom-> documentElement)) para preservar mis caracteres especiales. De lo contrario, simplemente se convirtieron en otra cosa. Solo mencionándolo en caso de que ayude a alguien más.
Jack M.
44
Gracias @ MrJack. También tuve que hacer lo mismo para que se mostrara sin los personajes extraños$str = utf8_decode($dom->saveHTML($dom->documentElement));
Pamela el
1
utf8_decode($dom->saveHTML($dom->documentElement));Lo hizo perfectamente para mí.
Marco Aurélio Deleu
2
Me salvaste la vida con esto. ¡Busqué esta respuesta POR TODAS PARTES! ¡Gracias!
Paulo Hgo
15

Asegúrese de que el archivo fuente real se guarde como UTF-8 (incluso puede probar los caracteres BOM no recomendados con UTF-8 para asegurarse).

También en el caso de HTML, asegúrese de haber declarado la codificación correcta usando metaetiquetas:

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

Si se trata de un CMS (como ha etiquetado su pregunta con Joomla), es posible que deba configurar los ajustes adecuados para la codificación.

Hossein
fuente
Entiendo lo que estás diciendo, pero no tengo problemas para mostrar los personajes. si hago "echo $ profile;" funciona bien. Es cuando el DomDocument se apodera de él que comienza a fallar.
Ligeramente A.
2
Su meta evita que saveHTML codifique todo por encima de ASCII en entidades. La solución que estaba buscando :)
sod
2
Como nota al margen, la <meta charset="UTF-8">etiqueta más nueva no funciona con DOMDocument.
Taylan
10

Puede prefijar una utf-8codificación de imposición de línea , como esta:

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

Y luego puede continuar con el código que ya tiene, como:

$doc->saveXML()
Ivan
fuente
10

Me tomó un tiempo darme cuenta, pero aquí está mi respuesta.

Antes de usar DomDocument, usaría file_get_contents para recuperar las URL y luego procesarlas con funciones de cadena. Quizás no sea la mejor manera pero rápido. Después de convencerme de que Dom era igual de rápido, probé lo siguiente:

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

Esto falló espectacularmente al preservar la codificación UTF-8 a pesar de las metaetiquetas adecuadas, la configuración de php y todos los demás remedios ofrecidos aquí y en otros lugares. Esto es lo que funciona:

$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. Ahora todo está bien con el mundo. Espero que esto ayude.

Sam
fuente
Solo quería agregar a mi respuesta anterior que otra forma de abordar esto es con lo siguiente, sugerido también en otro lugar: if ($ dom-> loadHTML ('<? Xml encoding = "UTF-8">'. $ Str) = = falso) Después de publicar mi respuesta, encontré una ocasión en la que mi primera sugerencia falló pero la segunda funcionó.
Sam
Funciona para mí incluso sin los parámetros DomDocument('1.0', 'UTF-8'). Pero en mi caso solo se carga html parcial.
JKB
5

Debe alimentar el DOMDocument con una versión de su HTML con un encabezado que tenga sentido. Al igual que HTML5.

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

tal vez sea una buena idea mantener su html tan válido como sea posible, para que no tenga problemas cuando comience la consulta ... alrededor de :-) y ¡¡manténgase alejado de htmlentities!!!! Ese es un desperdicio necesario de recursos. mantén tu código loco !!!!

Lazaros Kosmidis
fuente
5

Estoy usando php 7.3.8 en un manjaro y estaba trabajando con contenido persa. Esto resolvió mi problema:

$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
fuente
Este mismo consejo fue dado por Sam años antes en esta misma página. No publique información redundante.
mickmackusa
4

Funciona para mí:

$dom = new \DOMDocument;
$dom->loadHTML(utf8_decode($html));
...
return  utf8_encode( $dom->saveHTML());
mMo
fuente
2
Tenga cuidado, utf8_decode puede perder información (reemplazado por a ?)
jwal
2

Úselo para obtener el resultado correcto

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

Esta operacion

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

Es una mala manera, porque símbolos especiales como & lt; , & gt; puede estar en $ profile y no se convertirán dos veces después de mb_convert_encoding. Es el agujero para XSS y HTML incorrecto.

Alexander Goncharov
fuente
1

Lo único que funcionó para mí fue la respuesta aceptada de

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

SIN EMBARGO

Esto provocó nuevos problemas, de tener <?xml encoding="utf-8" ?>en la salida del documento.

La solución para mí era hacer

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

Algunas soluciones me dijeron que para eliminar el xmlencabezado, tenía que realizar

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

Esto no funcionó para mí como para un documento parcial (por ejemplo, un documento con dos <p>etiquetas), solo una de las <p>etiquetas fue devuelta.

Luke Madhanga
fuente
0

El problema es que cuando agrega un parámetro a la función DOMDocument :: saveHTML (), pierde la codificación. En algunos casos, deberá evitar el uso del parámetro y utilizar la función de cadena antigua para encontrar lo que está buscando.

Creo que la respuesta anterior funciona para usted, pero como esta solución no funcionó para mí, estoy agregando esa respuesta para ayudar a las personas que pueden estar en mi caso.

Copndz
fuente
0

También puede codificar como a continuación ... recopilado 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();
Anbarasi Selvaraj
fuente