Python UTF-8 XML解析(SUDS):去除“无效标记”

6 投票
2 回答
8112 浏览
提问于 2025-04-17 09:29

在处理UTF-8时,有一个常见的错误,就是出现“无效的字符”。

在我的例子中,这个问题来自于一个SOAP服务提供商,他们对unicode字符不太尊重,直接把值截断到100个字节,完全忽视了第100个字节可能正好处于一个多字节字符的中间。例如:

<name xsi:type="xsd:string">浙江家庭教会五十人遭驱散及抓打 圣诞节聚会被断电及抢走物品(图、视频\xef\xbc</name>

最后两个字节是一个3字节unicode字符的残余部分,因为截断的时候假设大家都在用1字节的字符。接下来,使用sax解析器时:

xml.sax._exceptions.SAXParseException: <unknown>:1:2392: not well-formed (invalid token)

我不再关心这个字符了。它应该从文档中删除,以便让sax解析器正常工作。

这个XML的回复在其他方面都是有效的,除了这些值。

问题是:如何在不解析整个文档和不重新发明UTF-8编码来检查每个字节的情况下,去掉这个字符呢?

使用的工具是:Python+SUDS

2 个回答

0

@FlipMcF 的回答是正确的 - 我只是想分享我对他解决方案的过滤器,因为原来的方法对我来说不太管用(我在我的 XML 文件中有一些表情符号字符,它们虽然在 UTF-8 编码下是正确的,但还是导致了 XML 解析器崩溃):

class UnicodeFilter(MessagePlugin):
    def received(self, context):
        from lxml import etree
        from StringIO import StringIO
        parser = etree.XMLParser(recover=True) # recover=True is important here
        doc = etree.parse(StringIO(context.reply), parser)
        context.reply = etree.tostring(doc)
16

结果发现,SUDS把xml当作'字符串'(而不是unicode)来看,所以这些都是编码过的值。

1) 过滤器:

badXML = "your bad utf-8 xml here"  #(type <str>)

#Turn it into a python unicode string - ignore errors, kick out bad unicode
decoded = badXML.decode('utf-8', errors='ignore')  #(type <unicode>)

#turn it back into a string, using utf-8 encoding.
goodXML = decoded.encode('utf-8')   #(type <str>)

2) SUDS: 见 https://fedorahosted.org/suds/wiki/Documentation#MessagePlugin

from suds.plugin import MessagePlugin
class UnicodeFilter(MessagePlugin):
    def received(self, context):
        decoded = context.reply.decode('utf-8', errors='ignore')
        reencoded = decoded.encode('utf-8')
        context.reply = reencoded

还有

from suds.client import Client
client = Client(WSDL_url, plugins=[UnicodeFilter()])

希望这能帮到某个人。


注意:感谢 John Machin

见: 为什么python解码会替换掉比无效字节更多的内容?

关于 errors='ignore'issue8271 可能会在这里给你带来麻烦。如果这个bug没有修复,使用'ignore'时会消耗掉接下来的几个字节,以满足长度要求。

在解码一个无效的UTF-8字节序列时,只有
开始字节和后续字节被视为无效,而不是开始字节所指定的字节数。

这个问题在以下版本中修复:
Python 2.6.6 rc1
Python 2.7.1 rc1 (以及所有未来的2.7版本)
Python 3.1.3 rc1 (以及所有未来的3.x版本)

Python 2.5及以下版本会有这个问题。

在上面的例子中,"\xef\xbc</name".decode('utf-8', errors='ignore') 应该返回 "</name",但在有bug的python版本中,它返回 "/name"

前四位(0xe)描述了一个3字节的UTF字符,所以字节0xef0xbc,然后(错误地)0x3c'<')被消耗掉了。

0x3c 不是一个有效的后续字节,这本身就造成了无效的3字节UTF字符。

修复后的python版本只会移除第一个字节和仅有效的后续字节,留下 0x3c 不被消耗。

撰写回答