Modo efficace per trovare la codifica di qualsiasi file


115

Sì è una domanda molto frequente, e questa questione è vaga per me e poiché non ne so molto.

Ma vorrei un modo molto preciso per trovare un file Encoding. Così preciso come lo è Notepad ++.



Quali codifiche? UTF-8 vs UTF-16, big vs little endian? O ti riferisci alle vecchie tabelle codici MSDos, come shift-JIS o cirillico ecc?
dthorpe

Un altro possibile duplicato: stackoverflow.com/questions/436220/…
Oded

@Oded: Quote "Il metodo getEncoding () restituirà la codifica che è stata impostata (leggi JavaDoc) per lo stream. Non indovinerà la codifica per te.".
Fábio Antunes

2
Per alcune letture in background, joelonsoftware.com/articles/Unicode.html è una buona lettura. Se c'è una cosa che dovresti sapere sul testo, è che non esiste il testo normale.
Martijn

Risposte:


155

La StreamReader.CurrentEncodingproprietà restituisce raramente la codifica del file di testo corretta per me. Ho avuto più successo nel determinare l'endianness di un file, analizzando il suo byte order mark (BOM). Se il file non dispone di una distinta materiali, non è possibile determinare la codifica del file.

* AGGIORNATO 4/08/2020 per includere il rilevamento UTF-32LE e restituire la codifica corretta per 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;
}

3
+1. Questo ha funzionato anche per me (mentre detectEncodingFromByteOrderMarks no). Ho usato "new FileStream (filename, FileMode.Open, FileAccess.Read)" per evitare una IOException perché il file è di sola lettura.
Polyfun

56
I file UTF-8 possono essere senza BOM, in questo caso restituirà ASCII in modo errato.
user626528

3
Questa risposta è sbagliata. Guardando la fonte di riferimento per StreamReader, che l'implementazione è ciò che più la gente vuole. Creano nuove codifiche piuttosto che utilizzare gli Encoding.Unicodeoggetti esistenti , quindi i controlli di uguaglianza falliranno (cosa che potrebbe comunque accadere raramente perché, ad esempio, Encoding.UTF8può restituire oggetti diversi), ma (1) non usa il formato UTF-7 davvero strano, (2) il valore predefinito è UTF-8 se non viene trovata alcuna BOM e (3) può essere sovrascritto per utilizzare una codifica predefinita diversa.
hangar

2
ho avuto un successo migliore con il nuovo StreamReader (nome file, true) .CurrentEncoding
Benoit

4
C'è un errore fondamentale nel codice; quando rilevi la firma UTF32 big-endian ( ), restituisci la codifica fornita dal sistema , che è una codifica little-endian (come indicato qui ). Inoltre, come notato da @Nyerguds, non stai ancora cercando UTF32LE, che ha la firma (secondo en.wikipedia.org/wiki/Byte_order_mark ). Come quell'utente ha notato, poiché è sussunto, tale controllo deve precedere i controlli a 2 byte. 00 00 FE FFEncoding.UTF32FF FE 00 00
Glenn Slayden

44

Il seguente codice funziona bene per me, usando la StreamReaderclasse:

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

Il trucco sta nell'usare la Peekchiamata, altrimenti .NET non ha fatto nulla (e non ha letto il preambolo, il BOM). Ovviamente, se usi qualsiasi altra ReadXXXchiamata prima di controllare la codifica, funziona anche tu.

Se il file non ha BOM, defaultEncodingIfNoBomverrà utilizzata la codifica. Esiste anche uno StreamReader senza questo metodo di sovraccarico (in questo caso, la codifica predefinita (ANSI) verrà utilizzata come defaultEncodingIfNoBom), ma ti consiglio di definire ciò che consideri la codifica predefinita nel tuo contesto.

L'ho testato con successo con file con BOM per UTF8, UTF16 / Unicode (LE & BE) e UTF32 (LE & BE). Non funziona per UTF7.


Torno indietro quello che è stato impostato come codifica predefinita. Potrei perdermi qualcosa?
Ram

1
@DRAM - questo può accadere se il file non ha BOM
Simon Mourier

Grazie @Simon Mourier. Non mi aspetto che il mio pdf / qualsiasi file non sia nato. Questo link stackoverflow.com/questions/4520184/… potrebbe essere utile per qualcuno che cerca di rilevare senza bom.
Ram

1
In PowerShell ho dovuto eseguire $ reader.close (), oppure è stato bloccato dalla scrittura. 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 Questo non funziona se la codifica del file èUTF-8 without BOM
Ozkan

11

Proverei i seguenti passaggi:

1) Controlla se c'è un Byte Order Mark

2) Controlla se il file è UTF8 valido

3) Usa la tabella codici "ANSI" locale (ANSI come la definisce Microsoft)

Il passaggio 2 funziona perché la maggior parte delle sequenze non ASCII nelle tabelle codici diverse da UTF8 non sono UTF8 valide.


Questa sembra la risposta più corretta, poiché l'altra risposta non funziona per me. Si può farlo con File.OpenRead e .Read-ing i primi pochi byte del file.
user420667

1
Il passaggio 2 è un intero lavoro di programmazione per controllare gli schemi di bit, però.
Nyerguds

1
Non sono sicuro che la decodifica generi effettivamente delle eccezioni, o se sostituisce semplicemente le sequenze non riconosciute con "?". Sono andato con la scrittura di un po 'di classe di controllo dei modelli, comunque.
Nyerguds

3
Quando crei un'istanza di Utf8Encodingpuoi passare un parametro aggiuntivo che determina se deve essere generata un'eccezione o se preferisci il danneggiamento silenzioso dei dati.
CodesInChaos

1
Mi piace questa risposta. La maggior parte delle codifiche (come il 99% dei tuoi casi d'uso probabilmente) sarà UTF-8 o ANSI (tabella codici di Windows 1252). È possibile verificare se la stringa contiene il carattere sostitutivo (0xFFFD) per determinare se la codifica non è riuscita.
marsze

10

Controllare questo.

UDE

Questo è un port di Mozilla Universal Charset Detector e puoi usarlo in questo modo ...

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

Dovresti sapere che UDE è GPL
lindexi

Ok, se sei preoccupato per la licenza, puoi usare questa. Concesso in licenza come MIT e puoi usarlo sia per software open source che per software closed source. nuget.org/packages/SimpleHelpers.FileEncoding
Alexei Agüero Alba

La licenza è MPL con un'opzione 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

Sembra che questo fork sia attualmente il più attivo e abbia un pacchetto nuget UDE.Netstandard. github.com/yinyue200/ude
jbtule

libreria molto utile, gestita con molte codifiche diverse e insolite! carri armati!
mshakurov

6

Fornire i dettagli di implementazione per i passaggi proposti da @CodesInChaos:

1) Controlla se c'è un Byte Order Mark

2) Controlla se il file è UTF8 valido

3) Usa la tabella codici "ANSI" locale (ANSI come la definisce Microsoft)

Il passaggio 2 funziona perché la maggior parte delle sequenze non ASCII nelle tabelle codici diverse da UTF8 non sono UTF8 valide. https://stackoverflow.com/a/4522251/867248 spiega la tattica in modo più dettagliato.

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();
        ...

Grazie! Questo ha risolto per me. Ma preferirei usare solo al reader.Peek() posto di while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva

reader.Peek()non legge l'intero flusso. Ho scoperto che con flussi più grandi, Peek()era inadeguato. Ho usato reader.ReadToEndAsync()invece.
Gary Pendlebury

E cos'è Utf8EncodingVerifier?
Peter Moore

1
@PeterMoore È una codifica per utf8, var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());viene utilizzata nel tryblocco durante la lettura di una riga. Se il codificatore non riesce ad analizzare il testo fornito (il testo non è codificato con utf8), Utf8EncodingVerifier genererà. L'eccezione viene rilevata e quindi sappiamo che il testo non è utf8 e il valore predefinito è ISO-8859-1
Berthier Lemieux

2

I seguenti codici sono i miei codici Powershell per determinare se alcuni file cpp o ho ml codificano con ISO-8859-1 (Latin-1) o UTF-8 senza BOM, se nessuno dei due si suppone che sia GB18030. Sono un cinese che lavora in Francia e MSVC salva come Latin-1 sul computer francese e salva come GB sul computer cinese, quindi questo mi aiuta a evitare problemi di codifica quando si scambiano file sorgente tra il mio sistema ei miei colleghi.

Il modo è semplice, se tutti i caratteri sono compresi tra x00-x7E, ASCII, UTF-8 e Latin-1 sono tutti uguali, ma se leggo un file non ASCII di UTF-8, troveremo il carattere speciale visualizzato , quindi prova a leggere con Latin-1. In Latin-1, tra \ x7F e \ xAF è vuoto, mentre GB usa full tra x00-xFF quindi se ne ho uno tra i due, non è Latin-1

Il codice è scritto in PowerShell, ma usa .net quindi è facile da tradurre in 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');

2

.NET non è molto utile, ma puoi provare il seguente algoritmo:

  1. prova a trovare la codifica per BOM (byte order mark) ... molto probabilmente non verrà trovata
  2. prova ad analizzare in diverse codifiche

Ecco la chiamata:

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

Ecco il codice:

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;    // 
    }
}


0

Può essere utile

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

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.