Wie kann ich meinen Code beim Arbeiten mit XML-Namespaces in Python lesbarer und DRYer gestalten?

8

Pythons integriertes xml.etree -Paket unterstützt das Parsen von XML-Dateien mit Namespaces, aber Namespace-Präfixe werden auf den vollständigen, in eckigen Klammern eingeschlossenen URI erweitert. Also in der Beispieldatei in der offiziellen Dokumentation:

%Vor%

Das actor -Tag wird auf {http://people.example.com}actor und fictional:character auf {http://characters.example.com}character erweitert.

Ich kann sehen, wie dies alles sehr explizit macht und Ambiguität reduziert (die Datei könnte denselben Namespace mit einem anderen Präfix haben, etc.), aber es ist sehr umständlich mit ihnen zu arbeiten. Die Methode Element.find() und andere erlauben die Übergabe eines dict Zuordnungspräfixes an Namespace-URIs, so dass ich immer noch element.find('fictional:character', nsmap) ausführen kann, aber meines Wissens gibt es nichts Ähnliches für Tag-Attribute. Dies führt zu lästigen Sachen wie element.attrib['{{{}}}attrname'.format(nsmap['prefix'])] .

Das beliebte Paket lxml stellt die gleiche API mit einigen Erweiterungen bereit, von denen eine nsmap -Eigenschaft für die Elemente ist, die sie vom Dokument erben. Jedoch scheint keine der Methoden es tatsächlich zu benutzen, also muss ich immer noch element.find('fictional:character', element.nsmap) machen, was sich einfach unnötig wiederholt, um jedes Mal zu tippen. Es funktioniert auch immer noch nicht mit Attributen.

Glücklicherweise unterstützt lxml die Unterklasse BaseElement , also habe ich eine mit der Eigenschaft p (für Präfix) erstellt, die dieselbe API hat, aber automatisch Namespacepräfixe verwendet, indem sie nsmap des Elements verwendet ( Bearbeiten: ist es wahrscheinlich am besten, eine benutzerdefinierte nsmap im Code definiert zuzuweisen. Also mache ich einfach element.p.find('fictional:character') oder element.p.attrib['prefix:attrname'] , was viel weniger repetitiv ist und ich denke viel besser lesbar.

Ich habe gerade das Gefühl, dass ich wirklich etwas verpasse, aber es fühlt sich wirklich so an, als ob dies schon ein Feature von lxml wäre, wenn nicht das eingebaute etree -Paket. Mache ich das irgendwie falsch?

    
JaredL 16.05.2016, 21:14
quelle

2 Antworten

6

Ist es möglich, das Namespace-Mapping loszuwerden?

Müssen Sie es als Parameter an jeden Funktionsaufruf übergeben? Eine Option wäre, die Präfixe festzulegen, die im XML-Dokument in einer Eigenschaft verwendet werden sollen.

Das ist in Ordnung, bis Sie das XML-Dokument in eine Funktion von Drittanbietern übergeben. Diese Funktion möchte auch Präfixe verwenden, also setzt sie die Eigenschaft auf etwas anderes, weil sie nicht weiß, worauf Sie sie setzen.

Sobald Sie das XML-Dokument zurückbekommen, wurde es modifiziert, so dass Ihre Präfixe nicht mehr funktionieren.

Alles in allem: Nein, es ist nicht sicher und deshalb ist es so gut wie es ist.

Dieser Entwurf existiert nicht nur in Python, er existiert auch in .NET. Die SelectNodes() [MSDN] kann verwendet werden, wenn Sie brauche keine Präfixe. Aber sobald ein Präfix vorhanden ist, wird eine Ausnahme ausgelöst. Daher müssen Sie das überladene SelectNodes() [MSDN] verwendet einen XmlNamespaceManager als Parameter.

XPath als Lösung

Ich schlage vor, XPath (lxml specific link) zu lernen, wo Sie Präfixe verwenden können. Da dies versionsspezifisch sein kann, möchte ich sagen, dass ich diesen Code mit Python 2.7 x64 und lxml 3.6.0 ausgeführt habe (ich bin nicht vertraut mit Python, also ist dies vielleicht nicht der sauberste Code, aber es dient als Demonstration) :

%Vor%

Die Ausgabe ist

%Vor%

Beachten Sie, wie gut XPath das Mapping vom x Präfix in der Namespacetabelle zum d Präfix im XML Dokument gelöst hat.

Dies beseitigt das wirklich schreckliche zu lesen element.attrib['{{{}}}attrname'.format(nsmap['prefix'])] .

Kurze (und unvollständige) XPath-Einführung

Um ein Element auszuwählen, schreiben Sie /element , optional ein Präfix.

%Vor%

Um ein Attribut auszuwählen, schreiben Sie /@attribute , optional ein Präfix.

%Vor%

Um nach unten zu navigieren, verketten Sie mehrere Elemente. Verwenden Sie // , wenn Sie keine Elemente dazwischen kennen. Um nach oben zu gehen, verwenden Sie /.. . Attribute müssen zuletzt sein, wenn nicht /.. folgt.

%Vor%

Um eine Bedingung zu verwenden, schreiben Sie sie in eckige Klammern. /element[@attribute] bedeutet: Wählen Sie alle Elemente mit diesem Attribut aus. /element[@attribute='value'] bedeutet: Wählen Sie alle Elemente mit diesem Attribut aus, und das Attribut hat einen bestimmten Wert. /element[./subelement] bedeutet: wähle alle Elemente aus, die ein Unterelement mit einem bestimmten Namen haben. Verwenden Sie optional Präfixe an beliebiger Stelle.

%Vor%

Es gibt viel mehr zu entdecken, wie text() , verschiedene Arten der Geschwisterauswahl und sogar Funktionen.

Über das 'Warum'

Der ursprüngliche Fragetitel, der

war
  

Warum erscheint das Arbeiten mit XML-Namespaces in Python so schwierig?

Für einige Benutzer verstehen sie das Konzept einfach nicht. Wenn der Benutzer das Konzept versteht, hat der Entwickler dies möglicherweise nicht getan. Und vielleicht war es nur eine von vielen Optionen und die Entscheidung war, in diese Richtung zu gehen. Die einzige Person, die in einem solchen Fall eine Antwort auf den "Warum" -Teil geben könnte, wäre der Entwickler selbst.

Referenzen

Thomas Weller 02.06.2016, 21:11
quelle
2

Wenn Sie vermeiden möchten, dass nsmap-Parameter mit ElementTree in Python wiederholt werden, sollten Sie in Betracht ziehen, Ihren XML-Code mit XSLT zu transformieren, um Namespaces zu entfernen und lokale Elementnamen zurückzugeben. Und Pythons lxml kann XSLT 1.0-Skripte ausführen.

Als Information ist XSLT eine spezielle deklarative Sprache (dieselbe Familie wie XPath, aber interagiert mit ganzen Dokumenten). Wird speziell zur Transformation von XML-Quellen verwendet. Tatsächlich sind XSLT-Skripte wohlgeformte XML-Dokumente! Das Entfernen von Namespaces ist eine häufig verwendete Aufgabe für Endbenutzer.

Betrachten Sie Folgendes, wobei XML und XSLT als Strings eingebettet sind (aber jeder kann aus einer Datei analysiert werden). Nach der Umwandlung können Sie .findall() , iter() und .xpath() für das transformierte neue Baumobjekt ausführen, ohne Namensraumpräfixe definieren zu müssen:

Skript

%Vor%

Ausgabe

%Vor%     
Parfait 04.06.2016 15:11
quelle