Domanda

Ho scritto un filtro abbastanza semplice in Python usando ElementTree per munge i contesti di alcuni file xml. E funziona, più o meno.

Ma riordina gli attributi di vari tag, e mi piacerebbe che a non farlo.

Qualcuno sa un interruttore posso buttare per renderlo tenerli in ordine specificato?

Context per questo

sto lavorando con e su uno strumento fisica delle particelle che ha un sistema di configurazione complessa, ma stranamente limitato sulla base di file XML. Tra l'impostazione molte cose in questo modo sono i percorsi per vari file di dati statici. Questi percorsi sono insita nel XML esistente e non ci sono strutture per l'impostazione o variabile in base a variabili d'ambiente, e nella nostra installazione locale sono necessariamente in un posto diverso.

Questo non è un disastro perché lo strumento combinato di sorgente e di accumulo di controllo che stiamo usando ci permette di ombra alcuni file con le copie locali. Ma anche pensato che i campi dati sono statici l'XML non è, così ho scritto uno script per il fissaggio dei percorsi, ma con le diff attributo riarrangiamento tra le versioni locali e master sono più difficili da leggere del necessario.


Questa è la mia prima volta prendendo ElementTree per un giro (e solo il mio quinto o sesto progetto python) quindi forse sto solo facendo male.

Sottratto per semplicità gli sguardi codice come questo:

tree = elementtree.ElementTree.parse(inputfile)
i = tree.getiterator()
for e in i:
    e.text = filter(e.text)
tree.write(outputfile)

ragionevole o stupido?


Link correlati:

È stato utile?

Soluzione

Con l'aiuto di risposta di @ bobince e questi due ( impostazione ordine attributo , imperativi metodi modulo )

Sono riuscito ad ottenere questa scimmia rattoppato è sporco e io suggerirei di utilizzare un altro modulo che gestisce meglio questo scenario, ma quando ciò non è una possibilità:

# =======================================================================
# 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)

# =======================================================================

Poi nel codice:

tree = ET.parse(pathToFile, OrderedXMLTreeBuilder())

Altri suggerimenti

No. ElementTree utilizza un dizionario per memorizzare i valori degli attributi, quindi è intrinsecamente ordinata.

Anche DOM non garantisce attribuisce ordinazione, e DOM espone molto di più particolare della infoset XML di ElementTree fa. (Ci sono alcuni DOM che offrono come una funzione, ma non è standard.)

Può essere fisso? Può essere. Ecco una pugnalata a lui che sostituisce il dizionario durante l'analisi di un ordinato uno ( 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')])

appare potenzialmente promettenti.

>>> s = StringIO.StringIO()
>>> tree.write(s)
>>> s.getvalue()
'<a b="c" d="e" f="g" h="i" j="k" />'

Bah, il serialiser uscite in ordine canonico.

Questo appare come la linea per colpa, in ElementTree._write:

            items.sort() # lexical order

Subclassing o scimmia-patching che sta per essere fastidioso perché è proprio nel bel mezzo di un grande metodo.

A meno che non hai fatto qualcosa di brutto come sottoclasse OrderedDict e items hack per restituire una sottoclasse speciale di list che ignora le chiamate verso sort(). Nah, probabilmente quella di ancora peggio e dovrei andare a letto prima di venire su con qualcosa di più orribile di questo.

Domanda sbagliata. Dovrebbe essere:? "Dove posso trovare un gadget diff che funziona sensibilmente con i file XML

Risposta: Google è tuo amico. Primo risultato per la ricerca su "xml diff" => questo . Ci sono un paio di possibili.

Sì, con 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"/>'

Ecco diretto alla documentazione, da che l'esempio precedente è leggermente adattato.

Si noti inoltre che lxml ha, in base alla progettazione, un po 'di buon compatibilità API con standard di xml.etree.ElementTree

migliore opzione è quella di utilizzare il lxml http://lxml.de/ Installazione del lxml e solo il passaggio alla biblioteca ha la magia per me.

#import xml.etree.ElementTree as ET
from lxml import etree as ET

Dalla sezione 3.1 di la raccomandazione XML :

  

Si noti che l'ordine delle specifiche di attributi in una start-tag o tag di elemento vuoto non è significativa.

Qualsiasi sistema che si basa su l'ordine degli attributi in un elemento XML sta per rompere.

Hanno avuto il problema. In primo luogo cercato qualche script Python di canonizzare, non ha ancora trovato nessuno. Poi ha iniziato a pensare di fare uno. Infine xmllint risolto.

Questa è una soluzione parziale, per il caso in cui XML viene generato e un ordine prevedibile è desiderato. Non risolve il parsing di andata e ritorno e la scrittura. Sia 2.7 e 3.x uso sorted() per forzare un ordinamento attributo. Quindi, questo codice, in combinazione con l'uso di un OrderedDictionary per contenere gli attributi manterrà l'ordine di uscita XML corrisponda l'ordine utilizzato per creare l'elementi.

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>

Il problema di parsing XML in un albero elemento è che il codice crea internamente dicts semplici che sono passati per Element (), a quel punto l'ordine è perduto. Non semplice patch equivalente è possibile.

ho usato la risposta accettata sopra, con entrambe le istruzioni:

ET._serialize_xml = _serialize_xml
ET._serialize['xml'] = _serialize_xml

Mentre questo fissato l'ordine in ogni nodo, attributo di ordinazione nuovi nodi inseriti da copie di nodi esistenti omesso di conservare senza deepcopy. Attenzione per il riutilizzo di nodi per creare altri ... Nel mio caso ho avuto un elemento con più attributi, così ho voluto riutilizzarli:

to_add = ET.fromstring(ET.tostring(contract))
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)

Il fromstring(tostring) sarà riordinare gli attributi in memoria. Esso non può comportare l'alfa allineati dict di attributi, ma anche non può avere l'ordinamento previsto.

to_add = copy.deepcopy(contract)
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)

Ora i persiste ordinazione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top