Zwei XML Dateien vergleichen mit etree!

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

Hallo Freunde,

ich möchte mit Python zwei XML - Dateien vergleichen.
Habe einen Code geschrieben, dass mir die ChildNodes, Attribute und die Werte darin ausgibt.
Jetzt möchte ich diese Werte mit der anderen Datei vergleichen. Ein Enter in eines der XML Datei darf keine Auswirkungen auf mein Vergleich haben, lediglich nur die Inhalte.
Wäre echt dankbar, wenn mir jemand helfen könnte.

Vielen Dank im voraus!


XML Datei 1 und Datei 2:

Code: Alles auswählen

<?xml version='1.0' encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE adresse SYSTEM "adresse.dtd">

<adresse>
    <einzeladresse>
        <name>Müller</name>
        <vorn>Lieschen</vorn>
        <strasse>Nirgendwostr. 10</strasse>
        <plz>10000</plz>
        <ort>Nirgendwo</ort>
        <tel art="priv">00000/12345</tel>
        <tel art="off">00000/54321</tel>
    </einzeladresse>
    
    <einzeladresse1>
        <name>Müller</name>
        <vorn>Margaret</vorn>
        <strasse>Nirgendwostr. 15</strasse>
        <plz>10000</plz>
        <ort>Nirgendwo</ort>
        <tel art="priv">00000/56789</tel>
        <email>l.mueller@provider.net</email>
    </einzeladresse1>
    
</adresse>

Python Code:

Code: Alles auswählen

import xml.etree.ElementTree as ET
tree = ET.parse('xxx.xml')
tree2 = ET.parse('xxx2.xml')


class XMLCompare():

    def file1(self):
        root = tree.getroot()
        for elem1 in root.iter():
            global a
            a = elem1.tag, elem1.attrib, elem1.text
            print a

    def file2(self):
        root = tree2.getroot()
        for elem in root.iter():
            global b
            b = elem.tag, elem.attrib, elem.text
            print b
                
        

comp = XMLCompare()

comp.file1()
comp.file2() 
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich würde parallel über *beide* XML-Bäume iterieren; das kannst Du mittels ``zip`` lösen. Dann vergleichst Du eben einfach jeweils beide Elemente.

Ach ja: ``global`` ist böse und Du vergisst dessen Existenz am besten sofort :-D
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@machupicchu: Ich würde ein Python-Grundlagentutorial empfehlen bevor Du mit dem konkreten Problem weiter machst.

Die Klasse dort ist total unsinnig, weil sie gar keine Methoden enthält. Das sind noch nicht einmal Funktionen sondern einfach nur Namen für ein bisschen Quelltext der auch noch gobale Namen neu bindet. Das ganze unnötigerweise zweimal fast identisch.

Funktionen sollten Werte die keine Konstanten sind als Argumente übergeben bekommen, und Ergebnisse als Rückgabewerte an den Aufrufer zurück geben. Das Schlüsselwort ``global`` solltest Du am besten erst einmal komplett vergessen.

Klassen machen nur Sinn, wenn sie zusammengehörige Daten und Funktionen zu einem Objekt zusammen fassen. Wenn eine Klasse keine `__init__()` hat, und auch keine sinnvolle `__init__()` erbt, dann kann da schon etwas nicht stimmen. Und Methoden müssen das Objekt auf dem sie definiert sind, auch verwenden, denn sonst muss man gut erklären können, warum sie überhaupt in der Klasse stehen und nicht einfach als Funktion definiert wurden.

Ich würde `ElementTree.iterparse()` verwenden und dann `itertools.izip()` um die einzelnen Elemente der Iteratoren zu „paaren“, und dann braucht man die nur noch passend vergleichen.
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

Hi,

danke für die Antworten.
Ich habe die Klasse und die Methode entfernt. Hatte es zum ausprobieren eingefügt.
Komme eigentlich nicht aus der Python Ecke, aber finde die Sprache schon sehr interessant. Allgemein fehlt mir praktische Übung in Programmieren.
Habe es jetzt mit zip realisiert und gebe beide Dateien nebeneinander aus und vergleich diese.

Habt Ihr vlt noch einen Tipp, wie man gewünschte Element oder Attribute excluden kann? Gibt für Python da ein Zauberschlüssel wie zip oder ähnliches?

Nochmal vielen Dank!

machupicchu :-)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

machupicchu hat geschrieben: Habt Ihr vlt noch einen Tipp, wie man gewünschte Element oder Attribute excluden kann? Gibt für Python da ein Zauberschlüssel wie zip oder ähnliches?
``iter`` und andere Methoden zum Traversieren kennen das Attribut ``tag``, mit welchem man nach gewünschten Werten für Tags oder Attribute filtern kann.

Alternativ könntest Du Dir mal "XPath" oder CSS-Selektoren angucken. Damit kann man noch feingranularer nur bestimmte Elemente herausfiltern. ``lxml`` sollte das alles unterstützen; ob die Etree-Implementierung der Stdnard-Lib das auch kann, müsstest Du ggf. recherchieren.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

Hallo,
danke erstmal für Eure Hilfe.
Also, ich bin schon ein Stück weiter gekommen. Aber das selektieren bereitet mir echt noch Kopfschmerzen.

Habe jetzt eine dritte Datei als Textformat, wo ich die zu selektierenden Wörter einfüge. Lese diese Dateien über die Kommandozeile via Argumente.

Die Probleme habe ich beim einlesen und vergleichen der Attribute mit der Textdatei. Zum Beispiel habe ich den Attribut "art"(siehe oben) und möchte diesen ausschließen. Weiter unten in meinem Code iteriere ich die Textdatei und vergleiche mit dem if statement.
Denke da ist irgendwo ein Denkfehler, vlt hat da jemand noch einen Tipp.

Code: Alles auswählen

            for key3 in tree_root3:
                if key3 == key:
                    print key3

Code: Alles auswählen

import xml.etree.ElementTree as ET, sys


try:
    tree = ET.parse(doc1)

    tree2 = ET.parse(doc2)

    tree3 = open(doc3, 'r')


except Exception, e:
    print "Error occured, can not parse or open the file", e

#Getting root from xml file
tree_root = tree.getroot()
tree_root2 = tree2.getroot()
#Document for excluding attributes which are necessary
tree_root3 = tree3.readlines()

#Loops to get the entire attributes and the selected attributes from the third file
for element1, element2 in zip(tree_root.iter(), tree_root2.iter()):
    if element1.attrib and element2.attrib:
        for key, key2 in zip(element1.attrib, element2.attrib):
            for key3 in tree_root3:
                if key3 == key:
                    print key3
Zuletzt geändert von machupicchu am Donnerstag 13. Juni 2013, 00:09, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Was willst Du mit Deiner dritten Datei erreichen?
Zum Code: Die dritte Datei ist keine Baum, »tree« also ein ziemlich schlechter Name.
Wenn Du zeilenweise einliest, hat jede Zeile noch das Zeileendezeichen '\n', also schlägt der Vergleich »key3 == key« immer fehl.
Wie wärs mit

Code: Alles auswählen

tree_root3 = set(tree3.read().split())

#Loops to get the entire attributes and the selected attributes from the third file
for element1, element2 in zip(tree_root.iter(), tree_root2.iter()):
    for key in element1.attrib:
        if key in tree_root3:
            print key3
BlackJack

@machupicchu: Also ich werde so gar nicht schlau aus dem was Du da versuchst. Das liegt zum Teil wohl auch an den verwirrenden Namen. Bei den XML-Dateien heisst das `root` weil es die Wurzel eines Objektbaums darstellt. Eine Liste von Zeilen `tree_root3` zu nennen, hilft nicht gerade beim Verständnis, um das mal vorsichtig auszudrücken.

Das `zip()` auf die `attrib`-Attrubute der XML-Elemente macht keinen Sinn, da hinter `attrib` letztendlich ein Wörterbuch (`dict`) steckt und die Elemente haben dort keine Reihenfolge auf die man sich verlassen kann. Zumal ja auch gar nicht garantiert ist, dass die beide die gleiche Anzahl von Einträgen haben. Ausserdem wird `key2` dann innerhalb der Schleife überhaupt nicht benutzt.

Die innere Schleife könnte man auch mit dem ``in``-Operator ohne explizite Schleife schreiben. Hier würde sich aus Laufzeitgründen dann ein `set()` statt einer Liste für den Inhalt der dritten Datei eignen. Was hier überhaupt nicht berücksichtigt wird, sind „whitespace”-Zeichen. Schau Dir mal an was `tree3` tatsächlich enthält. Da sind noch die Zeilenendezeichen bei jedem Element enthalten. So dürfte das ``print`` ganz innen nie erreicht werden.

Die Fehlerbehandlung mit dem ``try``/``except`` ist in der Form fehlerhaft. Wenn bei einer der drei Operationen im ``try``-Block eine Ausnahme auftritt wird nach dem ``except`` weiter gemacht als wäre nichts passiert. Das führt *zwangsläufig* im weiteren Code zu einem `NameError` weil ja mindestens einer der drei Namen aus den Zuweisungen dann gar nicht existiert. Fehlerbehandlung sollte man nur machen wenn man den Fehler auch tatsächlich behandeln kann und das auch sinnvoll macht.

Dateien die man öffnet, sollte man auch wieder schliessen. Dafür bietet sich die ``with``-Anweisung an.

Zerlege Deine Aufgabe am besten in kleinere Teilaufgaben, die in separaten in sich abgeschlossenen Funktionen, die man einzeln testen kann, umgesetzt werden. Statt das alles in einem monolithischen Quelltextblob auf Modulebene zu lösen.
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

Ja, das mit der set Methode klappt soweit. Ich muss nur noch so hinbekommen, dass er mir diese Zeilen überspringt. Die dritte Datei brauche ich, um dort die auszuschließenden Wörter einzutragen, wenn es sich um eine größere Datei handelt etc.
Du hast am ende print key3 geschrieben, ich nehme an, dass du key schreiben wolltest?

Vielen Dank
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

@Black Jack, ich werde mir in Zukunft mehr Mühe geben die Wörter sinngemäß zu schreiben. Da steckt auch eine ganze Menge Kritik in deinem Text. Hast Dir jetzt soviel Mühe gegeben für so einen langen Text, da hättest Du ruhig mal ein paar Beispiele aufschreiben können. :-) Aber trotzdem danke! :mrgreen:
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

Hallo Freunde,

ich habe mein XML Programm erweitert und möchte mit meinem Programm Attribute und Werte beider XML Dateien vergleichen. Ich gebe in der Kommandozeile als Parameter zwei XML Dateien und eine exclude.txt Datei ein. In der exclude.txt sollen Attribute ausgeschlossen werden und das funktioniert soweit.

Ich möchte die zwei XML Dateien nach Attributen und Werten Zeile für Zeile vergleichen. Wenn ich zum Beispiel mehrere Attribute mit gleichen Namen in meiner XML Datei habe, dann sollen lediglich nur die Attribute in der jeweiligen Zeile verglichen werden. Bei meinem Programm wird einfach das Attribut genommen, dass identisch ist, aber nicht in der gleichen Zeile.

Code: Alles auswählen

 Zeile    Datei1  Attribute               Datei2 Attribute
  3         Sequence = "77"              Sequence = "76"
  ...        .....................                .....................
  ...        ....................                  ...................
  22       Sequence = "76"              Sequence = "77" 
Leider habe ich das mit meinem Programm nicht hinbekommen.
Zuletzt geändert von machupicchu am Donnerstag 13. Juni 2013, 00:08, insgesamt 1-mal geändert.
BlackJack

@machupicchu: Du verarbeitest die beiden XML-Objektbäume ja auch nicht parallel sondern nacheinander. Da kannst Du die Elemente an den gleichen Positionen im Baum ja auch nicht miteinander vergleichen, weil Du die Information was zusammengehört gar nicht mehr hast.

Das Du hier von Zeilen in den XML-Dateien sprichst deutet auch auf ein falsches Bild was Du von XML hast. Die XML-Datei hat zwar Zeilen, aber auf der Darstellung der Datei arbeitet man ja nicht. Man behandelt XML-Dateien nicht als Textdateien. Im Speicher hat man einen Objektbaum, der die Struktur wiederspiegelt. Wenn zwei Dateien die gleiche Baumstruktur haben, bedeutet das nicht zwangsläufig, dass die Informationen in den XML-Dateien in den gleichen Zeilen stehen oder dort in der gleichen Spalte anfangen müssen.
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

@blackjack, leuchtet bei mir nicht so ganz ein. :|

Wenn ich zum Beispiel

Code: Alles auswählen

print key, value
in der Zeile 27 ausgebe, erhalte ich als Ausgabe:

Datei1:

Code: Alles auswählen

 Preis 17.30
 Telefon 0020018887
 Name Krüger
 Vorname Michael
Datei2:

Code: Alles auswählen

 Preis 17.35
 Telefon 0020018887
 Name Krüger
 Vorname Michael
Anschließend gebe ich diese an meine Listen weiter und erhalte als Ausgaben:

Code: Alles auswählen

print L1
[('Preis, 17.30'),( 'Telefon, 0020018887'),(' Name, Krüger'),( 'Vorname, Michael')]

Code: Alles auswählen

print L2
[( 'Telefon, 0020018887'),('Preis, 17.35'),(' Name, Krüger'),( 'Vorname, Michael')]
Nun, stimmt die Reihenfolge in der zweiten Liste nicht und weiß nicht wie ich das vergleichen kann. Ich möchte mit meinem Code erreichen, dass sich die Reihenfolge nicht ändert und das es von oben nach unten vergleicht.

Wollte es folgendermaßen umsetzen, dass mir nicht gelungen ist:

Gehe zu Element 1 von Datei1
Nehme Attribute und Werte

Gehe zu Element1 von Datei2
Nehme Attribute und Werte

Vergleiche ob Attribut und Wert identisch, dann
Ausgabe der Werte
BlackJack

@machupicchu: Wörterbücher (`dict`) haben keine verlässliche Reihenfolge. Das heisst auch wenn zwei davon die gleichen Schlüssel besitzen, können die beim Aufruf von `items()` in einer anderen Reihenfolge geliefert werden. Das „flachklopfen” von verschiedenen Datensätzen in einer Liste ist aber auch unschön. Zusammengehörige Daten sollten in *einer* Struktur zusammengefasst werden. Also zum Beispiel eine Liste mit je einem Wörterbuch pro Datensatz.

Ich finde es auch bedenklich dass Du einfach alle Elemente in der XML-Datei gleich behandelst, egal welcher Tag das nun ist. Es muss ja *mindestens* eine Unterscheidung zwischen dem Wurzeltag und den den Tags für die einzelnen Datensätze geben. Die sollte man dann auch geziehlt herausfiltern und verarbeiten. XML hat ja unter anderem den Vorzug, dass man ein Format erweitern kann und Programme nur die Elemente verarbeiten, die sie auch verstehen.

Deine Ausgabe sieht übrigens auch nicht danach aus, dass die Datei tatsächlich wie im ersten Beitrag aussieht. Und dieses Sequenz-Attribut scheint dort auch nicht vorzukommen. Manchmal kann oder will man keine Originaldaten zeigen, dann könnte man zumindest die Originalstruktur mit erfundenen Daten als Beispiel(e) zeigen. Aber Beispieldaten die von der Struktur dann nicht zum Code passen sind keine gute Idee, weil man dann ja gar nicht wirklich weiss wie die Daten aussehen, die *tatsächlich* verarbeitet werden.

Im XML-Quelltext im ersten Beitrag scheint auch ein Fehler zu sein, denn nach dem <einzeladresse>-Element kommt sicher noch ein <einzeladresse>-Element und keines bei dem der Tagname einen Nummernzusatz hat. Die Menge der möglichen Tags ist konstant, sonst könnte man weder vernünftig nach Tags filtern noch eine Strukturbeschreibung für das Format anfertigen.
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

@blackjack,
danke erstmal für deine Hilfe. Für den code kann eine x beliebige xml datei verwendet werden. Die xml dateien, die ich habe sind sehr groß und würde sich eventuell hier im forum auf zwei seiten erstrecken.

Zusammengehörige Daten sollten in *einer* Struktur zusammengefasst werden. Also zum Beispiel eine Liste mit je einem Wörterbuch pro Datensatz.
Hast du vlt ein Beispiel? Habe es nicht ganz verstanden?
BlackJack

@machupicchu: Ich bin nicht wirklich überzeugt, dass genug über die vorliegenden Daten, das Problem, und das gewünschte Ergebnis bekannt ist, um an einer sinnvollen Lösung zu arbeiten.
Benutzeravatar
machupicchu
User
Beiträge: 61
Registriert: Samstag 1. Juni 2013, 14:04

Verstehe, dann werde ich mal noch bisschen herumprobieren.
Antworten