example - python xml to dict
Cómo generar CDATA usando ElementTree (12)
Descubrí que cElementTree es aproximadamente 30 veces más rápido que xml.dom.minidom
y estoy reescribiendo mi código de codificación / decodificación XML. Sin embargo, necesito generar XML que contenga secciones CDATA y no parece haber una manera de hacerlo con ElementTree.
Se puede hacer?
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 _write
de ElementTree que realmente genera el XML, hay un manejo de casos especial para los comentarios:
if tag is Comment:
file.write("<!-- %s -->" % _escape_cdata(node.text, encoding))
Para admitir secciones CDATA, creo una función de fábrica llamada CDATA
, extendí la clase _write
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 hacerlo de nuevo con las secciones CDATA, pero al menos le permite crear XMLs con secciones CDATA programáticamente, que es lo que necesitaba 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")
El DOM tiene (al menos en el nivel 2) una interfaz DATASection y una operación Document :: createCDATASection. Son interfaces de extensión, compatibles solo si una implementación admite la función "xml".
de xml.dom import minidom
my_xmldoc = minidom.parse (xmlfile)
my_xmldoc.createCDATASection (datos)
ahora tienes un nodo cadata, agrégalo donde quieras ...
Aquí hay 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")
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)
Llegué aquí buscando una forma de "analizar un XML con secciones CDATA y luego volver a generarlo 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",)
Descubrí un truco para que CDATA funcione con los comentarios:
node.append(etree.Comment('' --><![CDATA['' + data.replace('']]>'', '']]]]><![CDATA[>'') + '']]><!-- ''))
No es posible AFAIK ... lo cual es una pena. 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.
Vea este hilo en la lista de correo de Python para más información. Básicamente, recomiendan algún tipo de biblioteca XML basada en DOM.
No sé si las versiones anteriores del código propuesto funcionaron muy bien y si el módulo ElementTree se ha actualizado pero he tenido 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, el serializador vuelve a tratarlo como una etiqueta normal. Estaba recibiendo 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 examinar el código una vez más a través de la función de serialización original si hemos atrapado nuestro CDATA y lo hemos pasado con éxito. 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 se ve así (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 que obtuve 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!
¡Te deseo el mismo resultado!
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
En realidad, este código tiene un error, ya que no captas ]]>
apareciendo en los datos que estás insertando como CDATA
¿Hay alguna manera de escapar de un token final CDATA en xml?
deberías dividirlo en dos CDATA en ese caso, dividiendo el ]]>
entre los dos.
básicamente data = data.replace("]]>", "]]]]><![CDATA[>")
(no necesariamente correcto, verifique)