你必须做出假设
如果你不能对你将要遇到的信件做出假设,你可能就有麻烦了。因此,在我们的文档中,我们可以合理地假设挪威字母A-Å。没有什么神奇的工具可以自动更正您遇到的每个文档。在
因此,在这个域中,我们知道一个文件可能包含å,其UTF-8 2字节表示0xc3 0xa5或{a1},Latin-1和{a3}将其表示为0xe5。一般来说,这个character lookup非常好,如果你发现自己在研究一个字符,它可能会成为一个很好的书签。在
示例挪威人
损坏的版本Ã¥
您可以在这个方便的debugging chart中找到这类问题的长列表。在
基本Python编码、解码
如果你知道到底是什么地方出了问题,这是最简单的方法把绳子打回原形。在our_broken_string = 'Ã¥'
broken_unicode = our_broken_string.decode('UTF-8')
print broken_unicode # u'\xc3\xa5' yikes -> two different unicode characters
down_converted_string = broken_unicode.encode('LATIN-1')
print down_converted_string # '\xc3\xa5' those are the right bytes
correct_unicode = down_converted_string.decode('UTF-8')
print correct_unicode # u'\xe5' correct unicode value
文件
在处理文档时,可以做出一些相对较好的假设。单词、空格和行。即使文档是XML,您仍然可以将其视为单词,而不必太担心标记,或者如果单词确实是单词,则只需要能找到的最小单元。我们还可以假设,如果文件有文本编码问题,它可能也有行尾问题,这取决于有多少不同的操作系统损坏了该文件。我将在行尾中断rstrip,并使用print将数组重新组合到StringIO文件句柄。在
在保留空白时,通过一个漂亮的print函数运行XML文档可能很诱人,但是您不应该这样做,我们只想在不改变任何其他内容的情况下更正小文本单元的编码。一个好的起点是看看是否可以逐行逐字地阅读文档,而不是在任意的字节块中,而忽略您正在处理的是XML。在
在这里,我利用这样一个事实:如果文本超出UTF-8的范围,那么您将得到unicodedecoderrors,然后尝试使用拉丁语-1。这在这个文件中起作用了。在
^{pr2}$
处理Mojibake
如果你的问题是真的Mojibake,比如一个错误的文件名。您可以使用FTFY尝试试探性地更正您的问题。同样,我会采取逐字逐句的方法来获得最佳效果。在import os
import sys
import ftfy
import unicodedata
if __name__ == '__main__':
path = sys.argv[1]
file_system_encoding = sys.getfilesystemencoding()
unicode_path = path.decode(file_system_encoding)
for root, dirs, files in os.walk(unicode_path):
for f in files:
comparable_original_filename = unicodedata.normalize('NFC', f)
comparable_new_filename = ftfy.fix_text(f, normalization='NFC')
if comparable_original_filename != comparable_new_filename:
original_path = os.path.join(root, f)
new_path = os.path.join(root, comparable_new_filename)
print "Renaming:" + original_path + " to:" + new_path
os.rename(original_path, new_path)
这通过目录更正了很多更丑陋的错误,其中å被拼成了{}。这是什么?大写字母A+COMBINING LETTER TILDE0xcc 0x83是使用unicode equivalence表示Ã的几种方法之一。这确实是FTFY的一项工作,因为它实际上将执行一个启发式的任务,并解决这些类型的问题。在
用于比较和文件系统的Unicode规范化
另一种方法是使用unicode的规范化来获得正确的字节。在import unicodedata
a_combining_tilde = 'A\xcc\x83'
# Assume: Expecting UTF-8
unicode_version = a_combining_tilde.decode('UTF-8') # u'A\u0303' and this cannot be converted to LATIN-1 and get Ã
normalized = unicodedata.normalize('NFC', unicode_version) # u'\c3'
broken_but_better = normalized.encode('UTF-8') # '\xc3\x83` correct UTF-8 bytes for Ã.
总之,如果将其作为UTF-8编码的字符串A\xcc\x83\xc2\xa5,对其进行规范化,然后向下转换为拉丁语-1字符串,然后再返回到UTF-8,您将获得正确的unicode。在
你需要注意操作系统是如何编码文件名的。您可以通过以下方式检索该信息:file_system_encoding = sys.getfilesystemencoding()
假设file_system_encoding是{},对吗?然后比较两个看起来完全相同的unicode字符串,结果它们不相等!FTFY,默认规格化为NFC,HFS规范化为NFD的旧版本。因此,仅仅知道编码是相同的是不够的,你必须用同样的方式规范化比较才能有效。在Windows NTFS存储unicode而不进行规范化。在
Linux存储unicode而不进行规范化。在
machfs使用专有HFD规范化存储UTF-8。在
在节点.js有关于dealing with different filesystems的好指南。总之,规范化是为了比较,不要任意地重新规范化文件名。在
最后说明
谎言、该死的谎言和XML声明
在XML d中文档您将得到这样的东西,它应该通知XML解析器有关文本编码的信息。在<?xml version="1.0" encoding="ISO-8859-1"?>
如果你看到了这一点,在被证明是真的之前,它应该被视为谎言。在将此文档交给XML解析器之前,您需要验证和处理编码问题,并且需要更正声明。在
谎言,该死的谎言和BOM标记
字节顺序标记听起来是个不错的主意,但就像它们的XML声明一样,它们是文件编码情况的完全不可靠的指示器。Within UTF-8, BOMs are NOT recommended并且对于字节顺序没有意义。它们唯一的值是指示某些东西是用UTF-8编码的。但是,考虑到文本编码的问题,默认值是而且应该是预期的UTF-8。在