Wie erkenne ich die Zeichenkodierung einer Textdatei?

76

Ich versuche festzustellen, welche Zeichenkodierung in meiner Datei verwendet wird.

Ich versuche mit diesem Code die Standardkodierung zu erhalten

public static Encoding GetFileEncoding(string srcFile)
    {
      // *** Use Default of Encoding.Default (Ansi CodePage)
      Encoding enc = Encoding.Default;

      // *** Detect byte order mark if any - otherwise assume default
      byte[] buffer = new byte[5];
      FileStream file = new FileStream(srcFile, FileMode.Open);
      file.Read(buffer, 0, 5);
      file.Close();

      if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
        enc = Encoding.UTF8;
      else if (buffer[0] == 0xfe && buffer[1] == 0xff)
        enc = Encoding.Unicode;
      else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
        enc = Encoding.UTF32;
      else if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
        enc = Encoding.UTF7;
      else if (buffer[0] == 0xFE && buffer[1] == 0xFF)      
        // 1201 unicodeFFFE Unicode (Big-Endian)
        enc = Encoding.GetEncoding(1201);      
      else if (buffer[0] == 0xFF && buffer[1] == 0xFE)      
        // 1200 utf-16 Unicode
        enc = Encoding.GetEncoding(1200);


      return enc;
    }

Meine fünf ersten Bytes sind 60, 118, 56, 46 und 49.

Gibt es ein Diagramm, das zeigt, welche Codierung mit diesen fünf ersten Bytes übereinstimmt?

Cédric Boivin
quelle
4
Das Byte-Ordnungszeichen sollte nicht zum Erkennen von Codierungen verwendet werden. Es gibt Fälle, in denen nicht eindeutig ist, welche Codierung verwendet wird: UTF-16 LE und UTF-32 LE beginnen beide mit denselben zwei Bytes. Die Stückliste sollte nur zur Erkennung der Bytereihenfolge (daher der Name) verwendet werden. Außerdem sollte UTF-8 streng genommen nicht einmal eine Byte-Ordnungsmarke haben, und das Hinzufügen einer kann einige Software stören, die dies nicht erwartet.
Mark Byers
@ Mark Bayers, gibt es also eine Möglichkeit, die Verwendung der Kodierung in meiner Datei zu löschen?
Cédric Boivin
4
@ Mark Byers: UTF-32 LE beginnt mit denselben 2 Bytes wie UTF-16 LE. Es folgt jedoch auch mit Bytes 00 00, was in UTF-16 LE (meiner Meinung nach sehr) unwahrscheinlich ist. Theoretisch sollte die Stückliste auch angeben, wie Sie sagen, aber in der Praxis dient sie als Signatur, um zu zeigen, welche Codierung sie enthält. Siehe: unicode.org/faq/utf_bom.html#bom4
Dan W
Ist die UTF7-Stückliste tatsächlich eine echte Sache? Ich habe versucht, ein UTF7Encoding-Objekt zu erstellen und GetPreamble () darauf auszuführen, und es hat ein leeres Array zurückgegeben. Und im Gegensatz zu utf8 hat es keinen Konstruktorparameter dafür.
Nyerguds
Mark Beyers: Ihr Kommentar ist VOLLSTÄNDIG falsch. Die Stückliste ist eine kugelsichere Methode zum Erkennen der Codierung. UTF16 BE und UTF32 BE sind nicht mehrdeutig. Sie sollten das Thema studieren, bevor Sie falsche Kommentare schreiben. Wenn eine Software keine UTF8-Stückliste verarbeitet, stammt diese Software entweder aus den 1980er Jahren oder ist schlecht programmiert. Heute sollte jede Software Stücklisten verarbeiten und erkennen.
Elmue

Antworten:

84

Sie können sich nicht darauf verlassen, dass die Datei eine Stückliste enthält. UTF-8 benötigt es nicht. Und Nicht-Unicode-Codierungen haben nicht einmal eine Stückliste. Es gibt jedoch andere Möglichkeiten, die Codierung zu erkennen.

UTF-32

Die Stückliste ist 00 00 FE FF (für BE) oder FF FE 00 00 (für LE).

UTF-32 ist jedoch auch ohne Stückliste leicht zu erkennen. Dies liegt daran, dass der Unicode-Codepunktbereich auf U + 10FFFF beschränkt ist und UTF-32-Einheiten daher immer das Muster 00 {00-10} xx xx (für BE) oder xx xx {00-10} 00 (für LE) haben. . Wenn die Daten eine Länge haben, die ein Vielfaches von 4 ist und einem dieser Muster folgt, können Sie davon ausgehen, dass es sich um UTF-32 handelt. False Positives sind aufgrund der Seltenheit von 00 Bytes in byteorientierten Codierungen nahezu unmöglich.

US-ASCII

Keine Stückliste, aber Sie brauchen keine. ASCII kann leicht durch das Fehlen von Bytes im 80-FF-Bereich identifiziert werden.

UTF-8

Stückliste ist EF BB BF. Aber darauf kann man sich nicht verlassen. Viele UTF-8-Dateien haben keine Stückliste, insbesondere wenn sie von Nicht-Windows-Systemen stammen.

Sie können jedoch davon ausgehen, dass eine Datei, die als UTF-8 validiert wird, UTF-8 ist. False Positives sind selten.

Insbesondere da die Daten nicht ASCII sind, beträgt die falsch positive Rate für eine 2-Byte-Sequenz nur 3,9% (1920/49152). Bei einer 7-Byte-Sequenz sind es weniger als 1%. Bei einer 12-Byte-Sequenz sind es weniger als 0,1%. Bei einer 24-Byte-Sequenz ist es weniger als 1 zu 1 Million.

UTF-16

Stückliste ist FE FF (für BE) oder FF FE (für LE). Beachten Sie, dass sich die UTF-16LE-Stückliste am Anfang der UTF-32LE-Stückliste befindet. Überprüfen Sie daher zuerst UTF-32.

Wenn Sie zufällig eine Datei haben, die hauptsächlich aus ISO-8859-1-Zeichen besteht, ist es auch ein starker Indikator für UTF-16, wenn die Hälfte der Bytes der Datei 00 ist.

Andernfalls besteht die einzige zuverlässige Möglichkeit, UTF-16 ohne Stückliste zu erkennen, darin, nach Ersatzpaaren (D [8-B] xx D [CF] xx) zu suchen. Nicht-BMP-Zeichen werden jedoch zu selten verwendet, um diesen Ansatz praktikabel zu machen .

XML

Wenn Ihre Datei mit den Bytes 3C 3F 78 6D 6C beginnt (dh den ASCII-Zeichen "<? Xml"), suchen Sie nach einer encoding=Deklaration. Wenn vorhanden, verwenden Sie diese Codierung. Wenn nicht vorhanden, nehmen Sie UTF-8 an, die Standard-XML-Codierung.

Wenn Sie EBCDIC unterstützen müssen, suchen Sie auch nach der entsprechenden Sequenz 4C 6F A7 94 93.

Wenn Sie ein Dateiformat haben, das eine Codierungsdeklaration enthält, suchen Sie im Allgemeinen nach dieser Deklaration, anstatt zu versuchen, die Codierung zu erraten.

Nichts des oben Genannten

Es gibt Hunderte anderer Codierungen, deren Erkennung mehr Aufwand erfordert. Ich empfehle, Mozillas Zeichensatzdetektor oder einen .NET-Port davon zu testen .

Ein vernünftiger Standard

Wenn Sie die UTF-Codierungen ausgeschlossen haben und keine Codierungsdeklaration oder statistische Erkennung haben, die auf eine andere Codierung hinweist, nehmen Sie ISO-8859-1 oder das eng verwandte Windows-1252 an . (Beachten Sie, dass der neueste HTML-Standard erfordert, dass eine „ISO-8859-1“ -Deklaration als Windows-1252 interpretiert wird.) Als Windows-Standardcodepage für Englisch (und andere beliebte Sprachen wie Spanisch, Portugiesisch, Deutsch und Französisch). Es ist die am häufigsten anzutreffende Codierung außer UTF-8.

dan04
quelle
1
OK, was ich erwartet hatte. Können Sie sich mit der Unterscheidung von UTF-8 / UTF-16 befassen? PS: Danke für eine sehr hilfreiche Antwort. +1
Ira Baxter
2
Wenn bei UTF-16BE-Textdateien ein bestimmter Prozentsatz gerader Bytes auf Null gesetzt ist (oder ungerade Bytes auf UTF-16LE überprüft werden), besteht eine gute Wahrscheinlichkeit, dass die Codierung UTF-16 ist. Was denken Sie?
Dan W
1
Die Gültigkeit von UTF-8 kann durch Bitmusterprüfungen gut erkannt werden. Das Bitmuster des ersten Bytes gibt genau an, wie viele Bytes folgen werden, und die folgenden Bytes haben auch zu überprüfende Steuerbits. Die Muster werden alle hier gezeigt: ianthehenry.com/2015/1/17/decoding-utf-8
Nyerguds
2
@marsze Dies ist nicht meine Antwort ... und es wird nicht erwähnt, da es um Erkennung geht, und wie ich bereits erwähnt habe, können Sie einfache Codierungen mit einem Byte pro Symbol nicht wirklich erkennen. Ich habe persönlich eine Antwort auf diese Stelle gepostet, um sie (vage) zu identifizieren.
Nyerguds
2
@ Marsze: Dort habe ich einen Abschnitt für Latin-1 hinzugefügt.
Dan04
11

Wenn Sie eine "einfache" Lösung verfolgen möchten, ist diese von mir zusammengestellte Klasse möglicherweise hilfreich:

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

Die Stücklistenerkennung wird zuerst automatisch durchgeführt und versucht dann, zwischen Unicode-Codierungen ohne Stückliste und einer anderen Standardcodierung (im Allgemeinen Windows-1252, in .Net fälschlicherweise als Encoding.ASCII gekennzeichnet) zu unterscheiden.

Wie oben erwähnt, kann eine "schwerere" Lösung mit NCharDet oder MLang geeigneter sein, und wie ich auf der Übersichtsseite dieser Klasse feststelle, ist es am besten, wenn möglich, irgendeine Form der Interaktivität mit dem Benutzer bereitzustellen, da dies einfach möglich ist ist keine 100% Erkennungsrate möglich!

Snippet für den Fall, dass die Site offline ist:

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

namespace KlerksSoft
{
    public static class TextFileEncodingDetector
    {
        /*
         * Simple class to handle text file encoding woes (in a primarily English-speaking tech 
         *      world).
         * 
         *  - This code is fully managed, no shady calls to MLang (the unmanaged codepage
         *      detection library originally developed for Internet Explorer).
         * 
         *  - This class does NOT try to detect arbitrary codepages/charsets, it really only
         *      aims to differentiate between some of the most common variants of Unicode 
         *      encoding, and a "default" (western / ascii-based) encoding alternative provided
         *      by the caller.
         *      
         *  - As there is no "Reliable" way to distinguish between UTF-8 (without BOM) and 
         *      Windows-1252 (in .Net, also incorrectly called "ASCII") encodings, we use a 
         *      heuristic - so the more of the file we can sample the better the guess. If you 
         *      are going to read the whole file into memory at some point, then best to pass 
         *      in the whole byte byte array directly. Otherwise, decide how to trade off 
         *      reliability against performance / memory usage.
         *      
         *  - The UTF-8 detection heuristic only works for western text, as it relies on 
         *      the presence of UTF-8 encoded accented and other characters found in the upper 
         *      ranges of the Latin-1 and (particularly) Windows-1252 codepages.
         *  
         *  - For more general detection routines, see existing projects / resources:
         *    - MLang - Microsoft library originally for IE6, available in Windows XP and later APIs now (I think?)
         *      - MLang .Net bindings: http://www.codeproject.com/KB/recipes/DetectEncoding.aspx
         *    - CharDet - Mozilla browser's detection routines
         *      - Ported to Java then .Net: http://www.conceptdevelopment.net/Localization/NCharDet/
         *      - Ported straight to .Net: http://code.google.com/p/chardetsharp/source/browse
         *  
         * Copyright Tao Klerks, 2010-2012, [email protected]
         * Licensed under the modified BSD license:
         * 
Redistribution and use in source and binary forms, with or without modification, are 
permitted provided that the following conditions are met:
 - Redistributions of source code must retain the above copyright notice, this list of 
conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice, this list 
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
 - The name of the author may not be used to endorse or promote products derived from 
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
OF SUCH DAMAGE.
         * 
         * CHANGELOG:
         *  - 2012-02-03: 
         *    - Simpler methods, removing the silly "DefaultEncoding" parameter (with "??" operator, saves no typing)
         *    - More complete methods
         *      - Optionally return indication of whether BOM was found in "Detect" methods
         *      - Provide straight-to-string method for byte arrays (GetStringFromByteArray)
         */

        const long _defaultHeuristicSampleSize = 0x10000; //completely arbitrary - inappropriate for high numbers of files / high speed requirements

        public static Encoding DetectTextFileEncoding(string InputFilename)
        {
            using (FileStream textfileStream = File.OpenRead(InputFilename))
            {
                return DetectTextFileEncoding(textfileStream, _defaultHeuristicSampleSize);
            }
        }

        public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize)
        {
            bool uselessBool = false;
            return DetectTextFileEncoding(InputFileStream, _defaultHeuristicSampleSize, out uselessBool);
        }

        public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize, out bool HasBOM)
        {
            if (InputFileStream == null)
                throw new ArgumentNullException("Must provide a valid Filestream!", "InputFileStream");

            if (!InputFileStream.CanRead)
                throw new ArgumentException("Provided file stream is not readable!", "InputFileStream");

            if (!InputFileStream.CanSeek)
                throw new ArgumentException("Provided file stream cannot seek!", "InputFileStream");

            Encoding encodingFound = null;

            long originalPos = InputFileStream.Position;

            InputFileStream.Position = 0;


            //First read only what we need for BOM detection
            byte[] bomBytes = new byte[InputFileStream.Length > 4 ? 4 : InputFileStream.Length];
            InputFileStream.Read(bomBytes, 0, bomBytes.Length);

            encodingFound = DetectBOMBytes(bomBytes);

            if (encodingFound != null)
            {
                InputFileStream.Position = originalPos;
                HasBOM = true;
                return encodingFound;
            }


            //BOM Detection failed, going for heuristics now.
            //  create sample byte array and populate it
            byte[] sampleBytes = new byte[HeuristicSampleSize > InputFileStream.Length ? InputFileStream.Length : HeuristicSampleSize];
            Array.Copy(bomBytes, sampleBytes, bomBytes.Length);
            if (InputFileStream.Length > bomBytes.Length)
                InputFileStream.Read(sampleBytes, bomBytes.Length, sampleBytes.Length - bomBytes.Length);
            InputFileStream.Position = originalPos;

            //test byte array content
            encodingFound = DetectUnicodeInByteSampleByHeuristics(sampleBytes);

            HasBOM = false;
            return encodingFound;
        }

        public static Encoding DetectTextByteArrayEncoding(byte[] TextData)
        {
            bool uselessBool = false;
            return DetectTextByteArrayEncoding(TextData, out uselessBool);
        }

        public static Encoding DetectTextByteArrayEncoding(byte[] TextData, out bool HasBOM)
        {
            if (TextData == null)
                throw new ArgumentNullException("Must provide a valid text data byte array!", "TextData");

            Encoding encodingFound = null;

            encodingFound = DetectBOMBytes(TextData);

            if (encodingFound != null)
            {
                HasBOM = true;
                return encodingFound;
            }
            else
            {
                //test byte array content
                encodingFound = DetectUnicodeInByteSampleByHeuristics(TextData);

                HasBOM = false;
                return encodingFound;
            }
        }

        public static string GetStringFromByteArray(byte[] TextData, Encoding DefaultEncoding)
        {
            return GetStringFromByteArray(TextData, DefaultEncoding, _defaultHeuristicSampleSize);
        }

        public static string GetStringFromByteArray(byte[] TextData, Encoding DefaultEncoding, long MaxHeuristicSampleSize)
        {
            if (TextData == null)
                throw new ArgumentNullException("Must provide a valid text data byte array!", "TextData");

            Encoding encodingFound = null;

            encodingFound = DetectBOMBytes(TextData);

            if (encodingFound != null)
            {
                //For some reason, the default encodings don't detect/swallow their own preambles!!
                return encodingFound.GetString(TextData, encodingFound.GetPreamble().Length, TextData.Length - encodingFound.GetPreamble().Length);
            }
            else
            {
                byte[] heuristicSample = null;
                if (TextData.Length > MaxHeuristicSampleSize)
                {
                    heuristicSample = new byte[MaxHeuristicSampleSize];
                    Array.Copy(TextData, heuristicSample, MaxHeuristicSampleSize);
                }
                else
                {
                    heuristicSample = TextData;
                }

                encodingFound = DetectUnicodeInByteSampleByHeuristics(TextData) ?? DefaultEncoding;
                return encodingFound.GetString(TextData);
            }
        }


        public static Encoding DetectBOMBytes(byte[] BOMBytes)
        {
            if (BOMBytes == null)
                throw new ArgumentNullException("Must provide a valid BOM byte array!", "BOMBytes");

            if (BOMBytes.Length < 2)
                return null;

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

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

            if (BOMBytes.Length < 3)
                return null;

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

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

            if (BOMBytes.Length < 4)
                return null;

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

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

            return null;
        }

        public static Encoding DetectUnicodeInByteSampleByHeuristics(byte[] SampleBytes)
        {
            long oddBinaryNullsInSample = 0;
            long evenBinaryNullsInSample = 0;
            long suspiciousUTF8SequenceCount = 0;
            long suspiciousUTF8BytesTotal = 0;
            long likelyUSASCIIBytesInSample = 0;

            //Cycle through, keeping count of binary null positions, possible UTF-8 
            //  sequences from upper ranges of Windows-1252, and probable US-ASCII 
            //  character counts.

            long currentPos = 0;
            int skipUTF8Bytes = 0;

            while (currentPos < SampleBytes.Length)
            {
                //binary null distribution
                if (SampleBytes[currentPos] == 0)
                {
                    if (currentPos % 2 == 0)
                        evenBinaryNullsInSample++;
                    else
                        oddBinaryNullsInSample++;
                }

                //likely US-ASCII characters
                if (IsCommonUSASCIIByte(SampleBytes[currentPos]))
                    likelyUSASCIIBytesInSample++;

                //suspicious sequences (look like UTF-8)
                if (skipUTF8Bytes == 0)
                {
                    int lengthFound = DetectSuspiciousUTF8SequenceLength(SampleBytes, currentPos);

                    if (lengthFound > 0)
                    {
                        suspiciousUTF8SequenceCount++;
                        suspiciousUTF8BytesTotal += lengthFound;
                        skipUTF8Bytes = lengthFound - 1;
                    }
                }
                else
                {
                    skipUTF8Bytes--;
                }

                currentPos++;
            }

            //1: UTF-16 LE - in english / european environments, this is usually characterized by a 
            //  high proportion of odd binary nulls (starting at 0), with (as this is text) a low 
            //  proportion of even binary nulls.
            //  The thresholds here used (less than 20% nulls where you expect non-nulls, and more than
            //  60% nulls where you do expect nulls) are completely arbitrary.

            if (((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 
                && ((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6
                )
                return Encoding.Unicode;


            //2: UTF-16 BE - in english / european environments, this is usually characterized by a 
            //  high proportion of even binary nulls (starting at 0), with (as this is text) a low 
            //  proportion of odd binary nulls.
            //  The thresholds here used (less than 20% nulls where you expect non-nulls, and more than
            //  60% nulls where you do expect nulls) are completely arbitrary.

            if (((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 
                && ((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6
                )
                return Encoding.BigEndianUnicode;


            //3: UTF-8 - Martin Dürst outlines a method for detecting whether something CAN be UTF-8 content 
            //  using regexp, in his w3c.org unicode FAQ entry: 
            //  http://www.w3.org/International/questions/qa-forms-utf-8
            //  adapted here for C#.
            string potentiallyMangledString = Encoding.ASCII.GetString(SampleBytes);
            Regex UTF8Validator = new Regex(@"\A(" 
                + @"[\x09\x0A\x0D\x20-\x7E]"
                + @"|[\xC2-\xDF][\x80-\xBF]"
                + @"|\xE0[\xA0-\xBF][\x80-\xBF]"
                + @"|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}"
                + @"|\xED[\x80-\x9F][\x80-\xBF]"
                + @"|\xF0[\x90-\xBF][\x80-\xBF]{2}"
                + @"|[\xF1-\xF3][\x80-\xBF]{3}"
                + @"|\xF4[\x80-\x8F][\x80-\xBF]{2}"
                + @")*\z");
            if (UTF8Validator.IsMatch(potentiallyMangledString))
            {
                //Unfortunately, just the fact that it CAN be UTF-8 doesn't tell you much about probabilities.
                //If all the characters are in the 0-127 range, no harm done, most western charsets are same as UTF-8 in these ranges.
                //If some of the characters were in the upper range (western accented characters), however, they would likely be mangled to 2-byte by the UTF-8 encoding process.
                // So, we need to play stats.

                // The "Random" likelihood of any pair of randomly generated characters being one 
                //   of these "suspicious" character sequences is:
                //     128 / (256 * 256) = 0.2%.
                //
                // In western text data, that is SIGNIFICANTLY reduced - most text data stays in the <127 
                //   character range, so we assume that more than 1 in 500,000 of these character 
                //   sequences indicates UTF-8. The number 500,000 is completely arbitrary - so sue me.
                //
                // We can only assume these character sequences will be rare if we ALSO assume that this
                //   IS in fact western text - in which case the bulk of the UTF-8 encoded data (that is 
                //   not already suspicious sequences) should be plain US-ASCII bytes. This, I 
                //   arbitrarily decided, should be 80% (a random distribution, eg binary data, would yield 
                //   approx 40%, so the chances of hitting this threshold by accident in random data are 
                //   VERY low). 

                if ((suspiciousUTF8SequenceCount * 500000.0 / SampleBytes.Length >= 1) //suspicious sequences
                    && (
                           //all suspicious, so cannot evaluate proportion of US-Ascii
                           SampleBytes.Length - suspiciousUTF8BytesTotal == 0 
                           ||
                           likelyUSASCIIBytesInSample * 1.0 / (SampleBytes.Length - suspiciousUTF8BytesTotal) >= 0.8
                       )
                    )
                    return Encoding.UTF8;
            }

            return null;
        }

        private static bool IsCommonUSASCIIByte(byte testByte)
        {
            if (testByte == 0x0A //lf
                || testByte == 0x0D //cr
                || testByte == 0x09 //tab
                || (testByte >= 0x20 && testByte <= 0x2F) //common punctuation
                || (testByte >= 0x30 && testByte <= 0x39) //digits
                || (testByte >= 0x3A && testByte <= 0x40) //common punctuation
                || (testByte >= 0x41 && testByte <= 0x5A) //capital letters
                || (testByte >= 0x5B && testByte <= 0x60) //common punctuation
                || (testByte >= 0x61 && testByte <= 0x7A) //lowercase letters
                || (testByte >= 0x7B && testByte <= 0x7E) //common punctuation
                )
                return true;
            else
                return false;
        }

        private static int DetectSuspiciousUTF8SequenceLength(byte[] SampleBytes, long currentPos)
        {
            int lengthFound = 0;

            if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC2
                )
            {
                if (SampleBytes[currentPos + 1] == 0x81 
                    || SampleBytes[currentPos + 1] == 0x8D 
                    || SampleBytes[currentPos + 1] == 0x8F
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] == 0x90 
                    || SampleBytes[currentPos + 1] == 0x9D
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] >= 0xA0 
                    && SampleBytes[currentPos + 1] <= 0xBF
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC3
                )
            {
                if (SampleBytes[currentPos + 1] >= 0x80 
                    && SampleBytes[currentPos + 1] <= 0xBF
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC5
                )
            {
                if (SampleBytes[currentPos + 1] == 0x92 
                    || SampleBytes[currentPos + 1] == 0x93
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] == 0xA0 
                    || SampleBytes[currentPos + 1] == 0xA1
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] == 0xB8 
                    || SampleBytes[currentPos + 1] == 0xBD 
                    || SampleBytes[currentPos + 1] == 0xBE
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC6
                )
            {
                if (SampleBytes[currentPos + 1] == 0x92)
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xCB
                )
            {
                if (SampleBytes[currentPos + 1] == 0x86 
                    || SampleBytes[currentPos + 1] == 0x9C
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 2 
                && SampleBytes[currentPos] == 0xE2
                )
            {
                if (SampleBytes[currentPos + 1] == 0x80)
                {
                    if (SampleBytes[currentPos + 2] == 0x93 
                        || SampleBytes[currentPos + 2] == 0x94
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0x98 
                        || SampleBytes[currentPos + 2] == 0x99 
                        || SampleBytes[currentPos + 2] == 0x9A
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0x9C 
                        || SampleBytes[currentPos + 2] == 0x9D 
                        || SampleBytes[currentPos + 2] == 0x9E
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xA0 
                        || SampleBytes[currentPos + 2] == 0xA1 
                        || SampleBytes[currentPos + 2] == 0xA2
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xA6)
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xB0)
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xB9 
                        || SampleBytes[currentPos + 2] == 0xBA
                        )
                        lengthFound = 3;
                }
                else if (SampleBytes[currentPos + 1] == 0x82 
                    && SampleBytes[currentPos + 2] == 0xAC
                    )
                    lengthFound = 3;
                else if (SampleBytes[currentPos + 1] == 0x84 
                    && SampleBytes[currentPos + 2] == 0xA2
                    )
                    lengthFound = 3;
            }

            return lengthFound;
        }

    }
}
Tao
quelle
1
Gibt eigentlich Encoding.GetEncoding("Windows-1252")eine andere Objektklasse als Encoding.ASCII. Während des Debuggens wird Windows-1252 als System.Text.SBCSCodePageEncodingObjekt angezeigt, während ASCII ein System.Text.ASCIIEncodingObjekt ist. Ich benutze nie die ASCII, wenn ich Windows-1252
Nyerguds
Um reguläre Ausdrücke mit Binärdaten (Bytes) abzugleichen, lautet die richtige Methode: string data = Encoding.GetEncoding("iso-8859-1").GetString(bytes); Da dies die einzige Einzelbyte-Codierung ist, die eine 1-zu-1-Byte-Zuordnung zu einer Zeichenfolge aufweist.
Amr Ali
6

Verwenden StreamReaderund leiten Sie es, um die Codierung für Sie zu ermitteln:

using (var reader = new System.IO.StreamReader(path, true))
{
    var currentEncoding = reader.CurrentEncoding;
}

Verwenden Sie die Codepage-IDs https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx , um die Logik abhängig davon zu wechseln.

Phil Hunt
quelle
3
Nicht funktioniert, der StreamReader nimmt an, dass Ihre Datei in UTF-8
Cédric Boivin
@Cedric: Überprüfen Sie die MSDN für diesen Konstruktor. Haben Sie Beweise dafür, dass der Konstruktor nicht konsistent mit der Dokumentation arbeitet? Zugegeben, das ist in Microsofts Dokumenten möglich :-)
Phil Hunt
4
Diese Version prüft auch nur für Stückliste
Daniel Bişar
2
Müssen Sie nicht Read()vor dem Lesen anrufen CurrentEncoding? In der MSDN für CurrentEncoding heißt es: "Der Wert kann nach dem ersten Aufruf einer beliebigen Read-Methode von StreamReader unterschiedlich sein, da die automatische Codierungserkennung erst beim ersten Aufruf einer Read-Methode erfolgt."
Carl Walsh
1
Meine Tests haben gezeigt, dass dies nicht zuverlässig verwendet werden kann und daher überhaupt nicht verwendet werden sollte.
Geoffrey McGrath
5

Hier gibt es mehrere Antworten, aber niemand hat nützlichen Code gepostet.

Hier ist mein Code, der alle Codierungen erkennt, die Microsoft in Framework 4 in der StreamReader-Klasse erkennt.

Natürlich müssen Sie diese Funktion sofort nach dem Öffnen des Streams aufrufen, bevor Sie etwas anderes aus dem Stream lesen, da die Stückliste die ersten Bytes im Stream sind.

Diese Funktion erfordert einen Stream, der suchen kann (z. B. einen FileStream). Wenn Sie einen Stream haben, der nicht suchen kann, müssen Sie einen komplizierteren Code schreiben, der einen Bytepuffer mit den bereits gelesenen Bytes zurückgibt, die jedoch keine Stückliste sind.

/// <summary>
/// UTF8    : EF BB BF
/// UTF16 BE: FE FF
/// UTF16 LE: FF FE
/// UTF32 BE: 00 00 FE FF
/// UTF32 LE: FF FE 00 00
/// </summary>
public static Encoding DetectEncoding(Stream i_Stream)
{
    if (!i_Stream.CanSeek || !i_Stream.CanRead)
        throw new Exception("DetectEncoding() requires a seekable and readable Stream");

    // Try to read 4 bytes. If the stream is shorter, less bytes will be read.
    Byte[] u8_Buf = new Byte[4];
    int s32_Count = i_Stream.Read(u8_Buf, 0, 4);
    if (s32_Count >= 2)
    {
        if (u8_Buf[0] == 0xFE && u8_Buf[1] == 0xFF)
        {
            i_Stream.Position = 2;
            return new UnicodeEncoding(true, true);
        }

        if (u8_Buf[0] == 0xFF && u8_Buf[1] == 0xFE)
        {
            if (s32_Count >= 4 && u8_Buf[2] == 0 && u8_Buf[3] == 0)
            {
                i_Stream.Position = 4;
                return new UTF32Encoding(false, true);
            }
            else
            {
                i_Stream.Position = 2;
                return new UnicodeEncoding(false, true);
            }
        }

        if (s32_Count >= 3 && u8_Buf[0] == 0xEF && u8_Buf[1] == 0xBB && u8_Buf[2] == 0xBF)
        {
            i_Stream.Position = 3;
            return Encoding.UTF8;
        }

        if (s32_Count >= 4 && u8_Buf[0] == 0 && u8_Buf[1] == 0 && u8_Buf[2] == 0xFE && u8_Buf[3] == 0xFF)
        {
            i_Stream.Position = 4;
            return new UTF32Encoding(true, true);
        }
    }

    i_Stream.Position = 0;
    return Encoding.Default;
}
Elmue
quelle
1

Wenn Ihre Datei mit den Bytes 60, 118, 56, 46 und 49 beginnt, liegt ein mehrdeutiger Fall vor. Dies kann UTF-8 (ohne Stückliste) oder eine der Einzelbyte-Codierungen wie ASCII, ANSI, ISO-8859-1 usw. sein.

Codo
quelle
Hummmm ... also muss ich alles testen?
Cédric Boivin
Das ist nur reines ASCII. UTF-8 ohne Sonderzeichen entspricht einfach ASCII, und wenn es Sonderzeichen gibt, verwenden diese bestimmte erkennbare Bitmuster.
Nyerguds
@ Nyerguds vielleicht nicht. Ich habe eine UTF-8-Textdatei (ohne "bestimmte erkennbare Bitmuster" - und meistens alle englischen Zeichen). Wenn ich es mit ASCII lese, kann es ein bestimmtes "-" Symbol nicht lesen.
Amit
Unmöglich. Wenn das Zeichen nicht ASCII ist, wird es unter Verwendung dieser spezifischen erkennbaren Bitmuster codiert. Das ist , wie utf-8 funktioniert . Wahrscheinlicher ist, dass Ihr Text weder ASCII noch UtF-8 ist, sondern nur eine 8-Bit-Codierung wie Windows-1252.
Nyerguds
1

Ich verwende Ude , einen C # -Port des Mozilla Universal Charset Detector. Es ist einfach zu bedienen und liefert einige wirklich gute Ergebnisse.

Julien Jacobs
quelle