python – lxml: imponer un orden específico para los atributos

Tengo un script de escritura XML que genera XML para una herramienta de terceros específica.

He utilizado el XML original como plantilla para asegurarme de que estoy creando todos los elementos correctos, pero el XML final no se parece al original.

Escribo los atributos en el mismo orden, pero lxml los escribe en su propio orden.

No estoy seguro, pero sospecho que la herramienta de la tercera parte espera que los atributos aparezcan en un orden específico, y me gustaría resolver este problema para poder ver si es el orden de atribución lo que hace que falle, o algo más.

Elemento fuente:

 

Mi script fuente:

 sig.fileformat = etree.SubElement(sig.fileformats, "FileFormat", ID = str(db.ID), Name = db.name, PUID="fileSig/{}".format(str(db.ID)), Version = "", MIMEType = "") 

Mi XML resultante:

  

¿Hay alguna forma de restringir el orden en que se escriben?

Ordenamiento y legibilidad de los atributos Como han mencionado los comentaristas, el orden de los atributos no tiene importancia semántica en XML, lo que quiere decir que no cambia el significado de un elemento:

    

Hay una característica análoga en SQL, donde el orden de las columnas no cambia el significado de una definición de tabla. Los atributos XML y las columnas SQL son un conjunto (no un conjunto ordenado ), por lo que todo lo que se puede decir “oficialmente” sobre cualquiera de ellos es si el atributo o la columna están presentes en el conjunto.

Dicho esto, definitivamente hace una diferencia en la legibilidad humana en qué orden aparecen estas cosas y en situaciones donde las construcciones como esta se escriben y aparecen en texto (por ejemplo, código fuente) y deben interpretarse, un ordenamiento cuidadoso tiene mucho sentido para mí .

Comportamiento típico del analizador

Cualquier analizador XML que considere el orden de los atributos como significativo estaría fuera de cumplimiento con el estándar XML. Eso no significa que no pueda suceder, pero en mi experiencia es ciertamente inusual. Aún así, dependiendo de la procedencia de la herramienta que mencionas, es una posibilidad que vale la pena probar.

Por lo que sé, lxml no tiene ningún mecanismo para especificar los atributos de orden que aparecen en XML serializado, y me sorprendería si lo hiciera.

Para probar el comportamiento, me inclinaría por escribir una plantilla basada en texto para generar suficiente XML para probarlo:

 id = 1 name = 'Development Signature' puid = 'dev/1' version = '1.0' mimetype = 'text/x-test-signature' template = ('') xml = template % (id, name, puid, version, mimetype) 

Decreto ordenado de atributos

A partir de lxml 3.3.3 (quizás también en versiones anteriores) puede pasar un OrderedDict de atributos al constructor de lxml.etree.(Sub)Element orden se conservará cuando se use lxml.etree.tostring(root) :

 sig.fileformat = etree.SubElement(sig.fileformats, "FileFormat", OrderedDict([("ID",str(db.ID)), ("Name",db.name), ("PUID","fileSig/{}".format(str(db.ID))), ("Version",""), ("MIMEType","")])) 

Tenga en cuenta que la API de xml.etree.ElementTree ( xml.etree.ElementTree ) no conserva el orden de los atributos, incluso si proporciona un OrderedDict al xml.etree.ElementTree.(Sub)Element ¡Constructor de xml.etree.ElementTree.(Sub)Element !

ACTUALIZACIÓN: también tenga en cuenta que el uso del parámetro **extra del constructor del lxml.etree.(Sub)Element Para la especificación de atributos no conserva el orden de los atributos:

 >>> from lxml.etree import Element, tostring >>> from collections import OrderedDict >>> root = Element("root", OrderedDict([("b","1"),("a","2")])) # attrib parameter >>> tostring(root) b'' # preserved >>> root = Element("root", b="1", a="2") # **extra parameter >>> tostring(root) b'' # not preserved 

Parece que lxml serializa los atributos en el orden en que los configura:

 >>> from lxml import etree as ET >>> x = ET.Element("x") >>> x.set('a', '1') >>> x.set('b', '2') >>> ET.tostring(x) '' >>> y= ET.Element("y") >>> y.set('b', '2') >>> y.set('a', '1') >>> ET.tostring(y) '' 

Tenga en cuenta que cuando pasa atributos utilizando el constructor ET.SubElement (), Python construye un diccionario de argumentos de palabras clave y pasa ese diccionario a lxml. Esto pierde cualquier orden que tenía en el archivo fuente, ya que los diccionarios de Python no están ordenados (o, más bien, su orden está determinado por los valores de hash de cadena, que pueden diferir de una plataforma a otra o, de hecho, de una ejecución a otra).