Cómo adivinar de manera confiable la codificación entre MacRoman, CP1252, Latin1, UTF-8 y ASCII

99

En el trabajo, parece que no pasa una semana sin alguna connipción, calamidad o catástrofe relacionada con la codificación. El problema suele derivar de los programadores que piensan que pueden procesar de forma fiable un archivo de "texto" sin especificar la codificación. Pero no puedes.

Por lo tanto, se decidió prohibir a partir de ahora que los archivos tengan nombres que terminen en *.txto *.text. La idea es que esas extensiones inducen a error al programador casual a una aburrida complacencia con respecto a las codificaciones, y esto conduce a un manejo inadecuado. Casi sería mejor no tener ninguna extensión, porque al menos entonces sabes que no sabes lo que tienes.

Sin embargo, no vamos a llegar tan lejos. En su lugar, se esperará que utilice un nombre de archivo que termine en la codificación. Así que para archivos de texto, por ejemplo, estos serían algo así como README.ascii, README.latin1, README.utf8, etc.

Para los archivos que exigen una extensión en particular, si se puede especificar la codificación dentro del archivo en sí, como en Perl o Python, entonces debe hacerlo. Para archivos como la fuente de Java donde no existe tal facilidad interna al archivo, colocará la codificación antes de la extensión, como SomeClass-utf8.java.

Para la salida, se prefiere mucho UTF-8 .

Pero para la entrada, tenemos que averiguar cómo tratar con los miles de archivos en nuestra base de código denominada *.txt. Queremos cambiar el nombre de todos ellos para que se ajusten a nuestro nuevo estándar. Pero no podemos mirarlos a todos. Entonces necesitamos una biblioteca o programa que realmente funcione.

Estos están en ASCII, ISO-8859-1, UTF-8, Microsoft CP1252 o Apple MacRoman. Aunque sabemos que podemos decir si algo es ASCII, y tenemos un buen cambio de saber si algo es probablemente UTF-8, estamos perplejos acerca de las codificaciones de 8 bits. Debido a que estamos funcionando en un entorno Unix mixto (Solaris, Linux, Darwin) y la mayoría de los equipos de escritorio son Mac, tenemos bastantes archivos MacRoman molestos. Y estos especialmente son un problema.

Desde hace algún tiempo, he estado buscando una forma de determinar mediante programación cuál de

  1. ASCII
  2. ISO-8859-1
  3. CP1252
  4. MacRoman
  5. UTF-8

hay un archivo y no he encontrado un programa o biblioteca que pueda distinguir de manera confiable entre esas tres codificaciones diferentes de 8 bits. Probablemente solo tengamos más de mil archivos MacRoman, por lo que cualquier detector de juego de caracteres que usemos debe ser capaz de detectarlos. Nada de lo que he visto puede manejar el truco. Tenía grandes esperanzas para la biblioteca de detectores de juegos de caracteres ICU , pero no puede manejar MacRoman. También miré módulos para hacer el mismo tipo de cosas tanto en Perl como en Python, pero una y otra vez siempre es la misma historia: no hay soporte para detectar MacRoman.

Por lo tanto, lo que estoy buscando es una biblioteca o programa existente que determine de manera confiable en cuál de esas cinco codificaciones se encuentra un archivo, y preferiblemente más que eso. En particular, tiene que distinguir entre las tres codificaciones de 3 bits que he citado, especialmente MacRoman . Los archivos tienen más del 99% de texto en inglés; hay algunos en otros idiomas, pero no muchos.

Si se trata de código de biblioteca, nuestra preferencia de idioma es que esté en Perl, C, Java o Python, y en ese orden. Si es solo un programa, entonces realmente no nos importa en qué idioma esté siempre que venga en código fuente completo, se ejecute en Unix y no tenga ningún tipo de carga.

¿Alguien más ha tenido este problema de un trillón de archivos de texto heredados codificados aleatoriamente? Si es así, ¿cómo intentó resolverlo y qué éxito tuvo? Este es el aspecto más importante de mi pregunta, pero también me interesa si cree que animar a los programadores a nombrar (o cambiar el nombre) a sus archivos con la codificación real en la que se encuentran nos ayudará a evitar el problema en el futuro. Alguien ha tratado de hacer cumplir esta sobre una base institucional, y si es así, fue que el éxito o no, y por qué?

Y sí, comprendo perfectamente por qué no se puede garantizar una respuesta definitiva dada la naturaleza del problema. Este es especialmente el caso de archivos pequeños, en los que no tiene suficientes datos para continuar. Afortunadamente, nuestros archivos rara vez son pequeños. Aparte del READMEarchivo aleatorio , la mayoría están en el rango de tamaño de 50k a 250k, y muchos son más grandes. Se garantiza que todo lo que tenga un tamaño superior a unos pocos K estará en inglés.

El dominio del problema es la minería de textos biomédicos, por lo que a veces tratamos con corpora extensos y extremadamente grandes, como todos los repositorios de acceso abierto de PubMedCentral. Un archivo bastante grande es el BioThesaurus 6.0, de 5,7 gigabytes. Este archivo es especialmente molesto porque casi todo es UTF-8. Sin embargo, algunos tontos fueron y pegaron algunas líneas que están en una codificación de 8 bits — Microsoft CP1252, creo. Toma bastante tiempo antes de que te tropieces con eso. :(

tchrist
fuente
Consulte stackoverflow.com/questions/4255305/… para obtener una solución
mpenkov

Respuestas:

86

Primero, los casos fáciles:

ASCII

Si sus datos no contienen bytes por encima de 0x7F, entonces es ASCII. (O una codificación ISO646 de 7 bits, pero son muy obsoletas).

UTF-8

Si sus datos se validan como UTF-8, entonces puede asumir con seguridad que es UTF-8. Debido a las estrictas reglas de validación de UTF-8, los falsos positivos son extremadamente raros.

ISO-8859-1 frente a windows-1252

La única diferencia entre estas dos codificaciones es que ISO-8859-1 tiene los caracteres de control C1 donde windows-1252 tiene los caracteres imprimibles € ‚ƒ„… † ‡ ˆ ‰ Š ‹ŒŽ ''“ ”• –—˜ ™ š› œžŸ. He visto muchos archivos que usan comillas o guiones, pero ninguno que usa caracteres de control C1. Así que ni siquiera se moleste con ellos, o ISO-8859-1, simplemente detecte windows-1252 en su lugar.

Eso ahora te deja con una sola pregunta.

¿Cómo distingue MacRoman de cp1252?

Esto es mucho más complicado.

Caracteres indefinidos

Los bytes 0x81, 0x8D, 0x8F, 0x90, 0x9D no se utilizan en windows-1252. Si ocurren, asuma que los datos son MacRoman.

Caracteres idénticos

Los bytes 0xA2 (¢), 0xA3 (£), 0xA9 (©), 0xB1 (±), 0xB5 (µ) resultan ser los mismos en ambas codificaciones. Si estos son los únicos bytes que no son ASCII, entonces no importa si elige MacRoman o cp1252.

Enfoque estadístico

Cuente las frecuencias de caracteres (¡NO de bytes!) En los datos que sabe que son UTF-8. Determina los personajes más frecuentes. Luego, use estos datos para determinar si los caracteres cp1252 o MacRoman son más comunes.

Por ejemplo, en una búsqueda que acabo de realizar en 100 artículos aleatorios de Wikipedia en inglés, los caracteres no ASCII más comunes son ·•–é°®’èö—. Basado en este hecho,

  • Los bytes 0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9 o 0xF6 sugieren windows-1252.
  • Los bytes 0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5 o 0xE1 sugieren MacRoman.

Cuente los bytes que sugieren cp1252 y los bytes que sugieren MacRoman, y elija el que sea mayor.

dan04
fuente
6
Acepté tu respuesta porque no se ha presentado ninguna mejor, e hiciste un buen trabajo escribiendo los mismos problemas con los que había estado jugando. De hecho, tengo programas para olfatear esos bytes, aunque tienes aproximadamente el doble del número que yo mismo se me ocurrió.
tchrist
10
Finalmente pude implementar esto. Resulta que Wikipedia no tiene buenos datos de entrenamiento. De 1k artículos aleatorios en.wikipedia, sin contar la sección IDIOMAS, obtuve 50k puntos de código unASCII, pero la distribución no es creíble: el punto medio y la viñeta son demasiado altos, & c & c & c. Así que utilicé el corpus de acceso abierto PubMed totalmente UTF8, extrayendo + 14M de puntos de código unASCII. Los uso para construir un modelo de frecuencia relativa de todas las codificaciones de 8 bits, más elegante que el tuyo pero basado en esa idea. Esto demuestra ser altamente predictivo de la codificación de textos biomédicos, el dominio objetivo. Debería publicar esto. ¡Gracias!
tchrist
5
Todavía no tengo ningún archivo MacRoman, pero el uso de CR como delimitadores de línea no proporcionaría una prueba útil. Esto funcionaría para versiones anteriores de Mac OS, aunque no conozco OS9.
Milliways
10

Mozilla nsUniversalDetector (enlaces de Perl: Encode :: Detect / Encode :: Detect :: Detector ) está probado en un millón de veces.

daxim
fuente
Puede encontrar más documentación aquí: mozilla.org/projects/intl/detectorsrc.html , a partir de ahí, sugiere que si profundiza en los documentos puede encontrar los conjuntos de caracteres admitidos
Joel Berger
@Joel: He investigado la fuente. Fué una pregunta retórica. x-mac-cyrillices compatible, x-mac-hebrewse discute extensamente en los comentarios, x-mac-anything-elseno se menciona.
John Machin
@John Machin: extraño que el cirílico y el hebreo reciban un asentimiento, pero nada más. Solo estaba lanzando otra fuente de documentación, no había leído más, ¡gracias por hacer eso!
Joel Berger
7

Mi intento de tal heurística (asumiendo que ha descartado ASCII y UTF-8):

  • Si 0x7f a 0x9f no aparecen en absoluto, probablemente sea ISO-8859-1, porque esos son códigos de control que se usan muy raramente.
  • Si 0x91 a 0x94 aparecen en el lote, probablemente sea Windows-1252, porque esas son las "comillas tipográficas", con mucho los caracteres más probables en ese rango para ser usados ​​en texto en inglés. Para estar más seguro, puede buscar pares.
  • De lo contrario, es MacRoman, especialmente si ve mucho de 0xd2 a 0xd5 (ahí es donde están las comillas tipográficas en MacRoman).

Nota al margen:

Para archivos como la fuente de Java donde no existe tal función interna al archivo, colocará la codificación antes de la extensión, como SomeClass-utf8.java

¡¡No hagas esto!!

El compilador de Java espera que los nombres de los archivos coincidan con los nombres de las clases, por lo que cambiar el nombre de los archivos hará que el código fuente no se pueda compilar. Lo correcto sería adivinar la codificación y luego usar la native2asciiherramienta para convertir todos los caracteres no ASCII en secuencias de escape Unicode .

Michael Borgwardt
fuente
7
Stoopid kompilor! No, no podemos decirle a la gente que solo pueden usar ASCII; esto ya no es la década de 1960. No sería un problema si hubiera una anotación @encoding para que el hecho de que la fuente esté en una codificación particular no se viera forzado a ser almacenada externamente al código fuente, una deficiencia realmente idiota de Java que ni Perl ni Python sufren. . Debería estar en la fuente. Sin embargo, ese no es nuestro principal problema; son los miles de *.textarchivos.
tchrist
3
@tchrist: En realidad, no sería tan difícil escribir su propio procesador de anotaciones para admitir dicha anotación. Sigue siendo un descuido vergonzoso no tenerlo en la API estándar.
Michael Borgwardt
Incluso si Java admitiera @encoding, eso no garantizaría que la declaración de codificación sea correcta .
dan04
4
@ dan04: Puede decir lo mismo sobre la declaración de codificación en XML, HTML o en cualquier otro lugar. Pero al igual que con esos ejemplos, si se definiera en la API estándar, la mayoría de las herramientas que funcionan con código fuente (especialmente editores e IDE) lo admitirían, lo que evitaría de manera bastante confiable que las personas creen accidentalmente archivos cuya codificación de contenido no coincide la declaración.
Michael Borgwardt
4
"El compilador de Java espera que los nombres de los archivos coincidan con los nombres de las clases". Esta regla solo se aplica si el archivo define una clase pública de nivel superior.
Matthew Flaschen
6

"Perl, C, Java o Python, y en ese orden": actitud interesante :-)

"Tenemos un buen cambio al saber si algo es probablemente UTF-8": En realidad, existe la posibilidad de que un archivo que contenga texto significativo codificado en algún otro juego de caracteres que use bytes de conjunto de bits altos se decodifique con éxito ya que UTF-8 es extremadamente pequeño.

Estrategias UTF-8 (en el idioma menos preferido):

# 100% Unicode-standard-compliant UTF-8
def utf8_strict(text):
    try:
        text.decode('utf8')
        return True
    except UnicodeDecodeError:
        return False

# looking for almost all UTF-8 with some junk
def utf8_replace(text):
    utext = text.decode('utf8', 'replace')
    dodgy_count = utext.count(u'\uFFFD') 
    return dodgy_count, utext
    # further action depends on how large dodgy_count / float(len(utext)) is

# checking for UTF-8 structure but non-compliant
# e.g. encoded surrogates, not minimal length, more than 4 bytes:
# Can be done with a regex, if you need it

Una vez que haya decidido que no es ASCII ni UTF-8:

Los detectores de juegos de caracteres de origen Mozilla que conozco no son compatibles con MacRoman y, en cualquier caso, no hacen un buen trabajo con los juegos de caracteres de 8 bits, especialmente con el inglés porque AFAICT dependen de verificar si la decodificación tiene sentido en el dado. idioma, ignorando los caracteres de puntuación y basado en una amplia selección de documentos en ese idioma.

Como han señalado otros, realmente solo tiene los caracteres de puntuación de conjunto de bits altos disponibles para distinguir entre cp1252 y macroman. Sugeriría entrenar un modelo tipo Mozilla en sus propios documentos, no en Shakespeare o Hansard o la Biblia KJV, y tener en cuenta los 256 bytes. Supongo que sus archivos no tienen marcado (HTML, XML, etc.), eso distorsionaría las probabilidades de algo impactante.

Ha mencionado archivos que son en su mayoría UTF-8 pero no se pueden decodificar. También deberías sospechar mucho de:

(1) archivos que supuestamente están codificados en ISO-8859-1 pero que contienen "caracteres de control" en el rango 0x80 a 0x9F inclusive ... esto es tan frecuente que el borrador del estándar HTML5 dice decodificar TODOS flujos HTML declarados como ISO-8859 -1 usando cp1252.

(2) archivos que decodifican OK como UTF-8 pero el Unicode resultante contiene "caracteres de control" en el rango U + 0080 a U + 009F inclusive ... esto puede resultar de la transcodificación de cp1252 / cp850 (¡visto que sucede!) / Etc archivos de "ISO-8859-1" a UTF-8.

Antecedentes: tengo un proyecto húmedo de domingo por la tarde para crear un detector de conjuntos de caracteres basado en Python que esté orientado a archivos (en lugar de orientado a la web) y que funcione bien con conjuntos de caracteres de 8 bits, incluidos legacy ** nlos como cp850 y cp437. Aún no está cerca del horario de máxima audiencia. Me interesan los archivos de entrenamiento; ¿Están sus archivos ISO-8859-1 / cp1252 / MacRoman igualmente "libres de cargas" como espera que sea la solución de código de cualquier persona?

John Machin
fuente
1
el motivo del ordenamiento lingüístico es el medio ambiente. La mayoría de nuestras aplicaciones principales tienden a estar en Java y las utilidades menores y algunas aplicaciones están en Perl. Tenemos un poco de código aquí y allá que está en Python. Soy sobre todo un programador de C y perl, al menos por primera opción, así que estaba buscando una solución java para conectarla a nuestra biblioteca de aplicaciones, o una biblioteca perl para la misma. Si es C, podría construir una capa de pegamento XS para conectarla a la interfaz de Perl, pero nunca antes lo había hecho en Python.
tchrist
3

Como ha descubierto, no existe una manera perfecta de resolver este problema, porque sin el conocimiento implícito sobre qué codificación utiliza un archivo, todas las codificaciones de 8 bits son exactamente iguales: una colección de bytes. Todos los bytes son válidos para todas las codificaciones de 8 bits.

Lo mejor que puede esperar es algún tipo de algoritmo que analice los bytes y, en función de las probabilidades de que un determinado byte se utilice en un determinado idioma con una determinada codificación, adivinará qué codificación utilizan los archivos. Pero eso tiene que saber qué idioma usa el archivo, y se vuelve completamente inútil cuando tiene archivos con codificaciones mixtas.

Por el lado positivo, si sabe que el texto de un archivo está escrito en inglés, entonces es poco probable que note alguna diferencia con la codificación que decida usar para ese archivo, ya que las diferencias entre todas las codificaciones mencionadas están localizadas en las partes de las codificaciones que especifican caracteres que no se utilizan normalmente en el idioma inglés. Es posible que tenga algunos problemas cuando el texto utiliza un formato especial o versiones especiales de puntuación (CP1252 tiene varias versiones de los caracteres de las comillas, por ejemplo), pero en lo esencial del texto, probablemente no habrá problemas.

Epcylon
fuente
1

Si puede detectar todas las codificaciones EXCEPTO para macroman, entonces sería lógico suponer que las que no se pueden descifrar están en macroman. En otras palabras, simplemente haga una lista de archivos que no se pueden procesar y manipúlelos como si fueran macroman.

Otra forma de ordenar estos archivos sería crear un programa basado en servidor que permita a los usuarios decidir qué codificación no está distorsionada. Por supuesto, sería dentro de la empresa, pero con 100 empleados haciendo unos pocos cada día, tendrá miles de archivos terminados en poco tiempo.

Finalmente, ¿no sería mejor simplemente convertir todos los archivos existentes a un formato único y requerir que los archivos nuevos estén en ese formato?

Eric Pauley
fuente
5
¡Gracioso! Cuando leí este comentario por primera vez después de haber sido interrumpido durante 30 minutos, leí "macroman" como "macro man" y no hice la conexión con MacRoman hasta que hice una búsqueda de esa cadena para ver si el OP lo había mencionado.
Adrian Pronk
+1 esta respuesta es algo interesante. No estoy seguro de si es una buena o mala idea. ¿Alguien puede pensar en una codificación existente que también podría pasar desapercibida? ¿Es probable que haya uno en el futuro?
nombre de usuario
1

¿Alguien más ha tenido este problema de un trillón de archivos de texto heredados codificados aleatoriamente? Si es así, ¿cómo intentó resolverlo y qué éxito tuvo?

Actualmente estoy escribiendo un programa que traduce archivos a XML. Tiene que detectar automáticamente el tipo de cada archivo, que es un superconjunto del problema de determinar la codificación de un archivo de texto. Para determinar la codificación, estoy usando un enfoque bayesiano. Es decir, mi código de clasificación calcula una probabilidad (probabilidad) de que un archivo de texto tenga una codificación particular para todas las codificaciones que comprende. A continuación, el programa selecciona el decodificador más probable. El enfoque bayesiano funciona así para cada codificación.

  1. Establecer la inicial ( antes probabilidad ) de que el archivo esté en la codificación, en función de las frecuencias de cada codificación.
  2. Examine cada byte por turno en el archivo. Busque el valor de byte para determinar la correlación entre ese valor de byte que está presente y un archivo que realmente está en esa codificación. Utilice esa correlación para calcular una nueva ( posterior probabilidad ) de que el archivo esté en la codificación. Si tiene más bytes para examinar, use la probabilidad posterior de ese byte como probabilidad previa cuando examine el siguiente byte.
  3. Cuando llegas al final del archivo (en realidad solo miro los primeros 1024 bytes), la probabilidad que tienes es la probabilidad de que el archivo esté codificado.

Resulta que el teorema de Bayes se vuelve muy fácil de hacer si en lugar de calcular probabilidades, calcula el contenido de la información , que es el logaritmo de las probabilidades :info = log(p / (1.0 - p)) .

Tendrá que calcular la probabilidad inicial a priori y las correlaciones examinando un corpus de archivos que ha clasificado manualmente.

Raedwald
fuente