处理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
答案 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字符,因此字节为0xef
,0xbc
,然后(错误地)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)