java python perl macos character-encoding

java - Cómo adivinar de forma fiable la codificación entre MacRoman, CP1252, Latin1, UTF-8 y ASCII



python perl (7)

En el trabajo, parece que no pasa una semana sin una connipción, calamidad o catástrofe relacionada con la codificación. El problema generalmente proviene de programadores que piensan que pueden procesar de manera confiable un archivo de "texto" sin especificar la codificación. Pero no puedes.

Por lo tanto, se ha decidido prohibir de ahora en adelante que los archivos tengan nombres que terminen en *.txt o *.text . La idea es que esas extensiones engañan al programador casual en una complacencia sorda con respecto a las codificaciones, y esto conduce a un manejo inadecuado. Sería casi mejor no tener ninguna extensión, porque al menos entonces sabes que no sabes lo que tienes.

Sin embargo, no vamos a ir tan lejos. En su lugar, se espera que use un nombre de archivo que termine en la codificación. Entonces, para los 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 particular, si uno puede especificar la codificación dentro del archivo, como en Perl o Python, deberá hacerlo. Para archivos como el origen de Java, donde no existe ninguna facilidad interna para el archivo, colocará la codificación antes de la extensión, como SomeClass-utf8.java .

Para la salida, UTF-8 debe ser fuertemente preferido.

Pero para obtener información, tenemos que descubrir cómo lidiar con los miles de archivos en nuestra base de código llamados *.txt . Queremos cambiarles el nombre a todos para que se ajusten a nuestro nuevo estándar. Pero no podemos posiblemente mirarlos a todos. Entonces, necesitamos una biblioteca o programa que realmente funcione.

Estos son varios 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 ejecutando en un entorno Unix mixto (Solaris, Linux, Darwin) con la mayoría de los equipos de escritorio que son Mac, tenemos bastantes archivos molestos de MacRoman. Y estos especialmente son un problema.

Desde hace un tiempo he estado buscando una forma de determinar programáticamente cuál de

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

un archivo está dentro, y no he encontrado un programa o una biblioteca que pueda distinguir confiablemente entre esas tres codificaciones de 8 bits diferentes. Es probable que tengamos más de mil archivos MacRoman en solitario, por lo que cualquier detector de charset que usemos debe ser capaz de detectarlos. Nada que he visto puede manejar el truco. Tenía grandes esperanzas para la biblioteca de detectores de chatset de la ICU , pero no puede manejar MacRoman. También busqué 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.

Lo que estoy buscando es una biblioteca o programa existente que determine de manera confiable en cuál de las 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 un código de biblioteca, nuestra preferencia de idioma es que esté en Perl, C, Java o Python, y en ese orden. Si solo se trata de un programa, entonces realmente no nos importa en qué idioma está, siempre que se encuentre en su totalidad, se ejecute en Unix y no tenga ningún problema.

¿Alguien más ha tenido este problema de un billón de archivos de texto heredados codificados aleatoriamente? Si es así, ¿cómo intentó resolverlo y qué tan exitoso fue? Este es el aspecto más importante de mi pregunta, pero también me interesa si crees que alentar a los programadores a nombrar (o renombrar) sus archivos con la codificación real en la que están esos archivos nos ayudará a evitar el problema en el futuro. ¿Alguna vez alguien intentó imponer esto en una base institucional, y si fue así, fue exitoso o no, y por qué?

Y sí, entiendo completamente por qué no se puede garantizar una respuesta definitiva dada la naturaleza del problema. Este es especialmente el caso con archivos pequeños, donde no tiene suficientes datos para continuar. Afortunadamente, nuestros archivos rara vez son pequeños. Además del archivo README aleatorio, la mayoría se encuentra en el rango de tamaño de 50k a 250k, y muchos son más grandes. Se garantiza que cualquier tamaño superior a unos pocos K en inglés.

El dominio del problema es la minería de texto biomédica, por lo que a veces lidiamos con corporaciones extensas y extremadamente grandes, como todos los repositorios de acceso abierto de PubMedCentral. Un archivo bastante grande es el BioThesaurus 6.0, con 5.7 gigabytes. Este archivo es especialmente molesto porque es casi todo UTF-8. Sin embargo, algunos numbskull fueron y pegaron algunas líneas que están en una codificación de 8 bits: Microsoft CP1252, creo. Toma un buen tiempo antes de que te tropieces con ese. :(


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

Actualmente estoy escribiendo un programa que traduce archivos a XML. Tiene que autodetectar 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. El programa luego selecciona el decodificador más probable. El enfoque bayesiano funciona así para cada codificación.

  1. Establezca la probabilidad inicial ( anterior ) de que el archivo esté en la codificación, según las frecuencias de cada codificación.
  2. Examine cada byte a su vez en el archivo. Busca el valor del byte para determinar la correlación entre ese valor de byte presente y un archivo que realmente está en esa codificación. Use esa correlación para calcular una probabilidad nueva ( posterior ) 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 capacidad de prueba que tienes es la probabilidad de que el archivo esté en la codificación.

Resulta que el teorema de Bayes es muy fácil de hacer si en vez de calcular las probabilidades, se 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 a priori initail y las correlaciones, examinando un corpus de archivos que ha clasificado manualmente.


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

"podemos soportar un buen cambio de saber si algo es probablemente UTF-8": en realidad, la posibilidad de que un archivo codificado con texto significativo en otro juego de caracteres que utiliza bytes de alto bit se decodifique con éxito ya que UTF-8 es muy 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 ni ASCII ni UTF-8:

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

Como han comentado otros, realmente solo tiene los caracteres de puntuación de alto bit disponibles para distinguir entre cp1252 y macroman. Sugiero que entrenes un modelo tipo Mozilla en tus propios documentos, no Shakespeare o Hansard o la Biblia KJV, y teniendo en cuenta los 256 bytes. Supongo que sus archivos no tienen marcado (HTML, XML, etc.) en ellos, eso distorsionaría las probabilidades de algo impactante.

Ha mencionado archivos que son principalmente UTF-8 pero que no se decodifican. También deberías desconfiar de:

(1) archivos supuestamente codificados en ISO-8859-1 pero que contienen "caracteres de control" en el rango de 0x80 a 0x9F inclusive ... esto es tan frecuente que el borrador del estándar HTML5 dice que decodifique TODAS las transmisiones HTML declaradas 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 ser el resultado de la transcodificación cp1252 / cp850 (¡ya ha pasado!) / Etc archivos de "ISO-8859-1" a UTF-8.

Antecedentes: tengo un proyecto de domingo por la tarde húmedo para crear un detector de charset basado en Python orientado a archivos (en lugar de orientado a la web) y que funciona bien con juegos de caracteres de 8 bits, incluidos los legacy ** n como cp850 y cp437. Todavía no está cerca del horario estelar. Estoy interesado en archivos de entrenamiento; ¿Están sus archivos ISO-8859-1 / cp1252 / MacRoman tan "libres de cargas" como espera que sea la solución de código de cualquier persona?


Como ha descubierto, no existe una forma 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 las mismas: 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 cierta codificación, adivinará qué codificación utilizan los archivos. Pero eso tiene que saber qué idioma utiliza el archivo y se vuelve completamente inútil cuando tienes archivos con codificaciones mixtas.

Por el lado positivo, si sabes que el texto de un archivo está escrito en inglés, es poco probable que notes cualquier diferencia, cualquiera que sea la codificación que decidas usar para ese archivo, ya que las diferencias entre todas las codificaciones mencionadas están todas localizadas en las partes de las codificaciones que especifican caracteres que normalmente no se usan en el idioma inglés. Es posible que tengas algunos problemas cuando el texto utiliza un formato especial o versiones especiales de puntuación (CP1252 tiene varias versiones de los caracteres de comillas, por ejemplo), pero para la esencia del texto probablemente no haya problemas.


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

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

Nota al margen:

En el caso de archivos como el origen de Java, donde no existe ninguna función interna en el archivo, deberá 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 esté disponible. Lo correcto sería adivinar la codificación, luego usar la herramienta native2ascii para convertir todos los caracteres que no sean ASCII a secuencias de escape Unicode .


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 esas son muy obsoletas).

UTF-8

Si sus datos se validan como UTF-8, entonces puede asumir 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 rizados, pero ninguno que use caracteres de control C1. Así que no se moleste con ellos, o ISO-8859-1, solo detecte windows-1252 en su lugar.

Eso ahora te deja con solo una pregunta.

¿Cómo se distingue MacRoman de cp1252?

Esto es mucho más complicado.

Caracteres indefinidos

Los bytes 0x81, 0x8D, 0x8F, 0x90, 0x9D no se usan en windows-1252. Si ocurren, entonces suponga 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 byte!) En los datos que sabe que son UTF-8. Determine 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 sugeridos por cp1252 y los bytes que sugieren MacRoman, y vaya con el que sea más grande.


Si puede detectar cada codificación EXCEPTO para macroman, entonces sería lógico suponer que las que no se pueden descifrar están en macroman. En otras palabras, solo haga una lista de archivos que no se pudieron procesar y maneje esos como si fueran macroman.

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

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