programing

C#에서 문자열 인코딩 결정

yellowcard 2023. 8. 18. 22:27
반응형

C#에서 문자열 인코딩 결정

C#에서 문자열의 인코딩을 결정할 수 있는 방법이 있습니까?

예를 들어, 나는 파일 이름 문자열을 가지고 있지만, 나는 그것이 암호화되어 있는지 모릅니다. 유니코드 UTF-16 또는 시스템 기본 인코딩, 어떻게 알 수 있습니까?

아래 코드에는 다음과 같은 기능이 있습니다.

  1. UTF-7, UTF-8/16/32 탐지 또는 시도(폭탄, 폭탄 없음, 리틀 & 빅엔디언)
  2. 유니코드 인코딩을 찾을 수 없는 경우 로컬 기본 코드 페이지로 돌아갑니다.
  3. BOM/시그니처가 누락된 유니코드 파일을 탐지합니다(높은 확률로).
  4. 인코딩을 결정하는 데 도움이 되도록 charset=xyz 및 encoding=xyz 내부 파일을 검색합니다.
  5. 처리를 저장하기 위해 파일(정의 가능한 바이트 수)을 '맛보기'할 수 있습니다.
  6. 인코딩 및 디코딩된 텍스트 파일이 반환됩니다.
  7. 효율성을 위한 순수 바이트 기반 솔루션

다른 사람들이 말했듯이, 어떤 솔루션도 완벽할 수는 없지만(전 세계적으로 사용되는 다양한 8비트 확장 ASCII 인코딩을 쉽게 구별할 수는 없습니다), 특히 개발자가 다음과 같이 사용자에게 대체 인코딩 목록을 제공한다면 우리는 '충분히 좋은' 솔루션을 얻을 수 있습니다.각 언어의 가장 일반적인 인코딩은 무엇입니까?

인코딩의 전체 목록은 다음을 사용하여 확인할 수 있습니다.Encoding.GetEncodings();

// Function to detect the encoding for UTF-7, UTF-8/16/32 (bom, no bom, little
// & big endian), and local default codepage, and potentially other codepages.
// 'taster' = number of bytes to check of the file (to save processing). Higher
// value is slower, but more reliable (especially UTF-8 with special characters
// later on may appear to be ASCII initially). If taster = 0, then taster
// becomes the length of the file (for maximum reliability). 'text' is simply
// the string with the discovered encoding applied to the file.
public Encoding detectTextEncoding(string filename, out String text, int taster = 1000)
{
    byte[] b = File.ReadAllBytes(filename);

    //////////////// First check the low hanging fruit by checking if a
    //////////////// BOM/signature exists (sourced from http://www.unicode.org/faq/utf_bom.html#bom4)
    if (b.Length >= 4 && b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF) { text = Encoding.GetEncoding("utf-32BE").GetString(b, 4, b.Length - 4); return Encoding.GetEncoding("utf-32BE"); }  // UTF-32, big-endian 
    else if (b.Length >= 4 && b[0] == 0xFF && b[1] == 0xFE && b[2] == 0x00 && b[3] == 0x00) { text = Encoding.UTF32.GetString(b, 4, b.Length - 4); return Encoding.UTF32; }    // UTF-32, little-endian
    else if (b.Length >= 2 && b[0] == 0xFE && b[1] == 0xFF) { text = Encoding.BigEndianUnicode.GetString(b, 2, b.Length - 2); return Encoding.BigEndianUnicode; }     // UTF-16, big-endian
    else if (b.Length >= 2 && b[0] == 0xFF && b[1] == 0xFE) { text = Encoding.Unicode.GetString(b, 2, b.Length - 2); return Encoding.Unicode; }              // UTF-16, little-endian
    else if (b.Length >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF) { text = Encoding.UTF8.GetString(b, 3, b.Length - 3); return Encoding.UTF8; } // UTF-8
    else if (b.Length >= 3 && b[0] == 0x2b && b[1] == 0x2f && b[2] == 0x76) { text = Encoding.UTF7.GetString(b,3,b.Length-3); return Encoding.UTF7; } // UTF-7

        
    //////////// If the code reaches here, no BOM/signature was found, so now
    //////////// we need to 'taste' the file to see if can manually discover
    //////////// the encoding. A high taster value is desired for UTF-8
    if (taster == 0 || taster > b.Length) taster = b.Length;    // Taster size can't be bigger than the filesize obviously.


    // Some text files are encoded in UTF8, but have no BOM/signature. Hence
    // the below manually checks for a UTF8 pattern. This code is based off
    // the top answer at: https://stackoverflow.com/questions/6555015/check-for-invalid-utf8
    // For our purposes, an unnecessarily strict (and terser/slower)
    // implementation is shown at: https://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c
    // For the below, false positives should be exceedingly rare (and would
    // be either slightly malformed UTF-8 (which would suit our purposes
    // anyway) or 8-bit extended ASCII/UTF-16/32 at a vanishingly long shot).
    int i = 0;
    bool utf8 = false;
    while (i < taster - 4)
    {
        if (b[i] <= 0x7F) { i += 1; continue; }     // If all characters are below 0x80, then it is valid UTF8, but UTF8 is not 'required' (and therefore the text is more desirable to be treated as the default codepage of the computer). Hence, there's no "utf8 = true;" code unlike the next three checks.
        if (b[i] >= 0xC2 && b[i] < 0xE0 && b[i + 1] >= 0x80 && b[i + 1] < 0xC0) { i += 2; utf8 = true; continue; }
        if (b[i] >= 0xE0 && b[i] < 0xF0 && b[i + 1] >= 0x80 && b[i + 1] < 0xC0 && b[i + 2] >= 0x80 && b[i + 2] < 0xC0) { i += 3; utf8 = true; continue; }
        if (b[i] >= 0xF0 && b[i] < 0xF5 && b[i + 1] >= 0x80 && b[i + 1] < 0xC0 && b[i + 2] >= 0x80 && b[i + 2] < 0xC0 && b[i + 3] >= 0x80 && b[i + 3] < 0xC0) { i += 4; utf8 = true; continue; }
        utf8 = false; break;
    }
    if (utf8 == true) {
        text = Encoding.UTF8.GetString(b);
        return Encoding.UTF8;
    }


    // The next check is a heuristic attempt to detect UTF-16 without a BOM.
    // We simply look for zeroes in odd or even byte places, and if a certain
    // threshold is reached, the code is 'probably' UF-16.          
    double threshold = 0.1; // proportion of chars step 2 which must be zeroed to be diagnosed as utf-16. 0.1 = 10%
    int count = 0;
    for (int n = 0; n < taster; n += 2) if (b[n] == 0) count++;
    if (((double)count) / taster > threshold) { text = Encoding.BigEndianUnicode.GetString(b); return Encoding.BigEndianUnicode; }
    count = 0;
    for (int n = 1; n < taster; n += 2) if (b[n] == 0) count++;
    if (((double)count) / taster > threshold) { text = Encoding.Unicode.GetString(b); return Encoding.Unicode; } // (little-endian)


    // Finally, a long shot - let's see if we can find "charset=xyz" or
    // "encoding=xyz" to identify the encoding:
    for (int n = 0; n < taster-9; n++)
    {
        if (
            ((b[n + 0] == 'c' || b[n + 0] == 'C') && (b[n + 1] == 'h' || b[n + 1] == 'H') && (b[n + 2] == 'a' || b[n + 2] == 'A') && (b[n + 3] == 'r' || b[n + 3] == 'R') && (b[n + 4] == 's' || b[n + 4] == 'S') && (b[n + 5] == 'e' || b[n + 5] == 'E') && (b[n + 6] == 't' || b[n + 6] == 'T') && (b[n + 7] == '=')) ||
            ((b[n + 0] == 'e' || b[n + 0] == 'E') && (b[n + 1] == 'n' || b[n + 1] == 'N') && (b[n + 2] == 'c' || b[n + 2] == 'C') && (b[n + 3] == 'o' || b[n + 3] == 'O') && (b[n + 4] == 'd' || b[n + 4] == 'D') && (b[n + 5] == 'i' || b[n + 5] == 'I') && (b[n + 6] == 'n' || b[n + 6] == 'N') && (b[n + 7] == 'g' || b[n + 7] == 'G') && (b[n + 8] == '='))
            )
        {
            if (b[n + 0] == 'c' || b[n + 0] == 'C') n += 8; else n += 9;
            if (b[n] == '"' || b[n] == '\'') n++;
            int oldn = n;
            while (n < taster && (b[n] == '_' || b[n] == '-' || (b[n] >= '0' && b[n] <= '9') || (b[n] >= 'a' && b[n] <= 'z') || (b[n] >= 'A' && b[n] <= 'Z')))
            { n++; }
            byte[] nb = new byte[n-oldn];
            Array.Copy(b, oldn, nb, 0, n-oldn);
            try {
                string internalEnc = Encoding.ASCII.GetString(nb);
                text = Encoding.GetEncoding(internalEnc).GetString(b);
                return Encoding.GetEncoding(internalEnc);
            }
            catch { break; }    // If C# doesn't recognize the name of the encoding, break.
        }
    }


    // If all else fails, the encoding is probably (though certainly not
    // definitely) the user's local codepage! One might present to the user a
    // list of alternative encodings as shown here: https://stackoverflow.com/questions/8509339/what-is-the-most-common-encoding-of-each-language
    // A full list can be found using Encoding.GetEncodings();
    text = Encoding.Default.GetString(b);
    return Encoding.Default;
}

그것은 그 끈이 어디서 왔는지에 따라 다릅니다..NET 문자열은 유니코드(UTF-16)입니다.데이터베이스의 데이터를 바이트 배열로 읽는 것과 같은 유일한 방법은 다를 수 있습니다.

이 CodeProject 문서는 다음과 같습니다.입력 및 송신 텍스트 인코딩 탐지 인코딩

스키트의 현악기 C#과 .NET은 에 대한 훌륭한 설명입니다.NET 문자열.

이것이 조금 늦은 것은 알지만, 분명히 해야 할 것은:

문자열에 인코딩이 없습니다....NET에서 문자열은 char 개체의 모음입니다.기본적으로 문자열인 경우 이미 디코딩되었습니다.

그러나 바이트로 구성된 파일의 내용을 읽고 이를 문자열로 변환하려면 파일의 인코딩을 사용해야 합니다.

.NET에는 ASCII, UTF7, UTF8, UTF32 등에 대한 인코딩 및 디코딩 클래스가 포함되어 있습니다.

대부분의 인코딩에는 사용된 인코딩 유형을 구별하는 데 사용할 수 있는 특정 바이트 순서 표시가 포함되어 있습니다.

.NET 클래스 시스템.IO.StreamReader는 이러한 바이트 순서 표시를 읽음으로써 스트림 내에서 사용되는 인코딩을 결정할 수 있습니다.

다음은 예입니다.

    /// <summary>
    /// return the detected encoding and the contents of the file.
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="contents"></param>
    /// <returns></returns>
    public static Encoding DetectEncoding(String fileName, out String contents)
    {
        // open the file with the stream-reader:
        using (StreamReader reader = new StreamReader(fileName, true))
        {
            // read the contents of the file into a string
            contents = reader.ReadToEnd();

            // return the encoding.
            return reader.CurrentEncoding;
        }
    }

또 다른 선택사항이 있습니다. 매우 늦었습니다. 죄송합니다.

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

이 작은 C# 전용 클래스는 존재하는 경우 BOMS를 사용하고, 그렇지 않으면 가능한 유니코드 인코딩을 자동으로 탐지하려고 시도하며, 유니코드 인코딩이 가능하지 않거나 가능하지 않으면 폴백합니다.

위에서 언급한 UTF8Checker가 비슷한 작업을 하는 것처럼 들리지만, UTF8 대신 BOM이 누락될 수 있는 다른 유니코드 인코딩(UTF-16LE 또는 BE)도 확인합니다.

이것이 누군가에게 도움이 되기를 바랍니다!

단순한 조력자들.FileEncoding Nuget 패키지는 Mozilla Universal Charset Detector의 C# 포트를 단순한 API로 래핑합니다.

var encoding = FileEncoding.DetectFileEncoding(txtFile);

제 해결책은 내장된 것들을 약간의 예비 부품과 함께 사용하는 것입니다.

스택 오버플로에 대한 다른 유사한 질문에 대한 답변에서 전략을 선택했지만 지금은 찾을 수 없습니다.

BOM이 StreamReader 됩니다. BOM은 BOM과 동일합니다.Encoding.Default그리고 우리는 그 결과를 믿어야 합니다.

그렇지 않은 경우 바이트 시퀀스가 유효한 UTF-8 시퀀스인지 확인합니다.이 경우 UTF-8을 인코딩으로 추측하고, 그렇지 않으면 기본 ASCII 인코딩이 결과가 됩니다.

static Encoding getEncoding(string path) {
    var stream = new FileStream(path, FileMode.Open);
    var reader = new StreamReader(stream, Encoding.Default, true);
    reader.Read();

    if (reader.CurrentEncoding != Encoding.Default) {
        reader.Close();
        return reader.CurrentEncoding;
    }

    stream.Position = 0;

    reader = new StreamReader(stream, new UTF8Encoding(false, true));
    try {
        reader.ReadToEnd();
        reader.Close();
        return Encoding.UTF8;
    }
    catch (Exception) {
        reader.Close();
        return Encoding.Default;
    }
}

참고: UTF-8 인코딩이 내부적으로 어떻게 작동하는지 알아보기 위한 실험이었습니다.vilicvane이 제공하는 해결책은, 사용하기 위해.UTF8Encoding디코딩 실패 시 예외를 발생시키도록 초기화된 객체는 훨씬 단순하며 기본적으로 동일한 작업을 수행합니다.


UTF-8과 Windows-1252를 구별하기 위해 이 코드를 작성했습니다.그러나 전체를 메모리에 로드하고 완전히 스캔하기 때문에 거대한 텍스트 파일에는 사용해서는 안 됩니다..srt 부제 파일에 사용하여 로드된 인코딩으로 다시 저장할 수 있었습니다.

파일이 유효하지 않은 UTF-8로 탐지될 경우 사용할 8비트 폴백 인코딩이 ref로 지정되어야 합니다. 일반적으로 Windows 시스템에서는 Windows-1252가 됩니다.하지만 이것은 실제 유효한 ASCII 범위를 확인하는 것처럼 화려한 일은 하지 않으며, 바이트 순서 표시에서도 UTF-16을 감지하지 못합니다.

비트별 탐지 뒤에 있는 이론은 https://ianthehenry.com/2015/1/17/decoding-utf-8/ 에서 찾을 수 있습니다.

기본적으로 첫 번째 바이트의 비트 범위는 UTF-8 엔티티의 일부가 된 후의 바이트 수를 결정합니다.다음 바이트는 항상 동일한 비트 범위에 있습니다.

/// <summary>
/// Reads a text file, and detects whether its encoding is valid UTF-8 or ascii.
/// If not, decodes the text using the given fallback encoding.
/// Bit-wise mechanism for detecting valid UTF-8 based on
/// https://ianthehenry.com/2015/1/17/decoding-utf-8/
/// </summary>
/// <param name="docBytes">The bytes read from the file.</param>
/// <param name="encoding">The default encoding to use as fallback if the text is detected not to be pure ascii or UTF-8 compliant. This ref parameter is changed to the detected encoding.</param>
/// <returns>The contents of the read file, as String.</returns>
public static String ReadFileAndGetEncoding(Byte[] docBytes, ref Encoding encoding)
{
    if (encoding == null)
        encoding = Encoding.GetEncoding(1252);
    Int32 len = docBytes.Length;
    // byte order mark for utf-8. Easiest way of detecting encoding.
    if (len > 3 && docBytes[0] == 0xEF && docBytes[1] == 0xBB && docBytes[2] == 0xBF)
    {
        encoding = new UTF8Encoding(true);
        // Note that even when initialising an encoding to have
        // a BOM, it does not cut it off the front of the input.
        return encoding.GetString(docBytes, 3, len - 3);
    }
    Boolean isPureAscii = true;
    Boolean isUtf8Valid = true;
    for (Int32 i = 0; i < len; ++i)
    {
        Int32 skip = TestUtf8(docBytes, i);
        if (skip == 0)
            continue;
        if (isPureAscii)
            isPureAscii = false;
        if (skip < 0)
        {
            isUtf8Valid = false;
            // if invalid utf8 is detected, there's no sense in going on.
            break;
        }
        i += skip;
    }
    if (isPureAscii)
        encoding = new ASCIIEncoding(); // pure 7-bit ascii.
    else if (isUtf8Valid)
        encoding = new UTF8Encoding(false);
    // else, retain given encoding. This should be an 8-bit encoding like Windows-1252.
    return encoding.GetString(docBytes);
}

/// <summary>
/// Tests if the bytes following the given offset are UTF-8 valid, and
/// returns the amount of bytes to skip ahead to do the next read if it is.
/// If the text is not UTF-8 valid it returns -1.
/// </summary>
/// <param name="binFile">Byte array to test</param>
/// <param name="offset">Offset in the byte array to test.</param>
/// <returns>The amount of bytes to skip ahead for the next read, or -1 if the byte sequence wasn't valid UTF-8</returns>
public static Int32 TestUtf8(Byte[] binFile, Int32 offset)
{
    // 7 bytes (so 6 added bytes) is the maximum the UTF-8 design could support,
    // but in reality it only goes up to 3, meaning the full amount is 4.
    const Int32 maxUtf8Length = 4;
    Byte current = binFile[offset];
    if ((current & 0x80) == 0)
        return 0; // valid 7-bit ascii. Added length is 0 bytes.
    Int32 len = binFile.Length;
    for (Int32 addedlength = 1; addedlength < maxUtf8Length; ++addedlength)
    {
        Int32 fullmask = 0x80;
        Int32 testmask = 0;
        // This code adds shifted bits to get the desired full mask.
        // If the full mask is [111]0 0000, then test mask will be [110]0 0000. Since this is
        // effectively always the previous step in the iteration I just store it each time.
        for (Int32 i = 0; i <= addedlength; ++i)
        {
            testmask = fullmask;
            fullmask += (0x80 >> (i+1));
        }
        // figure out bit masks from level
        if ((current & fullmask) == testmask)
        {
            if (offset + addedlength >= len)
                return -1;
            // Lookahead. Pattern of any following bytes is always 10xxxxxx
            for (Int32 i = 1; i <= addedlength; ++i)
            {
                if ((binFile[offset + i] & 0xC0) != 0x80)
                    return -1;
            }
            return addedlength;
        }
    }
    // Value is greater than the maximum allowed for utf8. Deemed invalid.
    return -1;
}

저의 마지막 작업 접근 방식은 인코딩에 의해 바이트 배열에서 생성된 문자열에서 잘못된 문자를 감지하여 예상 인코딩의 잠재적인 후보를 시도하는 것입니다.잘못된 문자가 발생하지 않으면 테스트된 데이터에 대해 테스트된 인코딩이 제대로 작동합니다.

바이트 배열에 대한 올바른 인코딩을 결정하기 위해 라틴 및 독일 특수 문자만 고려할 수 있도록 다음 방법을 사용하여 문자열에서 잘못된 문자를 탐지하려고 합니다.

    /// <summary>
    /// detect invalid characters in string, use to detect improper encoding
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static bool DetectInvalidChars(string s)
    {
        const string specialChars = "\r\n\t .,;:-_!\"'?()[]{}&%$§=*+~#@|<>äöüÄÖÜß/\\^€";
        return s.Any(ch => !(
            specialChars.Contains(ch) ||
            (ch >= '0' && ch <= '9') ||
            (ch >= 'a' && ch <= 'z') ||
            (ch >= 'A' && ch <= 'Z')));
    }

(NB: 고려해야 할 다른 라틴어 기반 언어가 있는 경우 코드의 특수 Chars const 문자열을 조정할 수 있습니다.)

그러면 다음과 같이 사용합니다(UTF-8 또는 기본 인코딩만 예상).

        // determine encoding by detecting invalid characters in string
        var invoiceXmlText = Encoding.UTF8.GetString(invoiceXmlBytes); // try utf-8 first
        if (StringFuncs.DetectInvalidChars(invoiceXmlText))
            invoiceXmlText = Encoding.Default.GetString(invoiceXmlBytes); // fallback to default

GitHub에서 새 라이브러리를 찾았습니다.문자 집합 디텍터/UTF-알 수 없음

C# - .NET Core 2-3의 Charset 디텍터 빌드.NET 표준 1-2 & .NET 4+

또한 다른 리포지토리에 기반한 Mozilla Universal Charset Detector의 포트이기도 합니다.

CharsetDetector/UTF-unknown 클래스에 이름이 지정되었습니다.CharsetDetector.

CharsetDetector에는 다음과 같은 몇 가지 정적 인코딩 탐지 방법이 포함되어 있습니다.

  • CharsetDetector.DetectFromFile()
  • CharsetDetector.DetectFromStream()
  • CharsetDetector.DetectFromBytes()

탐지된 결과가 클래스에 있습니다.DetectionResult속성 있음Detected그것은 수업의 예입니다.DetectionDetail다음 속성을 사용합니다.

  • EncodingName
  • Encoding
  • Confidence

다음은 사용법을 보여주는 예입니다.

// Program.cs
using System;
using System.Text;
using UtfUnknown;

namespace ConsoleExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string filename = @"E:\new-file.txt";
            DetectDemo(filename);
        }

        /// <summary>
        /// Command line example: detect the encoding of the given file.
        /// </summary>
        /// <param name="filename">a filename</param>
        public static void DetectDemo(string filename)
        {
            // Detect from File
            DetectionResult result = CharsetDetector.DetectFromFile(filename);
            // Get the best Detection
            DetectionDetail resultDetected = result.Detected;

            // detected result may be null.
            if (resultDetected != null)
            {
                // Get the alias of the found encoding
                string encodingName = resultDetected.EncodingName;
                // Get the System.Text.Encoding of the found encoding (can be null if not available)
                Encoding encoding = resultDetected.Encoding;
                // Get the confidence of the found encoding (between 0 and 1)
                float confidence = resultDetected.Confidence;

                if (encoding != null)
                {
                    Console.WriteLine($"Detection completed: {filename}");
                    Console.WriteLine($"EncodingWebName: {encoding.WebName}{Environment.NewLine}Confidence: {confidence}");
                }
                else
                {
                    Console.WriteLine($"Detection completed: {filename}");
                    Console.WriteLine($"(Encoding is null){Environment.NewLine}EncodingName: {encodingName}{Environment.NewLine}Confidence: {confidence}");
                }
            }
            else
            {
                Console.WriteLine($"Detection failed: {filename}");
            }
        }
    }
}

결과 스크린샷의 예:

다른 사람들이 언급했듯이, a.stringC#에서 항상 UTF-16LE로 인코딩됩니다(System.Text.Encoding.Unicode).

행간을 읽어보면, 당신이 실제로 걱정하는 것은 당신의 등장인물들이string다른 알려진 인코딩과 호환됩니다(즉, 다른 코드 페이지에 "맞을까요?").

이 경우, 제가 찾은 가장 정확한 해결책은 변환을 시도하고 문자열이 변경되는지 확인하는 것입니다.만약 당신의 캐릭터가string대상 인코딩에서 "적합"하지 않으면 인코더가 일부 센티넬 문자로 대체합니다(예: '?'가 일반적임).


    // using System.Text;

    // And if you're using the "System.Text.Encoding.CodePages" NuGet package, you 
    // need to call this once or GetEncoding will raise a NotSupportedException:
    // Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

    var srcEnc = Encoding.Unicode;
    var dstEnc = Encoding.GetEncoding(1252); // 1252 Requires use of the "System.Text.Encoding.CodePages" NuGet package.
    string srcText = "Some text you want to check";
    string dstText = dstEnc.GetString(Encoding.Convert(srcEnc, dstEnc, srcEnc.GetBytes(srcText)));

    // if (srcText == dstText) the srcText "fits" (it's compatible).
    // else the srcText doesn't "fit" (it's not compatible)

언급URL : https://stackoverflow.com/questions/1025332/determine-a-strings-encoding-in-c-sharp

반응형