2009-10-06 12 views
18

Estoy usando PHP para manejar el texto de una variedad de fuentes. No anticipo que vaya a ser algo más que UTF-8, ISO-8859-1, o quizás WINDOWS-1252. Si se trata de algo más que uno de esos, solo necesito asegurarme de que el texto se convierta en una cadena UTF-8 válida, incluso si se pierden caracteres. ¿La opción // TRANSLIT de iconv lo soluciona? Por ejemplo, ¿este código aseguraría que una cadena es segura de insertar en un documento codificado UTF-8 (o base de datos)?Asegurando utf-8 válido en PHP

function make_safe_for_utf8_use($string) { 

    $encoding = mb_detect_encoding($string, "UTF-8,ISO-8859-1,WINDOWS-1252"); 

    if ($encoding != 'UTF-8') { 
     return iconv($encoding, 'UTF-8//TRANSLIT', $string); 
    } else { 
     return $string; 
    } 
} 

Respuesta

32

UTF-8 puede almacenar cualquier caracter Unicode. Si su codificación es otra cosa, incluyendo ISO-8859-1 o Windows-1252, UTF-8 puede almacenar cada carácter en ella. Por lo tanto, no tiene que preocuparse por perder ningún carácter al convertir una cadena de cualquier otra codificación a UTF-8.

Además, tanto ISO-8859-1 como Windows-1252 son codificaciones de un solo byte donde cualquier byte es válido. No es técnicamente posible distinguir entre ellos. Elegiría Windows-1252 como su coincidencia predeterminada para secuencias que no sean UTF-8, ya que los únicos bytes que decodifican de manera diferente son el rango 0x80-0x9F. Estos decodifican a varios personajes como comillas inteligentes y al euro en Windows-1252, mientras que en ISO-8859-1 son caracteres de control invisibles que casi nunca se usan. Los navegadores web a veces dicen que están usando ISO-8859-1, pero a menudo usarán Windows-1252.

sería el código asegurar que una cadena es seguro para insertar en un documento codificado en UTF-8

seguramente querrá para establecer el parámetro opcional ‘estricta’ TRUE para este propósito. Pero no estoy seguro de que esto realmente cubra todas las secuencias UTF-8 no válidas. La función no pretende verificar una secuencia de bytes para la validez UTF-8 explícitamente. Se han conocido casos en los que mb_detect_encoding adivinaría incorrectamente el UTF-8 anteriormente, aunque no sé si aún puede suceder en modo estricto.

Si usted quiere estar seguro, hágalo usted mismo usando el W3-recommended regex:

if (preg_match('%^(?: 
     [\x09\x0A\x0D\x20-\x7E]   # ASCII 
    | [\xC2-\xDF][\x80-\xBF]    # non-overlong 2-byte 
    | \xE0[\xA0-\xBF][\x80-\xBF]   # excluding overlongs 
    | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte 
    | \xED[\x80-\x9F][\x80-\xBF]   # excluding surrogates 
    | \xF0[\x90-\xBF][\x80-\xBF]{2}  # planes 1-3 
    | [\xF1-\xF3][\x80-\xBF]{3}   # planes 4-15 
    | \xF4[\x80-\x8F][\x80-\xBF]{2}  # plane 16 
)*$%xs', $string)) 
    return $string; 
else 
    return iconv('CP1252', 'UTF-8', $string); 
+0

Muchas gracias. Sé que los desarrolladores siempre comentan sobre la lentitud de las expresiones regulares. ¿Cuán cuidadoso debería estar usando esto en grandes loops con mucho texto? Por ejemplo, un ciclo que itera 200 veces y limpia el texto de 10.000 caracteres en cada iteración. – Brian

+0

Si bien no soy partidario de regex, en este caso no debería ser tan malo. Regex se vuelve lento cuando tienes secuencias sucesivas o anidadas '?'/'*'/'+' Que pueden hacer que tenga que retroceder buscando diferentes formas de hacer coincidir. Eso no sucederá en este caso. – bobince

+0

Excelente. Entonces, cuando use iconv como usted describe arriba, si especifico CP1252 como el conjunto de caracteres de entrada, y la cadena es algo distinto a CP1252 o ISO-8859-1, devolverá una cadena segura UTF-8, aunque algunos caracteres pueden perderse. ¿Es eso correcto? – Brian

-1

No estoy seguro si esto podría lograr lo mismo, pero no podía usar simplemente utf8_encode() en todo el texto sin tener que preocuparse acerca de la detección? Si el texto ya es UTF-8, no lo dañará. Y si no lo es, se convertirá. Si ya ha pensado en hacer esto, ¿hay alguna razón por la que esto no funcione para usted?

+3

utf8_encode no es idempotente para secuencias de bytes que ya están en UTF-8. En su lugar, los convierte a UTF-8 como si anteriormente fueran ISO-8859-1; así que obtendrás, por ej. 'Î ±' en lugar de 'α'. – bobince

12

Con mbstring biblioteca, que tienen mb_check_encoding().

Ejemplo de uso:

mb_check_encoding($string, 'UTF-8'); 

conseguir un alto rendimiento, esto es más rápido que la expresión regular proporcionada en la respuesta aceptada.

Una prueba rápida en mis programas de configuración (por 20.000 iteraciones):

  • regex: ~ 310ms
  • mb_check_encoding: ~ 90ms

EDITAR

Con PHP 7.1.9 en un reciente sistema de Windows 10, la solución de expresiones regularessupera mb_check_encoding() para cualquier longitud de la cadena (aún 20.000 iteraciones):

  • 10 caracteres de expresiones regulares: => 4 ms, mb_check_encoding() => 64 ms
  • 10000 caracteres de expresiones regulares: => 125 ms, mb_check_encoding() => 2.4s
+0

Su sistema debe estar gritando rápido, porque tengo ~ 5 segundos en 7500 iteraciones en un sistema bastante moderno (aunque estoy tratando con algunas cadenas bastante grandes, creo que el HTML de un sitio web bastante moderno). –

3

Sólo una nota: en lugar de utilizar la frecuencia recomendada (bastante complejo) regular expression by W3C, sólo tiene que utilizar la 'U' modificador para probar una cadena de Validez UTF-8:

<?php 
    if (preg_match("//u", $string)) { 
     // $string is valid UTF-8 
    } 
+0

también en los días: [¿Cómo detectar si tiene que aplicar decodificación utf8 o codificar en una cadena?] (http: // stackoverflow .com/a/4407996/367456) – hakre

+0

Verificación de caso común fácil, pero no completamente confiable. Su comportamiento depende de la versión de PHP, pero más importante aún, permite secuencias multibyte no válidas. http://www.phpwact.org/php/ i18n/charsets # checking_utf-8_for_well_formedness –

0

respuesta a "iconv es idempotente"

tampoco es iconv - iconv no es idempotente

una gran diferencia entre utf8_encode() & iconv() es que iconv puede provocar errores como este " detectado un carácter incompleto de varios bytes en la cadena de entrada" incluso con

iconv ('ISO-8859-1', 'UTF-8'. '// ignora', $ cadena)

en el código anterior:

$ encoding = mb_detect_encoding ($ string, "UTF-8, ISO-8859-1, WINDOWS-1252");

usted tiene que saber mb_detect_encoding puede responder UFT-8 no válidos incluso para las series utf8 (UTF8 mal formada)