2012-01-03 25 views
6

Aquí es un error común cuando se trata de UTF-8 - 'tokens no válidos'Python UTF-8 análisis XML (SUDS): Extracción 'invalid token'

En mi ejemplo, se deriva de trabajar con un proveedor de servicio SOAP que no tenían respeto por los caracteres Unicode, simplemente truncar los valores de 100 bytes y dejar de lado que el byte 100'th puede estar en el medio de un carácter multi-byte: por ejemplo:

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

los dos últimos bytes son lo restos de un carácter unicode de 3 bytes, después de que el cuchillo de truncamiento supone que el mundo usa caracteres de 1 byte. Siguiente parada, analizador de saxo y:

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

Ya no me importa este personaje. Debe eliminarse del documento y permitir que funcione el analizador de saxofón.

La respuesta XML es válida en todos los demás aspectos excepto en estos valores.

Pregunta: ¿Cómo se elimina este carácter sin analizar todo el documento y reinventar la codificación UTF-8 para verificar cada byte?

Uso: Python + SUDS

Respuesta

17

resulta, SUDS ve xml como 'cadena' tipo (no Unicode) lo que estos son valores codificados.

1) el filtro:

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: ver 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 

y

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

Espero que esto ayude a alguien.


Nota: ¡Gracias a John Machin!

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

Python issue8271 respecto errors='ignore' puede ponerse en su camino aquí. Sin este error corregido en Python, 'ignorar' consumirá los próximos pocos bytes para satisfacer la longitud

durante la decodificación de una secuencia de bytes UTF-8 no válida, sólo el
byte inicial y el byte de continuación (s) son ahora considerados válidos, en lugar del número de bytes especificados por el byte de inicio

problema se solucionó en:
Python 2.6.6 RC1
Python 2.7.1 RC1 (y todas las futuras versiones de 2.7)
Python 3.1.3 rc1 (y todas las versiones futuras de 3.x)

Python 2.5 y siguientes contendrán este problema.

En el ejemplo anterior, "\xef\xbc</name".decode('utf-8', errors='ignore') debería
volver "</name", pero en versiones 'fastidiado' de pitón devuelve "/name".

Los primeros cuatro bits (0xe) describe un carácter 3-byte UTF, por lo que el bytes 0xef, 0xbc, y luego (erróneamente) 0x3c ('<') se consumen.

0x3c no es un byte de continuación válido que crea el carácter UTF de 3 bytes no válido en primer lugar.

versiones fijas de pitón sólo eliminan el primer byte y continuación válidas sólo bytes, dejando 0x3c no consumido

+1

sí, me acaba de responder a mi propia pregunta. : P – FlipMcF

+0

Bueno para ti. +1. – sberry

+0

Insignia autodidacta obtenida ... (¡ese era el punto, realmente!) Gracias. – FlipMcF

0

@ FlipMcF de es la respuesta correcta - Sólo estoy publicar mi filtro para su solución, ya que el original no funcionó para mí (que tenía algunos caracteres emoji en mi XML, que fueron correctamente codificado en UTF-8, pero todavía se estrelló analizadores 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)