Kann ElementTree die Reihenfolge der Attribute zu erhalten gesagt werden?
-
02-10-2019 - |
Frage
Ich habe eine ziemlich einfache Filter in Python geschrieben ElementTree mit den Kontexten von einigen XML-Dateien munge. Und es funktioniert, mehr oder weniger.
Aber es ordnet die Attribute von verschiedenen Tags, und ich möchte, dass nicht tun.
Hat jemand einen Schalter weiß, dass ich werfen kann, um es ihnen zu machen halten in der angegebenen Reihenfolge?
Kontext für dieses
Ich arbeite mit und auf einem Teilchenphysik-Tool, das ein komplexes, aber seltsam Konfigurationssystem beschränkt, basierend auf XML-Dateien. Unter den vielen Sachen Setup sind auf diese Weise die Pfade zu verschiedenen statischen Datendateien. Diese Pfade werden in die bestehenden XML fest einprogrammiert, und es gibt keine Einrichtungen zum Einstellen oder Verändern sie anhand von Umgebungsvariablen und in unserer lokalen Installation sind sie unbedingt an einem anderen Ort.
Dies ist keine Katastrophe, da die kombinierte Quellen- und Build-Steuerungstool verwenden wir uns bestimmte Dateien mit lokalen Kopien Schatten ermöglicht. Aber dachte auch die Datenfelder sind statisch die xml nicht der Fall, so dass ich ein Skript geschrieben haben, für die Pfade Festsetzung, aber mit dem Attribut Umlagerung diffs zwischen den lokalen und Master-Versionen sind härter als nötig zu lesen.
Dies ist mein erstes Mal ElementTree für eine Spritztour nehmen (und nur meinem fünften oder sechsten Python-Projekt) so vielleicht falsch, ich mache es einfach.
Abstracted der Einfachheit halber Der Code sieht wie folgt aus:
tree = elementtree.ElementTree.parse(inputfile)
i = tree.getiterator()
for e in i:
e.text = filter(e.text)
tree.write(outputfile)
Vernünftige oder stumm?
Links zum Thema:
Lösung
Mit Hilfe Antwort der @ bobince und dieses beide ( Einstellung Attribut um , übergeordnete Modul Methoden )
konnte ich diesen Affen bekommen es schmutzigen gepatcht und ich würde ein anderes Modul vorschlagen, mit, dass eine bessere Griffe dieses Szenario aber wenn das nicht eine Möglichkeit:
# =======================================================================
# Monkey patch ElementTree
import xml.etree.ElementTree as ET
def _serialize_xml(write, elem, encoding, qnames, namespaces):
tag = elem.tag
text = elem.text
if tag is ET.Comment:
write("<!--%s-->" % ET._encode(text, encoding))
elif tag is ET.ProcessingInstruction:
write("<?%s?>" % ET._encode(text, encoding))
else:
tag = qnames[tag]
if tag is None:
if text:
write(ET._escape_cdata(text, encoding))
for e in elem:
_serialize_xml(write, e, encoding, qnames, None)
else:
write("<" + tag)
items = elem.items()
if items or namespaces:
if namespaces:
for v, k in sorted(namespaces.items(),
key=lambda x: x[1]): # sort on prefix
if k:
k = ":" + k
write(" xmlns%s=\"%s\"" % (
k.encode(encoding),
ET._escape_attrib(v, encoding)
))
#for k, v in sorted(items): # lexical order
for k, v in items: # Monkey patch
if isinstance(k, ET.QName):
k = k.text
if isinstance(v, ET.QName):
v = qnames[v.text]
else:
v = ET._escape_attrib(v, encoding)
write(" %s=\"%s\"" % (qnames[k], v))
if text or len(elem):
write(">")
if text:
write(ET._escape_cdata(text, encoding))
for e in elem:
_serialize_xml(write, e, encoding, qnames, None)
write("</" + tag + ">")
else:
write(" />")
if elem.tail:
write(ET._escape_cdata(elem.tail, encoding))
ET._serialize_xml = _serialize_xml
from collections import OrderedDict
class OrderedXMLTreeBuilder(ET.XMLTreeBuilder):
def _start_list(self, tag, attrib_in):
fixname = self._fixname
tag = fixname(tag)
attrib = OrderedDict()
if attrib_in:
for i in range(0, len(attrib_in), 2):
attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
return self._target.start(tag, attrib)
# =======================================================================
Dann im Code:
tree = ET.parse(pathToFile, OrderedXMLTreeBuilder())
Andere Tipps
Nein. ElementTree verwendet ein Wörterbuch zu speichern Attributwerte, so ist es von Natur aus ungeordnet.
Auch DOM nicht garantieren Sie Ordnungsattribut und DOM macht viel mehr Detail des XML-Infoset als ElementTree tut. (Es gibt einige Dome, die es als Feature bieten, aber es ist nicht Standard.)
Kann es behoben werden? Vielleicht. Hier ist ein Stich an, dass das Wörterbuch ersetzt, wenn sie mit einem Parsen bestellt ein ( collections.OrderedDict()
).
from xml.etree import ElementTree
from collections import OrderedDict
import StringIO
class OrderedXMLTreeBuilder(ElementTree.XMLTreeBuilder):
def _start_list(self, tag, attrib_in):
fixname = self._fixname
tag = fixname(tag)
attrib = OrderedDict()
if attrib_in:
for i in range(0, len(attrib_in), 2):
attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
return self._target.start(tag, attrib)
>>> xmlf = StringIO.StringIO('<a b="c" d="e" f="g" j="k" h="i"/>')
>>> tree = ElementTree.ElementTree()
>>> root = tree.parse(xmlf, OrderedXMLTreeBuilder())
>>> root.attrib
OrderedDict([('b', 'c'), ('d', 'e'), ('f', 'g'), ('j', 'k'), ('h', 'i')])
Sieht aus potenziell vielversprechend.
>>> s = StringIO.StringIO()
>>> tree.write(s)
>>> s.getvalue()
'<a b="c" d="e" f="g" h="i" j="k" />'
Bah, die Serializer gibt sie in kanonischer Reihenfolge.
Das sieht aus wie die Linie Schuld, in ElementTree._write
:
items.sort() # lexical order
Subclassing oder Affen-Patching, das ärgerlich sein wird, wie es ist direkt in der Mitte einer großen Methode.
Es sei denn, du hast etwas Gemeines wie Unterklasse OrderedDict
und Hack items
eine spezielle Unterklasse von list
zurückzukehren, dass ignoriert Anrufe sort()
. Nein, wahrscheinlich, dass noch schlimmer und ich ins Bett gehen sollte, bevor ich komme mit etwas schrecklicher als dem.
Falsche Frage. Sollte sein: „Wo finde ich eine diff
Gadget finden, die sinnvollerweise mit XML-Dateien funktioniert
Antwort: Google ist dein Freund. Erstes Ergebnis für die Suche auf "xml diff" => dieser . Es gibt noch ein paar possibles.
Ja, mit lxml
>>> from lxml import etree
>>> root = etree.Element("root", interesting="totally")
>>> etree.tostring(root)
b'<root interesting="totally"/>'
>>> print(root.get("hello"))
None
>>> root.set("hello", "Huhu")
>>> print(root.get("hello"))
Huhu
>>> etree.tostring(root)
b'<root interesting="totally" hello="Huhu"/>'
Hier ist direkt Link Dokumentation von welche das obige Beispiel leicht angepasst ist.
Beachten Sie auch, dass lxml hat, durch Design, einige gute API Kompatibilität mit Standard xml.etree.ElementTree
beste Option ist, die lxml Bibliothek http://lxml.de/ verwenden Installieren des lxml und Schalt nur die Bibliothek hat die Magie zu mir.
#import xml.etree.ElementTree as ET
from lxml import etree as ET
Aus Abschnitt 3.1 der der XML-Empfehlung :
Beachten Sie, dass die Reihenfolge der Attributspezifikationen in einem Start-Tag oder Leeres-Element-Tag ist nicht signifikant.
Jedes System, das auf der Reihenfolge der Attribute in einem XML-Elemente setzt wird brechen.
hatte ihr Problem. Zum einen sah für einige Python-Skript canonize, fand nur knapp sein Ziel jedermann. Dann begann man darüber nachzudenken, zu machen. Schließlich xmllint
gelöst.
Dies ist eine Teillösung, für den Fall, xml emittiert wird, und eine vorhersagbare Reihenfolge gewünscht wird. Dabei spielt es keine Rundreise Parsen und Schreiben lösen. Sowohl 2.7 und 3.x Verwendung sorted()
zu zwingen, ein Attribut Bestellung. Also, dieser Code in Verbindung mit der Verwendung eines OrderedDictionary die Attribute halten wird die Reihenfolge für XML-Ausgabe zu erhalten, um die Bestellung entspricht verwendet, um die Elemente zu erstellen.
from collections import OrderedDict
from xml.etree import ElementTree as ET
# Make sorted() a no-op for the ElementTree module
ET.sorted = lambda x: x
try:
# python3 use a cPython implementation by default, prevent that
ET.Element = ET._Element_Py
# similarly, override SubElement method if desired
def SubElement(parent, tag, attrib=OrderedDict(), **extra):
attrib = attrib.copy()
attrib.update(extra)
element = parent.makeelement(tag, attrib)
parent.append(element)
return element
ET.SubElement = SubElement
except AttributeError:
pass # nothing else for python2, ElementTree is pure python
# Make an element with a particular "meaningful" ordering
t = ET.ElementTree(ET.Element('component',
OrderedDict([('grp','foo'),('name','bar'),
('class','exec'),('arch','x86')])))
# Add a child element
ET.SubElement(t.getroot(),'depend',
OrderedDict([('grp','foo'),('name','util1'),('class','lib')]))
x = ET.tostring(n)
print (x)
# Order maintained...
# <component grp="foo" name="bar" class="exec" arch="x86"><depend grp="foo" name="util1" class="lib" /></component>
# Parse again, won't be ordered because Elements are created
# without ordered dict
print ET.tostring(ET.fromstring(x))
# <component arch="x86" name="bar" grp="foo" class="exec"><depend name="util1" grp="foo" class="lib" /></component>
Das Problem mit XML in eine Elementstruktur Parsen ist, dass der Code intern Ebene dict
s erzeugt, die in dem Elemente geführt werden (), an welchem ??Punkt der Ordnung verloren geht. Keine Entsprechung einfache Patch ist möglich.
habe ich die akzeptierte Antwort oben, mit beiden Aussagen:
ET._serialize_xml = _serialize_xml
ET._serialize['xml'] = _serialize_xml
Während dies die Bestellung in jedem Knoten festgelegt, Attribut Bestellung auf neue Knoten von Kopien des vorhandenen Knoten eingefügt ausgefallen ohne deep zu bewahren. Achten Sie auf Knoten Wiederverwendung andere zu schaffen ... In meinem Fall hatte ich ein Element mit mehreren Attributen, so dass ich sie wiederverwenden wollte:
to_add = ET.fromstring(ET.tostring(contract))
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)
Die fromstring(tostring)
werden die Attribute im Speicher neu anordnen. Es kann nicht dazu führen, in der Alpha-dict von Attributen sortiert, aber es kann auch nicht die erwartete Ordnung haben.
to_add = copy.deepcopy(contract)
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)
Nun ist die Bestellung weiter besteht.