Python: Actualice el archivo XML usando ElementTree mientras conserva el diseño lo más posible

Tengo un documento que utiliza un espacio de nombres XML para el cual quiero boost /group/house/dogs en uno: (el archivo se llama houses.xml )

    2821 2   

Mi resultado actual con el siguiente código es: (el archivo creado se llama houses2.xml )

   2821 3   

Me gustaría arreglar dos cosas (si es posible usar ElementTree. Si no es así, me gustaría recibir una sugerencia sobre lo que debería usar en su lugar):

  1. Quiero mantener la línea .
  2. No quiero prefijar todas las tags, me gustaría mantenerlas como están.

En conclusión, no quiero meterme con el documento más de lo necesario.

A continuación aparece mi código actual (que funciona a excepción de las fallas mencionadas anteriormente) que genera el resultado anterior.

He creado una función de utilidad que carga un archivo XML utilizando ElementTree y devuelve elementTree y el espacio de nombres (ya que no quiero codificar el espacio de nombres y estoy dispuesto a asumir el riesgo que implica):

 def elementTreeRootAndNamespace(xml_file): from xml.etree import ElementTree import re element_tree = ElementTree.parse(xml_file) # Search for a namespace on the root tag namespace_search = re.search('^({\S+})', element_tree.getroot().tag) # Keep the namespace empty if none exists, if a namespace exists set # namespace to {namespacename} namespace = '' if namespace_search: namespace = namespace_search.group(1) return element_tree, namespace 

Este es mi código para actualizar el número de perros y guardarlo en el nuevo archivo houses2.xml :

 elementTree, namespace = elementTreeRootAndNamespace('houses.xml') # Insert the namespace before each tag when when finding current number of dogs, # as ElementTree requires the namespace to be prefixed within {...} when a # namespace is used in the document. dogs = elementTree.find('{ns}house/{ns}dogs'.format(ns = namespace)) # Increase the number of dogs by one dogs.text = str(int(dogs.text) + 1) # Write the result to the new file houses2.xml. elementTree.write('houses2.xml') 

El viaje de ida y vuelta, desafortunadamente, no es un problema trivial. Con XML, generalmente no es posible conservar el documento original a menos que use un analizador especial (como DecentXML pero eso es para Java).

Dependiendo de tus necesidades, tienes las siguientes opciones:

  • Si controla la fuente y puede asegurar su código con pruebas unitarias, puede escribir su propio y sencillo analizador. Este analizador no acepta XML, sino solo un subconjunto limitado. Puede, por ejemplo, leer todo el documento como una cadena y luego usar las operaciones de cadena de Python para ubicar y reemplazar cualquier cosa hasta la siguiente < . ¿Cortar? Sí.

  • Puedes filtrar la salida. XML permite la cadena solo en un lugar, por lo que puede buscarla y reemplazarla con < y luego lo mismo con . Esto es bastante seguro a menos que pueda tener CDATA en su XML.

  • Puedes escribir tu propio y simple analizador XML. Lea la entrada como una cadena y luego cree Elementos para cada par de <> más sus posiciones en la entrada. Eso le permite separar la entrada rápidamente, pero solo funciona para entradas pequeñas.

Una solución basada en XML para este problema es escribir una clase auxiliar para ElementTree que:

  • Coge la línea de statement XML antes de analizar como ElementTree en el momento de la escritura no puede escribir una línea de statement XML sin escribir también un atributo de encoding (verifiqué la fuente).
  • Analiza el archivo de entrada una vez, toma el espacio de nombres del elemento raíz. Registra ese espacio de nombres con ElementTree que tiene la cadena vacía como prefijo. Cuando se hace eso, el archivo de origen se analiza utilizando ElementTree de nuevo , con esa nueva configuración.

Tiene un gran inconveniente:

  • Se pierden los comentarios XML. Lo que he aprendido no es aceptable para esta situación (inicialmente no pensé que los datos de entrada tuvieran ningún comentario, pero resultó que sí).

Mi clase de ayudante con ejemplo:

 from xml.etree import ElementTree as ET import re class ElementTreeHelper(): def __init__(self, xml_file_name): xml_file = open(xml_file_name, "rb") self.__parse_xml_declaration(xml_file) self.element_tree = ET.parse(xml_file) xml_file.seek(0) root_tag_namespace = self.__root_tag_namespace(self.element_tree) self.namespace = None if root_tag_namespace is not None: self.namespace = '{' + root_tag_namespace + '}' # Register the root tag namespace as having an empty prefix, as # this has to be done before parsing xml_file we re-parse. ET.register_namespace('', root_tag_namespace) self.element_tree = ET.parse(xml_file) def find(self, xpath_query): return self.element_tree.find(xpath_query) def write(self, xml_file_name): xml_file = open(xml_file_name, "wb") if self.xml_declaration_line is not None: xml_file.write(self.xml_declaration_line + '\n') return self.element_tree.write(xml_file) def __parse_xml_declaration(self, xml_file): first_line = xml_file.readline().strip() if first_line.startswith(''): self.xml_declaration_line = first_line else: self.xml_declaration_line = None xml_file.seek(0) def __root_tag_namespace(self, element_tree): namespace_search = re.search('^{(\S+)}', element_tree.getroot().tag) if namespace_search is not None: return namespace_search.group(1) else: return None def __main(): el_tree_hlp = ElementTreeHelper('houses.xml') dogs_tag = el_tree_hlp.element_tree.getroot().find( '{ns}house/{ns}dogs'.format( ns=el_tree_hlp.namespace)) one_dog_added = int(dogs_tag.text.strip()) + 1 dogs_tag.text = str(one_dog_added) el_tree_hlp.write('hejsan.xml') if __name__ == '__main__': __main() 

La salida:

    2821 3   

Si alguien tiene una mejora en esta solución, no dude en tomar el código y mejorarlo.

cuando es fácil evitar el argumento Save xml add default_namespace, ns0, en mi código

código de clave: xmltree.write (xmlfiile, “utf-8”, default_namespace = xmlnamespace)

 if os.path.isfile(xmlfiile): xmltree = ET.parse(xmlfiile) root = xmltree.getroot() xmlnamespace = root.tag.split('{')[1].split('}')[0] //get namespace initwin=xmltree.find("./{"+ xmlnamespace +"}test") initwin.find("./{"+ xmlnamespace +"}content").text = "aaa" xmltree.write(xmlfiile,"utf-8",default_namespace=xmlnamespace) 

etree de lxml proporciona esta característica.

  1. elementTree.write('houses2.xml',encoding = "UTF-8",xml_declaration = True) ayuda a no omitir la statement

  2. Mientras escribe en el archivo, no cambia los espacios de nombres.

http://lxml.de/parsing.html es el enlace de su tutorial.

PS: lxml se debe instalar por separado.