Crear un archivo XML simple usando python

161

¿Cuáles son mis opciones si quiero crear un archivo XML simple en Python? (biblioteca inteligente)

El xml que quiero se ve así:

<root>
 <doc>
     <field1 name="blah">some value1</field1>
     <field2 name="asdfasd">some vlaue2</field2>
 </doc>

</root>
Blankman
fuente

Respuestas:

310

En estos días, la opción más popular (y muy simple) es la API ElementTree , que se ha incluido en la biblioteca estándar desde Python 2.5.

Las opciones disponibles para eso son:

  • ElementTree (implementación básica de Python puro de ElementTree. Parte de la biblioteca estándar desde 2.5)
  • cElementTree (Implementación C optimizada de ElementTree. También se ofrece en la biblioteca estándar desde 2.5)
  • LXML (basado en libxml2. Ofrece un rico superconjunto de la API ElementTree, así como XPath, selectores CSS y más)

Aquí hay un ejemplo de cómo generar su documento de ejemplo usando el cElementTree en stdlib:

import xml.etree.cElementTree as ET

root = ET.Element("root")
doc = ET.SubElement(root, "doc")

ET.SubElement(doc, "field1", name="blah").text = "some value1"
ET.SubElement(doc, "field2", name="asdfasd").text = "some vlaue2"

tree = ET.ElementTree(root)
tree.write("filename.xml")

Lo probé y funciona, pero supongo que el espacio en blanco no es significativo. Si necesita una sangría de "impresión bonita", avíseme y buscaré cómo hacerlo. (Puede ser una opción específica de LXML. No uso mucho la implementación de stdlib)

Para leer más, aquí hay algunos enlaces útiles:

Como nota final, cElementTree o LXML deben ser lo suficientemente rápidos para todas sus necesidades (ambos son código C optimizado), pero en el caso de que se encuentre en una situación en la que necesite exprimir hasta el último bit de rendimiento, los puntos de referencia en el sitio LXML indica que:

  • LXML claramente gana por serializar (generar) XML
  • Como efecto secundario de la implementación del recorrido primario adecuado, LXML es un poco más lento que cElementTree para el análisis.
ssokolow
fuente
1
@ Kasper: no tengo una Mac, así que no puedo intentar duplicar el problema. Dime la versión de Python y veré si puedo replicarla en Linux.
ssokolow
44
@nonsensickle Realmente deberías haber hecho una nueva pregunta y luego enviarme un enlace para que todos puedan beneficiarse de ella. Sin embargo, te señalaré en la dirección correcta. Las bibliotecas DOM (Modelo de objetos de documento) siempre crean un modelo en memoria, por lo que desea una implementación SAX (API simple para XML). Nunca he analizado las implementaciones de SAX, pero aquí hay un tutorial para usar el in-stdlib para la salida en lugar de la entrada.
ssokolow
1
@YonatanSimson No sé cómo agregar esa cadena exacta , ya que ElementTree parece obedecer solo xml_declaration=Truesi especifica una codificación ... pero, para obtener un comportamiento equivalente, llame de tree.write()esta manera: tree.write("filename.xml", xml_declaration=True, encoding='utf-8')puede usar cualquier codificación siempre que especifique explícitamente uno. ( asciiforzará que todos los caracteres Unicode fuera del conjunto ASCII de 7 bits se codifiquen por entidad si no confía en que un servidor web esté configurado correctamente.)
ssokolow
1
Sólo un recordatorio a cualquier otra persona que trata de corregir vlaue2a value2: El error tipográfico se encuentra en la salida XML solicitada en la pregunta original. Hasta que eso cambie, el error tipográfico aquí en realidad es correcto.
ssokolow
3
Según la documentación , cElementTreese depreció en Python 3.3
Stevoisiak el
63

La biblioteca lxml incluye una sintaxis muy conveniente para la generación de XML, llamada E-factory . Así es como haría el ejemplo que das:

#!/usr/bin/python
import lxml.etree
import lxml.builder    

E = lxml.builder.ElementMaker()
ROOT = E.root
DOC = E.doc
FIELD1 = E.field1
FIELD2 = E.field2

the_doc = ROOT(
        DOC(
            FIELD1('some value1', name='blah'),
            FIELD2('some value2', name='asdfasd'),
            )   
        )   

print lxml.etree.tostring(the_doc, pretty_print=True)

Salida:

<root>
  <doc>
    <field1 name="blah">some value1</field1>
    <field2 name="asdfasd">some value2</field2>
  </doc>
</root>

También admite agregar a un nodo ya hecho, por ejemplo, después de lo anterior, podría decir

the_doc.append(FIELD2('another value again', name='hithere'))
rescdsk
fuente
3
Si el nombre de la etiqueta no cumple las reglas de identificadores Python, entonces se podría utilizar getattr, por ejemplo, getattr(E, "some-tag").
haridsv
para mí imprimir lxml.etree.tostring estaba causando AttributeError: el objeto 'lxml.etree._Element' no tiene el atributo 'etree'. Funcionó sin iniciar "lxml". como: etree.tostring (the_doc, pretty_print = True)
kodlan
19

Yattag http://www.yattag.org/ o https://github.com/leforestier/yattag proporciona una API interesante para crear dicho documento XML (y también documentos HTML).

Está usando el administrador de contexto y la withpalabra clave.

from yattag import Doc, indent

doc, tag, text = Doc().tagtext()

with tag('root'):
    with tag('doc'):
        with tag('field1', name='blah'):
            text('some value1')
        with tag('field2', name='asdfasd'):
            text('some value2')

result = indent(
    doc.getvalue(),
    indentation = ' '*4,
    newline = '\r\n'
)

print(result)

entonces obtendrás:

<root>
    <doc>
        <field1 name="blah">some value1</field1>
        <field2 name="asdfasd">some value2</field2>
    </doc>
</root>
scls
fuente
4

Para una estructura XML tan simple, es posible que no desee involucrar un módulo XML completo. Considere una plantilla de cadena para las estructuras más simples, o Jinja para algo un poco más complejo. Jinja puede manejar el bucle sobre una lista de datos para producir el xml interno de su lista de documentos. Eso es un poco más complicado con las plantillas de cadenas de Python sin procesar

Para un ejemplo de Jinja, vea mi respuesta a una pregunta similar .

Aquí hay un ejemplo de cómo generar su xml con plantillas de cadena.

import string
from xml.sax.saxutils import escape

inner_template = string.Template('    <field${id} name="${name}">${value}</field${id}>')

outer_template = string.Template("""<root>
 <doc>
${document_list}
 </doc>
</root>
 """)

data = [
    (1, 'foo', 'The value for the foo document'),
    (2, 'bar', 'The <value> for the <bar> document'),
]

inner_contents = [inner_template.substitute(id=id, name=name, value=escape(value)) for (id, name, value) in data]
result = outer_template.substitute(document_list='\n'.join(inner_contents))
print result

Salida:

<root>
 <doc>
    <field1 name="foo">The value for the foo document</field1>
    <field2 name="bar">The &lt;value&gt; for the &lt;bar&gt; document</field2>
 </doc>
</root>

La decepción de la plantilla de enfoque es que no se va a escapar de <y >de forma gratuita. Bailé alrededor de ese problema sacando una utilidad dexml.sax

bigh_29
fuente
1

Acabo de terminar de escribir un generador xml, utilizando el método de Plantillas de bigh_29 ... es una buena manera de controlar lo que se genera sin que demasiados Objetos se interpongan en el camino.

En cuanto a la etiqueta y el valor, utilicé dos matrices, una que daba el nombre y la posición de la etiqueta en la salida xml y otra que hacía referencia a un archivo de parámetros que tenía la misma lista de etiquetas. Sin embargo, el archivo de parámetros también tiene el número de posición en el archivo de entrada (csv) correspondiente de donde se tomarán los datos. De esta manera, si hay algún cambio en la posición de los datos provenientes del archivo de entrada, el programa no cambia; calcula dinámicamente la posición del campo de datos a partir de la etiqueta apropiada en el archivo de parámetros.

Cloughie
fuente