¿Cómo puedo detectar la codificación / página de códigos de un archivo de texto?

295

En nuestra aplicación, recibimos archivos de texto ( .txt,.csv , etc.) a partir de diversas fuentes. Al leer, estos archivos a veces contienen basura porque los archivos se crearon en una página de códigos diferente / desconocida.

¿Hay alguna manera de detectar (automáticamente) la página de códigos de un archivo de texto?

El detectEncodingFromByteOrderMarks, por el StreamReaderconstructor, trabaja para UTF8 y otros archivos Unicode marcada, pero estoy buscando una manera de detectar las páginas de códigos, como ibm850, windows1252.


Gracias por sus respuestas, esto es lo que he hecho.

Los archivos que recibimos son de usuarios finales, no tienen idea de las páginas de códigos. Los receptores también son usuarios finales, por ahora esto es lo que saben sobre las páginas de códigos: las páginas de códigos existen y son molestas.

Solución:

  • Abra el archivo recibido en el Bloc de notas, mire un texto confuso. Si alguien se llama François o algo así, con tu inteligencia humana puedes adivinar esto.
  • He creado una pequeña aplicación que el usuario puede usar para abrir el archivo e ingresar un texto que el usuario sabe que aparecerá en el archivo cuando se use la página de códigos correcta.
  • Recorra todas las páginas de códigos y muestre las que dan una solución con el texto proporcionado por el usuario.
  • Si aparece más de una página de códigos, solicite al usuario que especifique más texto.
GvS
fuente

Respuestas:

260

No puede detectar la página de códigos, necesita que le digan. Puede analizar los bytes y adivinar, pero eso puede dar algunos resultados extraños (a veces divertidos). No puedo encontrarlo ahora, pero estoy seguro de que se puede engañar al Bloc de notas para que muestre texto en inglés en chino.

De todos modos, esto es lo que necesita leer: el mínimo absoluto que todo desarrollador de software debe saber absolutamente, positivamente sobre los conjuntos de caracteres y Unicode (¡sin excusas!) .

Específicamente Joel dice:

El hecho más importante sobre las codificaciones

Si olvida por completo todo lo que acabo de explicar, recuerde un hecho extremadamente importante. No tiene sentido tener una cadena sin saber qué codificación utiliza. Ya no puedes meter la cabeza en la arena y pretender que el texto "simple" es ASCII. No hay tal cosa como texto sin formato.

Si tiene una cadena, en la memoria, en un archivo o en un mensaje de correo electrónico, debe saber en qué codificación está o no puede interpretarla o mostrarla a los usuarios correctamente.

JV
fuente
43
Voté esta respuesta por dos razones. Primero, decir que "necesitas que te digan" no es útil. ¿Quién me lo diría y por qué medio lo harían? Si soy yo quien guardó el archivo, ¿a quién le preguntaría? ¿Yo mismo? En segundo lugar, el artículo no es especialmente útil como recurso para responder la pregunta. El artículo es más una historia de codificación escrita en un estilo de David Sedaris. Aprecio la narrativa, pero no responde simple / directamente a la pregunta.
geneorama
99
@geneorama, creo que el artículo de Joel aborda sus preguntas mejor que nunca, pero aquí va ... El medio seguramente depende del entorno en el que se recibe el texto. Mejor que el archivo (o lo que sea) contenga esa información (estoy pensando en HTML y XML). De lo contrario, la persona que envía el texto debe poder proporcionar esa información. Si usted fue quien creó el archivo, ¿cómo puede no saber qué codificación utiliza?
JV.
44
@geneorama, continúa ... Finalmente, supongo que la razón principal por la que el artículo no responde la pregunta simplemente es porque no hay una respuesta simple a esa pregunta. Si la pregunta fuera "¿Cómo puedo adivinar ...", entonces habría respondido de manera diferente.
JV.
1
@JV Más tarde supe que xml / html puede especificar la codificación de caracteres, gracias por mencionar ese útil tidbit.
geneorama
1
@JV "Crear un archivo" puede ser una mala elección de palabras. Supongo que un usuario puede especificar la codificación de un archivo que genera el usuario. Recientemente "creé" un archivo de un Hadoop Cluster usando Hive, y lo pasé a un FTP antes de descargarlo a varias máquinas cliente. El resultado tenía algo de basura unicode, pero no sé qué paso creó el problema. Nunca especifiqué explícitamente la codificación. Desearía poder verificar la codificación en cada paso.
geneorama
31

Si está buscando detectar codificaciones que no sean UTF (es decir, sin BOM), básicamente se debe a la heurística y al análisis estadístico del texto. Es posible que desee echar un vistazo al documento de Mozilla sobre detección universal de juegos de caracteres ( mismo enlace, con mejor formato a través de Wayback Machine ).

Tomer Gabel
fuente
99
Curiosamente, mi instalación de Firefox 3.05 detecta esa página como UTF-8, mostrando varios glifos de interrogación en un diamante, aunque la fuente tiene una metaetiqueta para Windows-1252. Cambiar manualmente la codificación de caracteres muestra el documento correctamente.
devstuff
55
Su oración "Si está buscando detectar codificaciones que no sean UTF (es decir, sin BOM)" es un poco engañosa; ¡El estándar Unicode no recomienda agregar una lista de materiales a los documentos utf-8! (y esta recomendación, o la falta de ella, es la fuente de muchos dolores de cabeza). ref: en.wikipedia.org/wiki/Byte_order_mark#UTF-8
Tao
Esto se hace para que pueda concatenar cadenas UTF-8 sin acumular BOM redundantes. Además, no se necesita una marca de orden de bytes para UTF-8, a diferencia de UTF-16, por ejemplo.
sashoalm
26

¿Has probado el puerto C # para Mozilla Universal Charset Detector?

Ejemplo de http://code.google.com/p/ude/

public static void Main(String[] args)
{
    string filename = args[0];
    using (FileStream fs = File.OpenRead(filename)) {
        Ude.CharsetDetector cdet = new Ude.CharsetDetector();
        cdet.Feed(fs);
        cdet.DataEnd();
        if (cdet.Charset != null) {
            Console.WriteLine("Charset: {0}, confidence: {1}", 
                 cdet.Charset, cdet.Confidence);
        } else {
            Console.WriteLine("Detection failed.");
        }
    }
}    
ITmeze
fuente
1
Funcionó a la perfección para el tipo de Windows-1252.
seebiscuit
¿Y cómo puedes usarlo para leer un archivo de texto a cadena usando eso? CharsetDetector devuelve el nombre de la codificación en formato de cadena y eso es todo ...
Bartosz
@Bartosz private Encoding GetEncodingFromString(string encoding) { try { return Encoding.GetEncoding(encoding); } catch { return Encoding.ASCII; } }
PrivatePyle
15

No puedes detectar la página de códigos

Esto es claramente falso. Cada navegador web tiene algún tipo de detector universal de caracteres para manejar páginas que no tienen ninguna indicación de codificación. Firefox tiene uno. Puede descargar el código y ver cómo lo hace. Vea alguna documentación aquí . Básicamente, es una heurística, pero que funciona muy bien.

Dada una cantidad razonable de texto, incluso es posible detectar el idioma.

Aquí hay otro que acabo de encontrar usando Google:

shoosh
fuente
39
"heurística": por lo que el navegador no lo detecta, está haciendo una suposición bien informada. "funciona muy bien", ¿entonces no funciona todo el tiempo? Me parece que estamos de acuerdo.
JV.
10
El estándar para HTML dicta que, si el juego de caracteres no está definido por el documento, se debe considerar que está codificado como UTF-8.
Jon Trauntvein
55
Lo cual es genial a menos que estemos leyendo documentos HTML no estándar. O documentos no HTML.
Kos
2
Esta respuesta es incorrecta, así que tuve que votar a favor. Decir que sería falso que no puede detectar la página de códigos, está mal. Puede adivinar y sus conjeturas pueden ser bastante buenas, pero no puede "detectar" una página de códigos.
z80crew el
1
@ JonTrauntvein De acuerdo con las especificaciones HTML5 a character encoding declaration is required even if the encoding is US-ASCII , una declaración deficiente resulta en el uso de un algoritmo heurístico, no en retroceder a UTF8.
z80crew el
9

Sé que es muy tarde para esta pregunta y esta solución no será atractiva para algunos (debido a su sesgo centrado en el inglés y su falta de pruebas estadísticas / empíricas), pero me funcionó muy bien, especialmente para procesar datos CSV cargados:

http://www.architectshack.com/TextFileEncodingDetector.ashx

Ventajas:

  • Detección de lista de materiales incorporada
  • Codificación predeterminada / alternativa personalizable
  • bastante confiable (en mi experiencia) para archivos basados ​​en Europa occidental que contienen algunos datos exóticos (por ejemplo, nombres franceses) con una mezcla de archivos de estilo UTF-8 y Latin-1, básicamente la mayor parte de los entornos de EE. UU. y Europa occidental.

Nota: Yo escribí esta clase, ¡así que obviamente tómalo con un grano de sal! :)

Tao
fuente
7

Notepad ++ tiene esta característica lista para usar. También admite cambiarlo.

hegearon
fuente
7

Buscando una solución diferente, encontré que

https://code.google.com/p/ude/

Esta solución es un poco pesada.

Necesitaba algo de detección de codificación básica, basada en 4 primeros bytes y probablemente detección de juego de caracteres xml, así que tomé un código fuente de muestra de Internet y agregué una versión ligeramente modificada de

http://lists.w3.org/Archives/Public/www-validator/2002Aug/0084.html

escrito para Java

    public static Encoding DetectEncoding(byte[] fileContent)
    {
        if (fileContent == null)
            throw new ArgumentNullException();

        if (fileContent.Length < 2)
            return Encoding.ASCII;      // Default fallback

        if (fileContent[0] == 0xff
            && fileContent[1] == 0xfe
            && (fileContent.Length < 4
                || fileContent[2] != 0
                || fileContent[3] != 0
                )
            )
            return Encoding.Unicode;

        if (fileContent[0] == 0xfe
            && fileContent[1] == 0xff
            )
            return Encoding.BigEndianUnicode;

        if (fileContent.Length < 3)
            return null;

        if (fileContent[0] == 0xef && fileContent[1] == 0xbb && fileContent[2] == 0xbf)
            return Encoding.UTF8;

        if (fileContent[0] == 0x2b && fileContent[1] == 0x2f && fileContent[2] == 0x76)
            return Encoding.UTF7;

        if (fileContent.Length < 4)
            return null;

        if (fileContent[0] == 0xff && fileContent[1] == 0xfe && fileContent[2] == 0 && fileContent[3] == 0)
            return Encoding.UTF32;

        if (fileContent[0] == 0 && fileContent[1] == 0 && fileContent[2] == 0xfe && fileContent[3] == 0xff)
            return Encoding.GetEncoding(12001);

        String probe;
        int len = fileContent.Length;

        if( fileContent.Length >= 128 ) len = 128;
        probe = Encoding.ASCII.GetString(fileContent, 0, len);

        MatchCollection mc = Regex.Matches(probe, "^<\\?xml[^<>]*encoding[ \\t\\n\\r]?=[\\t\\n\\r]?['\"]([A-Za-z]([A-Za-z0-9._]|-)*)", RegexOptions.Singleline);
        // Add '[0].Groups[1].Value' to the end to test regex

        if( mc.Count == 1 && mc[0].Groups.Count >= 2 )
        {
            // Typically picks up 'UTF-8' string
            Encoding enc = null;

            try {
                enc = Encoding.GetEncoding( mc[0].Groups[1].Value );
            }catch (Exception ) { }

            if( enc != null )
                return enc;
        }

        return Encoding.ASCII;      // Default fallback
    }

Es suficiente para leer probablemente los primeros 1024 bytes del archivo, pero estoy cargando el archivo completo.

TarmoPikaro
fuente
7

Si alguien está buscando una solución del 93.9%. Esto funciona para mi:

public static class StreamExtension
{
    /// <summary>
    /// Convert the content to a string.
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <returns></returns>
    public static string ReadAsString(this Stream stream)
    {
        var startPosition = stream.Position;
        try
        {
            // 1. Check for a BOM
            // 2. or try with UTF-8. The most (86.3%) used encoding. Visit: http://w3techs.com/technologies/overview/character_encoding/all/
            var streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true);
            return streamReader.ReadToEnd();
        }
        catch (DecoderFallbackException ex)
        {
            stream.Position = startPosition;

            // 3. The second most (6.7%) used encoding is ISO-8859-1. So use Windows-1252 (0.9%, also know as ANSI), which is a superset of ISO-8859-1.
            var streamReader = new StreamReader(stream, Encoding.GetEncoding(1252));
            return streamReader.ReadToEnd();
        }
    }
}
Magu
fuente
Muy buena solución. Uno puede envolver fácilmente el cuerpo de ReadAsString () en un bucle de codificaciones permitidas si se deben permitir más de 2 codificaciones (UTF-8 y ASCI 1252).
ViRuSTriNiTy
Después de probar toneladas de ejemplos, finalmente llegué al tuyo. Estoy en un lugar feliz en este momento. jajaja Gracias !!!!!!!
Sedrick
¡Esta puede no ser la respuesta a cómo detectar 1252 vs 1250, pero debería ser absolutamente la respuesta para "Cómo detectar UTF-8" con o sin una lista de materiales!
chuckc
4

He hecho algo similar en Python. Básicamente, necesita muchos datos de muestra de varias codificaciones, que se desglosan en una ventana deslizante de dos bytes y se almacenan en un diccionario (hash), codificados en pares de bytes que proporcionan valores de listas de codificaciones.

Dado ese diccionario (hash), toma su texto de entrada y:

  • si comienza con cualquier carácter BOM ('\ xfe \ xff' para UTF-16-BE, '\ xff \ xfe' para UTF-16-LE, '\ xef \ xbb \ xbf' para UTF-8, etc.), I tratarlo como se sugiere
  • de lo contrario, tome una muestra lo suficientemente grande del texto, tome todos los pares de bytes de la muestra y elija la codificación que sea la menos común sugerida del diccionario.

Si también ha muestreado textos codificados en UTF que no comienzan con ninguna lista de materiales, el segundo paso cubrirá los que se deslizaron desde el primer paso.

Hasta ahora, funciona para mí (los datos de muestra y los datos de entrada posteriores son subtítulos en varios idiomas) con tasas de error decrecientes.

tzot
fuente
4

La herramienta "uchardet" hace esto bien usando modelos de distribución de frecuencia de caracteres para cada juego de caracteres. Los archivos más grandes y más archivos "típicos" tienen más confianza (obviamente).

En ubuntu, tu solo apt-get install uchardet.

En otros sistemas, obtenga la fuente, el uso y los documentos aquí: https://github.com/BYVoid/uchardet

Erik Aronesty
fuente
En Mac vía homebrew:brew install uchardet
Paul B
3

El constructor de la clase StreamReader toma un parámetro 'detectar codificación'.

leppie
fuente
Es sólo "codifica" enlace aquí .. y la descripción dice que tenemos que proporcionar a la codificación ..
SurajS
@SurajS: Mira las otras sobrecargas.
leppie
el autor original quiere detectar la codificación de un archivo, que potencialmente no tendría el marcador BOM. StreamReader detecta la codificación del encabezado BOM según la firma. Public StreamReader (Stream stream, bool detectEncodingFromByteOrderMarks)
ibondre
1

Si puede vincular a una biblioteca C, puede usar libenca. Ver http://cihar.com/software/enca/ . Desde la página del manual:

Enca lee los archivos de texto dados, o la entrada estándar cuando no se da ninguno, y utiliza el conocimiento sobre su idioma (debe ser compatible con usted) y una mezcla de análisis, análisis estadístico, adivinanzas y magia negra para determinar sus codificaciones.

Es GPL v2.

Nick Matteo
fuente
0

Tengo el mismo problema pero aún no encontré una buena solución para detectarlo automáticamente. Ahora estoy usando PsPad (www.pspad.com) para eso;) Funciona bien

DeeCee
fuente
0

Dado que básicamente se trata de heurística, puede ser útil usar la codificación de los archivos recibidos previamente de la misma fuente como primera sugerencia.

La mayoría de las personas (o aplicaciones) hacen cosas casi en el mismo orden cada vez, a menudo en la misma máquina, por lo que es muy probable que cuando Bob cree un archivo .csv y lo envíe a Mary, siempre usará Windows-1252 o cualquiera que sea el valor predeterminado de su máquina.

Siempre que sea posible, un poco de capacitación del cliente nunca está de más :-)

devstuff
fuente
0

En realidad estaba buscando una forma genérica, no de programación, de detectar la codificación del archivo, pero aún no la encontré. Lo que encontré al probar con diferentes codificaciones fue que mi texto era UTF-7.

Entonces, donde estaba haciendo por primera vez: StreamReader file = File.OpenText (fullfilename);

Tuve que cambiarlo a: archivo StreamReader = nuevo StreamReader (fullfilename, System.Text.Encoding.UTF7);

OpenText supone que es UTF-8.

También puede crear el StreamReader como este nuevo StreamReader (fullfilename, true), el segundo parámetro significa que debería intentar detectar la codificación desde el byteordermark del archivo, pero eso no funcionó en mi caso.

Consejos intradía
fuente
@JohnMachin Estoy de acuerdo en que es raro, pero es obligatorio, por ejemplo, en algunas partes del protocolo IMAP. Sin embargo, si es allí donde está, no tendría que adivinar.
tripleee
0

Abra el archivo en AkelPad (o simplemente copie / pegue un texto ilegible), vaya a Editar -> Selección -> Recodificar ... -> marque "Detectar automáticamente".

plavozont
fuente
0

Como complemento de la publicación ITmeze, he usado esta función para convertir la salida del puerto C # para Mozilla Universal Charset Detector

    private Encoding GetEncodingFromString(string codePageName)
    {
        try
        {
            return Encoding.GetEncoding(codePageName);
        }
        catch
        {
            return Encoding.ASCII;
        }
    }

MSDN

PrivatePyle
fuente
-1

Uso este código para detectar la página de códigos ansi predeterminada de Unicode y Windows cuando leo un archivo. Para otras codificaciones, es necesario verificar el contenido, manualmente o mediante programación. Esto se puede usar para guardar el texto con la misma codificación que cuando se abrió. (Yo uso VB.NET)

'Works for Default and unicode (auto detect)
Dim mystreamreader As New StreamReader(LocalFileName, Encoding.Default) 
MyEditTextBox.Text = mystreamreader.ReadToEnd()
Debug.Print(mystreamreader.CurrentEncoding.CodePage) 'Autodetected encoding
mystreamreader.Close()
Thommy Johansson
fuente
-1

10Y (!) Había pasado desde que se le preguntó esto, y todavía no veo ninguna mención de la buena solución de MS, no GPL: la API IMultiLanguage2 .

La mayoría de las bibliotecas ya mencionadas se basan en UDE de Mozilla, y parece razonable que los navegadores ya hayan abordado problemas similares. No sé cuál es la solución de Chrome, pero desde que IE 5.0 MS ha lanzado la suya, es:

  1. Libre de problemas de licencia GPL y similares,
  2. Respaldado y mantenido probablemente para siempre,
  3. Ofrece una salida rica: todos los candidatos válidos para codificación / páginas de códigos junto con puntajes de confianza,
  4. Sorprendentemente fácil de usar (es una llamada de función única).

Es una llamada COM nativa, pero aquí hay un trabajo muy bueno de Carsten Zeumer, que maneja el desorden de interoperabilidad para el uso de .net. Hay algunos otros alrededor, pero en general esta biblioteca no recibe la atención que merece.

Ofek Shilon
fuente