Синтаксический анализ Python UTF-8 XML (SUDS): удаление «неверного токена» - PullRequest
6 голосов
/ 04 января 2012

Вот типичная ошибка при работе с UTF-8 - «недопустимые токены»

В моем примере это происходит из-за того, что мы работаем с поставщиком услуг SOAP, который не уважает символы Юникода, просто обрезая значения до 100байт и игнорирование того, что 100-й байт может быть в середине многобайтового символа: например:

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

Последние два байта - это то, что остается от 3-байтового символа Юникода после усеченияНож предположил, что в мире используются 1-байтовые символы.Следующая остановка, синтаксический анализатор и:

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

Меня больше не волнует этот персонаж.Он должен быть удален из документа и позволить парсеру саксофона функционировать.

Ответ XML действителен во всех остальных отношениях, кроме этих значений.

Вопрос: Как удалить этот символ, не анализируя весь документ и не заново изобретая кодировку UTF-8проверить каждый байт?

Использование: Python + SUDS

Ответы [ 2 ]

16 голосов
/ 04 января 2012

Оказывается, 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()])

Надеюсь, это кому-нибудь поможет.


Примечание: благодаря Джону Мачину !

См .: Почему декодер python заменяет больше, чем недопустимые байты из закодированной строки?

Python issue8271 относительно errors='ignore' может встать на вашем пути здесь. Без этой ошибки, исправленной в 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 он возвращает "/name".

Первые четыре бита (0xe) описывают 3-байтовый символ UTF, поэтому используются байты 0xef, 0xbc, а затем (по ошибке) 0x3c ('<').

0x3c не является допустимым байтом продолжения, который в первую очередь создает недопустимый 3-байтовый символ UTF.

Исправленные версии python удаляют только первый байт и только допустимое продолжение байт, оставляя 0x3c неиспользованным

0 голосов
/ 23 января 2018

@ 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)
...