Meine Antwort befasst sich mit dem spezifischen (und etwas häufigen) Fall, in dem Sie nicht wirklich die gesamte XML in JSON konvertieren müssen , sondern nur bestimmte Teile der XML durchlaufen / darauf zugreifen müssen und schnell sein müssen einfach (mit json / dict-ähnlichen Operationen).
Ansatz
Aus diesem Grund ist es wichtig zu beachten, dass das Parsen einer XML-Datei mit etree verwendet wird lxml
ist schnell ist. Der langsame Teil in den meisten anderen Antworten ist der zweite Durchgang: Durchqueren der Etree-Struktur (normalerweise im Python-Land) und Konvertieren in JSON.
Was mich zu dem Ansatz führt, den ich für diesen Fall am besten gefunden habe: Parsen der XML mit lxml
und anschließendes Umschließen der etree-Knoten (träge), um ihnen eine diktartige Schnittstelle zu bieten.
Code
Hier ist der Code:
from collections import Mapping
import lxml.etree
class ETreeDictWrapper(Mapping):
def __init__(self, elem, attr_prefix = '@', list_tags = ()):
self.elem = elem
self.attr_prefix = attr_prefix
self.list_tags = list_tags
def _wrap(self, e):
if isinstance(e, basestring):
return e
if len(e) == 0 and len(e.attrib) == 0:
return e.text
return type(self)(
e,
attr_prefix = self.attr_prefix,
list_tags = self.list_tags,
)
def __getitem__(self, key):
if key.startswith(self.attr_prefix):
return self.elem.attrib[key[len(self.attr_prefix):]]
else:
subelems = [ e for e in self.elem.iterchildren() if e.tag == key ]
if len(subelems) > 1 or key in self.list_tags:
return [ self._wrap(x) for x in subelems ]
elif len(subelems) == 1:
return self._wrap(subelems[0])
else:
raise KeyError(key)
def __iter__(self):
return iter(set( k.tag for k in self.elem) |
set( self.attr_prefix + k for k in self.elem.attrib ))
def __len__(self):
return len(self.elem) + len(self.elem.attrib)
# defining __contains__ is not necessary, but improves speed
def __contains__(self, key):
if key.startswith(self.attr_prefix):
return key[len(self.attr_prefix):] in self.elem.attrib
else:
return any( e.tag == key for e in self.elem.iterchildren() )
def xml_to_dictlike(xmlstr, attr_prefix = '@', list_tags = ()):
t = lxml.etree.fromstring(xmlstr)
return ETreeDictWrapper(
t,
attr_prefix = '@',
list_tags = set(list_tags),
)
Diese Implementierung ist nicht vollständig, z. B. werden Fälle, in denen ein Element sowohl Text als auch Attribute oder sowohl Text als auch untergeordnete Elemente enthält, nicht sauber unterstützt (nur weil ich es beim Schreiben nicht benötigt habe ...). Es sollte einfach sein um es jedoch zu verbessern.
Geschwindigkeit
In meinem speziellen Anwendungsfall, in dem ich nur bestimmte Elemente der XML verarbeiten musste, ergab dieser Ansatz eine überraschende und bemerkenswerte Beschleunigung um den Faktor 70 (!) Im Vergleich zur Verwendung von @Martin Blechs xmltodict und anschließendem direkten Durchlaufen des Diktats .
Bonus
Als Bonus erhalten wir, da unsere Struktur bereits diktiert ist, eine weitere alternative Implementierung xml2json
kostenlos. Wir müssen nur unsere diktartige Struktur weitergeben json.dumps
. Etwas wie:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
Wenn Ihre XML-Datei Attribute enthält, müssen Sie alphanumerische Zeichen verwenden attr_prefix
Datei (z. B. "ATTR_") verwenden, um sicherzustellen, dass die Schlüssel gültige JSON-Schlüssel sind.
Ich habe diesen Teil nicht bewertet.