【发布时间】:2012-02-13 18:09:39
【问题描述】:
tl;dr 摘要:给定一个表示未知编码的字符串的字节流,我应该尝试使用什么编码以及以什么顺序来解释这些字节以获得找到'的最佳机会正确的编码?
问题示例
我有一个文件arrows.txt,我碰巧知道它是使用UTF-8 保存的,其单字符内容为⇈。如果我假装我不知道这个文件的编码是什么,Windows 上的以下 Ruby 代码会失败:
s = IO.read('foo.txt')
p s.encoding, #=> #<Encoding:IBM437>
s.valid_encoding?, #=> true
s.chars.to_a #=> ["\xE2", "\x87", "\x88"]
它“失败”是因为它告诉我文件实际上包含 Γçê 的内容,并且一切正常(编码有效)。
真实世界场景
我有 Nginx 日志文件和 Akamai 日志文件,它们记录的查询没有任何特定编码,我需要在数据库中处理数据并将其存储为 UTF-8。大多数情况下,将每一行解释为 UTF-8 会产生一个具有有效编码的字符串,但有时不会。
我想请 Ruby 为每一行尝试各种编码,以找到一个有效且可能(但当然不能保证)正确的编码。
尝试失败
我最初写了以下代码:
def guess_encoding( str, result='utf-8', *encodings )
# Try every encoding if none were passed in
encodings = Encoding.list if encodings.empty?
# Keep forcing a new encoding until we find one that is valid
unless encodings.find{ |e| str.force_encoding(e) && str.valid_encoding? }
raise "None of the supplied encodings was valid"
end
# Convert from the valid encoding to the desired, replacing 'bad' characters
str.encode(result, invalid: :replace, undef: :replace)
end
问题在于Encoding.list 中的第一个编码是ASCII-8BIT,它对所有字节流都有效。因此,如果我使用上面的代码并调用s2 = guess_encoding(s),则结果是字符串���,表示上面的三字节双箭头字符。
最后是问题
我应该以什么顺序测试编码以提供最大的机会让第一个 valid_encoding? 成为正确的?哪些常见的编码对使用的字节最挑剔,我应该先尝试它们,哪些常见的编码是完全允许的,所以我应该最后尝试它们?
在猜测正确性时我应该使用其他启发式方法吗? (如果特定编码导致的字符少于另一个,是否更可能是正确的?)
【问题讨论】:
-
你可以试试rchardet19 gem。它“采用未知字符编码的字节序列,并尝试确定编码。”
-
@louism 您应该将此作为答案。 :)
-
谢谢,我不确定宝石推荐是否算作答案,但既然其他人都在这样做,我想我也会这样做:-)。
-
要记住的一点是,
bytes返回构成字符的字节,但chars返回多字节字符本身。因此,s.bytes.to_a返回三个字节,s.chars.to_a将只返回单个字符。 -
@theTinMan 一个很好的说明;输出
bytes真的很吵。我更新了问题以表明在该特定编码下chars.to_a.length也是三个坏字符(问题的症结所在)。