【问题标题】:Detect the encoding of a text file using C#使用 C# 检测文本文件的编码
【发布时间】:2018-01-22 11:00:42
【问题描述】:

我有一组 markdown 文件要传递给 jekyll 项目,需要找到它们的编码格式,即 UTF-8 带 BOM 或 UTF-8 不带 BOM 或 ANSI 使用程序或 API。

如果我传递了文件的位置,则必须列出、读取文件并生成编码作为结果。

是否有任何代码或API?

我已经尝试过 sr.CurrentEncoding 用于流阅读器,如有效方式找到任何文件的编码中所述,但结果因 notepad++ 结果的结果而异。

还尝试按照https://social.msdn.microsoft.com/Forums/vstudio/en-US/862e3342-cc88-478f-bca2-e2de6f60d2fb/detect-encoding-of-the-file?forum=csharpgeneral 中的建议使用https://github.com/errepi/ude(Mozilla Universal Charset Detector),方法是在c#项目中实现ude.dll,但结果不像notepad++那样有效,文件编码显示为utf- 8,但是从程序来看,结果是带有BOM的utf-8。

但我应该从两种方式得到相同的结果,那么问题出在哪里?

【问题讨论】:

  • 这不是任何其他问题的重复,因为我尝试了其他答案来查找编码,但它对我不起作用。
  • 您是否有理由相信 Notepad++ 是正确的,而所有其他解决方案都不正确? (特别是,为什么您认为有问题的文件是 ANSI 而不是 UTF-8?文件的内容是什么?)这看起来像是一个逆向工程问题,以复制 Notepad++ 使用的特定算法。由于它是一个闭源产品,您是否曾向他们询问有关他们产品的信息?
  • “应该从两种方式得到相同的结果”:可能不是。猜测程序选择自己的算法。然而,大多数人的共同点是在有很多可能性时给出一个答案。也许这就是让你感到困惑的地方。它是选择编码的任何文本文件的作者,所以你可以问。
  • @RobNapier,“因为它是一个闭源产品” - 没有it is not。但由于它是 C++ 它,它看起来确实是这样的。
  • 谢谢@HenkHolterman。我误读了他们的网站!

标签: c# encoding utf-8


【解决方案1】:

检测编码始终是一项棘手的工作,但检测 BOM 非常简单。要将 BOM 获取为字节数组,只需使用编码对象的 GetPreamble() 函数。这应该允许您通过前导码检测整个范围的编码。

现在,至于检测没有前导码的 UTF-8,实际上这也不是很难。看,UTF8 has strict bitwise rules about what values are expected in a valid sequence,你可以初始化一个UTF8Encoding对象in a way that will fail by throwing an exception when these sequences are incorrect

因此,如果您先进行 BOM 检查,然后进行严格的解码检查,最后回退到 Win-1252 编码(您称之为“ANSI”),那么您的检测就完成了。

Byte[] bytes = File.ReadAllBytes(filename);
Encoding encoding = null;
String text = null;
// Test UTF8 with BOM. This check can easily be copied and adapted
// to detect many other encodings that use BOMs.
UTF8Encoding encUtf8Bom = new UTF8Encoding(true, true);
Boolean couldBeUtf8 = true;
Byte[] preamble = encUtf8Bom.GetPreamble();
Int32 prLen = preamble.Length;
if (bytes.Length >= prLen && preamble.SequenceEqual(bytes.Take(prLen)))
{
    // UTF8 BOM found; use encUtf8Bom to decode.
    try
    {
        // Seems that despite being an encoding with preamble,
        // it doesn't actually skip said preamble when decoding...
        text = encUtf8Bom.GetString(bytes, prLen, bytes.Length - prLen);
        encoding = encUtf8Bom;
    }
    catch (ArgumentException)
    {
        // Confirmed as not UTF-8!
        couldBeUtf8 = false;
    }
}
// use boolean to skip this if it's already confirmed as incorrect UTF-8 decoding.
if (couldBeUtf8 && encoding == null)
{
    // test UTF-8 on strict encoding rules. Note that on pure ASCII this will
    // succeed as well, since valid ASCII is automatically valid UTF-8.
    UTF8Encoding encUtf8NoBom = new UTF8Encoding(false, true);
    try
    {
        text = encUtf8NoBom.GetString(bytes);
        encoding = encUtf8NoBom;
    }
    catch (ArgumentException)
    {
        // Confirmed as not UTF-8!
    }
}
// fall back to default ANSI encoding.
if (encoding == null)
{
    encoding = Encoding.GetEncoding(1252);
    text = encoding.GetString(bytes);
}

请注意,Windows-1252(美国/西欧 ANSI)是每个字符一个字节的编码,这意味着其中的所有内容都会产生技术上有效的字符,因此 unless you go for heuristic methods,无法对其进行进一步检测将其与其他每字符一个字节的编码区分开来。

【讨论】:

  • 您当然可以在这些检查中添加其他编码,但要小心;某些编码的 BOM 与其他一些编码的 BOM 开始方式相同,因此您必须以正确的顺序测试它们。我知道在某个地方有一个关于该列表和逻辑的问题,但我目前找不到。
【解决方案2】:

亡灵术。

  • 首先,检查字节顺序标记:
  • 如果这不起作用,您可以尝试使用 Mozilla Universal Charset Detector C# port 从文本内容推断编码。
  • 如果这不起作用,您只需返回 CurrentCulture/InstalledUiCulture/System-Encoding - 或其他任何内容。
  • 如果系统编码不起作用,我们可以返回 ASCII 或 UTF8。由于 UTF8 的 0-127 项与 ASCII 相同,因此我们只需返回 UTF8。

示例(DetectOrGuessEncoding)

namespace SQLMerge
{


    class EncodingDetector
    {


        public static System.Text.Encoding BomInfo(string srcFile)
        {
            return BomInfo(srcFile, false);
        } // End Function BomInfo 



        public static System.Text.Encoding BomInfo(string srcFile, bool thorough)
        {
            byte[] b = new byte[5];

            using (System.IO.FileStream file = new System.IO.FileStream(srcFile, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
            {
                int numRead = file.Read(b, 0, 5);
                if (numRead < 5)
                    System.Array.Resize(ref b, numRead);

                file.Close();
            } // End Using file 

            if (b.Length >= 4 && b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF) // UTF32-BE 
                return System.Text.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) // UTF32-LE
                return System.Text.Encoding.UTF32; // UTF-32, little-endian
            // https://en.wikipedia.org/wiki/Byte_order_mark#cite_note-14    
            else if (b.Length >= 4 && b[0] == 0x2b && b[1] == 0x2f && b[2] == 0x76 && (b[3] == 0x38 || b[3] == 0x39 || b[3] == 0x2B || b[3] == 0x2F)) // UTF7
                return System.Text.Encoding.UTF7;  // UTF-7
            else if (b.Length >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF) // UTF-8
                return System.Text.Encoding.UTF8;  // UTF-8
            else if (b.Length >= 2 && b[0] == 0xFE && b[1] == 0xFF) // UTF16-BE
                return System.Text.Encoding.BigEndianUnicode; // UTF-16, big-endian
            else if (b.Length >= 2 && b[0] == 0xFF && b[1] == 0xFE) // UTF16-LE
                return System.Text.Encoding.Unicode; // UTF-16, little-endian

            // Maybe there is a future encoding ...
            // PS: The above yields more than this - this doesn't find UTF7 ...
            if (thorough)
            {
                System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.Text.Encoding, byte[]>> lsPreambles = 
                    new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<System.Text.Encoding, byte[]>>();

                foreach (System.Text.EncodingInfo ei in System.Text.Encoding.GetEncodings())
                {
                    System.Text.Encoding enc = ei.GetEncoding();

                    byte[] preamble = enc.GetPreamble();

                    if (preamble == null)
                        continue;

                    if (preamble.Length == 0)
                        continue;

                    if (preamble.Length > b.Length)
                        continue;

                    System.Collections.Generic.KeyValuePair<System.Text.Encoding, byte[]> kvp =
                        new System.Collections.Generic.KeyValuePair<System.Text.Encoding, byte[]>(enc, preamble);

                    lsPreambles.Add(kvp);
                } // Next ei

                // li.Sort((a, b) => a.CompareTo(b)); // ascending sort
                // li.Sort((a, b) => b.CompareTo(a)); // descending sort
                lsPreambles.Sort(
                    delegate (
                        System.Collections.Generic.KeyValuePair<System.Text.Encoding, byte[]> kvp1, 
                        System.Collections.Generic.KeyValuePair<System.Text.Encoding, byte[]> kvp2)
                    {
                        return kvp2.Value.Length.CompareTo(kvp1.Value.Length);
                    }
                );


                for (int j = 0; j < lsPreambles.Count; ++j)
                {
                    for (int i = 0; i < lsPreambles[j].Value.Length; ++i)
                    {
                        if (b[i] != lsPreambles[j].Value[i])
                        {
                            goto NEXT_J_AND_NOT_NEXT_I;
                        }
                    } // Next i 

                    return lsPreambles[j].Key;
                    NEXT_J_AND_NOT_NEXT_I: continue;
                } // Next j 

            } // End if (thorough)

            return null;
        } // End Function BomInfo 


        public static System.Text.Encoding DetectOrGuessEncoding(string fileName)
        {
            return DetectOrGuessEncoding(fileName, false);
        }


        public static System.Text.Encoding DetectOrGuessEncoding(string fileName, bool withOutput)
        {
            if (!System.IO.File.Exists(fileName))
                return null;


            System.ConsoleColor origBack = System.ConsoleColor.Black;
            System.ConsoleColor origFore = System.ConsoleColor.White;
            

            if (withOutput)
            {
                origBack = System.Console.BackgroundColor;
                origFore = System.Console.ForegroundColor;
            }
            
            // System.Text.Encoding systemEncoding = System.Text.Encoding.Default; // Returns hard-coded UTF8 on .NET Core ... 
            System.Text.Encoding systemEncoding = GetSystemEncoding();
            System.Text.Encoding enc = BomInfo(fileName);
            if (enc != null)
            {
                if (withOutput)
                {
                    System.Console.BackgroundColor = System.ConsoleColor.Green;
                    System.Console.ForegroundColor = System.ConsoleColor.White;
                    System.Console.WriteLine(fileName);
                    System.Console.WriteLine(enc);
                    System.Console.BackgroundColor = origBack;
                    System.Console.ForegroundColor = origFore;
                }

                return enc;
            }

            using (System.IO.Stream strm = System.IO.File.OpenRead(fileName))
            {
                UtfUnknown.DetectionResult detect = UtfUnknown.CharsetDetector.DetectFromStream(strm);

                if (detect != null && detect.Details != null && detect.Details.Count > 0 && detect.Details[0].Confidence < 1)
                {
                    if (withOutput)
                    {
                        System.Console.BackgroundColor = System.ConsoleColor.Red;
                        System.Console.ForegroundColor = System.ConsoleColor.White;
                        System.Console.WriteLine(fileName);
                        System.Console.WriteLine(detect);
                        System.Console.BackgroundColor = origBack;
                        System.Console.ForegroundColor = origFore;
                    }

                    foreach (UtfUnknown.DetectionDetail detail in detect.Details)
                    {
                        if (detail.Encoding == systemEncoding
                            || detail.Encoding == System.Text.Encoding.UTF8
                        )
                            return detail.Encoding;
                    }

                    return detect.Details[0].Encoding;
                }
                else if (detect != null && detect.Details != null && detect.Details.Count > 0)
                {
                    if (withOutput)
                    {
                        System.Console.BackgroundColor = System.ConsoleColor.Green;
                        System.Console.ForegroundColor = System.ConsoleColor.White;
                        System.Console.WriteLine(fileName);
                        System.Console.WriteLine(detect);
                        System.Console.BackgroundColor = origBack;
                        System.Console.ForegroundColor = origFore;
                    }

                    return detect.Details[0].Encoding;
                }

                enc = GetSystemEncoding();

                if (withOutput)
                {
                    System.Console.BackgroundColor = System.ConsoleColor.DarkRed;
                    System.Console.ForegroundColor = System.ConsoleColor.Yellow;
                    System.Console.WriteLine(fileName);
                    System.Console.Write("Assuming ");
                    System.Console.Write(enc.WebName);
                    System.Console.WriteLine("...");
                    System.Console.BackgroundColor = origBack;
                    System.Console.ForegroundColor = origFore;
                }

                return systemEncoding;
            } // End Using strm 

        } // End Function DetectOrGuessEncoding 


        public static System.Text.Encoding GetSystemEncoding()
        {
            // The OEM code page for use by legacy console applications
            // int oem = System.Globalization.CultureInfo.CurrentCulture.TextInfo.OEMCodePage;

            // The ANSI code page for use by legacy GUI applications
            // int ansi = System.Globalization.CultureInfo.InstalledUICulture.TextInfo.ANSICodePage; // Machine 
            int ansi = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ANSICodePage; // User 

            try
            {
                // https://stackoverflow.com/questions/38476796/how-to-set-net-core-in-if-statement-for-compilation
#if ( NETSTANDARD && !NETSTANDARD1_0 )  || NETCORE || NETCOREAPP3_0 || NETCOREAPP3_1 
                System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
#endif

                System.Text.Encoding enc = System.Text.Encoding.GetEncoding(ansi);
                return enc;
            }
            catch (System.Exception)
            { }


            try
            {

                foreach (System.Text.EncodingInfo ei in System.Text.Encoding.GetEncodings())
                {
                    System.Text.Encoding e = ei.GetEncoding();

                    // 20'127: US-ASCII 
                    if (e.WindowsCodePage == ansi && e.CodePage != 20127)
                    {
                        return e;
                    }

                }
            }
            catch (System.Exception)
            { }

            // return System.Text.Encoding.GetEncoding("iso-8859-1");
            return System.Text.Encoding.UTF8;
        } // End Function GetSystemEncoding 


    } // End Class 


}

【讨论】:

  • 无论如何,UTF-7 在技术上是不正确的;它是四个字节,第 4 个的最后 2 位属于下一个字符,因此必须使用位掩码进行检查。彻底的方法应该按前导码长度对编码进行预排序,最长的在前,否则您将在 UTF-32 上匹配 UTF-16 的前导码。
  • 很好地参考了“检测器”包。它似乎正在工作。
  • @Nyerguds:该死,你在 UTF-7 上是对的。修复了这个问题,并添加了预排序。真的必须非常非常非常仔细地阅读这些维基百科表格。
  • 注意,我不认为有任何东西真正使用 UTF-7。当任何东西都无法打开时,任何以 UTF-7 保存文本的人都会得到他们应得的。
  • @Nyerguds:Quake3 和 Java 使用它。现在,我们可以原谅和忘记 Quake3,但 Java ......例如,我多年前从瑞士邮政服务导入的那个文件,其中包括邮政编码和地名...... UTF-7 是我最后的猜测,但最后的猜测被证明是正确的;)除此之外,这似乎是电子邮件中的一个东西,除了安全问题之外什么都没有。
【解决方案3】:
namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
            List<FilePath> filePaths = new List<FilePath>();
            filePaths = GetLstPaths();
        }
        public static List<FilePath> GetLstPaths()
        {
            #region Getting Files

            DirectoryInfo directoryInfo = new DirectoryInfo(@"C:\Users\Safi\Desktop\ss\");
            DirectoryInfo directoryTargetInfo = new DirectoryInfo(@"C:\Users\Safi\Desktop\ss1\");
            FileInfo[] fileInfos = directoryInfo.GetFiles("*.txt");
            List<FilePath> lstFiles = new List<FilePath>();
            foreach (FileInfo fileInfo in fileInfos)
            {
                Encoding enco = GetLittleIndianFiles(directoryInfo + fileInfo.Name);
                string filePath = directoryInfo + fileInfo.Name;
                string targetFilePath = directoryTargetInfo + fileInfo.Name;
                if (enco != null)
                {
                    FilePath f1 = new FilePath();
                    f1.filePath = filePath;
                    f1.targetFilePath = targetFilePath;
                    lstFiles.Add(f1);
                }
            }
            int count = 0;
            lstFiles.ForEach(d =>
            {
                count++;
            });
            MessageBox.Show(Convert.ToString(count) + "Files are Converted");
            #endregion
            return lstFiles;
        }
        public static Encoding GetLittleIndianFiles(string srcFile)
        {
            byte[] b = new byte[5];

            using (System.IO.FileStream file = new System.IO.FileStream(srcFile, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read))
            {
                int numRead = file.Read(b, 0, 5);
                if (numRead < 5)
                    System.Array.Resize(ref b, numRead);

                file.Close();
            } // End Using file 
            if (b.Length >= 2 && b[0] == 0xFF && b[1] == 0xFE)
                return System.Text.Encoding.Unicode; // UTF-16, little-endian
            return null;
        }
    }

    public class FilePath
    {
        public string filePath { get; set; }
        public string targetFilePath { get; set; }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-30
    • 2010-09-10
    • 2011-05-22
    • 2011-05-30
    • 2011-03-20
    • 2012-03-16
    相关资源
    最近更新 更多