2012-01-03 13 views
6

Oto częsty błąd w kontaktach z UTF-8 - „nieważne tokeny”Python UTF-8 XML parsowania (mydlin): Usuwanie „Nieprawidłowy token”

W moim przykładzie, chodzi od czynienia z dostawcą usług SOAP że nie miał szacunku dla znaków unicode, po prostu obcinanie wartości do 100 bajtów, a zaniedbując że 100'th bajt może być w środku znaku multi-bajtowego: na przykład:

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

ostatnie dwa bajty są co resztki 3-bajtowego znaku Unicode, po tym, jak nóż ścinający założył, że świat używa znaków 1-bajtowych. Następny przystanek, parser sax i:

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

Nie obchodzi mnie już ta postać. Powinien zostać usunięty z dokumentu i umożliwić działanie analizatora składni saks.

Odpowiedź XML jest ważna pod każdym innym względem, z wyjątkiem tych wartości.

Pytanie: Jak usunąć tę postać bez analizowania całego dokumentu i ponownego wynalezienia kodowania UTF-8 w celu sprawdzenia każdego bajtu?

Zastosowanie: Python + mydliny

Odpowiedz

17

okazuje, mydliny widzi XML jako typu 'string' (nie unicode), więc są one zakodowane wartości.

1) Filtr:

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) mydliny: patrz 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 

i

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

nadzieję, że to pomaga kogoś.


Uwaga: Dzięki John Machin!

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

Python issue8271 dotyczące errors='ignore' można uzyskać w drodze tutaj. Bez tego błędu ustalonej pytona „ignoruj” zużywa się kilka kolejnych bajtów Spełniają długość

podczas dekodowania nieprawidłowym UTF-8 sekwencja bajtów tylko
początkowego bajtu i bajtu kontynuacji (ów) są uważane za nieważne, zamiast liczby bajtów określonych przez bajt startu

emisyjna została ustalona w:
Python 2.6.6 rc1
Python 2.7.1 RC1 (i wszystkich przyszłych wydaniach 2.7)
Python 3.1.3 rc1 (i wszystkie przyszłe wydania 3.x)

Python 2.5 i poniżej będzie zawierać ten problem.

W powyższym przykładzie "\xef\xbc</name".decode('utf-8', errors='ignore') powinien
wrócić "</name", ale w "błędne" wersje python zwraca "/name".

pierwsze cztery bity (0xe) opisuje 3-bajtowy UTF znaków, tak bajtów 0xef, 0xbc, a następnie (niesłusznie) 0x3c ('<') konsumowania.

0x3c nie jest prawidłowym bajtem kontynuacji, który tworzy niepoprawny 3-bajtowy znak UTF w pierwszej kolejności.

Stałe wersje Pythona usunąć tylko pierwszy bajt i ważne tylko kontynuacja bajtów, pozostawiając 0x3c nieużyte

+1

tak, po prostu odpowiedział na moje własne pytanie. : P – FlipMcF

+0

Dobry dla ciebie. +1. – sberry

+0

Odznaka Self Learner zarobiona ... (to było naprawdę ważne!) Dziękuję. – FlipMcF

0

@ FlipMcF jest poprawna odpowiedź - Ja tylko delegowania mój filtr do jego rozwiązania, ponieważ pierwotnego nie działa dla mnie (miałem kilka znaków emoji w moim XML, które zostały poprawnie zakodowany w UTF-8, ale nadal rozbił parsera 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)