2009-11-10 19 views
23

El XML specification enumera un grupo de caracteres Unicode que son ilegales o "desalentados". Dada una cadena, ¿cómo puedo eliminar todos los caracteres ilegales de ella?¿Forma rápida de filtrar caracteres xml unicode ilegales en python?

Se me ocurrió la siguiente expresión regular, pero es un poco apetecible.

illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 
clean = illegal_xml_re.sub('', dirty) 

(Python 2.5 no sabe nada de caracteres Unicode anteriores 0xFFFF, así que no hay necesidad de filtrar aquellos.)

+0

El punto de código Unicode máximo de Python depende de cómo se configuró cuando se compiló, marque 'sys.maxunicode'. – u0b34a0f6ae

+0

Tienes razón. Supongo que es aún más complicado. – itsadok

+2

En mi máquina, usar esta expresión regular para procesar una cadena de 2.3 MB demora .34 segundos. Eso me parece bastante rápido. –

Respuesta

11

Recientemente (Trac XmlRpcPlugin mantenedores) se han notificado del hecho de que la expresión regular las tiras anteriores representan parejas sustitutas en construcciones angostas de Python (ver th:comment:13:ticket:11050). Un enfoque alternativo consiste en utilizar la siguiente expresión regular (consulte th:changeset:13729).

_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), 
         (0x7F, 0x84), (0x86, 0x9F), 
         (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)] 
if sys.maxunicode >= 0x10000: # not narrow build 
     _illegal_unichrs.extend([(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), 
           (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF), 
           (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF), 
           (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), 
           (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF), 
           (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF), 
           (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), 
           (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)]) 

_illegal_ranges = ["%s-%s" % (unichr(low), unichr(high)) 
        for (low, high) in _illegal_unichrs] 
_illegal_xml_chars_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges)) 

p.s. Ver this post on surrogates explicando para qué sirven.

actualización a fin de que no coincida (sustituir) 0x0D que es un valid XML character.

+0

Tenga en cuenta que los pares indirectos están explícitamente excluidos de los caracteres legales en la especificación W3C XML, por lo que no está garantizado que cualquier XML que los contenga se pueda analizar correctamente en otras bibliotecas. Sin embargo, dado que normalmente se serializaría el XML en utf-8 o utf-16, el problema debería desaparecer. Solo manténgase alejado de utf-32. – itsadok

+0

He actualizado la expresión regular para que coincida con el carácter 0x0D. Ver [th: ticket: 11635] (http://trac-hacks.org/ticket/11635), [th: changeset: 13776] (http://trac-hacks.org/changeset/13776) y [XML character definición de rango] (http://www.w3.org/TR/REC-xml/#NT-Char). –

+0

Buen punto. He actualizado mi versión también. – itsadok

3

También es posible usar Unicode a traducir método para eliminar los puntos de código seleccionados. Sin embargo, el mapeo tenemos es bastante grande (2128 puntos de código) y que puede hacer que sea mucho más lento que simplemente usar una expresión regular:

ranges = [(0, 8), (0xb, 0x1f), (0x7f, 0x84), (0x86, 0x9f), (0xd800, 0xdfff), (0xfdd0, 0xfddf), (0xfffe, 0xffff)] 
# fromkeys creates the wanted (codepoint -> None) mapping 
nukemap = dict.fromkeys(r for start, end in ranges for r in range(start, end+1)) 
clean = dirty.translate(nukemap) 
+1

Después de algunas pruebas, esto parece ser mucho más lento que una expresión regular, especialmente para cadenas grandes. – itsadok

Cuestiones relacionadas