2012-07-03 14 views
50

El servidor web está publicando respuestas con codificación utf-8, todos los archivos se guardan con codificación utf-8, y todo lo que sé sobre configuración se ha establecido en codificación utf-8 .PHP DomDocument no puede manejar caracteres utf-8 (☆)

He aquí un programa rápido, para probar si la salida funciona:

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

La salida del programa es:

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

que hace como:

â ~ † hello a ~ † Mundo â~ †


¿Qué podría estar haciendo mal? ¿Cuánto más específico debo ser para decirle al DomDocument que maneje correctamente el utf-8?

+0

Gracias por traer a colación la cuestión, una similar es: [¿Cómo mantener la lengua extranjera china u otro, ya que son en vez de convertirlos en códigos?] (Http: // stackoverflow .com/q/10237238/367456) sin embargo, puede considerar que es un truco. – hakre

+0

Relacionados: [PHP Request # 47875 - No hay opción para establecer la codificación de entrada HTML] (https://bugs.php.net/bug.php?id=47875) – hakre

+1

Extrañamente: php-documentation dice: 'La extensión DOM usa codificación UTF-8. Utilice utf8_encode() y utf8_decode() para trabajar con textos en la codificación ISO-8859-1 o Iconv para otras codificaciones. consulte: http://www.php.net/manual/en/intro.dom.php – jens

Respuesta

107

DOMDocument::loadHTML() espera una cadena HTML.

HTML utiliza la codificación ISO-8859-1 (alfabeto latino ISO n. ° 1) como valor predeterminado para sus especificaciones. Eso es desde más largo, vea 6.1. The HTML Document Character Set. En realidad, esa es más la compatibilidad predeterminada para Windows-1252 en webbrowers comunes.

Vuelvo tan lejos porque el DOMDocument de PHP se basa en libxml y eso trae el HTMLparser que está diseñado para HTML 4.0.

Yo diría que es seguro asumir que puede cargar una cadena codificada ISO-8859-1.

Su cadena está codificada en UTF-8. Convierta todos los caracteres superiores a 127/h7F en HTML Entities y todo irá bien. Si no quiere hacer que su propia, eso es lo que mb_convert_encoding con la codificación HTML-ENTITIES el objetivo tiene:

  • Esos personajes que han nombradas entidades, tendrán la entitiy llamado. € -> &euro;
  • Los demás obtienen su entidad numérica (decimal), p. ☆ -> &#9734;

El siguiente es un ejemplo de código que hace que el avance un poco más visible mediante el uso de una función de devolución de llamada:

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

Este ejemplares salidas para la cadena:

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

De todos modos, eso es solo para profundizar en su cadena. Desea que se convierta en una codificación loadHTML puede tratar. Eso se puede hacer mediante la conversión de todo el exterior de US-ASCII en entidades HTML:

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

Tenga cuidado de que su entrada es en realidad codificación UTF-8.Si incluso ha mezclado codificaciones (eso puede suceder con algunas entradas) mb_convert_encoding solo puede manejar una codificación por cadena. Ya expliqué anteriormente cómo hacer reemplazos de cadenas más específicamente con la ayuda de expresiones regulares, así que dejo más detalles por el momento.

La otra alternativa es sugerencia la codificación. Esto se puede hacer en su caso, mediante la modificación del documento y la adición de un

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

que es un tipo de contenido que especifica un conjunto de caracteres. Esa también es la mejor práctica para las cadenas HTML que no están disponibles a través de un servidor web (por ejemplo, guardadas en el disco o dentro de una cadena como en su ejemplo). El servidor web normalmente establece eso como el encabezado de respuesta.

Si no le importa las advertencias fuera de lugar, sólo puedes añadir en frente de la cadena:

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

por el HTML 2.0 espec, elementos que sólo pueden aparecer en la sección <head> de un documento , se colocará automáticamente allí. Esto es lo que sucede aquí también. La salida (bastante-impresión):

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

@hakre: ¡eso fue perfecto! ¡resolviste mi problema grave y ahora no tengo dolores de cabeza! – Aliweb

+1

+1 Gran respuesta, pero ¿qué método recomienda, utilizando 'mb_convert_encoding()' o anteponiendo la metaetiqueta en 'loadHTML()'? – Nate

+1

@Nate: Yo diría que depende. Normalmente no recomiendo 'mb_convert_encoding()' pero en este caso lo hago de alguna manera. Sin embargo, ese es un detalle de preferencia personal. Y aún depende de si desea realizar la conversión en su propio paso o si simplemente desea convertirla en 'DOOMDocument :: loadHTML()', que filtra el metaelemento en el documento. No sé, por ejemplo, qué sucederá si ese elemento ya existiera. Nunca lo he probado en un punto de guardado, pero normalmente "simplemente funciona" (tm). Las diferentes formas en la respuesta son más para una explicación. – hakre

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

Salida:

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

@powtac: en realidad, esta variante no necesita esa línea 'header'. Todos los personajes que no son parte de nosotros-ascii son entidades aquí. Cualquier navegador en la Tierra siempre lo mostrará correctamente a menos que especifiques una codificación (errónea) que no nos comparte-ascii. Pero solo notando, tampoco está mal. – hakre

15

Hay una solución más rápida para que, después de cargar el documento HTML en DOMDocument, que acaba de establecer (o mejor dicho reset) la codificación original. He aquí un ejemplo de código:

$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 
+0

Esto funcionó mejor que la versión de hakre de agregar la metaetiqueta porque se agregaron las clases eliminadas meta del html etiqueta –

+4

Hmm, esta respuesta es como un déjà-vu - http://stackoverflow.com/a/10834989/367456 – hakre

+0

Hmm, podría ser ..Tenía el código en un txt con un montón de fragmentos útiles. No pretendo que sea algo original, aunque es un uso bastante estándar de la clase DOMDocument. – DeZeA

Cuestiones relacionadas