关于c#:如何检测文本文件的编码/代码页

关于c#:如何检测文本文件的编码/代码页

How can I detect the encoding/codepage of a text file

在我们的应用程序中,我们接收来自不同来源的文本文件(.txt.csv等)。在读取时,这些文件有时包含垃圾,因为在不同/未知代码页中创建的文件。

有没有办法(自动)检测文本文件的代码页?

detectEncodingFromByteOrderMarksStreamReader构造函数上,适用于UTF8和其他Unicode标记的文件,但我正在寻找一种检测代码页的方法,如ibm850windows1252

谢谢你的回答,这就是我所做的。

我们收到的文件来自最终用户,他们对代码页一无所知。接收者也是最终用户,到目前为止,这就是他们所知道的关于代码页的事情:代码页存在,而且很烦人。

解决方案:

  • 在记事本中打开收到的文件,看一段乱七八糟的文字。如果有人叫弗兰?或者别的什么,用你的人类智慧你可以猜到。
  • 我创建了一个小应用程序,用户可以用它打开文件,并输入一个文本,当使用正确的代码页时,用户知道它将出现在文件中。
  • 循环浏览所有代码页,并显示使用用户提供的文本提供解决方案的代码页。
  • 如果弹出多个as-one代码页,请要求用户指定更多文本。

你不能检测到代码页,你需要被告知。你可以分析字节并猜测它,但这会产生一些奇怪(有时很有趣)的结果。我现在找不到它,但我相信记事本可以被骗到用中文显示英文文本。

无论如何,这是你需要阅读的:绝对最小值每个软件开发人员绝对,肯定必须知道Unicode和字符集(没有借口!).

特别是乔尔说:

The Single Most Important Fact About Encodings

If you completely forget everything I just explained, please remember one extremely important fact. It does not make sense to have a string without knowing what encoding it uses. You can no longer stick your head in the sand and pretend that"plain" text is ASCII.
There Ain't No Such Thing As Plain Text.

If you have a string, in memory, in a file, or in an email message, you have to know what encoding it is in or you cannot interpret it or display it to users correctly.


如果您希望检测非UTF编码(即没有BOM),那么您基本上需要对文本进行启发式和统计分析。您可能想看一下Mozilla关于通用字符集检测的论文(同样的链接,通过回程机器有更好的格式)。


您尝试过Mozilla通用字符集检测器的C端口吗?

示例来自http://code.google.com/p/ude/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.");
        }
    }
}

You can't detect the codepage

这显然是错误的。每个Web浏览器都有某种通用的字符集检测器来处理没有任何编码指示的页面。火狐有一个。你可以下载代码,看看它是怎么做的。请参阅此处的一些文档。基本上,这是一个启发式的,但它确实很有效。

如果文本数量合理,甚至可以检测语言。

这是我刚刚用谷歌找到的另一个:


我知道这个问题已经很晚了,而且这个解决方案对某些人没有吸引力(因为它以英语为中心的偏见和缺乏统计/经验测试),但是它对我来说非常有效,特别是对于处理上传的csv数据:

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

优势:

  • 内置物料清单检测
  • 可自定义的默认/回退编码
  • 根据我的经验,对于包含一些外来数据(如法语名称)的基于西欧的文件来说相当可靠,这些外来数据混合了UTF-8和拉丁-1风格的文件——基本上是美国和西欧的大部分环境。

注意:我是写这门课的人,所以很明显,把它和一粒盐一起吃吧!:)


寻找不同的解决方案,我发现

https://code.google.com/p/ude网站/

这个解决方案有点重。

我需要一些基本的编码检测,基于4个第一字节,可能还有XML字符集检测——所以我从Internet上获取了一些样本源代码,并添加了稍微修改过的

网址:http://lists.w3.org/archives/public/www-validator/2002aug/0084.html

为Java编写的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
    public static Encoding DetectEncoding(byte[] fileContent)
    {
        if (fileContent == null)
            throw new ArgumentNullException();

        if (fileContent.Length < 2)
            return Encoding.ASCII;      // Default fallback

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

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

        if (fileContent.Length < 3)
            return null;

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

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

        if (fileContent.Length < 4)
            return null;

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

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

        String probe;
        int len = fileContent.Length;

        if( fileContent.Length >= 128 ) len = 128;
        probe = Encoding.ASCII.GetString(fileContent, 0, len);

        MatchCollection mc = Regex.Matches(probe,"^<\\?xml[^<>]*encoding[ \\t\
\
]?=[\\t\
\
]?['"
]([A-Za-z]([A-Za-z0-9._]|-)*)", RegexOptions.Singleline);
        // Add '[0].Groups[1].Value' to the end to test regex

        if( mc.Count == 1 && mc[0].Groups.Count >= 2 )
        {
            // Typically picks up 'UTF-8' string
            Encoding enc = null;

            try {
                enc = Encoding.GetEncoding( mc[0].Groups[1].Value );
            }catch (Exception ) { }

            if( enc != null )
                return enc;
        }

        return Encoding.ASCII;      // Default fallback
    }

从文件中读取前1024个字节就足够了,但我正在加载整个文件。


记事本++有现成的这个功能。它还支持更改它。


如果有人在寻找93.9%的解决方案。这对我很有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static class StreamExtension
{
    /// <summary>
    /// Convert the content to a string.
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <returns></returns>
    public static string ReadAsString(this Stream stream)
    {
        var startPosition = stream.Position;
        try
        {
            // 1. Check for a BOM
            // 2. or try with UTF-8. The most (86.3%) used encoding. Visit: http://w3techs.com/technologies/overview/character_encoding/all/
            var streamReader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true);
            return streamReader.ReadToEnd();
        }
        catch (DecoderFallbackException ex)
        {
            stream.Position = startPosition;

            // 3. The second most (6.7%) used encoding is ISO-8859-1. So use Windows-1252 (0.9%, also know as ANSI), which is a superset of ISO-8859-1.
            var streamReader = new StreamReader(stream, Encoding.GetEncoding(1252));
            return streamReader.ReadToEnd();
        }
    }
}

我在python中做过类似的事情。基本上,您需要来自各种编码的大量示例数据,这些数据通过一个滑动的双字节窗口分解并存储在字典(哈希)中,通过字节对键控,提供编码列表的值。

根据字典(hash),您将输入文本和:

  • 如果它以任何bom字符开头("xfexff"表示utf-16-be,"xffxfe"表示utf-16-le,"xefxbbxbf"表示utf-8等),我会按建议处理。
  • 如果没有,那么取足够大的文本样本,取样本的所有字节对,并选择字典中建议的最不常见的编码。

如果您还对不以任何BOM开头的UTF编码文本进行了抽样,那么第二步将涵盖从第一步开始的那些文本。

到目前为止,它对我(示例数据和后续输入数据是各种语言的字幕)起作用,错误率也在降低。


"uchardet"工具可以很好地使用每个字符集的字符频率分布模型。更大的文件和更"典型"的文件有更多的信心(显然)。

在Ubuntu上,你只需要1[1]。

在其他系统上,从以下位置获取源、用法和文档:https://github.com/byvoid/uchardet


streamreader类的构造函数接受"detect encoding"参数。


如果可以链接到C库,则可以使用libenca。请参见http://cihar.com/software/enca/。从手册页:

Enca reads given text files, or standard input when none are given,
and uses knowledge about their language (must be supported by you) and
a mixture of parsing, statistical analysis, guessing and black magic
to determine their encodings.

这是GPL V2。


找到了同样的问题,但还没有找到一个自动检测的好解决方案。现在我使用pspad(www.pspad.com)来解决这个问题;)很好。


因为它基本上是启发式的,所以使用先前从同一个源接收到的文件的编码作为第一个提示可能会有所帮助。

大多数人(或应用程序)每次的操作顺序几乎相同,通常在同一台机器上,所以很可能当Bob创建一个.csv文件并将其发送给Mary时,它总是使用Windows-1252或其机器默认的任何东西。

在可能的情况下,一点客户培训也不会有任何伤害:—)


我实际上在寻找一种通用的,而不是检测文件编码的编程方法,但是我还没有找到。通过测试不同的编码,我发现我的文本是UTF-7。

所以我第一次做的是:streamreader file=file.opentext(完整文件名);

我不得不把它改成:streamreader file=new streamreader(完整文件名,system.text.encoding.utf7);

opentext假定它是utf-8。

您也可以这样创建streamreader新的streamreader(fullfilename,true),第二个参数意味着它应该尝试从文件的byteordermark检测编码,但在我的例子中不起作用。


10Y(!)从被问到现在已经过去了,但我仍然没有看到提到微软优秀的非gpl'ed解决方案:imultilanguage2 api。

上面提到的大多数库都是基于Mozilla的UDE——浏览器已经解决了类似的问题,这似乎是合理的。我不知道Chrome的解决方案是什么,但是自从IE 5.0 ms发布了它们的解决方案,它是:

  • 不存在GPL等许可问题,
  • 可能永远支持和维护,
  • 提供丰富的输出-所有编码/代码页的有效候选项以及置信度得分,
  • 非常容易使用(它是一个函数调用)。
  • 这是一个本地的COM调用,但是CarstenZeumer做了一些非常好的工作,它为.NET的使用处理互操作混乱。周围还有一些其他的,但总的来说,这个图书馆并没有得到应有的关注。


    作为itmeze post的插件,我使用了这个函数来转换Mozilla通用字符集检测器的C端口的输出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        private Encoding GetEncodingFromString(string codePageName)
        {
            try
            {
                return Encoding.GetEncoding(codePageName);
            }
            catch
            {
                return Encoding.ASCII;
            }
        }

    MSDN


    在akelpad中打开文件(或只是复制/粘贴一个乱码的文本),转到编辑->选择->重新编码…->检查"自动检测"。


    感谢@erik aronesty提及uchardet。同时(相同?)该工具适用于Linux:chardet。或者,在Cygwin上,您可能需要使用:chardetect

    请参阅:chardet man page:https://www.commandlinux.com/man-page/man1/chardetect.1.html

    这将启发式地检测(猜测)每个给定文件的字符编码,并报告每个文件检测到的字符编码的名称和可信度。


    我使用此代码在读取文件时检测Unicode和Windows默认的ANSI代码页。对于其他编码,需要手动或通过编程检查内容。这可以用来保存与打开时编码相同的文本。(我使用VB.NET)

    1
    2
    3
    4
    5
    'Works for Default and unicode (auto detect)
    Dim mystreamreader As New StreamReader(LocalFileName, Encoding.Default)
    MyEditTextBox.Text = mystreamreader.ReadToEnd()
    Debug.Print(mystreamreader.CurrentEncoding.CodePage) '
    Autodetected encoding
    mystreamreader.Close()


    推荐阅读