xml parsing mit xml.etree.ElementTree (Python 3.7)

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
mp123
User
Beiträge: 5
Registriert: Donnerstag 5. März 2020, 14:32

Hallo zusammen,

ich bin neu hier, ein echter Python-Frischling und hätte eine Frage zu xml-parsing mit ElementTree.
Die xml-Datei ist folgendermaßen aufgebaut (Ausschnitt, es gibt dutzende <A>'s...):

Code: Alles auswählen

<Beispiel_A> 
                  <A>
                     <A1>Langbezeichnung der Funktion XYZ</A1>
                     <A2>Kurzbezeichnung der Funktion XYZ</A2>
                     <A3>1</A3>
                     <A4>0</A4>
                     <A5></A5>
		     <A6></A6>
                     <A7></A7>
                     <A8>1</A8>
                     <A9>1</A9>
                     <A10></A10>
                     <A11>2</A11>
                     <A12>2</A12>
                     <A13>2</A13>
                     <A14>
                        <A14.1>Modulblock RST.XYZ</A14.1>
                        <A14.2>SW-VERSION Modulblock RST</A14.2>
                        <A14.3>NOT-EXISTING</A14.3>
                        <A14.4></A14.4>
                     </A14>
                     <A15>[0]- 1</A15>
                     <A16>1</A16>
                     <A17>
                        <A17.1>Wert 1</A17.1>
                        <A17.1>Wert 2</A17.1>
                     </A17>
                     <A18>
                        <A18.1>50</A18.1>
                        <A18.1>160</A18.1>
                     </A18>
                  </A>
                  <A>
                  .
                  .
                  </A>
                  <A>
                  .
                  .
                  </A>
                			  
</Beispiel_A>	
Nun geht es darum die Werte in A1, A2, A17 und A18 auszulesen und in eine Excel-Tabelle zu schreiben. Soweit funktioniert das auch einwandfrei, nur bei den Werten unter A17 und A18 komme ich auf keinen grünen Zweig da die childs von A17 und A18 jeweils den selben Namen haben (A17.1) und ich beim auslesen nur den 1. Wert zurückbekomme.

Die Fertige Tabelle sollte folgendermaßen aussehen:

A (1)| A1 | A2 | A17.1 (1. Wertepaar, hier Wert_1) | A18.1 (1. Wertepaar, hier 50)
A (1)| A1 | A2 | A17.1 (1. Wertepaar, hier Wert_2) | A18.1 (1. Wertepaar, hier 160)
A(2)
A(3)
.
.
A(n)

Mein Python-Code sieht wie folgt aus:

Code: Alles auswählen

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

import xml.etree.ElementTree as ET
import xlsxwriter

time = '{0:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())
time_file ='{0:%Y-%m-%d}'.format(datetime.datetime.now())

root = tk.Tk()
root.withdraw()

file_path = filedialog.askopenfilename(title='XML-Reader', filetypes=[(".xml-Datei", "*.xml")]) #Öffnet den File-Dialog um Dateien auswählen zu können

tree = ET.parse(file_path)
root = tree.getroot()

save_file = filedialog.asksaveasfilename(initialfile=(time_file+'_XML-Reader.xlsx'),title='XML-Reader',filetypes=[('Excel-Datei','*.xlsx')], defaultextension = '.xlsx')

worksheet2 = workbook.add_worksheet('Daten')
cell1 = workbook.add_format({'bold': True, 'font_color': 'blue',
                             'bg_color': '#E0E0E0'})
                             
worksheet1.write(0, 0, 'Langname',cell1)
worksheet1.write(0, 1, 'Kurzname',cell1)
worksheet1.write(0, 2, 'Variablen',cell1)
worksheet1.write(0, 3, 'Werte',cell1)


worksheet1.autofilter('A1:D1')


def sheet_data():

    row = 1
    
    for dfc in root.getiterator('A'):
    
        try:
            a=dfc.find('A1').text
            worksheet1.write(row, 0, a)
        except AttributeError: a = ('---')
        
        try:
            b=dfc.find('A2').text
            worksheet1.write(row, 1, b)
        except AttributeError: b = ('---')
        
        try:
            c=dfc.find('A17/A17.1').text
            worksheet1.write(row, 2, c)
        except AttributeError: c = ('---')
        
        try:
            d=dfc.find('A18/A18.1').text
            worksheet1.write(row, 3, d)
        except AttributeError: d = ('---')

        row = row+1
    
sheet_data()

workbook.close()
os.startfile(save_file) 

Ich hoffe es ist einigermaßen verständlich, ich wäre Euch für Eure Hilfe sehr dankbar!

Gruß mp123
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Wer hat denn dieses XML-Format erfunden. Auch Tags sollen aussagekräftige Namen sein. Alles A zu nennen, ist schlecht, weil man zu leicht Fehler machen kann, und sie absolut unleserlich sind.
Wenn es möglich ist, dringend reparieren.

Zu Deinem Code: alles gehört in Funktionen.
Dass `root` einmal eine Tk-Instanz und einmal ein XML-Element sind, sollte nicht sein und würde auch nicht passieren, wenn die beiden Variablen in zwei Funktionen wären.
`time` und `time_file` sollten wohl das selbe Datum sein und nicht zwei verschieden? Achso `time` wird gar nicht verwendet. `datetime` wird verwendet, aber nicht importiert.
Variablennamen mit zufälligen Nummern zu versehen (cell1) trägt nicht zu Lesbarkeit bei.
Auch ein `worksheet2` das plötzlich zu `worksheet1` wird.
Alles was eine Funktion braucht, muß sie auch über ihre Argumente bekommen.
Das was im except-Block zu `AttributeError` steht, wird gar nicht verwenden. findtext ist da auch besser, als den AttributeError abzufangen.

Code: Alles auswählen

import os
import datetime
import xml.etree.ElementTree as ET
import xlsxwriter

def input_filenames():
    root = tk.Tk()
    root.withdraw()
    # Öffnet den File-Dialog um Dateien auswählen zu können
    file_path = filedialog.askopenfilename(title='XML-Reader', filetypes=[(".xml-Datei", "*.xml")])
    now = datetime.datetime.now()
    time_file ='{0:%Y-%m-%d}'.format(now)
    save_filename = filedialog.asksaveasfilename(initialfile=(time_file+'_XML-Reader.xlsx'),title='XML-Reader',filetypes=[('Excel-Datei','*.xlsx')], defaultextension = '.xlsx')
    return file_path, save_filename


def convert_sheet_data(xml_root, data_sheet):
    for row, dfc in enumerate(xml_root.getiterator('A'), 1):
        langname = dfc.findtext('A1')
        data_sheet.write(row, 0, langname)
        kurzname = dfc.findtext('A2')
        data_sheet.write(row, 1, kurzname)
        variablen = dfc.findtext('A17/A17.1')
        data_sheet.write(row, 2, variablen)
        werte = dfc.findtext('A18/A18.1')
        data_sheet.write(row, 3, werte)


def main():
    file_path, save_filename = input_filenames()    
    tree = ET.parse(file_path)
    xml_root = tree.getroot()

    workbook = xlsxwriter.Workbook(save_filename)
    data_sheet = workbook.add_worksheet('Daten')
    cell_format = workbook.add_format({'bold': True, 'font_color': 'blue',
                                 'bg_color': '#E0E0E0'})                             
    data_sheet.write(0, 0, 'Langname', cell_format)
    data_sheet.write(0, 1, 'Kurzname', cell_format)
    data_sheet.write(0, 2, 'Variablen', cell_format)
    data_sheet.write(0, 3, 'Werte', cell_format)
    data_sheet.autofilter('A1:D1')
    convert_sheet_data(xml_root, data_sheet)
    workbook.close()
    os.startfile(save_file) 

if __name__ == '__main__':
    main()
So wie es aussieht sind in Zellen der Spalten B und C gar keine Variablen oder Werte sondern nur eine einzelne Variable oder ein Wert pro Zelle.
Wenn Du alle 17.1-Elemente untereinander speichern willst, dann brauchst Du eben zwei verschachtelte Schleifen.
mp123
User
Beiträge: 5
Registriert: Donnerstag 5. März 2020, 14:32

Vielen Dank für die ausführliche und schnelle Antwort Sirius3!

Ja Sie haben Recht, das XML-File ist total unübersichtlich, es ist ein altes Beispiel zur Messwert-Analyse.

Würde die verschachtelte Schleife wie folgt aussehen:

Code: Alles auswählen

def convert_sheet_data(xml_root, data_sheet):
    for row, dfc in enumerate(xml_root.getiterator('A'), 1):
    	for row, dfc in enumerate(xml_root.getiterator('A/A17'), 1):
    		variablen = dfc.findtext('A17.1')
        	data_sheet.write(row, 2, variablen)     
   	langname = dfc.findtext('A1')
    	data_sheet.write(row, 0, langname)
    	kurzname = dfc.findtext('A2')
    	data_sheet.write(row, 1, kurzname)
    	werte = dfc.findtext('A18/A18.1')
        data_sheet.write(row, 3, werte)  
Oder bin auch da auf dem Holzweg? Ich habe schon alles mögliche ausprobiert aber komme irgendwie nicht auf die Lösung.
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Nein, jetzt hast zwei unabhängige Schleifen ab xml_root, ineinander geschachtelt. Aber Du mußt ja nur die A17 in einem spezifischen <A> (dfc) durchgehen.
mp123
User
Beiträge: 5
Registriert: Donnerstag 5. März 2020, 14:32

Ich bin leider immer noch nicht auf den richtigen Trichter gekommen... hättest du noch einen weiteren Tipp für mich?

Gruß mp123
mp123
User
Beiträge: 5
Registriert: Donnerstag 5. März 2020, 14:32

Ich hab jetzt mal versucht das folgendermaßen zu lösen:

Code: Alles auswählen

def convert_sheet_data(xml_root, data_sheet):

        variablen= enumerate(xml_root.iter(tag='A17.1'), 1)
        werte = enumerate(xml_root.iter(tag='A18.1'), 1)
        langname = enumerate(xml_root.iter(tag='A1'), 1)
        kurzname = enumerate(xml_root.iter(tag='A2'), 1)

        for row, dfc in langname:
            print(dfc.text)
            data_sheet.write(row, 0, dfc.text)
        for row, dfc in kurzname:
            print(dfc.text)
            data_sheet.write(row, 1, dfc.text)
            for row, dfc in variablen:
                print(dfc.text)
                data_sheet.write(row, 2, str(dfc.text))
            for row, dfc in werte:
                print(dfc.text)
                data_sheet.write(row, 3, str(dfc.text))
das funktioniert soweit ganz gut, nur füllt das Programm im Excel-File logischerweise alles von oben her auf... ich komme irgendwie nicht auf den letzten Schritt das die zusammengehörigen Werte wirklich nebeneinander geschrieben werden...

Gruß mp123
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Code: Alles auswählen

def convert_sheet_data(xml_root, data_sheet):
    row = 1
    for dfc in xml_root.getiterator('A'):
        langname = dfc.findtext('A1')
        kurzname = dfc.findtext('A2')
        for a17, a18 in zip(dfc.getiterator('A17/A17.1'), dfc.getiterator('A18/A18.1')):
            data_sheet.write(row, 0, langname)
            data_sheet.write(row, 1, kurzname)
            data_sheet.write(row, 2, a17.text)
            data_sheet.write(row, 3, a18.text)
            row += 1
mp123
User
Beiträge: 5
Registriert: Donnerstag 5. März 2020, 14:32

Vielen herzlichen Dank! :)
Das hat mir wirklich sehr geholfen und ich sehe ich habe noch viel Arbeit vor mir was das programmieren mit Python betrifft (sofern man das von mir überhaupt so bezeichnen darf). Deine Beispiele waren wirklich sehr verständlich und gut strukturiert.

Schöne Grüße und bleib gesund!

mp123
Antworten