2010-03-24 35 views
9

Me gustaría trabajar solo con UTF8. El problema es que no sé el juego de caracteres de cada página web. ¿Cómo puedo detectarlo y convertirlo a UTF8?PHP: convertir la salida de curl_exec a UTF8

<?php 
$url = "http://vkontakte.ru"; 
$ch = curl_init($url); 
$options = array(
    CURLOPT_RETURNTRANSFER => true, 
); 
curl_setopt_array($ch, $options); 
$data = curl_exec($ch); 

// $data = magic($data); 

print $data; 

Ver esto en: http://paulisageek.com/tmp/curl-utf8

¿Cuál es magic()?

Respuesta

24

A juzgar por consejos Gumbo y Pekka, escribí curl_exec_utf8

/** The same as curl_exec except tries its best to convert the output to utf8 **/ 
function curl_exec_utf8($ch) { 
    $data = curl_exec($ch); 
    if (!is_string($data)) return $data; 

    unset($charset); 
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); 

    /* 1: HTTP Content-Type: header */ 
    preg_match('@([\w/+]+)(;\s*charset=(\S+))[email protected]', $content_type, $matches); 
    if (isset($matches[3])) 
     $charset = $matches[3]; 

    /* 2: <meta> element in the page */ 
    if (!isset($charset)) { 
     preg_match('@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*charset=([^\s"]+))[email protected]', $data, $matches); 
     if (isset($matches[3])) { 
      $charset = $matches[3]; 
      /* In case we want do do further processing downstream: */ 
      $data = preg_replace('@(<meta\s+http-equiv="Content-Type"\s+content="[\w/]+\s*;\s*charset=)([^\s"]+)@i', '$1utf-8', $data, 1); 
     } 
    } 

    /* 3: <xml> element in the page */ 
    if (!isset($charset)) { 
     preg_match('@<\?xml.+encoding="([^\s"]+)@si', $data, $matches); 
     if (isset($matches[1])) { 
      $charset = $matches[1]; 
      /* In case we want do do further processing downstream: */ 
      $data = preg_replace('@(<\?xml.+encoding=")([^\s"]+)@si', '$1utf-8', $data, 1); 
     } 
    } 

    /* 4: PHP's heuristic detection */ 
    if (!isset($charset)) { 
     $encoding = mb_detect_encoding($data); 
     if ($encoding) 
      $charset = $encoding; 
    } 

    /* 5: Default for HTML */ 
    if (!isset($charset)) { 
     if (strstr($content_type, "text/html") === 0) 
      $charset = "ISO 8859-1"; 
    } 

    /* Convert it if it is anything but UTF-8 */ 
    /* You can change "UTF-8" to "UTF-8//IGNORE" to 
     ignore conversion errors and still output something reasonable */ 
    if (isset($charset) && strtoupper($charset) != "UTF-8") 
     $data = iconv($charset, 'UTF-8', $data); 

    return $data; 
} 

Las expresiones regulares son en su mayoría de http://nadeausoftware.com/articles/2007/06/php_tip_how_get_web_page_content_type

+1

¡Ooohh dulce! Voy a probarlo cuando encuentre el tiempo. –

+0

Gracias por compartir, ¡me salvaste la vida! : D –

+0

Bien hecho, pero en caso de que hagamos un procesamiento adicional en sentido descendente, queremos reparar las etiquetas y sobre la marcha. Me tomé la libertad de actualizar su código con eso en mente. – DomQ

4

La conversión es fácil. La detección es la parte difícil. Puedes probar mb_detect_encoding pero es un método muy inestable, literalmente "adivina" el tipo de contenido y como @troelskn resalta en los comentarios, puede adivinar las diferencias "duras" en el mejor de los casos (¿Es una codificación de varios bytes?) Pero falla al detectar matices de juegos de caracteres similares.

La forma correcta sería la OMI:

  • interpretar cualquier content-type meta-tags en la página
  • interpretar cualquier content-type cabeceras enviadas por el servidor
  • Si eso produce nada, tratan de "oler" el codificación usando mb_detect_encoding()
  • Si eso no produce nada, vuelva a un valor predeterminado definido (tal vez ISO-8859-1, tal vez UTF-8).

Diferente que se indica en las directrices de @ respuesta de Gumbo, yo personalmente creo que los meta-tags deberían tener prioridad sobre las cabeceras de servidor porque estoy bastante seguro de que si una etiqueta meta está presente, es un indicador más fiable de la la codificación real de la página que la configuración de un servidor, algunos operadores de sitios ni siquiera saben cómo cambiar. La forma correcta, sin embargo, parece ser tratar los encabezados de tipo de contenido con mayor prioridad.

Para el primero, creo que puede usar get_meta_tags(). Lo último que deberías obtener de curl ya, solo tendrías que analizarlo. Here es un ejemplo completo sobre cómo procesar sistemáticamente los encabezados de respuesta servidos por cURL.

La conversión entonces estaría utilizando iconv:

$new_content = iconv("incoming-charset", "utf-8", $content); 
+0

no haga otras personas tienen que hacer esto? No puedo ser el primero en toparse con este problema. ¿No hay un código existente para detectar esto bien? –

+0

@Paul muy buena pregunta! Debería haber una biblioteca, pero no sé ninguna.Si no aparece nada más, su mejor apuesta puede ser buscar en las clases de PHP "simulador de navegador", si alguno de estos ha implementado bien. –

+0

Los encabezados http probablemente deberían tener una prioridad mayor que las metaetiquetas. – troelskn

0

hay un orden definido how to specify the character encoding in HTML:

[...] conformes agentes de usuario deben observar las siguientes prioridades cuando se determina la codificación de caracteres de un documento (de la prioridad más alta a la más baja):

  1. Par HTTP "charset" ameter en un campo "Content-Type".
  2. A META declaración con "http-equiv" establecido en "Content-Type" y un valor establecido para "charset".
  3. El atributo charset se establece en un elemento que designa un recurso externo.

Si ninguna declaración de codificación de caracteres está presente, HTTP defines ISO 8859-1 as default character encoding. También puede usar eso como codificación de caracteres predeterminada para HTML o simplemente rechazar procesar la respuesta.

para XHTML que, además, tienen la XML declaration as source for the encoding:

En un documento XML, la codificación de caracteres del documento se especifica en la declaración XML (por ejemplo, <?xml version="1.0" encoding="EUC-JP"?>). Para presentar documentos de forma portátil con codificaciones de caracteres específicas, el mejor enfoque es garantizar que el servidor web proporcione los encabezados correctos. Si esto no es posible, un documento que desee establecer su codificación de caracteres de forma explícita debe incluir tanto la declaración XML como una declaración de codificación y una declaración http-equiv meta (por ejemplo, <meta http-equiv="Content-type" content="text/html; charset=EUC-JP" />). En agentes de usuario que se ajustan a XHTML, el valor de la declaración de codificación de la declaración XML tiene prioridad.

Si ningún carácter de declaración de codificación, XML defines UTF-8 and UTF-16 as default character encoding:

A menos que una codificación está determinada por un protocolo de alto nivel, sino que también es un error fatal si una entidad XML no contiene ninguna declaración de codificación y su contenido es no es legal UTF-8 o UTF-16.

Por lo tanto, para resumir, el orden es:

  1. Un parámetro HTTP "charset" en un campo "Content-Type".
  2. Declaración XML con encoding atributo.
  3. A META declaración con "http-equiv" establecido en "Content-Type" y un valor establecido para "charset".

Si no hay presente ninguna declaración de codificación de caracteres, puede suponer que ISO 8859-1 es la codificación predeterminada para HTML y debe asumir UTF-8 o UTF-16 como codificación predeterminada para XHTML.

+0

maravilloso. ¿Hay una biblioteca para este protocolo? Me gustaría hacer la conversión de curl y caracteres juntos y hacer que UTF8 acabe de devolver –

+0

@Paul Tarjan: Puede el campo de encabezado * Content-Type * con 'curl_getinfo'. – Gumbo

+0

Puse su consejo en una función, ¿cómo se ve? –

1

yo estaba muy feliz de encontrar esta respuesta, pero me di cuenta que hay una falla en la detección <meta> etiqueta . Simplemente no parecía coincidir con ninguna etiqueta de contenido, y aún no está equipada para las nuevas etiquetas de estilo HTML5: <meta charset="UTF-8">. Así que escribí esto, espero que les ayude, ¡y gracias de nuevo por esta excelente solución!

/* 2: <meta> element in the page */ 
if (!isset($charset)) { 
    preg_match('/<[\s]*meta[^>]*charset="?([^\s"]+)\s?"/i', $data, $matches); 

    if (isset($matches[1])) { 
     $charset = $matches[1]; 
    } 
} 

(P. S. no podía encontrar la manera de enviar esto como un comentario, ya que no es, obviamente, una respuesta completa.)