2012-01-03 109 views
6

下面是使用UTF-8打交道時,一個常見的錯誤 - 「無效令牌」Python的UTF-8 XML解析(SUDS):刪除「無效令牌」

在我的例子,它來自處理SOAP服務提供商這已經爲Unicode字符的不尊重,只是截斷值100個字節,而忽略了100'th字節可以在多字節字符的中間:例如:

<name xsi:type="xsd:string">浙江家庭教會五十人遭驅散及抓打 聖誕節聚會被斷電及搶走物品(圖、視頻\xef\xbc</name> 

最後兩個字節是什麼在截斷刀假定世界使用1字節字符後,剩餘的是3字節的Unicode字符。下一站,薩克斯解析器和:

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

我不在乎這個角色了。它應該從文檔中刪除並允許sax解析器運行。

除了這些值之外,XML回覆在其他各方面均有效。

問:如何在不分析整個文檔並重新發明UTF-8編碼以檢查每個字節的情況下刪除此字符?

使用:Python之+ SUDS

回答

17

原來,泡沫看到XML作爲類型 '字符串'(不是Unicode)所以這些被編碼的值。

1)FILTER:

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)肥皂水:看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?

的Python issue8271關於errors='ignore'可以讓你的方式在這裏。如果沒有這種錯誤固定在Python,「忽略」將消耗接下來的幾個字節,以滿足一個無效的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未消費

+1

是的,我剛纔已經回答我的問題。 :P – FlipMcF 2012-01-03 22:18:49

+0

對你有好處。 +1。 – sberry 2012-01-03 23:18:49

+0

自我學習者徽章掙得...(那真是太棒了!)謝謝。 – FlipMcF 2012-01-12 21:33:59

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)