Maneira eficaz de encontrar a codificação de qualquer arquivo

115

Sim é a pergunta mais frequente, e este assunto é vago para mim e já que não sei muito sobre ele.

Mas eu gostaria de uma maneira muito precisa de encontrar a codificação de arquivos. Tão preciso quanto o Notepad ++ é.

Fábio Antunes
fonte
Quais codificações? UTF-8 vs UTF-16, big vs little endian? Ou você está se referindo às páginas de código do MSDos antigas, como shift-JIS ou cirílico etc?
dthorpe
Outra possível duplicata: stackoverflow.com/questions/436220/…
Data
@Oded: Quote "O método getEncoding () retornará a codificação que foi configurada (leia o JavaDoc) para o fluxo. Ele não adivinhará a codificação para você.".
Fábio Antunes
2
Para algumas leituras básicas , joelonsoftware.com/articles/Unicode.html é uma boa leitura. Se há algo que você deveria saber sobre texto, é que não existe texto simples.
Martijn

Respostas:

155

A StreamReader.CurrentEncodingpropriedade raramente retorna a codificação de arquivo de texto correta para mim. Tive maior sucesso ao determinar o endianness de um arquivo, analisando sua marca de ordem de byte (BOM). Se o arquivo não tiver um BOM, isso não poderá determinar a codificação do arquivo.

* ATUALIZADO em 08/04/2020 para incluir detecção de UTF-32LE e retornar a codificação correta 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;
}
2Toad
fonte
3
+1. Isso funcionou para mim também (enquanto detectEncodingFromByteOrderMarks não). Usei "new FileStream (filename, FileMode.Open, FileAccess.Read)" para evitar uma IOException porque o arquivo é somente leitura.
Polyfun
56
Arquivos UTF-8 podem estar sem BOM, neste caso ele retornará ASCII incorretamente.
user626528
3
Esta resposta está errada. Olhando para o fonte de referência para StreamReader, que a implementação é o que mais pessoas vão querer. Eles fazem novas codificações em vez de usar os Encoding.Unicodeobjetos existentes , então as verificações de igualdade irão falhar (o que pode acontecer raramente porque, por exemplo, Encoding.UTF8pode retornar objetos diferentes), mas (1) não usa o formato UTF-7 realmente estranho, (2) o padrão é UTF-8 se nenhum BOM for encontrado e (3) pode ser substituído para usar uma codificação padrão diferente.
hangar
2
Tive mais sucesso com o novo StreamReader (nome do arquivo, verdadeiro) .CurrentEncoding
Benoit
4
Existe um erro fundamental no código; ao detectar a assinatura UTF32 big-endian ( ), você retorna a fornecida pelo sistema , que é uma codificação little-endian (conforme observado aqui ). E também, como notado por @Nyerguds, você ainda não está procurando pelo UTF32LE, que tem assinatura (de acordo com en.wikipedia.org/wiki/Byte_order_mark ). Como o usuário observou, por ser subsumida, essa verificação deve vir antes das verificações de 2 bytes. 00 00 FE FFEncoding.UTF32FF FE 00 00
Glenn Slayden
44

O código a seguir funciona bem para mim, usando a StreamReaderclasse:

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

O truque é usar a Peekchamada, caso contrário, o .NET não fez nada (e não leu o preâmbulo, o BOM). Claro, se você usar qualquer outra ReadXXXchamada antes de verificar a codificação, ela também funcionará.

Se o arquivo não tiver BOM, a defaultEncodingIfNoBomcodificação será usada. Há também um StreamReader sem esse método de sobrecarga (neste caso, a codificação Padrão (ANSI) será usada como defaultEncodingIfNoBom), mas recomendo definir o que você considera a codificação padrão em seu contexto.

Eu testei isso com sucesso com arquivos com BOM para UTF8, UTF16 / Unicode (LE & BE) e UTF32 (LE & BE). Não funciona para UTF7.

Simon Mourier
fonte
Eu recebo de volta o que foi definido como codificação padrão. Eu poderia estar faltando alguma coisa?
Ram
1
@DRAM - isso pode acontecer se o arquivo não tiver BOM
Simon Mourier
Obrigado @Simon Mourier. Não esperava que meu pdf / qualquer arquivo não tivesse nascido. Este link stackoverflow.com/questions/4520184/… pode ser útil para quem tenta detectar sem BOM.
Ram
1
No PowerShell, tive que executar $ reader.close (), ou então não foi possível gravar. 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 Isso não funciona se a codificação do arquivo forUTF-8 without BOM
Ozkan
11

Eu tentaria as seguintes etapas:

1) Verifique se há uma Marca de Ordem de Byte

2) Verifique se o arquivo é UTF8 válido

3) Use a página de código "ANSI" local (ANSI como a Microsoft define)

A etapa 2 funciona porque a maioria das sequências não ASCII em páginas de código diferentes de UTF8 não são UTF8 válidas.

CodesInChaos
fonte
Esta parece ser a resposta mais correta, pois a outra resposta não funciona para mim. Pode-se fazer isso com File.OpenRead e .Read-ing os primeiros bytes do arquivo.
user420667
1
O passo 2 é um monte de trabalho de programação para verificar os padrões de bits, no entanto.
Nyerguds,
1
Não tenho certeza se a decodificação realmente lança exceções, ou se apenas substitui as sequências não reconhecidas por '?' Eu continuei escrevendo uma classe de verificação de padrões de bits, de qualquer maneira.
Nyerguds,
3
Ao criar uma instância de, Utf8Encodingvocê pode passar um parâmetro extra que determina se uma exceção deve ser lançada ou se você prefere corrupção de dados silenciosa.
CodesInChaos
1
Eu gosto dessa resposta. A maioria das codificações (como provavelmente 99% dos casos de uso) será UTF-8 ou ANSI (página de códigos 1252 do Windows). Você pode verificar se a string contém o caractere de substituição (0xFFFD) para determinar se a codificação falhou.
março de
10

Verifique isso.

UDE

Este é um porte do Mozilla Universal Charset Detector e você pode usá-lo assim ...

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
fonte
Você deve saber que UDE é GPL
lindexi
Ok, se você está preocupado com a licença, pode usar esta. Licenciado como MIT e você pode usá-lo para software de código aberto e fechado. nuget.org/packages/SimpleHelpers.FileEncoding
Alexei Agüero Alba
A licença é MPL com opção 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 de
Parece que esta bifurcação é atualmente a mais ativa e tem um pacote nuget UDE.Netstandard. github.com/yinyue200/ude
jbtule de
biblioteca muito útil, com muitas codificações diferentes e incomuns! tanques!
mshakurov
6

Fornecendo os detalhes de implementação para as etapas propostas por @CodesInChaos:

1) Verifique se há uma Marca de Ordem de Byte

2) Verifique se o arquivo é UTF8 válido

3) Use a página de código "ANSI" local (ANSI como a Microsoft define)

A etapa 2 funciona porque a maioria das sequências não ASCII em páginas de código diferentes de UTF8 não são UTF8 válidas. https://stackoverflow.com/a/4522251/867248 explica a tática em mais detalhes.

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
fonte
Obrigado! Isso está resolvido para mim. Mas eu prefiro usar apenas em reader.Peek() vez de while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva
reader.Peek()não lê o fluxo inteiro. Descobri que, com riachos maiores, Peek()era inadequado. Eu usei em seu reader.ReadToEndAsync()lugar.
Gary Pendlebury
E o que é Utf8EncodingVerifier?
Peter Moore
1
@PeterMoore É uma codificação para utf8, var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());é usado no trybloco ao ler uma linha. Se o codificador falhar em analisar o texto fornecido (o texto não está codificado com utf8), Utf8EncodingVerifier irá lançar. A exceção é detectada e então sabemos que o texto não é utf8 e o padrão é ISO-8859-1
Berthier Lemieux
2

Os códigos a seguir são meus códigos Powershell para determinar se alguns arquivos cpp ou h ou ml estão codificados com ISO-8859-1 (Latin-1) ou UTF-8 sem BOM, se nenhum deles supor que seja GB18030. Eu sou um chinês trabalhando na França e o MSVC salva como Latin-1 no computador francês e como GB no computador chinês, então isso me ajuda a evitar problemas de codificação quando faço trocas de arquivos de origem entre meu sistema e meus colegas.

O caminho é simples, se todos os caracteres estiverem entre x00-x7E, ASCII, UTF-8 e Latin-1 são todos iguais, mas se eu ler um arquivo não ASCII por UTF-8, encontraremos o caractere especial mostrado , então tente ler com Latin-1. Em Latin-1, entre \ x7F e \ xAF está vazio, enquanto GB usa full entre x00-xFF, então se eu tiver algum entre os dois, não é Latin-1

O código é escrito em PowerShell, mas usa .net, por isso é fácil de ser traduzido para C # ou 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
fonte
2

.NET não é muito útil, mas você pode tentar o seguinte algoritmo:

  1. tente encontrar a codificação por BOM (marca de ordem de byte) ... muito provavelmente não será encontrado
  2. tente analisar em codificações diferentes

Aqui está a chamada:

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");

Aqui está o 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
fonte
1

Procure aqui por c #

https://msdn.microsoft.com/en-us/library/system.io.streamreader.currentencoding%28v=vs.110%29.aspx

string path = @"path\to\your\file.ext";

using (StreamReader sr = new StreamReader(path, true))
{
    while (sr.Peek() >= 0)
    {
        Console.Write((char)sr.Read());
    }

    //Test for the encoding after reading, or at least
    //after the first read.
    Console.WriteLine("The encoding used was {0}.", sr.CurrentEncoding);
    Console.ReadLine();
    Console.WriteLine();
}
Sedrick
fonte
0

Pode ser útil

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

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
Raushan
fonte