在工作中似乎没有任何一周没有一些编码相关的conniption,灾难或灾难。这个问题通常来自程序员,他们认为他们可以在不指定编码的情况下可靠地处理“文本”文件。但你不能。
因此,我们决定禁止文件的名称以*.txt
或*.text
结尾。我们的想法是,这些扩展误导了偶然程序员对编码的沉闷,这会导致处理不当。没有,几乎会更好
扩展,因为至少那时你知道你不知道你得到了什么。
然而,我们并不是那么远。相反,您应该使用以编码结尾的文件名。因此,对于文本文件,例如,README.ascii
,README.latin1
,README.utf8
等等。
对于需要特定扩展名的文件,如果可以在文件本身内部指定编码,例如在Perl或Python中,那么您应该这样做。对于像Java源这样的文件,文件内部不存在这样的工具,您将把编码放在扩展名之前,例如SomeClass-utf8.java
。
对于输出,UTF-8应强烈首选。
但是对于输入,我们需要弄清楚如何处理名为*.txt
的代码库中的数千个文件。我们想重命名所有这些以符合我们的新标准。但我们不可能全都注意它们。所以我们需要一个真正有用的库或程序。
它们有各种ASCII,ISO-8859-1,UTF-8,Microsoft CP1252或Apple MacRoman。虽然我们知道我们可以判断某些东西是否是ASCII,并且我们知道某些东西是否可能是UTF-8,但我们对8位编码感到困惑。因为我们在混合的Unix环境(Solaris,Linux,Darwin)中运行,大多数桌面都是Mac,所以我们有很多烦人的MacRoman文件。这些都是一个问题。
一段时间以来,我一直在寻找一种以编程方式确定哪个
的方法文件在,我没有找到可以可靠地区分三种不同8位编码的程序或库。我们可能单独拥有超过一千个MacRoman文件,因此我们使用的任何字符集检测器都必须能够嗅出它们。我看过的任何东西都无法解决问题。我对ICU charset detector library寄予厚望,但它无法处理MacRoman。我也看过模块在Perl和Python中做同样的事情,但是一次又一次它总是相同的故事:不支持检测MacRoman。
我正在寻找的是一个现有的库或程序,它可靠地确定文件所处的五种编码中的哪一种 - 并且最好是更多。特别是它必须区分我引用的三个3位编码,特别是MacRoman 。这些文件的英文文本超过99%;其他语言中有一些,但不是很多。
如果它是库代码,我们的语言首选项是它在Perl,C,Java或Python中,并按此顺序。如果它只是一个程序,那么我们并不关心它是什么语言,只要它是完整的源代码,在Unix上运行,并且完全没有阻碍。
有没有其他人有过这个随机编码的遗留文本文件的问题?如果是这样,你是如何尝试解决它的,你有多成功?这是我的问题中最重要的方面,但我也对你是否认为鼓励程序员用这些文件所在的实际编码来命名(或重命名)他们的文件感兴趣,这将有助于我们避免将来出现这个问题。有没有人试图在制度基础上强制执行此操作,如果是, 是否成功,以及为什么?
是的,我完全理解为什么鉴于问题的性质,人们无法保证给出明确的答案。对于小文件尤其如此,您没有足够的数据可供使用。幸运的是,我们的文件很少。除了随机README
文件外,大多数文件的大小范围为50k到250k,而且许多文件的大小更大。任何超过几K的东西都保证是英文的。
问题领域是生物医学文本挖掘,因此我们有时会处理广泛且极其庞大的语料库,就像所有PubMedCentral的Open Access资源库一样。一个相当庞大的文件是BioThesaurus 6.0,为5.7千兆字节。这个文件特别烦人,因为它几乎所有UTF-8。但是,我相信,有些numbskull会在其中插入几行8位编码 - 微软CP1252。在你旅行之前需要一段时间。 :(
答案 0 :(得分:85)
首先,简单的案例:
如果您的数据不包含0x7F以上的字节,那么它是ASCII。 (或者是7位ISO646编码,但这些编码非常过时。)
如果您的数据验证为UTF-8,那么您可以放心地假设它 UTF-8。由于UTF-8严格的验证规则,误报极为罕见。
这两种编码之间的唯一区别是ISO-8859-1具有C1控制字符,其中windows-1252具有可打印的字符€,ƒ“......†‡‰Š<<ŒŽ''”“ - ~~ ™S>œžŸ。我见过很多使用弯引号或短划线的文件,但没有使用C1控制字符的文件。所以不要打扰他们,或ISO-8859-1,只需检测windows-1252。
现在只留下一个问题。
这非常棘手。
在Windows-1252中未使用字节0x81,0x8D,0x8F,0x90,0x9D。如果它们出现,则假设数据是MacRoman。
两个编码中的字节0xA2(¢),0xA3(£),0xA9(©),0xB1(±),0xB5(μ)恰好相同。如果这些是唯一的非ASCII字节,那么选择MacRoman或cp1252无关紧要。
计算您知道为UTF-8的数据中的字符(非字节!)频率。确定最常见的字符。然后使用此数据确定cp1252或MacRoman字符是否更常见。
例如,在我刚刚在100篇随机英语维基百科文章中进行的搜索中,最常见的非ASCII字符为·•–é°®’èö—
。基于这个事实,
计算cp1252建议字节和MacRoman建议字节,并以最大值为准。
答案 1 :(得分:10)
Mozilla nsUniversalDetector(Perl绑定:Encode::Detect / Encode::Detect::Detector)经过百万倍证明。
答案 2 :(得分:7)
我尝试这种启发式(假设您排除了ASCII和UTF-8):
旁注:
对于像Java源这样的文件,其中没有 这种设施存在于内部 文件,你将把编码放在之前 扩展名,如 SomeClass-utf8.java
不要这样做!!
Java编译器期望文件名与类名匹配,因此重命名文件将使源代码无法编译。正确的做法是猜测编码,然后使用native2ascii
工具将所有非ASCII字符转换为Unicode escape sequences。
答案 3 :(得分:6)
“Perl,C,Java或Python,按顺序”:有趣的态度: - )
“我们知道某些东西是否可能是UTF-8,这是一个很好的改变”:实际上,包含有意义文本的文件在使用高位设置字节的其他字符集中编码的机会将成功解码为UTF-8很小。
UTF-8策略(至少首选语言):
# 100% Unicode-standard-compliant UTF-8
def utf8_strict(text):
try:
text.decode('utf8')
return True
except UnicodeDecodeError:
return False
# looking for almost all UTF-8 with some junk
def utf8_replace(text):
utext = text.decode('utf8', 'replace')
dodgy_count = utext.count(u'\uFFFD')
return dodgy_count, utext
# further action depends on how large dodgy_count / float(len(utext)) is
# checking for UTF-8 structure but non-compliant
# e.g. encoded surrogates, not minimal length, more than 4 bytes:
# Can be done with a regex, if you need it
一旦你确定它既不是ASCII也不是UTF-8:
我所知道的Mozilla原点字符集检测器不支持MacRoman,在任何情况下都不能很好地处理8位字符集,特别是英语,因为AFAICT它们依赖于检查解码是否有意义在给定的语言中,忽略标点字符,并基于该语言的各种文档。
正如其他人所说,你真的只有高位设置的标点字符可用来区分cp1252和macroman。我建议在你自己的文件上训练一个Mozilla型模型,而不是莎士比亚或Hansard或KJV圣经,并考虑所有256个字节。我认为你的文件中没有标记(HTML,XML等) - 这会扭曲一些令人震惊的概率。
您提到的文件大多是UTF-8但无法解码。你也应该非常怀疑:
(1)据称在ISO-8859-1中编码但包含0x80到0x9F范围内的“控制字符”的文件......这种情况非常普遍,以至于草案HTML5标准说要解码 ALL < / em>使用cp1252声明为ISO-8859-1的HTML流。
(2)将OK解码为UTF-8的文件,但生成的Unicode包含U + 0080到U + 009F范围内的“控制字符”...这可能是因为转码cp1252 / cp850(看到它发生了! )/ etc文件从“ISO-8859-1”到UTF-8。
背景:我有一个湿的周日下午项目来创建一个基于Python的字符集检测器,它是面向文件的(而不是面向Web的),适用于8位字符集,包括legacy ** n
个cp850和cp437。它还没有接近黄金时间。我对培训文件感兴趣;您的ISO-8859-1 / cp1252 / MacRoman文件是否与您期望任何人的代码解决方案一样“无阻碍”?
答案 4 :(得分:3)
正如您所发现的,没有完美的方法来解决这个问题,因为如果没有关于文件使用哪种编码的隐含知识,所有8位编码都是完全相同的:字节集合。所有字节对所有8位编码都有效。
您可以期待的最好的是某种分析字节的算法,并且基于某种语言中使用的某个字节的概率以及某种编码将猜测文件使用的编码。但是,这必须知道文件使用哪种语言,并且当你有混合编码的文件时变得完全没用。
从好的方面来说,如果您知道文件中的文本是用英文编写的,那么您不太可能注意到您决定使用该文件的编码有什么不同,因为所有提到的编码之间的差异是所有这些都局限在编码的部分中,这些部分指定了英语中通常不使用的字符。您可能会遇到一些麻烦,其中文本使用特殊格式或特殊版本的标点符号(例如,CP1252有几个版本的引号字符),但对于文本的要点,可能没有问题。
答案 5 :(得分:1)
如果你可以检测宏观的每个编码EXCEPT,那么假设无法解密的那些编码在宏观中是合乎逻辑的。换句话说,只需创建一个无法处理的文件列表,并将它们作为宏人处理。
对这些文件进行排序的另一种方法是制作一个基于服务器的程序,允许用户决定哪个编码没有乱码。当然,它将在公司内部,但每天有100名员工在做几件事,你很快就会完成数千个文件。
最后,将所有现有文件转换为单一格式并且要求新文件采用该格式不是更好。
答案 6 :(得分:1)
有没有其他人有过这个随机编码的遗留文本文件的问题?如果是这样,你是如何尝试解决它的,你有多成功?
我目前正在编写一个将文件转换为XML的程序。它必须自动检测每个文件的类型,这是确定文本文件编码问题的超集。为了确定编码,我使用贝叶斯方法。也就是说,我的分类代码计算文本文件对其理解的所有编码具有特定编码的概率(似然)。然后程序选择最可能的解码器。对于每种编码,贝叶斯方法都是这样的。
如果不是计算概率,你可以计算信息内容,这是赔率<的对数,发现贝叶斯定理变得非常容易 / em>:info = log(p / (1.0 - p))
。
您必须通过检查手动分类的文件语料库来计算初始概率和相关性。
答案 7 :(得分:1)
由公认的答案https://stackoverflow.com/a/4200765/2731103带头,我可以改进红宝石宝石“夏洛特”,以识别所请求的编码大部分正确。
我们在生产环境中将其用于在导入之前检测CSV文件编码
那是合理的部分(Ruby)
UTF8HASBOM = /^\xEF\xBB\xBF/n # [239, 187, 191]
UTF32LEBOM = /^\xFF\xFE\x00\x00/n # [255, 254, 0, 0]
UTF32BEBOM = /^\x00\x00\xFE\xFF/n # [0, 0, 254, 255]
UTF16LEBOM = /^\xFF\xFE/n # [255, 254]
UTF16BEBOM = /^\xFE\xFF/n # [254, 255]
NOTIN1BYTE = /[\x00-\x06\x0B\x0E-\x1A\x1C-\x1F\x7F]/n
NOTISO8859 = /[\x00-\x06\x0B\x0E-\x1A\x1C-\x1F\x7F\x80-\x84\x86-\x9F]/n
# Information to identify MacRoman
# https://stackoverflow.com/questions/4198804/
NOTINCP1252 = /[\x81\x8D\x8F\x90\x9D]/n
CP1252CHARS = /[\x92\x95\x96\x97\xAE\xB0\xB7\xE8\xE9\xF6]/n
MCROMNCHARS = /[\x8E\x8F\x9A\xA1\xA5\xA8\xD0\xD1\xD5\xE1]/n
detect.force_encoding('BINARY') # Needed to prevent non-matching regex charset.
sample = detect[0..19] # Keep sample string under 23 bytes.
detect.sub!(UTF8HASBOM, '') if sample[UTF8HASBOM] # Strip any UTF-8 BOM.
# See: http://www.daniellesucher.com/2013/07/23/ruby-case-versus-if/
if sample.ascii_only? && detect.force_encoding('UTF-8').valid_encoding?
elsif sample[UTF32LEBOM] && detect.force_encoding('UTF-32LE').valid_encoding?
elsif sample[UTF32BEBOM] && detect.force_encoding('UTF-32BE').valid_encoding?
elsif sample[UTF16LEBOM] && detect.force_encoding('UTF-16LE').valid_encoding?
elsif sample[UTF16BEBOM] && detect.force_encoding('UTF-16BE').valid_encoding?
elsif detect.force_encoding('UTF-8').valid_encoding?
elsif detect.force_encoding('BINARY')[NOTISO8859].nil?
detect.force_encoding('ISO-8859-1')
elsif detect.force_encoding('BINARY')[NOTIN1BYTE].nil?
if detect.force_encoding('BINARY')[NOTINCP1252].nil? &&
detect.force_encoding('BINARY').scan(MCROMNCHARS).length < detect.force_encoding('BINARY').scan(CP1252CHARS).length
detect.force_encoding('Windows-1252')
else
detect.force_encoding('MacRoman')
end
else detect.force_encoding('BINARY')
end