Python UTF-8 XML解析(SUDS):删除“无效令牌”

时间:2012-01-03 22:08:26

标签: python xml soap unicode suds

处理UTF-8时出现常见错误 - “无效令牌”

在我的例子中,它来自处理不尊重unicode字符的SOAP服务提供者,只是将值截断为100字节而忽略了第100个字节可能位于多字节字符的中间:例如:

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

在截断刀假定世界使用1字节字符之后,最后两个字节是3字节unicode字符的剩余字节。下一站,sax解析器和:

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

我不再关心这个角色了。它应该从文档中删除并允许sax解析器运行。

除了这些值之外,XML回复在其他所有方面都有效。

问题:如何在不解析整个文档并重新发明UTF-8编码来检查每个字节的情况下删除此字符?

使用:Python + SUDS

2 个答案:

答案 0 :(得分:16)

事实证明,SUDS将xml看作类型'string'(不是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

请参阅:Why is python decode replacing more than the invalid bytes from an encoded string?

关于errors='ignore'的Python issue8271可能会妨碍您。如果没有在python中修复此错误,'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",但在python的'bugged'版本中,它返回"/name"

前四位(0xe)描述了一个3字节的UTF字符,因此字节为0xef0xbc,然后(错误地)0x3c({{ 1}})消耗。

'<'不是有效的连续字节,它首先创建无效的3字节UTF字符。

固定版本的python只删除第一个字节而只删除有效的连续字节,使0x3c无法消耗

答案 1 :(得分: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)