用Python读取UTF-8 XML并写入文件
我正在尝试解析一个UTF-8编码的XML文件,并把其中的一些部分保存到另一个文件里。问题是,这是我写的第一个Python脚本,我对遇到的字符编码问题感到非常困惑。
我的脚本在尝试将非ASCII字符写入文件时立刻就失败了,但它可以在命令提示符中打印这些字符(至少在某种程度上可以)。
这是XML文件的相关部分(这是一个*.resx文件,里面包含了用户界面的字符串):
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="foo">
<value>bar</value>
</resheader>
<data name="lorem" xml:space="preserve">
<value>ipsum öä</value>
</data>
</root>
这是我的Python脚本:
from xml.dom.minidom import parse
names = []
values = []
def getStrings(path):
dom = parse(path)
data = dom.getElementsByTagName("data")
for i in range(len(data)):
name = data[i].getAttribute("name")
names.append(name)
value = data[i].getElementsByTagName("value")
values.append(value[0].firstChild.nodeValue.encode("utf-8"))
def writeToFile():
with open("uiStrings-fi.py", "w") as f:
for i in range(len(names)):
line = names[i] + '="'+ values[i] + '"' #varName='varValue'
f.write(line)
f.write("\n")
getStrings("ResourceFile.fi-FI.resx")
writeToFile()
这是错误追踪信息:
Traceback (most recent call last): File "GenerateLanguageFiles.py", line 24, in writeToFile() File "GenerateLanguageFiles.py", line 19, in writeToFile line = names[i] + '="'+ values[i] + '"' #varName='varValue' UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 2: ordinal not in ran ge(128)
我该如何修复我的脚本,以便它能够正确读取和写入UTF-8字符?我想生成的文件将用于与Robot Framework的测试自动化。
2 个回答
当XML解析器读取文件时,它会把输入的UTF-8编码解码成内容,这样生成的DOM(文档对象模型)中的所有文本节点和属性就变成了unicode对象。然后,当你从DOM中选择有趣的数据时,你会把这些values
重新编码成UTF-8,但names
则不需要编码。最终得到的values
数组里是编码后的字节字符串,而names
数组里还是unicode对象。
在出现编码错误的那一行,Python试图把一个unicode名称和一个字节字符串值连接在一起。为了做到这一点,这两个值必须是同一种类型。Python会尝试把字节字符串values[i]
转换成unicode,但它不知道这个字符串是UTF-8编码的,所以在使用ASCII编码时就失败了。
解决这个问题最简单的方法是保持所有字符串都是Unicode对象,只有在写入文件时才把它们编码成UTF-8:
values.append(value[0].firstChild.nodeValue) # encode not yet
...
f.write(line.encode('utf-8')) # but now
你需要去掉对 encode()
的调用,也就是说,把 nodeValue.encode("utf-8")
替换成 nodeValue
。然后,把对 open()
的调用改成:
with open("uiStrings-fi.py", "w", "utf-8") as f:
这使用了一个“支持Unicode”的版本的 open()
,你需要从 codecs
模块导入它,所以还要在文件顶部添加:
from codecs import open
到文件的最上面。
问题在于,当你调用 nodeValue.encode("utf-8")
时,你把一个Unicode字符串(Python内部可以存储所有Unicode字符的表示方式)转换成了一个普通字符串(只能存储0到255的单字节字符)。后来,当你构建要写入输出文件的行时,names[i]
仍然是一个Unicode字符串,而 values[i]
是一个普通字符串。Python尝试将普通字符串转换为Unicode,因为Unicode是更通用的类型,但因为你没有明确指定转换方式,它使用了默认的ASCII编码,而ASCII无法处理字节值大于127的字符。不幸的是,values[i]
字符串中确实出现了几个这样的字符,因为UTF-8编码经常使用这些高位字节。所以Python会抱怨它遇到了无法处理的字符。解决办法,如我之前所说的,是把从Unicode到字节的转换推迟到最后一刻,而你可以通过使用支持Unicode的版本的open来做到这一点(它会为你处理编码)。
现在想想,其实除了我之前说的,另一种解决方案是把 names[i]
替换成 names[i].encode("utf-8")
。这样,你也把 names[i]
转换成了普通字符串,Python就没有理由再尝试把 values[i]
转换回Unicode了。不过,也可以说,保持字符串作为Unicode对象直到写入文件是个好习惯……如果没有别的,我相信在Python 3中,unicode
会成为默认类型。