Python XML Probleme

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.
hammelwade
User
Beiträge: 19
Registriert: Dienstag 6. März 2012, 10:11

Mittwoch 14. März 2012, 11:42

Hallo,

ich versuche zurzeit eine XML Datei zu erstellen und komme grade an einem Punkt nicht weiter.
Und zwar verwende ich grade elementTree zum erstellen der Datei was soweit auch gut funktioniert, doch leider ist die Form in der etree die Daten in das File schreibt inaktzeptabel...
Dazu habe ich mich dann schon der Funktion toprettyxml von minidom bedient, doch kann ich dies dann nicht in eine Datei schreiben. Und bekomme immer den Fehler das ich das Attribut "writexml" nicht habe... (selbe mit etree) obwohl es ohne die Formatierung funktioniert.

Also wie schreibe ich eine Datei mit Formatierung in etree oder sollte ich dazu doch besser minidom verwenden ?

Hier der Code:

Code: Alles auswählen

import xml.etree.ElementTree as xml
from ElementTree_pretty import prettify

top = Element('top')

comment = Comment('Generated for PyMOTW')
top.append(comment)

child = SubElement(top, 'child')
child.text = 'This child contains text.'

child_with_tail = SubElement(top, 'child_with_tail')
child_with_tail.text = 'This child has regular text.'
child_with_tail.tail = 'And "tail" text.'

child_with_entity_ref = SubElement(top, 'child_with_entity_ref')
child_with_entity_ref.text = 'This & that'

#Eine Print funktioniert, aber leider nicht meine write file anweisung
print prettify(top)
#Write file von etree

Code: Alles auswählen

prettyTop = prettify(top)
xml.ElementTree(prettyTop).write(myFile)
#Write file von mindiom

Code: Alles auswählen

prettyTop = prettify(top)
XML_to_file(prettyTop)
Und hier die Funktion:

Code: Alles auswählen

def prettify(elem):
    #Return a pretty-printed XML string for the Element.
    rough_string = ElementTree.tostring(elem, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent="  ")

def XML_to_file(rootElem):
    #Open a file
    myFile = open("c:/test/test.xml", 'w')
    #Write data to file
    rootElem.writexml(myFile, "", "\t", "\n")
    #Close file like a good programmer
    myFile.close()
BlackJack

Mittwoch 14. März 2012, 12:02

@hammelwade: Also erstmal ist es ziemlich umständlich die Datenstruktur von `ElementTree` als XML zu serialisieren, das dann mit `minidom` zu parsen und „pretty printed” als XML zu serialisieren, um das Ergebnis dann wieder mit `ElementTree` zu parsen, nur um es als XML dann zum *dritten* mal zu serialisieren.

Viel direkter wäre es die Datenstruktur aus `ElementTree`-Objekten so zu verändern, dass die entsprechenden Leezeichen und Zeilenumbrüche dort enthalten sind und das dann genau *einmal* zu serialisieren.

So eine Funktion kannst Du Dir selber schreiben, oder im Internet finden, oder auf `lxml` umsteigen. Das benutzt grundsätzlich die `ElementTree`-API, aber hat ein paar Erweiterungen. Zum Beispiel ein `pretty_print`-Argument bei der `ElementTree.write()`-Methode.

Wirklich gute Programmierer öffnen Dateien übrigens im Kontext einer ``with``-Anweisung. ;-)
hammelwade
User
Beiträge: 19
Registriert: Dienstag 6. März 2012, 10:11

Mittwoch 14. März 2012, 13:09

@BlackJack: Hehe ja die Kommentare waren auch copy-paste :D
hmm gut dann werde ich mir mal lxml anschauen, ich hab nicht soviel Zeit dafür und meine Kenntnisse in Python sind noch stark begrenzt.

Mir wurde halt der etree vorgeschlagen und dazu hatte ich dann pretty print gesucht und die Sache mit minidom gefunden was ja auch bis zu dem Punkt mit dem print funktioniert ;)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7477
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Mittwoch 14. März 2012, 13:12

hammelwade hat geschrieben: Mir wurde halt der etree vorgeschlagen ...
Daran bin ich wohl "schuld". Es stimmt schon, dass im ``xml.etree.ElemenetTree``-Modul der Standard-Lib eine ``prettyprint``-Funktion fehlt. Zu meiner Ehrenrettung muss ich aber sagen, dass die hübsche Ausgabe ja keine Anforderung war und ich vor allem auf die API hingewiesen habe... und ``lxml`` bildet die ja auch ab :mrgreen:

Wenn es kein Problem darstellt, ``lxml`` zu verwenden, würde ich immer dieses wählen im Kontext XML.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
hammelwade
User
Beiträge: 19
Registriert: Dienstag 6. März 2012, 10:11

Mittwoch 14. März 2012, 13:45

@Hyperion: Hehe ja stimmt schon du hattest mir das Vorgeschlagen aber wie du schon sagtest, diese Vorgabe habe ich ja nicht mitgegeben und du hast auch ausdrücklich ElementTree -API geschrieben ;)
Ich schaue mir grade lxml an bzw. bin grad schon wieder bei minidom gelandet, da dieser das schon in einer respektablen Form automatisch darstellt ohne das ich sonst etwas erst einbinden/installieren muss, jetzt wo ich weiß wie man damit in eine Datei schreibt... Ich weiß ist nicht so schön aber es funktioniert und das auch anständig mehr will ich erstmal gar nicht, bzw. kann ich dann nachholen wenn ich mal mehr Zeit für sowas habe ;)
hammelwade
User
Beiträge: 19
Registriert: Dienstag 6. März 2012, 10:11

Mittwoch 21. März 2012, 15:10

Hmm also ich bin jetzt schon ein ganzes Stück weiter, nur habe ich probleme beim auslesen einer xml Datei...

Ich verwende zurzeit "lxml" (ja ich habe die vorzüge nun kennen gelernt nachdem ich die installation davon endlich hinbekommen habe...)

Ich habe es geschafft meine xml struktur pretty printed in eine Datei zu schreiben, soweit so gut. Nun möchte ich aber die Attribute der einzelnen Knoten auslesen, doch komme ich nur zum Parsen... ich kann die Datei einlesen und auch mit tostring wieder ausgeben doch ein zugriff auf die einzelnen Knoten funktioniert nicht.
bsp. print(benutzer[1].attrib)

Wenn ich die XML struktur neu erstelle funktioniert es, nur wenn ich aus einer Datei auslese nicht... was mache ich denn falsch? ich möchte die xml struktur genauso manipulieren können, als hätte ich die xml struktur zuvor erstellt und nicht aus einer Datei.

Mein Ziel ist es, die Attribute von allen benutzern auszulesen und später ein weiteres hinzuzufügen.

hier meine XML Datei:

Code: Alles auswählen

<?xml version='1.0' encoding='utf-8'?>
<userverwaltung>
  <acc>
    <benutzer name="berg" vorname="andrea"/>
    <benutzer name="berg" vorname="andrea"/>
  </acc>
</userverwaltung>
hier mein Code:

Code: Alles auswählen

import subprocess, sys
from lxml import etree, objectify

global filePath 
global fileName

filePath = "D:/Word Dukomente/Eclipse/testdir/"
fileName = "testfile.xml"

def create_new_file():
    #Create root node   
    userverwaltung = etree.Element("userverwaltung")
    #Create first subnode
    acc = etree.SubElement(userverwaltung, "acc")
    #Create subnodes with attributes
    benutzer = etree.SubElement(acc, "benutzer", name="berg", vorname="andrea")
    benutzer = etree.SubElement(acc, "benutzer", name="berg", vorname="andrea")
    print("----------------create_file-----------------") 
    print(benutzer[1].attrib)
    print("--------------------------------------") 

def load_xml():
    file = open(filePath + fileName, "r")
    parsedFile = etree.parse(file)
    fileAsString = etree.tostring(parsedFile)
    root = etree.XML(fileAsString)
    print("------------------load_xml-------------------")
    #AN DIESER STELLE FUNKTIONIERT ES NICHT!
    print(benutzer[1].attrib)

create_new_file()
load_xml()
Ich bekomme nur immer die Fehlermeldung das er den namen benutzer nicht kennt... nur weiß ich nicht warum nicht...

Ich habe schon diverse Tutorials durchgeschaut ect. doch leider finde ich keine mir verständliche Erklärung...
Zuletzt geändert von hammelwade am Mittwoch 21. März 2012, 17:29, insgesamt 1-mal geändert.
EyDu
User
Beiträge: 4872
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Mittwoch 21. März 2012, 15:21

Hallo.

Namen aus einer Funktion sind auch nicht magisch in einer anderen Funktion bekannt. Das würde auch ein unglaubliches Chaos geben. ``create_new_file`` sollte die Ergebnisse mittels return zurückgeben, ``load_xml`` kann diese dann mittels parameter entgegennehmen.

Noch ein paar weitere Hinweise:
- Dateien soltlest du mittels with-Statement öffnen, dann werden diese automatisch wieder geschlossen.
- ``file`` ist ein schlechter Bezeichner, da es bereits einen built-in-Typ mit dem Namen ``file`` gibt.
- Dateipfade solltest du mittels ``os.path.join`` zusammensetzen, nicht mit +
- Code sollte nicht auf Modulebene stehen, damit du das Modul noch importieren kannst. Verwende also am besten das Muster

Code: Alles auswählen

def main():
    benutzer = create_new_file()
    load_xml(benutzer)

if __name__ == "__main__":
    main()
Und bitte gib bei deinem nächsten Problem die gesamte Fehlermeldung inklusive Traceback an. Das macht die Suche nach einem Fehler viel einfacher als eine Beschreibung was die Fehlermeldug sagt.

Sebastian
Das Leben ist wie ein Tennisball.
BlackJack

Mittwoch 21. März 2012, 15:28

@hammelwade: Die `load_xml()`-Funktion parst die Datei in einen Objektbaum, wandelt den Objektbaum in eine Zeichenkette, und parst die dann *erneut* in einen Objektbaum. Das ist ein Haufen unnötiger Arbeit die da gemacht wird.

``global`` auf Modulebene hat übrigens keinen Effekt, kann man also weg lassen. Überhaupt sollte man auf die Anweisung verzichten, da globale Variablen die keine Konstanten darstellen, Programme schwerer verständlich und Funktionen voneinander abhängig machen.
hammelwade
User
Beiträge: 19
Registriert: Dienstag 6. März 2012, 10:11

Mittwoch 21. März 2012, 15:47

@EyDu:

Es bringt mir aber nichts wenn create_new_file mir dies zurück gibt, da ich diese Funktion nur als Muster verwenden will, falls die eigentliche Datei gelöscht oder fehlerhaft manipuliert wird. Der Name der nicht bekannt ist, ist der Name eines Childnodes welchen ich aus der Datei auslesen will.
Der Inhalt der Datei wird manuell in der vorgegeben Form erweitert, somit muss ich die Attribute explizit aus der Datei auslesen, nur wie stelle ich das an, müsste mit lxml ja eigentlich einfach möglich sein ?!

Hier die Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "D:\Word Dukomente\Eclipse\test\src\Main.py", line 51, in <module>
    create_benutzer()
  File "D:\Word Dukomente\Eclipse\test\src\Main.py", line 36, in create_hostlist
    print(benutzer[1].attrib)
NameError: global name 'benutzer' is not defined
Und danke für die Tipps, bin grad schon dabei sie umzusetzen :)

@BlackJack:

Ich habe es schon auf alle erdenklichen möglichkeiten versucht, mal nur mit parse und dann versucht auszulesen mal mit tostring, aber keins davon hat funktioniert, aber nachdem ich das so wie jetzt habe, bekomme ich zumindest keine Fehlermeldung mehr für getchildren oder ähnliches, aber Attribute auslesen kann ich damit immer noch nicht.

kannst du mir evtl. ein beispiel geben wie ich eine xml Datei einlesen muss, damit ich wieder ganz normal darauf zugreifen kann ? vielleicht fällt dann endlich der gröschen bei mir...

wieso, wenn ich die Variablen nicht global deklariere, müsste ich in jeder funktion die den Dateipfad benötigt, diesen erneut angeben. Mein Skript ist ja noch am Anfang, wenn es nachher einige Codezeilen mehr hat und sich der Pfad dann plötzlich ändert, muss ich den nur einmal ändern. Wäre das in dem Fall nicht sogar eine Konstante, da der Wert sich nicht ändert aber für alle zugänglich ist ? oder irre ich mich da?
Benutzeravatar
/me
User
Beiträge: 3262
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Mittwoch 21. März 2012, 16:00

hammelwade hat geschrieben:wieso, wenn ich die Variablen nicht global deklariere, müsste ich in jeder funktion die den Dateipfad benötigt, diesen erneut angeben.
Du definierst den Pfad nicht in der Funktion neu sondern übergibst ihn als Parameter in die Funktion. Damit hast du dann sehr große Flexibilität.

So wie du es jetzt hast kannst du die Funktionen nicht aus dem Programm lösen ohne dass sie zerbrechen. Das ist kein robuster Code und damit ... ach, sagen wir es wie es ist ... ein Haufen Mist. Wenn alles von allem anderen abhängt, dann hast du dein eigenes Programm nach einiger Zeit nicht mehr unter Kontrolle. Eine vergleichsweise kleine unschuldig aussehende Änderung kann dann dazu führen, dass das ganze fragile Konstrukt zusammenbricht.
BlackJack

Mittwoch 21. März 2012, 16:51

@hammelwade: Statt wild herum zu probieren, solltest Du einfach mal schauen was die einzelnen Funktionen, zum Beispiel `parse()` als Ergebnis liefern. Also zum Beispiel den Datentyp. Und dann kannst Du in der Dokumentation nachlesen was der repräsentiert und welche Daten und Methoden der zur Verfügung stellt.

Das Tutorial aus der `lxml`-Dokumentation hilft da vielleicht auch: http://lxml.de/tutorial.html

Du deklarierst keine Variablen als global. Nochmal: Die ``global``-Anweisung auf Modulebene hat *keinen Effekt*. Das Programm verhält sich exakt gleich, auch wenn diese Anweisungen nicht dort stehen. Die sind an der Stelle komplett sinnfrei.

Deine Pfade sind semantisch gesehen in der Tat Konstanten und damit vom Programmierstil her in Ordnung. Die übliche Namenskonvention wäre hier übrigens GROSS_BUCHSTABEN_MIT_UNTERSTRICHEN. Und mixedCase verwendet man nur für Klassennamen (dort mit einem Grossbuchstaben am Anfang).

Dein Problem ist weiterhin dass Du mit `benutzer` auf einen Namen zugreifen willst, der vorher nicht an einen Wert gebunden wurde. Wo soll der denn Deiner Meinung nach herkommen? Ausser wenn irgendwo *innerhalb der Funktion* eine Zuweisung ``benutzer = …`` steht, wird der Name nicht auf magische Weise an einen Wert gebunden werden. Mir ist auch nicht so ganz klar was Du an diesen Namen gebunden haben möchtest!? Wahrscheinlich das `Element`-Objekt, dass den ``<acc>``-Knoten aus dem XML repräsentiert, denn bei einem ``<benutzer>``-Knoten würde die 1 als Index keinen Sinn machen.

Edit: Die andere Funktion wird ähnlich auf die Nase fallen, weil der Name `hosts` vorher nicht an einen Wert gebunden wurde.
hammelwade
User
Beiträge: 19
Registriert: Dienstag 6. März 2012, 10:11

Mittwoch 21. März 2012, 17:57

@/me: ja da hast du recht, dann werde ich das mal ändern und versuchen mit übergabewerten zu arbeiten, habe ich in AutoIT ja auch immer gemacht...

@BlackJack: Die Tutorials der Seite verwende ich bereits und sie helfen mir auch recht gut, nur das auslesen von Dateien ist mangelhaft beschrieben...
Im Prinzip hast du recht, ich sollte mal schauen was ich zurück bekomme, ich hab mir das nur einfacher vorgestellt, als ich unter AutoIT mit DOM gearbeitet habe war das auch nicht so eine Tortur wie hier...

Was ich auslesen möchte ist der Inhalt der Attribute also als Beispiel habe ich folgende Datei mit mehreren Knoten mit "genau dem selben" Namen!

Code: Alles auswählen

<root>
    <account>
        <benutzer nachname="strauch">
        <benutzer nachname="laub"> 
        <benutzer nachname="munich">
        <benutzer nachname="wald">
    </account>
</root>
1. was ich nun jeweils abspeichern möchte ist der Inhalt des jeweiligen Attributes, also z.B. möchte ich in der Variable kunde den Wert des Attributes von benutzer nummer 2 haben also "laub".
Da die Benutzer alle gleich heißen, kann ich diese ansteuern indem ich account[1] abfrage was in dem Beispiel den zweiten Computer darstellt. (dies funktioniert auch siehe create_new_file)

2. danach möchte ich ein neues Attribut zu jedem benutzer hinzufügen also z.B. das Attribut vorname mit dem jeweiligen Vornamen als Inhalt.

So und das funktioniert ja auch wenn ich grade erst die xml struktur erstellt habe, aber nicht wenn ich die struktur aus einer Datei hole, da ich anscheinend zu blöd bin die struktur so zu laden als wenn ich sie grade erst erstellt hätte. Wenn ich die Struktur doch so in einer Datei ablegen kann warum sollte ich sie dann nicht umgekehrt wieder so einladen können wie zum Zeitpunkt des abspeicherns ?
Sonst wäre mir der sinn von xml mit Python nicht ganz geläufig, ich hab diese Art ja bereits mit einer anderen Sprache und DOM benutzt, nur das DOM bei AutoIT schon vor manipuliert war zum einfachen anwenden...

Edit: was ich von dem meisten aufrufen zurück bekomme ist das : [<Element host at 0x22d95f8>, <Element host at 0x22d94e0>]
In der Doku zur funktion list, wo diese Rückgabe herkommt, steht nur das das die Childnodes sind, aber was für ein Typ usw. steht da meistens nicht..
BlackJack

Mittwoch 21. März 2012, 19:57

@hammelwade: In dem Tutorial wird das erstellen aus Zeichenketten und Dateien beschrieben. Und auch was die Unterschiede bei den Funktionen sind. Und es sind Beispiele dabei. Ausserdem werden die *zwei* am meisten verwendeten Datentypen `Element` und `ElementTree` beschrieben.

Wie sieht denn XML-Verarbeitung unter AutiIt aus?

Wo kannst Du ``account[1]`` verwenden? Was hast Du denn an den Namen `account` gebunden? Falls das ein `Element`-Objekt ist, dass den ``<account>``-Knoten repräsentiert, hat es nichts damit zu tun das die enthaltenen Knoten alle den Tagnamen ``<benutzer`` haben. Damit greifst Du auf den zweiten Kindknoten zu — egal wie der Tagname davon ist.

Dass es nach dem Laden nicht so funktioniert wie nach dem erstellen von `Element`-Objekten liegt daran, dass das parsen von einem kompletten Dokument einen anderen Typ liefert als erstellen von einzelnen Elementen. Und das ist durchaus sinnvoll, denn in einem Dokument können noch zusätzliche Informationen ausserhalb der Element-Struktur existieren, wie zum Beispiel der Doctype, Kommentare, oder „processing instructions”.

Die Kindknoten können von jedem Typ sein, der in so einem XML-Dokument vorkommen kann. In Deiner Liste die Du zeigst sind es ganz offensichtlich Objekte vom Typ `Element`. Die geben ausserdem den Tagnamen mit in ihrer `repr()`-Darstellung an. Wenn Du der `repr()`-Darstellung nicht traust, kannst Du auch immer die `type()`-Funktion verwenden um einen Typ heraus zu finden.
Benutzeravatar
pillmuncher
User
Beiträge: 1144
Registriert: Samstag 21. März 2009, 22:59
Wohnort: München

Mittwoch 21. März 2012, 23:45

@hammelwade: So schwer ist es wirklich nicht:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from lxml import etree

def make_xml():
    userverwaltung = etree.Element('userverwaltung')
    acc = etree.SubElement(userverwaltung, 'acc')
    benutzer = etree.SubElement(acc, 'benutzer', name='berg', vorname='andrea')
    benutzer = etree.SubElement(acc, 'benutzer', name='berg', vorname='andrea')
    return userverwaltung

def main():
    root = make_xml()
    tree = etree.ElementTree(root)
    with file('test.xml', 'w') as xmlfile:
        tree.write(xmlfile, pretty_print=True, xml_declaration=True, encoding='utf-8')
    print
    with file('test.xml', 'r') as xmlfile:
        for line in xmlfile:
            print line.rstrip()
    print
    with file('test.xml', 'r') as xmlfile:
        tree = etree.parse(xmlfile)
    users = tree.xpath('//benutzer')
    for user in users:
        user.attrib['beruf'] = 'programmierer'
    print etree.tostring(tree)

if __name__ == '__main__':
    main()
Ergebnis:

Code: Alles auswählen

<?xml version='1.0' encoding='UTF-8'?>
<userverwaltung>
  <acc>
    <benutzer vorname="andrea" name="berg"/>
    <benutzer vorname="andrea" name="berg"/>
  </acc>
</userverwaltung>

<userverwaltung>
  <acc>
    <benutzer vorname="andrea" name="berg" beruf="programmierer"/>
    <benutzer vorname="andrea" name="berg" beruf="programmierer"/>
  </acc>
</userverwaltung>
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

Donnerstag 22. März 2012, 00:32

Alternative mit `findall()`:

Code: Alles auswählen

In [34]: doc = etree.parse('test.xml')

In [35]: for benutzer in doc.findall('./account/benutzer'):
   ....:     benutzer.attrib['beruf'] = 'programmierer'
   ....: 

In [36]: doc.write(sys.stdout)
<root>
    <account>
        <benutzer nachname="strauch" beruf="programmierer"/>
        <benutzer nachname="laub" beruf="programmierer"/>
        <benutzer nachname="munich" beruf="programmierer"/>
        <benutzer nachname="wald" beruf="programmierer"/>
    </account>
</root>
Antworten