2008-10-06 24 views
32

Descubrí que cElementTree es aproximadamente 30 veces más rápido que xml.dom.minidom y estoy reescribiendo mi código de codificación/descodificación XML. Sin embargo, necesito generar XML que contenga secciones CDATA y no parece haber una manera de hacerlo con ElementTree.Cómo generar CDATA usando ElementTree

¿Se puede hacer?

+0

> Necesito generar XML que contenga las secciones CDATA ¿Por qué? Parece un requerimiento extraño. – bortzmeyer

+1

Es un requisito que tengo: los trozos de CDATA a veces son mucho más legibles para los humanos. – grifaton

+0

@bortzmeyer Es útil para agregar HTML a KML (archivos XML de Google Maps). –

Respuesta

22

Después de un poco de trabajo, encontré la respuesta yo mismo. Al observar el código fuente de ElementTree.py, descubrí que había un manejo especial de los comentarios XML y las instrucciones de preprocesamiento. Lo que hacen es crear una función de fábrica para el tipo de elemento especial que utiliza un valor de etiqueta especial (sin cadena) para diferenciarlo de los elementos regulares.

def Comment(text=None): 
    element = Element(Comment) 
    element.text = text 
    return element 

Luego, en la función de elementtree _write que realmente genera el XML, hay un manejo de los comentarios caso especial:

if tag is Comment: 
    file.write("<!-- %s -->" % _escape_cdata(node.text, encoding)) 

Con el fin de apoyar las secciones CDATA, puedo crear una función de fábrica llamada CDATA, extendió la clase ElementTree y cambió la función _write para manejar los elementos CDATA.

Esto aún no ayuda si quiere analizar un XML con secciones CDATA y luego volverlo a generar con las secciones CDATA, pero al menos le permite crear XMLs con secciones CDATA programáticamente, que es lo que necesitaba para hacer.

La implementación parece funcionar tanto con ElementTree como con cElementTree.

import elementtree.ElementTree as etree 
#~ import cElementTree as etree 

def CDATA(text=None): 
    element = etree.Element(CDATA) 
    element.text = text 
    return element 

class ElementTreeCDATA(etree.ElementTree): 
    def _write(self, file, node, encoding, namespaces): 
     if node.tag is CDATA: 
      text = node.text.encode(encoding) 
      file.write("\n<![CDATA[%s]]>\n" % text) 
     else: 
      etree.ElementTree._write(self, file, node, encoding, namespaces) 

if __name__ == "__main__": 
    import sys 

    text = """ 
    <?xml version='1.0' encoding='utf-8'?> 
    <text> 
    This is just some sample text. 
    </text> 
    """ 

    e = etree.Element("data") 
    cdata = CDATA(text) 
    e.append(cdata) 
    et = ElementTreeCDATA(e) 
    et.write(sys.stdout, "utf-8") 
+1

Esto ya no parece posible porque el método _write no está allí, y las funciones _serialize_ * son estáticas –

+0

¿Qué debo hacer ya que no puedo usar _write? Entonces eso significa que no puedo usar xml.elementtree? Este es terrible. – elwc

+2

Thsio reciep no funcionará para Python 2.7 o 3.2 (y 3.3) - verifique la respuesta de @ amaury. Básicamente, el nuevo ElementTree no tiene un método de "escritura" que ya puede ser reemplazado. – jsbueno

6

No es posible AFAIK ... lo cual es una lástima. Básicamente, los módulos ElementTree suponen que el lector es 100% compatible con XML, por lo que no debería importar si generan una sección como CDATA o algún otro formato que genere el texto equivalente.

Consulte this thread en la lista de correo de Python para obtener más información. Básicamente, recomiendan algún tipo de biblioteca XML basada en DOM.

+2

No lo llamaría "una pena". Para el infoset de XML (el contenido), no hay diferencia entre "" y "&" ... La mayoría de los analizadores XML ni siquiera le permitirán saber lo que estaba en el documento original. – bortzmeyer

+1

Eso es cierto, pero algunos datos pueden descartarse y analizarse mucho más eficientemente en formato CDATA. Por lo tanto, es un dolor no poder decirle a una biblioteca XML que lo maneje de esta manera. –

+0

Parece que el enlace no está disponible ahora. –

6

En realidad este código tiene un error, ya que no se captura ]]> que aparece en los datos que se está insertando como CDATA

según Is there a way to escape a CDATA end token in xml?

usted debe dividirlo en dos de CDATA en ese caso, la división el ]]> entre los dos.

básicamente data = data.replace("]]>", "]]]]><![CDATA[>")
(no necesariamente correcta, verifique)

1

El DOM tiene (al menos en el nivel 2) una interfaz DATASection, y un documento de operación :: createCDATASection. Son interfaces de extensión , solo se admiten si una implementación admite la función "xml".

de xml.dom importación minidom

my_xmldoc = minidom.analizar (xmlfile)

my_xmldoc.createCDATASection (datos)

ahora u tener nodo cadata agregarlo donde quieres ....

10

Aquí es una variante de la solución de gooli que funciona para Python 3.2:

import xml.etree.ElementTree as etree 

def CDATA(text=None): 
    element = etree.Element('![CDATA[') 
    element.text = text 
    return element 

etree._original_serialize_xml = etree._serialize_xml 
def _serialize_xml(write, elem, qnames, namespaces): 
    if elem.tag == '![CDATA[': 
     write("\n<%s%s]]>\n" % (
       elem.tag, elem.text)) 
     return 
    return etree._original_serialize_xml(
     write, elem, qnames, namespaces) 
etree._serialize_xml = etree._serialize['xml'] = _serialize_xml 


if __name__ == "__main__": 
    import sys 

    text = """ 
    <?xml version='1.0' encoding='utf-8'?> 
    <text> 
    This is just some sample text. 
    </text> 
    """ 

    e = etree.Element("data") 
    cdata = CDATA(text) 
    e.append(cdata) 
    et = etree.ElementTree(e) 
    et.write(sys.stdout.buffer.raw, "utf-8") 
+0

Este shoudl funcionará también para Python 2.7, como no lo hace la receta original. Me vino a la mente otra cosa que es un modo complicado. – jsbueno

+1

Esto necesita actualizarse para agregar el kwarg de codificación al '_serialize_xml' def – Patrick

+3

para python 2.7 agregar un arg de codificación a la firma de serialización. cambio 'def _serialize_xml (escritura, elem, QNames, espacios de nombres):' a 'def _serialize_xml (escribir, elem, codificación, QNames, espacios de nombres):' cambio 'escritura, elem, QNames, espacios de nombres)' a 'write, elem, encoding, qnames, namespaces)' change 'et.write (sys.stdout.buffer.raw," utf-8 ")' a 'et.write (sys.stdout," utf-8 ")' – Kevin

0

Aquí está mi versión que se basa en las respuestas de gooli y amaury anteriores. Funciona tanto para ElementTree 1.2.6 como 1.3.0, que usan métodos muy diferentes para hacer esto.

Tenga en cuenta que Gooli no funciona con 1.3.0, que parece ser el estándar actual en Python 2.7.x.

También tenga en cuenta que esta versión no utiliza el método CDATA() que Gooli usó.

import xml.etree.cElementTree as ET 

class ElementTreeCDATA(ET.ElementTree): 
    """Subclass of ElementTree which handles CDATA blocks reasonably""" 

    def _write(self, file, node, encoding, namespaces): 
     """This method is for ElementTree <= 1.2.6""" 

     if node.tag == '![CDATA[': 
      text = node.text.encode(encoding) 
      file.write("\n<![CDATA[%s]]>\n" % text) 
     else: 
      ET.ElementTree._write(self, file, node, encoding, namespaces) 

    def _serialize_xml(write, elem, qnames, namespaces): 
     """This method is for ElementTree >= 1.3.0""" 

     if elem.tag == '![CDATA[': 
      write("\n<![CDATA[%s]]>\n" % elem.text) 
     else: 
      ET._serialize_xml(write, elem, qnames, namespaces) 
0

llegué aquí buscando una manera de "analizar un XML con secciones CDATA y luego la salida de nuevo con las secciones CDATA".

Pude hacer esto (tal vez lxml se ha actualizado desde esta publicación?) Con lo siguiente: (es un poco difícil, lo siento ;-). Alguien más puede tener una mejor manera de encontrar las secciones de CDATA programáticamente, pero yo era demasiado vago.

parser = etree.XMLParser(encoding='utf-8') # my original xml was utf-8 and that was a lot of the problem 
tree = etree.parse(ppath, parser) 

for cdat in tree.findall('./ProjectXMPMetadata'): # the tag where my CDATA lives 
    cdat.text = etree.CDATA(cdat.text) 

# other stuff here 

tree.write(opath, encoding="UTF-8",) 
1

La solución aceptada no puede trabajar con Python 2.7. Sin embargo, hay otro paquete llamado lxml que (aunque un poco más lento) compartió una sintaxis en gran parte idéntica con el xml.etree.ElementTree. lxml es capaz de escribir y analizar CDATA. Documentación here

3

Esto terminó trabajando para mí en Python 2.7. Similar a la respuesta de Amaury.

import xml.etree.ElementTree as ET 

ET._original_serialize_xml = ET._serialize_xml 


def _serialize_xml(write, elem, encoding, qnames, namespaces): 
    if elem.tag == '![CDATA[': 
     write("<%s%s]]>%s" % (elem.tag, elem.text, elem.tail)) 
     return 
    return ET._original_serialize_xml(
     write, elem, encoding, qnames, namespaces) 
ET._serialize_xml = ET._serialize['xml'] = _serialize_xml 
1

he descubierto un truco para conseguir CDATA para trabajar utilizando comentarios:

node.append(etree.Comment(' --><![CDATA[' + data.replace(']]>', ']]]]><![CDATA[>') + ']]><!-- ')) 
3

No sé si las versiones anteriores del código propuesto funcionaron muy bien y si el módulo elementtree se ha actualizado, pero me he enfrentado a problemas con el uso de este truco:

etree._original_serialize_xml = etree._serialize_xml 
def _serialize_xml(write, elem, qnames, namespaces): 
    if elem.tag == '![CDATA[': 
     write("\n<%s%s]]>\n" % (
       elem.tag, elem.text)) 
     return 
    return etree._original_serialize_xml(
     write, elem, qnames, namespaces) 
etree._serialize_xml = etree._serialize['xml'] = _serialize_xml 

El problema con este enfoque es que después de pasar esta excepción, serializador es tratar de nuevo como etiqueta de la normalidad después s. Obtenía algo así como:

<textContent> 
<![CDATA[this was the code I wanted to put inside of CDATA]]> 
<![CDATA[>this was the code I wanted to put inside of CDATA</![CDATA[> 
</textContent> 

Y, por supuesto, sabemos que solo causará muchos errores. ¿Por qué estaba pasando eso?

La respuesta está en este pequeño individuo:

return etree._original_serialize_xml(write, elem, qnames, namespaces) 

No queremos a examinar el código una vez más a través de la función serializar original si hemos atrapado nuestra CDATA y superado con éxito a través de. Por lo tanto, en el bloque "if" tenemos que devolver la función de serialización original solo cuando CDATA no estaba allí. Nos faltaba "else" antes de devolver la función original.

Además, en mi versión del módulo ElementTree, serializar la función estaba pidiendo desesperadamente el argumento "short_empty_element". Así que la versión más reciente que recomendaría es similar al siguiente (también con "cola"):

from xml.etree import ElementTree 
from xml import etree 

#in order to test it you have to create testing.xml file in the folder with the script 
xmlParsedWithET = ElementTree.parse("testing.xml") 
root = xmlParsedWithET.getroot() 

def CDATA(text=None): 
    element = ElementTree.Element('![CDATA[') 
    element.text = text 
    return element 

ElementTree._original_serialize_xml = ElementTree._serialize_xml 

def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): 

    if elem.tag == '![CDATA[': 
     write("\n<{}{}]]>\n".format(elem.tag, elem.text)) 
     if elem.tail: 
      write(_escape_cdata(elem.tail)) 
    else: 
     return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) 

ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml 


text = """ 
<?xml version='1.0' encoding='utf-8'?> 
<text> 
This is just some sample text. 
</text> 
""" 
e = ElementTree.Element("data") 
cdata = CDATA(text) 
root.append(cdata) 

#tests 
print(root) 
print(root.getchildren()[0]) 
print(root.getchildren()[0].text + "\n\nyay!") 

La salida me dio fue:

<Element 'Database' at 0x10062e228> 
<Element '![CDATA[' at 0x1021cc9a8> 

<?xml version='1.0' encoding='utf-8'?> 
<text> 
This is just some sample text. 
</text> 


yay! 

les deseo el mismo resultado!

+0

¡Gracias! Tu solución funciona muy bien para mí en Python 3.4.3, y es realmente interesante que solo la hayas publicado ayer, y la necesito hoy. No he probado en 3.5, pero creo que se romperá tarde o temprano, probablemente en la próxima versión. Suspiro. – 4ae1e1

+0

De nada. Tenga en cuenta que siempre, mientras usa ElementTree.parse, solo mostrará contenido CDATA (sin la etiqueta cdata). En mi código: 'xmlParsedWITHET = ElementTree.parse ("testing.xml")'. Descubrí cómo al modificar el código solo un poco, al usar lxml puedes conservar nuestras valiosas etiquetas de cdata. Avíseme si le interesan o solo las librerías estándar están bien para usted – Kamil

+0

Estaba escribiendo un generador para mi blog y tuve que ensamblar un feed Atom 1.0. Esta es una especie de tarea única (si se rompe en el futuro, siempre puedo utilizar un virtualenv de 3.4), por lo que un ataque de STL es aceptable para mí. – 4ae1e1