用Python读取UTF-8 XML并写入文件

3 投票
2 回答
11388 浏览
提问于 2025-04-15 23:47

我正在尝试解析一个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 个回答

0

当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
8

你需要去掉对 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 会成为默认类型。

撰写回答