Herhangi bir dosyanın Kodlamasını bulmanın etkili yolu


115

Evet, en sık sorulan sorudur ve bu konu benim için belirsizdir ve bu konuda pek bir şey bilmediğim için.

Ama Kodlayan bir dosya bulmanın çok kesin bir yolunu istiyorum. Notepad ++ kadar hassas.


1
olası Java
Oded

Hangi kodlamalar? UTF-8 ve UTF-16, büyük ve küçük endian? Yoksa shift-JIS veya Kiril alfabesi gibi eski MSDos kod sayfalarından mı bahsediyorsunuz?
dthorpe

Başka bir olası kopya: stackoverflow.com/questions/436220/…
Oded

@Oded: Alıntı "getEncoding () yöntemi akış için ayarlanan (JavaDoc'u okuyun) kodlamayı döndürür. Sizin için kodlamayı tahmin etmez."
Fábio Antunes

2
Bazı arka plan okumaları için joelonsoftware.com/articles/Unicode.html iyi bir okumadır . Metin hakkında bilmeniz gereken bir şey varsa, o da düz metin diye bir şeyin olmamasıdır.
Martijn

Yanıtlar:


155

StreamReader.CurrentEncodingMülkiyet nadiren benim için kodlama doğru metin dosyası döndürür. Bayt sırası işaretini (BOM) analiz ederek bir dosyanın geçerliliğini belirlemede daha büyük başarı elde ettim. Dosyanın bir Malzeme Listesi yoksa, bu dosyanın kodlamasını belirleyemez.

* UTF-32LE algılamasını içerecek ve UTF-32BE için doğru kodlamayı döndürecek şekilde GÜNCELLENDİ 4/08/2020

/// <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. Bu benim için de çalıştı (oysa DetectEncodingFromByteOrderMarks işe yaramadı). Dosya salt okunur olduğundan IOException'dan kaçınmak için "new FileStream (filename, FileMode.Open, FileAccess.Read)" kullandım.
Polyfun

56
UTF-8 dosyaları BOM olmadan olabilir, bu durumda ASCII'yi yanlış döndürür.
user626528

3
Bu cevap yanlış. Baktığımızda referans kaynağı için StreamReader, o uygulama daha fazla kişi isteyecektir şeydir. Mevcut Encoding.Unicodenesneleri kullanmak yerine yeni kodlamalar yaparlar , bu nedenle eşitlik kontrolleri başarısız olur (bu durum nadiren gerçekleşebilir çünkü örneğin Encoding.UTF8farklı nesneler döndürebilir), ancak (1) gerçekten tuhaf UTF-7 formatını kullanmaz, (2) BOM bulunmazsa varsayılan olarak UTF-8 olur ve (3) farklı bir varsayılan kodlama kullanmak için geçersiz kılınabilir.
hangar

2
Yeni StreamReader ile daha iyi başarı elde ettim (dosya adı, doğru) .CurrentEncoding
Benoit

4
Kodda temel bir hata var; big-endian UTF32 imzasını ( 00 00 FE FF) tespit ettiğinizde, sistem tarafından sağlanan Encoding.UTF32, küçük bir endian kodlaması olan ( burada belirtildiği gibi ) döndürürsünüz . Ayrıca, @Nyerguds tarafından belirtildiği gibi, imzası olan UTF32LE'yi hala aramıyorsunuz FF FE 00 00( en.wikipedia.org/wiki/Byte_order_mark'a göre ). Bu kullanıcının da belirttiği gibi, alt toplama olduğu için, bu kontrol 2 baytlık kontrollerden önce yapılmalıdır.
Glenn Slayden

44

Aşağıdaki kod, StreamReadersınıfı kullanarak benim için iyi çalışıyor :

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

İşin püf noktası Peekaramayı kullanmaktır , aksi takdirde .NET hiçbir şey yapmamıştır (ve başlangıç ​​ekini, BOM'u okumamıştır). Elbette ReadXXX, kodlamayı kontrol etmeden önce başka bir çağrı kullanırsanız , o da çalışır.

Dosyada BOM yoksa, defaultEncodingIfNoBomkodlama kullanılacaktır. Bu aşırı yükleme yöntemine sahip olmayan bir StreamReader da vardır (bu durumda, Default (ANSI) kodlaması defaultEncodingIfNoBom olarak kullanılacaktır), ancak bağlamınızda varsayılan kodlamayı düşündüğünüz şeyi tanımlamanızı tavsiye ederim.

Bunu UTF8, UTF16 / Unicode (LE & BE) ve UTF32 (LE & BE) için BOM içeren dosyalarla başarıyla test ettim. UTF7 için çalışmaz.


Varsayılan kodlama olarak ayarlanmış olanı geri alıyorum. Bir şey özleyebilir miyim?
Ram

1
@DRAM - dosyada BOM yoksa bu gerçekleşebilir
Simon Mourier

Teşekkürler @ Simon Mourier. Pdf'mde / herhangi bir dosyamda bom bulunmamasını beklemiyorum. Bu bağlantı stackoverflow.com/questions/4520184/… bom olmadan tespit etmeye çalışan biri için yararlı olabilir.
Ram

1
Powershell'de $ reader.close () çalıştırmam gerekiyordu, yoksa yazmaktan kilitlendi. 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 Dosyanın kodlanması ise işe yaramazUTF-8 without BOM
Özkan

11

Aşağıdaki adımları deneyeceğim:

1) Bayt Sırası İşareti olup olmadığını kontrol edin

2) Dosyanın geçerli UTF8 olup olmadığını kontrol edin

3) Yerel "ANSI" kod sayfasını kullanın (Microsoft'un tanımladığı şekliyle ANSI)

Adım 2, UTF8'in geçerli UTF8 olmadığı kod sayfalarındaki ASCII olmayan dizilerin çoğu olduğu için çalışır.


Diğer cevap benim için çalışmadığı için bu daha doğru cevap gibi görünüyor. Bunu File.OpenRead ve dosyanın ilk birkaç baytını okuyarak yapabilirsiniz.
user420667

1
Adım 2, bit modellerini kontrol etmek için bir dizi programlama çalışmasıdır.
Nyerguds

1
Kod çözme işleminin aslında istisnalar attığından veya tanınmayan dizileri '?' İle değiştirip değiştirmediğinden emin değilim. Yine de biraz kalıp kontrol dersi yazmaya gittim.
Nyerguds

3
Bir örneğini oluşturduğunuzda, Utf8Encodingbir istisna atılıp atılmayacağını veya sessiz veri bozulmasını tercih edip etmediğinizi belirleyen fazladan bir parametre iletebilirsiniz.
CodesInChaos

1
Bu cevabı beğendim. Çoğu kodlama (muhtemelen kullanım durumlarınızın% 99'u gibi) ya UTF-8 veya ANSI (Windows kod sayfası 1252) olacaktır. Kodlamanın başarısız olup olmadığını belirlemek için dizenin değiştirme karakteri (0xFFFD) içerip içermediğini kontrol edebilirsiniz.
marsze

10

Şuna göz at.

UDE

Bu, Mozilla Universal Charset Detector'ın bir limanıdır ve bu şekilde kullanabilirsiniz ...

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


Tamam, lisans konusunda endişeliyseniz, bunu kullanabilirsiniz. MIT olarak lisanslanmıştır ve hem açık kaynaklı hem de kapalı kaynaklı yazılımlar için kullanabilirsiniz. nuget.org/packages/SimpleHelpers.FileEncoding
Alexei Agüero Alba

Lisans, GPL seçeneğiyle MPL'dir. 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

Görünüşe göre bu çatal şu ​​anda en aktif olanıdır ve bir UDE.Netstandard nuget paketine sahiptir. github.com/yinyue200/ude
jbtule

çok kullanışlı kütüphane, birçok farklı ve alışılmadık kodlamayla başa çıkmış! tanklar!
mshakurov

6

@CodesInChaos tarafından önerilen adımlar için uygulama ayrıntılarını sağlamak:

1) Bayt Sırası İşareti olup olmadığını kontrol edin

2) Dosyanın geçerli UTF8 olup olmadığını kontrol edin

3) Yerel "ANSI" kod sayfasını kullanın (Microsoft'un tanımladığı şekliyle ANSI)

Adım 2, UTF8'in geçerli UTF8 olmadığı kod sayfalarındaki ASCII olmayan dizilerin çoğu olduğu için çalışır. https://stackoverflow.com/a/4522251/867248 taktiği daha ayrıntılı olarak açıklıyor.

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

Teşekkür ederim! Bu benim için çözüldü. Ama reader.Peek() bunun yerine kullanmayı tercih ederim while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva

reader.Peek()akışın tamamını okumaz. Daha büyük akışlarda Peek()yetersiz olduğunu buldum . Onun reader.ReadToEndAsync()yerine kullandım .
Gary Pendlebury

Ve Utf8EncodingVerifier nedir?
Peter Moore

1
@PeterMoore Bu bir utf8 kodlamasıdır, satır okurken blokta var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());kullanılır try. Kodlayıcı sağlanan metni ayrıştıramazsa (metin utf8 ile kodlanmamışsa), Utf8EncodingVerifier atacaktır. İstisna yakalandı ve sonra metnin utf8 olmadığını ve varsayılan olarak ISO-8859-1 olduğunu biliyoruz
Berthier Lemieux

2

Aşağıdaki kodlar, bazı cpp veya h veya ml dosyalarının ISO-8859-1 (Latin-1) veya UTF-8 ile BOM olmadan kodlanıp kodlanmadığını belirlemek için kullandığım Powershell kodlarımdır, eğer ikisinin de GB18030 olduğunu varsaymayın. Ben Fransa'da çalışan bir Çinliyim ve MSVC Fransız bilgisayarına Latin-1 olarak kaydediyor ve Çin bilgisayarına GB olarak kaydediyor, bu da sistemim ve meslektaşlarım arasında kaynak dosya alışverişi yaparken kodlama sorununu önlememe yardımcı oluyor.

Yol basit, eğer tüm karakterler x00-x7E arasındaysa, ASCII, UTF-8 ve Latin-1 hepsi aynıysa, ancak UTF-8 ile ASCII olmayan bir dosya okursam, special özel karakterini bulacağız , bu yüzden Latin-1 ile okumaya çalışın. Latin-1'de \ x7F ile \ xAF arası boştur, GB x00-xFF arasında full kullanır, bu yüzden ikisi arasında bir tane varsa, Latin-1 değildir.

Kod PowerShell'de yazılmıştır, ancak .net kullanır, bu nedenle C # veya F # 'a çevrilmesi kolaydır

$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 pek yararlı değildir, ancak aşağıdaki algoritmayı deneyebilirsiniz:

  1. kodlamayı BOM (bayt sırası işareti) ile bulmaya çalışın ... büyük olasılıkla bulunmayacaktır
  2. farklı kodlamaları ayrıştırmayı deneyin

İşte çağrı:

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

İşte kod:

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

Yararlı olabilir

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

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.