ElementTree (XML) speichert alles in eine Zeile?

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.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

Hallo Leute,

ich hätte da mal eine Frage.
Ich möchte halbwegs einfach ein XML-Dokument erstellen.
Nach etwas googlen habe ich mich dazu entschieden das ElementTree für diesen Zweck zu 'verwenden' weil es nicht allzu schwer ausschaut.

Die ersten Schritte habe ich gemacht und hat auch ganz gut geklappt.
Dann habe ich das XML-Dokument mal im Editor aufgemacht und sehe daß alles in eine Zeile gespeichert worden ist.
Natürlich ist es per definition gar nicht wichtig wie es gespeichert wird solange die Struktur eingehalten wird, dennoch ist eine Kontrolle des Ergebnisses so sehr schwer.

Jetzt die Frage:
Kann ich per ElementTree gar nicht anders oder mache ich etwas falsch bzw. gibt es auch die Möglichkeit es schön strukturiert 'aussehen' zu lassen im Editor?
Falls es so ist wie es ist: Wie macht ihr das dann? Ist es euch unwichtig oder benutzt ihr eine andere Art XML-Dokumente zu erzeugen? Wenn das zutrifft, welche wäre das dann?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Gewoehne dir bitte an Code zu posten der zeigt, was du gerade machst und wo das Problem damit ist.

Das ist hier besonders wahr, weil `lxml.etree` Pretty Printing direkt unterstuetzt. Fuer etree aus der Standardbibliothek muss man einen kleinen Umweg machen: https://pymotw.com/2/xml/etree/ElementTree/create.html
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@cofi

Ich bin jetzt bei der Arbeit (in der Pause natürlich) und kann leider kein Code posten (PC zu Hause).
Dennoch ist mir beim durchlesen deines Tips (bzw. des geposteten Links) aufgefallen das ich die 'prettify'-Funktion bei der Ausgabe ausführe, die Funktion also lediglich das 'reparsed-te' zurückliefert und es dann ausgegeben wird.
Wenn ich aber nicht ausgeben sondern eine XML-Datei schreiben möchte, dann habe ich da ja ein Problem da ich ja die Methode 'write' benutze und dort lediglich den Pfad als Parameter mitliefere.
Kann ich denn nicht das ganze Objekt mit prettify neu parsen so daß das Objekt die gewünschte Formatierung enthält?
Ich schreibe ja aus dem Objekt heraus und daher, denke ich, sollte es im Objekt selber richtig gestellt werden falls das möglich ist?

Ich habe das XML-Beispiel (welches ich gefunden habe) in ein Modul verfrachtet, Tree und ElementTree global deklariert und die verschiedenen Funktionen definiert.
Hier soweit ich mich an gestern Abend noch erinnern kann (bin mir ziemlich sicher das ich es genauso gemacht habe):

Orginal-Beispielcode:

Code: Alles auswählen

from xml.etree.ElementTree impoert ElementTree
from xml.etree.ElementTree impoert Element
import xml.etree.ElementTree as etree

root=Element('person')
tree=ElementTree(root)
name=Element('name')
root.append(name)
name.text='Julie'
print etree.tostring(root)
tree.write(open('person.xml','w'))
Mein Code in ein Modul verfrachtet (vielleicht schaffe ich es ja noch in eine Klasse zu verfrachten?):

Code: Alles auswählen

def xmlopen(rootname):
	public root
	public tree
	root=Element(rootname)
	tree=ElementTree(root)

def xmladd(tag, value):
	name=Element(tag)
	root.append(name)
	name.text=value

def xmlwrite(xmlFileName):
	tree.write(open(xmlFileName,'w'))
Somit habe ich ja keine 'Ausgabe' bei welcher ich das prettify 'zwischen quetschen zu können'.
Gibt es hierbei auch eine Möglichkeit das reparsen durchzuführen und es im gleichen Objekt zu speichern? Oder wenigstens einem anderen Objekt das 'reparste' zuzuweisen?

Ich Danke schonmal im Voraus für die Mühe!
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Das Objekt (= die Python Repraesentation des XML Baumes) hat absolut keine Formatierung und wenn du das XML Dokument nun formatiert speichern willst, dann speichere eben den formatierten String.

Damit wird

Code: Alles auswählen

tree.write(open('person.xml','w'))
zu (Bonus: richtiger Umgang mit der Datei)

Code: Alles auswählen

with open ('person.xml', 'w') as f:
    f.write(prettify(tree))
Oder du verwendest eben statt xml.etree (Standardbibliothek) lxml.etree (lxml).
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@cofi

Ich habe mal deinen Vorschlag ausprobiert und es hagelt Fehler.
Zuerst bekam ich
type object 'ElementTree' has no attribute 'tostring'

Dann habe ich danach gegoogelt und habe was gefunden:
tostring importieren aus xml.etree.ElementTree. Danach war das 'tostring' als Funktion verfügbar.

Dann habe ich es also so gemacht:

Code: Alles auswählen

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

from xml.etree.ElementTree import ElementTree
from xml.etree.ElementTree import Element, tostring
import xml.etree.ElementTree as etree

from xml.dom import minidom

def xmlopen(rootElement):
    global root
    global tree
    root=Element(rootElement)
    tree=ElementTree(root)
        
def xmladd(name, wert):
    name=Element(name)
    root.append(name)
    name.text=wert
        
def xmlwrite(xmlFileName):
    #tree.write(open(xmlFileName,'w'))
    with open(xmlFileName,'w') as f:
        f.write(prettify(tree))

def prettify(elem):
    """Return a pretty-printed XML string for the Element.
    """
    rough_string = tostring(elem, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent="  ")
Aber jetzt bekomme ich den Fehler:
'ElementTree' object has no attribute 'tag'

Und 'tag' benutze ich ja nicht und das muss dann im ElementTree was schief laufen?

Was könntei ch nun machen damit es funktioniert?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Wie der Docstring von `prettify` schon sagt: Das formatiert den (Unter-)Baum den ein `Element` darstellt:

Code: Alles auswählen

In [26]: top = Element('top')                   

In [27]: child = SubElement(top, 'child')

In [28]: child.text = 'This child contains text.'

In [29]: tree = ElementTree(top)

In [30]: prettify(tree.getroot())
Out[30]: u'<?xml version="1.0" ?>\n<top>\n  <child>This child contains text.</child>\n</top>\n'
Du solltest den Code aber wirklich aufraeumen: Entweder den `etree` Ansatz oder einzelne Namen importieren und dann am besten alle Namen eines Moduls zusammen.
`global` solltest du loswerden: Funktionen haben Rueckgabewerte, nutze sie.

Und poste in Zukunft bitte den ganzen Traceback den du bekommst. Selbst wenn du nichts damit anfangen kannst, kann es anderen vielleicht helfen.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@cofi

Mit dem Code aufräumen:
1.) will ich es, wenn das Gerüst mal läuft, in eine Klasse packen
2.) Werde ich in Zkunft alles posten was angezeigt wird
3.) Habe ich jetzt leider leider nicht verstanden wo der Fehler eigentlich liegt.
prettify-Funktion kann ich so lassen wie sie ist? Was ist dann falsch?
Ich übergebe ja der Funktion prettify den 'tree' (korrektes TreeElement) und erwarte den 'umgeparsten' Inhalt wieder zurück welchen ich dann wegschreibe? Aber der liefert mir ja die o.g. Fehler. Dann mache ich doch was falsch in der prettify-Funktion, richtig?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nochmal: Dein Aufruf ist falsch. Du uebergibst `prettify` den `ElementTree`, aber `prettify` arbeitet auf `Element`, darum musst du es auf dem Wurzelelement aufrufen, wie ich es gezeigt habe.

Ich weiss nicht in wie weit du das noch erweitern willst, aber ich glaube nicht, dass eine Klasse hier Sinn macht. Klassen sind kein Selbstzweck.
BlackJack

@cofi: Die globalen Variablen sind äusserst unschön, die würde man üblicherweise durch eine Klasse beseitigen.

@kaineanung: Bevor Du allerdings etwas wie den `TreeBuilder` aus der ElementTree-API selber schreibst, solltest Du Dir vielleicht erst einmal diese Klasse anschauen.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: warum sollte ich mir die zusätzliche Abhängigkeit von minidom ins Haus holen, nur um schönes XML zu haben?
Mein prettify sieht immer so aus:

Code: Alles auswählen

def prettify(elem, indent="  ", level=0):
    i = "\n" + level*indent
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + indent
        for el in elem:
            prettify(el, indent, level+1)
        if not el.tail or not el.tail.strip():
            el.tail = i
    if not elem.tail or not elem.tail.strip():
        elem.tail = i
xmlopen öffnet nichts und ist eigentlich auch völlig unnötig. Statt xmladd kannst Du auch gleich SubElement verwenden, das hat die bessere Funktionalität. Alles in allem sieht das dann so aus:

Code: Alles auswählen

import xml.etree.ElementTree as ET

def main():
    root = ET.Element("root_element")
    ET.SubElement(root, name).text = wert
    prettify(root)
    ET.ElementTree(root).write("output.xml", encoding="utf-8")
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius3
warum sollte ich mir die zusätzliche Abhängigkeit von minidom ins Haus holen, nur um schönes XML zu haben?
Weil ich es hier so gepostet bekommen habe bzw. auf der verlingen Seite zu prettify so abgeschrieben habe https://pymotw.com/2/xml/etree/ElementT ... ment-nodes

Und das Prettify funktioniert bei mir nicht. Ich bekomme folgendes 'Traceback':
File "/home/user1/work/tmparser/simplexml.py", line 23, in xmlwrite
f.write(prettify(tree))
File "/home/user1/work/tmparser/ElementTree_pretty.py", line 10, in prettify
rough_string = ElementTree.tostring(elem, 'utf-8')
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1126, in tostring
ElementTree(element).write(file, encoding, method=method)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 820, in write
serialize(write, self._root, encoding, qnames, namespaces)
File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 900, in _serialize_xml
tag = elem.tag
AttributeError: 'ElementTree' object has no attribute 'tag'
Das bedeutet ja er weist darauf hin das irgendwo tief im ElementTree einer variable tag ein elem.tag zugewiesen wird. Die Fehlermeldung besagt aber das ElementTree keine Eigenschaft 'Tag' kennt.
Dafür kann mein Code ja nichts, oder doch?
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: wenn Du die Funktion mit den richtigen Argumenten aufrufst, funktioniert sie auch. Bild Code anzuwenden, den man nicht versteht, funktioniert nicht.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@sirus3

Naja, von blind Code ausführen kann man ja so nicht sagen....
Schaue dir z.B. meine simplexml.py an: das habe ich umgemodelt, angepasst an meine Bedürfnisse und der Gleichen.

Aber gewissesweise hast du schon recht: ganz neue Funktionen, die ich noch gar nicht kenne, und wo die Beschreibungen bereits ein Vorwissen voraussetzen, muss ich einfach per try & error 'ergründen'.
Daher frage ich hier ja auch (es macht mir kein Spaß jemanden mit Fragen zu bohren und blöd dazustehen und mich vor allem blöd anzustellen. Also ist es sicherlich nicht aus Spaß na der Freude sondern weil ich python lernen will) und hoffe auf Antworten die mich weiterbringen.

Ein Tutorial von Python habe ich von Anfang bis Ende durchgerackert und die Grundkenntnisse von Python habe ich halbwegs. Aber neue Funktionen/Klassen sind eben neu und ich weiß nicht was und wie übergeben wird.
BlackJack

@kaineanung: Wenn Vorwissen vorausgesetzt wird *muss* man nicht „trial & error“ machen, man kann sich auch das nötige Wissen aneignen.

Was man Beispielsweise der `tostring()`-Funktion von der ElementTree-API übergeben kann/muss, das braucht man nicht zu erraten, das steht in der Dokumentation: https://docs.python.org/2/library/xml.e ... e.tostring

Du übergibst dort aber kein `Element`-Objekt, sondern ein `ElementTree`-Objekt und das hat offensichtlich kein `tag`-Attribut. Was `Element`- und `ElementTree`-Objekte jeweils repräsentieren steht ebenfalls in der Python-Dokumentation. Da muss man mal den Text durcharbeiten. Sich zusätzlich mit dem nicht-python-spezifischen Teil der Angelegenheit zu beschäftigen, nämlich XML, schadet sicher auch nicht.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack

Natürlich wäre die richtige Vorgehensweise sich auch das Vorwissen von all Dem anzueignen.
Aber jetzt habe ich Python 'gelernt' und will nicht die Hälfte vergessen ohne es ein paar mal in Code, in einem Beispielprojekt, umzusetzen.
Da spielt auch eine gewisses Maß an Euphorie mit.

Wenn ich jetzt nicht alle Klassen kenne, ich die aber nur als 'Hilfsmittel' (vorerst) heranziehe, dann kommt halt das 'Try&Error' bis man das Ergebnis hat. Dabei lernt man auch ganz gut und kurze Zeit später weiß man wie die Klasse aufgebaut ist und kann damit umgehen auch ohne es strikt zu trennen wie bisher: zuerst lesen (Theorie) und dann Umsetzen (Praxis).

Nichts desto trotz: eine echte Abkürzung soll es nicht geben und darum ist lesen angsagt.
Aber dann kommt man in seinem Projekt nicht weiter denn die eine oder andere Klasse setzt Vorwissen voraus. Dann liest man sich in diesem Manual durch und trifft auf andere Dinge die wiederrum Vorwissen benötigen. Und das kann lange so weitergehen.

Ist wie mit Wiki: man geht rein und interessiert sich für eine Information und ehe man sich versieht sitzt man schon beim 30igsten Artikel und 2 Stunden sind vorüber...

Ich hoffe auf etwas Nachsicht... :|
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@kaineanung: Du wurdest aber drei Mal auf den korrekten Aufruf hingewiesen, wenn ich mich nicht verzählt habe! Das hat nicht einmal mehr mit dem Lesen der Doku zu tun, sondern lediglich dem "korrekten" Abschreiben ;-) Denn cofi hatte Dir ja netter Weise bereits alles präsentiert...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: es sollte halt das TRY im Vordergrund stehen. Du mußt hier ja nicht hunderte Seiten Dokumentation lesen, sondern nur ein paar Beispiele, die man einfach kopieren, verstehen und abwandeln kann. Dazu noch die Beschreibung von wenigen Funktionen, die man auch sofort brauchen kann.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius3

Ich habe mein Code an dein Beispiel angepasst und es funktioniert.

Dir und all den anderen vielen Dank für die Mühe!

So hat es also geklappt:

Code: Alles auswählen

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

import xml.etree.ElementTree as etree
from xml.dom import minidom

def prettify(elem, indent="  ", level=0):
    i = "\n" + level*indent
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + indent
        for el in elem:
            prettify(el, indent, level+1)
        if not el.tail or not el.tail.strip():
            el.tail = i
    if not elem.tail or not elem.tail.strip():
        elem.tail = i
    
def xmlopen(rootElement):
    global root
    global tree
    root=etree.Element(rootElement)
    tree=etree.ElementTree(root)

def xmladd(name, wert):
    name=etree.Element(name)
    root.append(name)
    name.text=wert
        
def xmlwrite(xmlFileName):
    prettify(root)
    etree.ElementTree(root).write(xmlFileName,encoding="utf-8")
@cofi

Ich habe den Code aufgeräumt :)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Zum Dritten mal, was soll denn der Sinn der Funktionen xmlopen, xmladd oder xmlwrite sein?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du hast ja immer noch eine Abhängigkeit zu ``minidom`` drin... ich persönlich finde das auch besser, als die eigene ``prettify`` Funktion, aber aktuell ist das überhaupt nicht sinnvoll.

Solange du noch ``global`` in Deinem Code hast, ist er nicht aufgeräumt...

Die Namen finde ich auch nicht gut... ``xmladd``... was soll denn da "geadded" werden? Das "xml"-Präfix ist insofern sinnlos, als dass es sich aus dem Kontext ergibt, daß es sich um XML zentrierte Funktionen handelt. Interessant wäre es aus dem Namen zu erfahren, *was* hinzugefügt wird!

Und die ``write`` Funktion schreibt gar nichts, sondern formatiert irgendwie einen ``etree.Element``.

Wie ich gerade gesehen habe arbeitet ``prettify`` rekursiv... alleine das macht sie für mich unbrauchbar!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten