如何用Python将XML字符串转换为二进制形式?
首先,我是从一个用记事本保存的文本文件中解析数据,这个文件是用UTF-8编码的。这样做能确保它是UTF-8编码吗?我试过用chardet模块,但没什么帮助。这里有几行文本文件的内容,如果有人能帮我找出更多信息:
CUSTOMERLOC|1|N/A|N/A|LEGACY COPPER|N/A|Existing|N/A|NRZ|NRZ|N/A|N/A
FTSMAR08|01/A|N/A|N/A|LEGACY COPPER|N/A|Existing|N/A|NRZ|NRZ|N/A|N/A
FTSMAR08|01/B|N/A|N/A|LEGACY COPPER|N/A|Existing|N/A|NRZ|NRZ|N/A|N/A
我使用lxml模块来写我的XML,然后用tostring()
方法把它赋值给一个叫data
的变量。
接着,我用了binascii模块里的a2b_qp()
函数,把XML字符串转换成二进制,并把所有这些放进一个bytearray
里。
data = bytearray(binascii.a2b_qp(ET.tostring(root, pretty_print=True)), "UTF-8")
在我看来,这个data
变量应该包含我用二进制形式存储的XML,放在一个bytearray
里。
然后,我用了一个更新游标,把数据插入到表的BLOB字段中。
row[2] = data
cursor.updateRow(row)
一切看起来都正常,但当我用以下代码读取BLOB字段时:
with arcpy.da.SearchCursor("Point", ['BlobField']) as cursor:
for row in cursor:
binaryRep = row[0]
open("C:/Blob.xml, 'wb').write(binaryRep.tobytes())
当我打开Blob.xml
文件时,我本来期待看到我最初创建的XML字符串以可读的形式显示,但结果却是这个乱七八糟的内容,使用Notepad++设置为UTF-8编码:
而使用Notepad++设置为ANSI编码时又是这个乱七八糟的内容:
我想有经验的人可能通过看到这些图片知道发生了什么。我已经读了很多资料,试图弄明白,但我一直卡在这里。
3 个回答
存储:
- 先准备好你的XML数据
- 把它转成字符串
- 将这个字符串编码成UTF-8的二进制字符串(也就是用
xml_string.encode('utf-8')
这个方法) - 把得到的二进制字符串保存到你的数据库里
取出:
- 从数据库中取出这个二进制字符串
- 把它从UTF-8解码 - 用
xml_string.decode('utf-8')
这个方法 - 再把它转回XML格式
- 然后就可以对你的XML数据进行想做的操作了
我觉得你可能有些偏离主题了:
binascii.a2b_qp(ET.tostring(root, pretty_print=True))
a2b_qp
这个函数是以“引用可打印格式”(类似于base64)来处理输入的,但实际上你输入的是XML格式的内容。结果就是得到的二进制数据是垃圾。
你应该使用bytearray。把你的XML字符串和编码("utf-8"
)传给它,它会返回你需要的二进制数据。
编码是一个有趣的概念,理解起来需要一些思考。简单来说:
- 如果你在用Python 3,那基本上没问题。如果你在用2.x版本,那么你几乎肯定要使用
unicode
数据类型,而不是str
。 - Unicode是一个比编码更高级的概念。每个可以显示的字符在一个超过一百万个字符的逻辑空间中都有一个(有时是多个)代码点。
- 简单来说,把一个Unicode字符串写入磁盘,每个字符需要3个字节。这样的文件会比实际需要的要大很多,并且与大多数现有的ASCII文件不兼容——在1990年代,当大多数数据都是ASCII格式,磁盘又非常昂贵时,这种情况是不可接受的,所以就使用了编码(映射)。UTF-8是一个不错的选择,因为:
- 向后兼容:所有7位的ASCII文件都是有效的UTF-8文件。
- 效率:8位到14位的字符(大多数人使用的字符)在UTF-8中映射为2个字节。其他字符根据需要占用3或4个字节。
- 兼容性:很多重要的协议和标准都使用UTF-8。
- 你现在进入了另一种编码方式,叫做binascii。这是一组在你需要通过只允许或安全使用ASCII的媒介(比如URL和SMTP/email)发送二进制数据(例如JPG)时使用的例程。Base64的工作原理如下:
- 使用A-Z、a-z、0-9和几个其他字符,你有64个代码点,或者说6位信息。
- 4个这样的字符是6x4 = 24位,正好等于3个字节的数据(3x8)。
- 因此,Base64将3个字节的数据块映射为4个安全字符。
- 换句话说,你可以把任何二进制数据转换成一块安全字符,代价是增加30%的大小。
希望这些能帮到你。
我正在从一个用记事本保存为UTF-8编码的文本文件中解析数据。这样做就能确保它是UTF-8编码吗?我试过使用chardet模块,但没什么帮助。
是的,告诉你的编辑器以特定编码保存文件就足够了。如果可能的话,这种编码也应该在文件中某个地方记录下来,比如在XML文件中,<?xml encoding="utf-8"?>
是常用的指定方式。但这只是一些元数据,并不真正控制编码。chardet
模块在你不知道编码时很有用,但它的猜测应该作为最后的手段。通常情况下,UTF-8是一个不错的默认选择,尤其是在处理XML时。
下面这行代码:
data = bytearray(binascii.a2b_qp(ET.tostring(root, pretty_print=True)), "UTF-8")
给你带来乱码的原因是它做了一些不好的处理,结果变成了乱码。
ET.tostring()默认使用ASCII编码(因此会丢失任何不在ASCII范围内的数据,但这暂时不讨论)。所以,现在你得到的是一个ASCII字符串。binascii.a2b_qp
会使用可打印编码来解码它。也就是说,它把所有可打印的ASCII字符变成了可能不是可打印的字符(qp会用3个可打印的ASCII字符来编码任何不在可打印ASCII范围内的字节)。这意味着,如果你的文本中有=00,它会把它变成一个空字节。问题是你原来的内容并不是经过QP编码的,所以QP解码后就变成了乱码。
然后你使用bytearray再次将其编码为UTF-8。bytearray假设如果你给它一个编码,那么这个字符串就是一个unicode字符串——如果你打破了这个假设,给它原始的二进制数据(这已经没有意义了)。将原始的二进制数据编码为UTF-8并没有什么特别的意义,这让我觉得你在使用Python 2。Python 3在你尝试这样做时会正确抛出错误:
>>> bytearray(b'123', 'utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: encoding or errors without a string argument
Python 2在字节和解码字符之间的区分上比较模糊,这使得这种问题更容易出现。如果可以的话,升级到Python 3是个很好的理由。但这并不会解决你从a2b_qp得到的之前的乱码(因为这是字节<->字节的编码)。
解决办法是从一开始就以UTF-8编码,并忘记可打印编码。如果你真的想要QP编码,可以在UTF-8编码后再通过binascii.b2a处理。
ElementTree允许你指定编码:
ET.tostring(root, encoding='utf-8')
这样就能得到正确的UTF-8编码的XML,能够在Notepad++中正常打开。