如何检测文本文件的编码/代码页
-
01-07-2019 - |
题
在我们的应用程序中,我们收到文本文件(.txt
, .csv
, 等)来自不同的来源。读取时,这些文件有时包含垃圾,因为这些文件是在不同/未知的代码页中创建的。
有没有办法(自动)检测文本文件的代码页?
这 detectEncodingFromByteOrderMarks
, ,在 StreamReader
构造函数,适用于 UTF8
和其他 unicode 标记的文件,但我正在寻找一种检测代码页的方法,例如 ibm850
, windows1252
.
感谢您的回答,这就是我所做的。
我们收到的文件来自最终用户,他们对代码页一无所知。接收者也是最终用户,到目前为止,这是他们对代码页的了解:代码页是存在的,而且很烦人。
解决方案:
- 用记事本打开收到的文件,看有一段乱码的文字。如果某人叫弗朗索瓦或其他什么名字,以你的人类智慧你可以猜出来。
- 我创建了一个小应用程序,用户可以用它来打开文件,并输入用户知道在使用正确的代码页时它将出现在文件中的文本。
- 循环遍历所有代码页,并显示那些使用用户提供的文本提供解决方案的代码页。
- 如果弹出多个代码页,请要求用户指定更多文本。
解决方案
您无法检测到代码页,您需要被告知。您可以分析字节并猜测它,但这可能会给出一些奇怪的(有时是有趣的)结果。我现在找不到它,但我确信记事本可以被欺骗以中文显示英文文本。
无论如何,这是您需要阅读的内容:每个软件开发人员绝对必须了解 Unicode 和字符集的绝对最低限度(没有任何借口!).
乔尔具体说:
关于编码的最重要的事实
如果你完全忘记了我刚才解释的一切,请记住一个极其重要的事实。如果不知道字符串使用什么编码,那么它是没有意义的。您不能再把头埋在沙子里假装“纯”文本是 ASCII。不存在纯文本这样的东西。
如果内存、文件或电子邮件中有一个字符串,您必须知道它的编码方式,否则您无法正确解释它或将其显示给用户。
其他提示
如果您想要检测非 UTF 编码(即没有 BOM),您基本上需要对文本进行启发式分析和统计分析。您可能想看看 Mozilla 关于通用字符集检测的论文 (相同的链接,通过 Wayback Machine 具有更好的格式).
你有没有尝试过 Mozilla 通用字符集检测器的 C# 端口
示例来自 http://code.google.com/p/ude/
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.");
}
}
}
我知道这个问题已经很晚了,而且这个解决方案不会吸引某些人(因为它以英语为中心的偏见并且缺乏统计/实证测试),但它对我来说非常有效,特别是在处理上传的 CSV 数据时:
http://www.architectshack.com/TextFileEncodingDetector.ashx
优点:
- 内置BOM检测
- 可自定义默认/后备编码
- (根据我的经验)对于包含一些外来数据(例如法国名称)以及 UTF-8 和 Latin-1 样式文件混合的西欧文件非常可靠 - 基本上是美国和西欧环境的大部分。
笔记:我是这门课的作者,所以显然要持保留态度!:)
记事本++ 具有开箱即用的此功能。它还支持更改它。
寻找不同的解决方案,我发现
https://code.google.com/p/ude/
这个解决方案有点重。
我需要一些基本的编码检测,基于 4 个前字节和可能的 xml 字符集检测 - 所以我从互联网上获取了一些示例源代码并添加了稍微修改的版本
http://lists.w3.org/Archives/Public/www-validator/2002Aug/0084.html
为 Java 编写。
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\\n\\r]?=[\\t\\n\\r]?['\"]([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% 的解决方案。这对我有用:
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 中做过类似的事情。基本上,您需要来自各种编码的大量样本数据,这些数据被滑动的两字节窗口分解并存储在字典(散列)中,以提供编码列表值的字节对为键。
给定该字典(哈希),您将输入文本并:
- 如果它以任何 BOM 字符开头(对于 UTF-16-BE 为 '\xfe\xff',对于 UTF-16-LE 为 '\xff\xfe',对于 UTF-8 为 '\xef\xbb\xbf' 等),我按照建议对待它
- 如果没有,则获取足够大的文本样本,获取样本的所有字节对,并选择字典中建议的最不常见的编码。
如果您还采样了 UTF 编码文本 不是 从任何 BOM 开始,第二步将覆盖第一步中遗漏的 BOM。
到目前为止,它对我有用(示例数据和后续输入数据是各种语言的字幕),并且错误率不断降低。
StreamReader 类的构造函数采用“检测编码”参数。
工具“uchardet”使用每个字符集的字符频率分布模型很好地做到了这一点。较大的文件和更“典型”的文件更有信心(显然)。
在ubuntu上,你只需 apt-get install uchardet
.
在其他系统上,请在此处获取来源、用法和文档: https://github.com/BYVoid/uchardet
如果可以链接到 C 库,则可以使用 libenca
. 。看 http://cihar.com/software/enca/. 。从手册页:
ENCA读取给定的文本文件或当不给出的文本输入时,使用有关其语言的知识(必须由您支持)以及解析,统计分析,猜测和黑魔法的混合物来确定其编码。
它是 GPL v2。
遇到了同样的问题,但尚未找到自动检测它的好解决方案。现在我正在使用 PsPad (www.pspad.com) ;) 工作正常
由于它基本上归结为启发式,因此使用先前从同一源接收的文件的编码作为第一个提示可能会有所帮助。
大多数人(或应用程序)每次都以几乎相同的顺序执行操作,通常是在同一台计算机上,因此当 Bob 创建 .csv 文件并将其发送给 Mary 时,它很可能始终使用 Windows-1252 或无论他的机器默认为什么。
在可能的情况下,进行一些客户培训也不会造成任何伤害:-)
我实际上正在寻找一种通用的、非编程的方式来检测文件编码,但我还没有找到。通过使用不同的编码进行测试,我发现我的文本是 UTF-7。
所以我首先在做什么:StreamReader file = File.OpenText(完整文件名);
我不得不将其更改为:StreamReader 文件 = new StreamReader(fullfilename, System.Text.Encoding.UTF7);
OpenText 假定它是 UTF-8。
您还可以像这个新的StreamReader(FullFileName,true)一样创建StreamReader,第二个参数意味着它应该尝试从文件的字节标记检测编码,但在我的情况下这不起作用。
在 AkelPad 中打开文件(或者只是复制/粘贴乱码文本),转到编辑 -> 选择 -> 重新编码...-> 选中“自动检测”。
作为 ITmeze 帖子的插件,我使用此函数来转换 Mozilla 通用字符集检测器的 C# 端口的输出
private Encoding GetEncodingFromString(string codePageName)
{
try
{
return Encoding.GetEncoding(codePageName);
}
catch
{
return Encoding.ASCII;
}
}
谢谢 @埃里克·阿罗内斯蒂 提及 uchardet
.
同时,Linux 上也有(相同的?)工具: chardet
.
或者,在 cygwin 上您可能想使用: chardetect
.
看: 夏代手册页: https://www.commandlinux.com/man-page/man1/chardetect.1.html
这将启发式地检测(猜测)每个给定文件的字符编码,并将报告每个文件检测到的字符编码的名称和置信度。
自从提出这个问题以来,10Y(!)已经过去了,但我仍然没有看到任何提及 MS 的良好的非 GPL 解决方案: I多语言2 API。
已经提到的大多数库都是基于 Mozilla 的 UDE - 并且浏览器已经解决了类似的问题似乎是合理的。我不知道chrome的解决方案是什么,但自从IE 5.0 MS发布了他们的解决方案,它是:
- 没有 GPL 之类的许可问题,
- 可能永远支持和维护,
- 提供丰富的输出 - 所有有效的编码/代码页候选以及置信度分数,
- 使用起来出奇的简单(这是一个单一的函数调用)。
这是一个本机 COM 调用,但是 这是一些非常好的作品 由 Carsten Zeumer 编写,它处理 .net 使用中的互操作混乱。周围还有其他一些库,但总的来说,这个库没有得到应有的关注。
我在读取文件时使用此代码来检测 Unicode 和 Windows 默认的 ansi 代码页。对于其他编码,需要手动或通过编程检查内容。这可以用来以与打开时相同的编码保存文本。(我使用VB.NET)
'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()