Manera eficaz de encontrar la codificación de cualquier archivo

115

Sí es una pregunta más frecuente, y este asunto me resulta vago y no sé mucho al respecto.

Pero me gustaría una forma muy precisa de encontrar una codificación de archivos. Tan preciso como es Notepad ++.

Fábio Antunes
fuente
¿Qué codificaciones? UTF-8 vs UTF-16, ¿big vs little endian? ¿O te refieres a las antiguas páginas de códigos de MSDos, como shift-JIS o cirílico, etc.?
dthorpe
Otro posible duplicado: stackoverflow.com/questions/436220/…
Oded
@Oded: Quote "El método getEncoding () devolverá la codificación que se configuró (lea el JavaDoc) para la secuencia. No adivinará la codificación por usted".
Fábio Antunes
2
Para leer algunos antecedentes, joelonsoftware.com/articles/Unicode.html es una buena lectura. Si hay algo que debe saber sobre el texto, es que no existe el texto sin formato.
Martijn

Respuestas:

155

La StreamReader.CurrentEncodingpropiedad rara vez me devuelve la codificación correcta del archivo de texto. He tenido más éxito determinando el endianness de un archivo, analizando su marca de orden de bytes (BOM). Si el archivo no tiene una lista de materiales, esto no puede determinar la codificación del archivo.

* ACTUALIZADO 08/04/2020 para incluir detección UTF-32LE y devolver la codificación correcta para UTF-32BE

/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
public static Encoding GetEncoding(string filename)
{
    // Read the BOM
    var bom = new byte[4];
    using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
    {
        file.Read(bom, 0, 4);
    }

    // Analyze the BOM
    if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
    if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
    if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) return Encoding.UTF32; //UTF-32LE
    if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
    if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
    if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return new UTF32Encoding(true, true);  //UTF-32BE

    // We actually have no idea what the encoding is if we reach this point, so
    // you may wish to return null instead of defaulting to ASCII
    return Encoding.ASCII;
}
2 sapo
fuente
3
+1. Esto también funcionó para mí (mientras que detectEncodingFromByteOrderMarks no lo hizo). Usé "new FileStream (nombre de archivo, FileMode.Open, FileAccess.Read)" para evitar una IOException porque el archivo es de solo lectura.
Polyfun
56
Los archivos UTF-8 pueden estar sin BOM, en este caso devolverá ASCII incorrectamente.
user626528
3
Esta respuesta es incorrecta. En cuanto a la fuente de referencia para StreamReaderque la implementación es lo que más gente va a querer. Hacen nuevas codificaciones en lugar de usar los Encoding.Unicodeobjetos existentes , por lo que las verificaciones de igualdad fallarán (lo que rara vez sucederá de todos modos porque, por ejemplo, Encoding.UTF8puede devolver diferentes objetos), pero (1) no usa el formato UTF-7 realmente extraño, (2) se establece de forma predeterminada en UTF-8 si no se encuentra una lista de materiales, y (3) se puede anular para utilizar una codificación predeterminada diferente.
hangar
2
Tuve más éxito con el nuevo StreamReader (nombre de archivo, verdadero) .CurrentEncoding
Benoit
4
Hay un error fundamental en el código; cuando detecta la firma UTF32 big-endian ( ), devuelve la proporcionada por el sistema , que es una codificación little-endian (como se indica aquí ). Y también, como señaló @Nyerguds, todavía no está buscando UTF32LE, que tiene firma (según en.wikipedia.org/wiki/Byte_order_mark ). Como señaló ese usuario, debido a que está subsumiendo, esa verificación debe venir antes que las verificaciones de 2 bytes. 00 00 FE FFEncoding.UTF32FF FE 00 00
Glenn Slayden
44

El siguiente código funciona bien para mí, usando la StreamReaderclase:

  using (var reader = new StreamReader(fileName, defaultEncodingIfNoBom, true))
  {
      reader.Peek(); // you need this!
      var encoding = reader.CurrentEncoding;
  }

El truco consiste en utilizar la Peekllamada, de lo contrario .NET no ha hecho nada (y no ha leído el preámbulo, la lista de materiales). Por supuesto, si usa cualquier otra ReadXXXllamada antes de verificar la codificación, también funciona.

Si el archivo no tiene BOM, defaultEncodingIfNoBomse utilizará la codificación. También hay un StreamReader sin este método de sobrecarga (en este caso, la codificación predeterminada (ANSI) se utilizará como defaultEncodingIfNoBom), pero recomiendo definir lo que considera la codificación predeterminada en su contexto.

He probado esto con éxito con archivos con BOM para UTF8, UTF16 / Unicode (LE & BE) y UTF32 (LE & BE). No funciona para UTF7.

Simón Mourier
fuente
Vuelvo lo que se configuró como codificación predeterminada. ¿Podría estar perdiendo algo?
Ram
1
@DRAM: esto puede suceder si el archivo no tiene BOM
Simon Mourier
Gracias @Simon Mourier. No espero que mi pdf / cualquier archivo no haya nacido. Este enlace stackoverflow.com/questions/4520184/… podría ser útil para alguien que intente detectar sin bom.
Ram
1
En PowerShell tuve que ejecutar $ reader.close (), o de lo contrario estaba bloqueado para no escribir. foreach($filename in $args) { $reader = [System.IO.StreamReader]::new($filename, [System.Text.Encoding]::default,$true); $peek = $reader.Peek(); $reader.currentencoding | select bodyname,encodingname; $reader.close() }
js2010
1
@SimonMourier Esto no funciona si la codificación del archivo esUTF-8 without BOM
Ozkan
11

Intentaría los siguientes pasos:

1) Compruebe si hay una marca de orden de bytes

2) Compruebe si el archivo es UTF8 válido

3) Utilice la página de códigos "ANSI" local (ANSI como lo define Microsoft)

El paso 2 funciona porque la mayoría de las secuencias que no son ASCII en páginas de códigos que no sean UTF8 no son UTF8 válidas.

CódigosInChaos
fuente
Esta parece la respuesta más correcta, ya que la otra respuesta no me funciona. Uno puede hacerlo con File.OpenRead y .Read-ing los primeros bytes del archivo.
user420667
1
Sin embargo, el paso 2 es un montón de trabajo de programación para verificar los patrones de bits.
Nyerguds
1
Sin embargo, no estoy seguro de que la decodificación arroje excepciones, o si simplemente reemplaza las secuencias no reconocidas con '?'. De todos modos, fui escribiendo una clase de verificación de patrones de bits.
Nyerguds
3
Cuando crea una instancia de Utf8Encoding, puede pasar un parámetro adicional que determina si se debe lanzar una excepción o si prefiere la corrupción de datos silenciosa.
CodesInChaos
1
Me gusta esta respuesta. La mayoría de las codificaciones (como el 99% de los casos de uso probablemente) serán UTF-8 o ANSI (página de códigos de Windows 1252). Puede verificar si la cadena contiene el carácter de reemplazo (0xFFFD) para determinar si la codificación falló.
marsze
10

Mira esto.

UDE

Este es un puerto de Mozilla Universal Charset Detector y puede usarlo así ...

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.");
        }
    }
}
Alexei Agüero Alba
fuente
Debe saber que UDE es GPL
lindexi
De acuerdo, si está preocupado por la licencia, puede usar esta. Tiene licencia como MIT y puede usarlo tanto para software de código abierto como cerrado. nuget.org/packages/SimpleHelpers.FileEncoding
Alexei Agüero Alba
La licencia es MPL con opción GPL. The library is subject to the Mozilla Public License Version 1.1 (the "License"). Alternatively, it may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL").
jbtule
Parece que esta bifurcación es actualmente la más activa y tiene un paquete nuget UDE.Netstandard. github.com/yinyue200/ude
jbtule
biblioteca muy útil, que soporta muchas codificaciones diferentes e inusuales. tanques!
mshakurov
6

Proporcionando los detalles de implementación para los pasos propuestos por @CodesInChaos:

1) Compruebe si hay una marca de orden de bytes

2) Compruebe si el archivo es UTF8 válido

3) Utilice la página de códigos "ANSI" local (ANSI como lo define Microsoft)

El paso 2 funciona porque la mayoría de las secuencias que no son ASCII en páginas de códigos que no sean UTF8 no son UTF8 válidas. https://stackoverflow.com/a/4522251/867248 explica la táctica con más detalles.

using System; using System.IO; using System.Text;

// Using encoding from BOM or UTF8 if no BOM found,
// check if the file is valid, by reading all lines
// If decoding fails, use the local "ANSI" codepage

public string DetectFileEncoding(Stream fileStream)
{
    var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());
    using (var reader = new StreamReader(fileStream, Utf8EncodingVerifier,
           detectEncodingFromByteOrderMarks: true, leaveOpen: true, bufferSize: 1024))
    {
        string detectedEncoding;
        try
        {
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
            }
            detectedEncoding = reader.CurrentEncoding.BodyName;
        }
        catch (Exception e)
        {
            // Failed to decode the file using the BOM/UT8. 
            // Assume it's local ANSI
            detectedEncoding = "ISO-8859-1";
        }
        // Rewind the stream
        fileStream.Seek(0, SeekOrigin.Begin);
        return detectedEncoding;
   }
}


[Test]
public void Test1()
{
    Stream fs = File.OpenRead(@".\TestData\TextFile_ansi.csv");
    var detectedEncoding = DetectFileEncoding(fs);

    using (var reader = new StreamReader(fs, Encoding.GetEncoding(detectedEncoding)))
    {
       // Consume your file
        var line = reader.ReadLine();
        ...
Berthier Lemieux
fuente
¡Gracias! Esto me resolvió. Pero yo preferiría el uso justo reader.Peek() en lugar de while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva
reader.Peek()no lee toda la transmisión. Descubrí que con arroyos más grandes, Peek()era inadecuado. Usé en su reader.ReadToEndAsync()lugar.
Gary Pendlebury
¿Y qué es Utf8EncodingVerifier?
Peter Moore
1
@PeterMoore Es una codificación para utf8, var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());se usa en el trybloque al leer una línea. Si el codificador no puede analizar el texto proporcionado (el texto no está codificado con utf8), Utf8EncodingVerifier arrojará. Se detecta la excepción y luego sabemos que el texto no es utf8, y por defecto es ISO-8859-1
Berthier Lemieux hace
2

Los siguientes códigos son mis códigos de Powershell para determinar si algunos archivos cpp oh o ml están codificados con ISO-8859-1 (Latin-1) o UTF-8 sin BOM, si ninguno de los dos, suponga que es GB18030. Soy un chino que trabaja en Francia y MSVC guarda como Latin-1 en una computadora francesa y guarda como GB en una computadora china, por lo que esto me ayuda a evitar problemas de codificación cuando realizo intercambios de archivos fuente entre mi sistema y mis colegas.

La forma es simple, si todos los caracteres están entre x00-x7E, ASCII, UTF-8 y Latin-1 son todos iguales, pero si leo un archivo no ASCII por UTF-8, encontraremos el carácter especial aparecerá , intente leer con Latin-1. En Latin-1, entre \ x7F y \ xAF está vacío, mientras que GB usa lleno entre x00-xFF, así que si tengo algo entre los dos, no es Latin-1

El código está escrito en PowerShell, pero usa .net, por lo que es fácil de traducir a C # o F #

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach($i in Get-ChildItem .\ -Recurse -include *.cpp,*.h, *.ml) {
    $openUTF = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::UTF8)
    $contentUTF = $openUTF.ReadToEnd()
    [regex]$regex = '�'
    $c=$regex.Matches($contentUTF).count
    $openUTF.Close()
    if ($c -ne 0) {
        $openLatin1 = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::GetEncoding('ISO-8859-1'))
        $contentLatin1 = $openLatin1.ReadToEnd()
        $openLatin1.Close()
        [regex]$regex = '[\x7F-\xAF]'
        $c=$regex.Matches($contentLatin1).count
        if ($c -eq 0) {
            [System.IO.File]::WriteAllLines($i, $contentLatin1, $Utf8NoBomEncoding)
            $i.FullName
        } 
        else {
            $openGB = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::GetEncoding('GB18030'))
            $contentGB = $openGB.ReadToEnd()
            $openGB.Close()
            [System.IO.File]::WriteAllLines($i, $contentGB, $Utf8NoBomEncoding)
            $i.FullName
        }
    }
}
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
Enzojz
fuente
2

.NET no es muy útil, pero puede probar el siguiente algoritmo:

  1. intente encontrar la codificación por BOM (marca de orden de bytes) ... es muy probable que no se encuentre
  2. intente analizar en diferentes codificaciones

Aquí está la llamada:

var encoding = FileHelper.GetEncoding(filePath);
if (encoding == null)
    throw new Exception("The file encoding is not supported. Please choose one of the following encodings: UTF8/UTF7/iso-8859-1");

Aquí está el código:

public class FileHelper
{
    /// <summary>
    /// Determines a text file's encoding by analyzing its byte order mark (BOM) and if not found try parsing into diferent encodings       
    /// Defaults to UTF8 when detection of the text file's endianness fails.
    /// </summary>
    /// <param name="filename">The text file to analyze.</param>
    /// <returns>The detected encoding or null.</returns>
    public static Encoding GetEncoding(string filename)
    {
        var encodingByBOM = GetEncodingByBOM(filename);
        if (encodingByBOM != null)
            return encodingByBOM;

        // BOM not found :(, so try to parse characters into several encodings
        var encodingByParsingUTF8 = GetEncodingByParsing(filename, Encoding.UTF8);
        if (encodingByParsingUTF8 != null)
            return encodingByParsingUTF8;

        var encodingByParsingLatin1 = GetEncodingByParsing(filename, Encoding.GetEncoding("iso-8859-1"));
        if (encodingByParsingLatin1 != null)
            return encodingByParsingLatin1;

        var encodingByParsingUTF7 = GetEncodingByParsing(filename, Encoding.UTF7);
        if (encodingByParsingUTF7 != null)
            return encodingByParsingUTF7;

        return null;   // no encoding found
    }

    /// <summary>
    /// Determines a text file's encoding by analyzing its byte order mark (BOM)  
    /// </summary>
    /// <param name="filename">The text file to analyze.</param>
    /// <returns>The detected encoding.</returns>
    private static Encoding GetEncodingByBOM(string filename)
    {
        // Read the BOM
        var byteOrderMark = new byte[4];
        using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
        {
            file.Read(byteOrderMark, 0, 4);
        }

        // Analyze the BOM
        if (byteOrderMark[0] == 0x2b && byteOrderMark[1] == 0x2f && byteOrderMark[2] == 0x76) return Encoding.UTF7;
        if (byteOrderMark[0] == 0xef && byteOrderMark[1] == 0xbb && byteOrderMark[2] == 0xbf) return Encoding.UTF8;
        if (byteOrderMark[0] == 0xff && byteOrderMark[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
        if (byteOrderMark[0] == 0xfe && byteOrderMark[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
        if (byteOrderMark[0] == 0 && byteOrderMark[1] == 0 && byteOrderMark[2] == 0xfe && byteOrderMark[3] == 0xff) return Encoding.UTF32;

        return null;    // no BOM found
    }

    private static Encoding GetEncodingByParsing(string filename, Encoding encoding)
    {            
        var encodingVerifier = Encoding.GetEncoding(encoding.BodyName, new EncoderExceptionFallback(), new DecoderExceptionFallback());

        try
        {
            using (var textReader = new StreamReader(filename, encodingVerifier, detectEncodingFromByteOrderMarks: true))
            {
                while (!textReader.EndOfStream)
                {                        
                    textReader.ReadLine();   // in order to increment the stream position
                }

                // all text parsed ok
                return textReader.CurrentEncoding;
            }
        }
        catch (Exception ex) { }

        return null;    // 
    }
}
Pacurar Stefan
fuente
0

Puede ser útil

string path = @"address/to/the/file.extension";

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
raushan
fuente