XML

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.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Guten Tag miteinander

Ich bin gerade dabei, eine XML-Datei zu schreiben. Eine Methode soll dabei sein, Schulstunden hinzuzufügen. Input soll eine Liste (Tupel) im Format [(Fach, Lehrername)] sein und als root ein Element. Die Methode soll für jedes Tupel ein neues Kind "Stunde" mit dem Fach als Text und einem Attribut Lehrer mit dem Lehrername als Wert hinzufügen.

Ich habe das so gemacht:

Code: Alles auswählen

def addStunde(root, liste):
  e2 = ET.Element(root)
  e.append(e2)
  e2.text = liste[0]
  e2.attrib["Lehrer"] = liste[1]
Meine Frage ist nun: So wird ja noch nicht für jedes Tupel ein neues Kind "Stunde" gemacht - wie kann ich das noch hinzufügen?

Vielen Dank,
Marcel
deets

Na, dann iterier doch ueber deine Liste, und in der Schleife fuegst du ein neues Element hinzu.

Und im uebrigen hast du das nicht "so gemacht" wie dargestell - der Code ist *offensichtlich* nicht lauffaehig. Also bitte lauffaehige Beispiele oder konkrete Fehlermeldungen, ok?
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Der Code funktioniert und ist lauffähig - ich arbeite mit lxml.
Der Vollständigkeitshalber noch meine ersten Zeilen (sorry, die wären auch noch hilfreich gewesen):

Code: Alles auswählen

import lxml.etree as ElTree

e = ElTree.Element("jahr")
e.attrib["id"] = "2012"
Wieso über die Liste iterieren? Die Liste besteht ja jeweils immer nur aus 2 Einträgen (Fach und Lehrername), da muss ich ja nicht gross iterieren.
...oder wie hast du das gemeint?

Besten Dank
deets

Ja, *MIT* den Zeilen mag es irgendwie funktionieren.

Und natuerlich musst du ueber die Liste iterieren - da sind doch immer Paare von Lehrer/Stunde drin:

Code: Alles auswählen

liste = [("Peter Pasulke", "Mathematik"), ("Regina Ratlos", "Physik")]
for lehrer, fach in liste:
     print lehrer, fach
BlackJack

@MarcelF6: In der Liste sind doch wohl mehrere Tupel mit (Fach, Lehrername) oder? Wenn Du irgendwo im Programm Listen hast, in denen Grundsätzlich immer nur ein Element steht, dann stimmt was nicht mit dem Entwurf, weil das nicht wirklich sinnvoll ist immer nur ein Element in eine Liste zu stecken. Welchen Zweck würde denn dann die Liste erfüllen‽

Oder hast Du gar keinen Wert in der Form wie Du behauptest und es ist einfach nur ein Tupel mit den beiden Werten‽

Wenn `root` ein Element ist, warum rufst Du dann `Element()` damit auf? Das erzeugt Elemente. `root` ist ja aber angeblich schon eines‽

`e` sollte nicht so aus dem ”Nichts” kommen. Man verwendet in Funktionen nur Werte die als Argument übergeben wurden. Was nicht als Argument übergeben wurde, sollte konstant sein. Sonst verliert man schnell den Überblick über den Datenfluss in einem Programm und welche Funktion was manipuliert.

Im ersten Quelltextschnippsel ist `ElementTree` an den Namen `ET` gebunden, im letzten jetzt an den Namen `ElTree`. Das unterstützt weiterhin den Verdacht, dass Du hier nicht den Quelltext zeigst, den Du tatsächlich verwendest‽

Zur Ausgangsfrage: Natürlich wird da kein Element „Stunde” erstellt. Du erstellst das ja gar nicht. Mach das doch einfach mal. Beschreibe mal in Worten was Dein `addStunde()` macht. Dann sollte Dir auffallen was daran falsch ist.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Vielen, vielen Dank für eure Bemerkungen und Hints!
Ich Idiot hab die Liste mit nur einem Beispiel gefüllt (ja..wenig sinnvoll). Daher fragte ich so dumm wegen der Iteration und ja...meine Fragen / Bemerkungen sind echt dumm - mit der volleren Liste wurde mir auch klar, wie ich die Implementation vornehmen musste.
Danke nochmals :)

Und PS: Ja, in der Zwischenzeit habe ich den Code aktualisiert, daher sind die Bezeichnungen nicht identisch.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

PS2: Ich habe noch eine Funtion loeschen, die bei erster Betrachtung eigentlich funktioniert. Nun habe ich aber noch einen Spezialfall probiert - wenn beide Stunden gleich heissen nämlich. In diesem Fall möchte ich, dass nur die erst auftretende Stunde gelöscht wird.
Ich habs mit einer while, dann noch mit einer for-Schleife probiert, dass nur einmal etwas gelöscht wird. Aber so klappt es leider nicht. Habt ihr mir hierzu nochmals einen guten Hinweis?
Danke :)

Code: Alles auswählen

def loeschen(root, name):
  for i in range(1,2):
    for roots in e:
      if roots.text == name:
        e.remove(roots)
  return e
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

Würde das Snippet funktionieren, würde es ausreichen wenn du danach aus der Schleife ausbrichst. Also nach der Lösch-Anweisung ein break packst.

Allerdings scheint es mir sehr fehlerhaft zu sein. Einerseits von den Namen und deren (fehlenden) Einsatz und der Einrückung her, andererseits von der Nutzung von remove. Es löscht ja das erste Element aus einer Liste mit dem übergebenen Wert, allerdings übergibst du stattdessen das Element selbst.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Danke für den Tipp mit break und der Bemerkung bzgl. remove().
Alles angepasst und break hinzugefügt, funktioniert es jetzt wie gewünscht. Danke! :)
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Doch noch eine Frage.
Wenn ich sowas habe:

Code: Alles auswählen

e = ET.Element("Schule")
e.attrib["id"] = "2012"
Dann dürfte der Aufruf in einer Funktion

Code: Alles auswählen

e.find(root)
(mit root = "Schule", als Argument)
nicht = None sein, oder?
Bzw. anders gefragt: Der Wert hiervon ist bei mir None, wieso?
Es geht darum, dass ich noch abfragen will, ob 'root' im xml-file überhaupt existiert. Wenn ja, dann ist alles ok. Falls nein, so dürfte nichts erstellt / verändert werden.

Danke für jeglichen Hinweis! :)
BlackJack

@MarcelF6: Es *muss* `None` sein, denn wo sollte in einem gerade frisch erstellten ``<Schule>``-Element denn ein Unterelement mit dem gleichen Namen herkommen.

Es ist auch ein bisschen verwirrend was Du damit überhaupt prüfen willst. Ein Element was Du selbst gerade erstellt hast, ist natürlich auch vorhanden, was ist der Sinn das zu prüfen?

Du nennst auch komische Sachen `root` oder gar `roots`. Es gibt in einem XML-Dokument genau *ein* Wurzelelement. Und in einem Baum wird man das Wurzelelement *niemals* mit der `find()`-Methode finden können. (Obwohl; Ich weiss jetzt nicht ob man da bei `lxml` auch relative Pfade nach „oben” engeben kann…)

Der ``e.find(root)``-Aufruf in einer Funktion benutzt doch hoffentlich nicht immer noch das globale `e` wie die anderen gezeigten Funktionen?

Wenn Du `lxml` verwendest, solltest Du mal einen Blick auf das `lxml.builder`-Modul werfen. Das macht das Erstellen von neuen Elementen wesentlich einfacher.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Achsooo, du hast natürlich Recht!
Ich übergebe das Element ja in der Funktion - da "könnte" es ja sein, dass sich jemand vertippt. Aber eigentlich macht diese Überprüfung wirklich nicht allzu viel Sinn.

Yeah tatsächlich - der Builder ist echt ne super Hilfe. Danke für den Hinweis! :)

Nun habe ich noch ne Frage zum Gegenteil, also zum Lesen von XML:
Eigentlich wollte ich eine Funktion implementieren, die den ganzen Text des Elements 'node' und den Subelementen liest.
Das Lesen des Texts von 'node' hab ich gut hingekriegt:

Code: Alles auswählen

import lxml.etree as elTree
doc = elTree.parse("random.xml")
doc.getroot().tag

def read(node):
  for word in doc.findall(node):
    print word.text

if __name__=="__main__":
  readText("//title")
So werden aber die Subelemente nicht berücksichtigt. Wie kann ich das erreichen? Per for-loop?
Dankeschön!
BlackJack

@MarcelF6: Und schon *wieder* eine neue Schreibweise für das `etree`-Modul. Ist das ein Hobby von Dir alle Möglichkeiten mal durch zu probieren‽ Üblich ist es bei `etree` zu bleiben oder wie in der `lxml`-Doku oft zu sehen `ET` zu verwenden.

Die dritte Zeile hat keinen Effekt, was soll die bedeuten?

Die letzte Zeile gibt einen `NameError` weil `readText()` nicht definiert ist.

Du hast kein Problem mit dem Lesen von XML. Das passiert in der zweiten Zeile. Da wird die komplette XML-Datei gelesen.

Die Unterelemente von ``<title>``-Elementen werden nicht berücksichtigt, weil Du danach nicht suchst. Das müsstest Du halt entweder im Python-Code explizit noch mal auf den gefundenen ``<title>``-Elementen machen, oder Dich mit XPath auseinander setzen.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Hihi, neinein, das ist kein Hobby ;). Das war ein Auszug eines anderen Programms.

Ok, 3. Zeile gelöscht.
Und read wie folgt verändert:

Code: Alles auswählen

def read(node):
  root = node
  for element in root.iter(): #funktioniert so noch nicht.
    print ("%s - %s" % (element.tag, element.text))
Mit dem obigen Code wird (ohne die zweite Zeile und mit: for element in doc.iter()) kann ich den gesamten Inhalt des xml-files ausgeben. Nun will ich ja eben nur den Inhalt von 'node' und den Subelementen ausgeben. Meine Idee ist, dass ich .iter() bei der for-Schleife nicht über das ganze file, sondern eben nur über 'node' laufen lasse. Würde das so gehen? Falls ja, wie könnte ich das bewerkstelligen, dass es mit dem iter() klappt?
(falls nein wäre ich um jeden Tipp dankbar :) )

Besten Dank!
BlackJack

@MarcelF6: Und da haben wir schon wieder ein `root`… Was soll denn diese Zuweisung, die macht doch gar keinen Sinn.

Deine Beschreibung macht auch keinen Sinn. Die Funktion gibt alle Nachfahren vom übergebenen `node` aus. Das ist nur in einem einzigen Fall der *gesamte* Baum, nämlich wenn der Wurzelknoten übergeben wurde. Wenn Du von bestimmten Knoten die Nachfahren ausgegeben haben möchtest, dann darfst Du halt nicht den Wurzelknoten übergeben, sondern eben diese Knoten, die Du auch haben möchtest.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

So, ich hab nochmals etwas länger gegrübelt und in einem lxml-Tutorial gestöbert.
(PS: Ja, das mit 'root' ist absoluter Schwachsinn - sorry!)
Dabei habe ich Verschiedenstes probiert, und bin momentan zwar nahe am Ziel, aber die Subelemente fehlen mir nach wie vor.
Mein Code (ja, wieder überarbeitet, aber diesmal sinnvoller ;) ) sieht so aus:

Code: Alles auswählen

def read(node):
  for element in doc.iter(node):
    print ("%s - %s" % (element.tag, element.text))
Dabei werden aber jeweils alle Elemente mit dem Namen 'node' gesucht und ausgegeben. Aber wie ich nachher noch an die Subelemente kommen kann, verstehe ich noch nicht ganz. Hättest du mir hierzu evtl. noch einen Tipp? Vielen herzlichen Dank :)
BlackJack

@MarcelF6: Du möchtest für jeden Knoten eines bestimmten Typs den Knoten selbst und alle Nachfahren verarbeiten. Du weisst wie Du an alle Knoten eines bestimmten Typs heran kommst. Und Du weisst wie Du an einen Knoten und alle seine Nachfahren heran kommst. Jetzt musst Du das nur noch kombinieren.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Nicht ganz. Ich weiss immer noch nicht, wie ich an die Kindknoten komme. Ich versuchte es immer so, indem ich element.iterchildren(self, tag=None, reversed=False) der Iteration hinzugefügt habe, aber so funktioniert es hier nicht. Das Problem liegt wirklich nur noch darin, an die Kindknoten zu kommen...
Hilfe, bitte :)
BlackJack

@MarcelF6: Natürlich weisst Du wie Du an die Kindknoten kommst. Du hast das doch schon gemacht und Dich dann beschwert, dass das alle sind. :roll:
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

..wenn man Tomaten auf den Augen hat, und den Code schon so oft verändert hat..ja eben ;)
Auf alle Fälle klappt nun alles wie gewünscht. Danke vielmals!

Noch eine Schlussfrage: Was würdest du unter "Text des body-Elements" verstehen? Ist das einfach alles, inklusive Tags (wie z.B. titel) oder nur Text (ohne Tags) des nodes, den man übergibt?
Antworten