Forum: PC-Programmierung [Python] XML Parsen


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Rene K. (xdraconix)


Lesenswert?

Ich habe eine Datei in folgendem XML Format vorliegen:
1
<ADUReport>
2
  <MetaProperty id="ADU Version" value="5.30.6.0 2022-01-05"/>
3
  <MetaProperty id="Diagnostic Module Version" value="5.30.6.0 2022-01-05"/>
4
  <MetaProperty id="Time Generated" value="Friday June 09, 2023 5:02:03PM"/>
5
  <Device deviceType="Controller" id="AC:2799134361" mName="Mew Controller">
6
    <Errors>
7
      <Message id="1" value="undef Error"/>
8
      <Message id="2" value="undef Error"/>
9
    </Errors>
10
    <MetaStructure id="Identify Controller" size="1024">
11
      <MetaProperty id="Serial" value="123456"/>
12
      <MetaProperty id="Port" value="2"/>
13
    </MetaStructure>
14
    <MetaStructure id="Assigned Hardware size="1024">
15
      <MetaProperty id="lun 1" value="0x01">
16
      <MetaProperty id="lun 2" value="0x02">
17
    </MetaStructure>
18
  </Device>
19
</ADUReport>

Wie kann ich da am dümmsten durch die einzelnen MetaStructures bei 
Device parsen?

Aktuell habe ich:
1
from xml.dom import minidom
2
3
p1 = minidom.parse("ADUReport.xml")
4
tagname= p1.getElementsByTagName('Device')
5
6
for x in tagname:
7
  if x.attributes['deviceType'].value == "Controller":
8
    print(x.attributes['id'].value)
9
    tagchild = x.getElementsByTagName('MetaStructure')
10
    for y in tagchild:
11
      if y.attributes['id'].value == "Identify Controller":
12
        print(y.attributes['value'].value)

Das kann ja so irgendwie nicht richtig sein. Mit firstChild komme ich 
auch nicht weiter, da gibt er mir keine Daten zurück. Kann man das nicht 
irgendwie in ein Array parsen damit ich leichteren Zugriff darauf habe?

von Uwe B. (boerge) Benutzerseite


Lesenswert?

Rene K. schrieb:
> Mit firstChild komme ich
> auch nicht weiter, da gibt er mir keine Daten zurück.

...wer ist firstChild bzw./oder "er"?

von Rolf M. (rmagnus)


Lesenswert?

Rene K. schrieb:
> for y in tagchild:
>       if y.attributes['id'].value == "Identify Controller":
>         print(y.attributes['value'].value)
>
> Das kann ja so irgendwie nicht richtig sein.

Ist es nicht. y hat kein Attribut 'value'. Ich würde vorschlagen, etwas 
aussagekräftigere Namen als x und y zu verwenden.

Rene K. schrieb:
> Kann man das nicht irgendwie in ein Array parsen damit ich leichteren
> Zugriff darauf habe?

Du meinst so wie du es in den Ebenen darüber schon machst? Warum sollte 
das nicht gehen?

von Rene K. (xdraconix)


Lesenswert?

Rolf M. schrieb:
> Ist es nicht. y hat kein Attribut 'value'. Ich würde vorschlagen, etwas
> aussagekräftigere Namen als x und y zu verwenden.

Ja das war nur ein Test, wegen der Variablen. Und, das war nur ein 
Ausschnitt aus der XML Datei, das original ist ca. 2MB groß.

Rolf M. schrieb:
> Du meinst so wie du es in den Ebenen darüber schon machst? Warum sollte
> das nicht gehen?

Da es sich um viele weitere Ebenen nach unten verschachtelt. 
Stellenweise bis zu 7 Ebenen. Da wird der Code, die Schleifen und die 
Abfragen schon extrem unübersichtlich.

von Rolf M. (rmagnus)


Lesenswert?

Rene K. schrieb:
> Da es sich um viele weitere Ebenen nach unten verschachtelt.
> Stellenweise bis zu 7 Ebenen. Da wird der Code, die Schleifen und die
> Abfragen schon extrem unübersichtlich.

So ist das bei XML öfter mal. Du musst ja nicht alles in einer einzigen 
Funktion bearbeiten, sondern kannst das auf mehrere Funktionen 
verteilen. So bleibt jede für sich gesehen übersichtlich.
So hast du dann beispielsweise eine Funktion extractMetaProperties, der 
du dein Element übergibst, und die dann über alle MetaProperty-Elemente 
darin iteriert und dir die daraus extrahierten Daten als Python-Objekt 
oder Liste zurückgibt.

: Bearbeitet durch User
von Rene K. (xdraconix)


Lesenswert?

Super, ja das mit den einzelnen Funktionen ist natürlich so ne Sache wie 
mit den Bäumen und den Wald. :-D

Ich habe es nun wie folgt, und kann sie so auch relativ leicht parsen, 
ich kann mir ja dann auch die üblichen Verdächtigen in eine Separate 
Funktion übergeben. z.b. string GetDirectVal(Device, MetaStructure, 
MetaProperty). Das mache ich aber heute nimmer :-D
1
from xml.dom import minidom
2
3
XMLOut = minidom.parse("ADUReport.xml")
4
5
# Device
6
#   |
7
#    -> MetaStructure
8
#          |
9
#           -> MetaProperty
10
#                 |
11
#                  -> MetaPropertyStructure
12
#                        |
13
#                         -> MetaStructure (without ID)
14
#                               |
15
#                                -> MetaProperty (multi)
16
17
18
19
# ----------------- Sub Functions
20
21
def GetDevice(Parent, deviceName):
22
  device = []
23
  SubDevice = Parent.getElementsByTagName('Device')
24
  for x in SubDevice:
25
    if x.hasAttribute('deviceType'):
26
      if x.attributes['deviceType'].value == deviceName:
27
        device.append(x)
28
  return device
29
30
def GetMetaStructure(Parent, MSName):
31
  MetaStructure = []
32
  SubMetaStructure = Parent.getElementsByTagName('MetaStructure')
33
  for x in SubMetaStructure:
34
    if x.hasAttribute('id'):
35
      if x.attributes['id'].value == MSName:
36
        MetaStructure.append(x)
37
  return MetaStructure
38
39
def GetMetaProperty(Parent, MPName):
40
  MetaProperty = []
41
  SubMetaProperty = Parent.getElementsByTagName('MetaProperty')
42
  for x in SubMetaProperty:
43
    if x.hasAttribute('id'):
44
      if x.attributes['id'].value == MPName:
45
        MetaProperty.append(x)
46
  return MetaProperty
47
48
def GetValue(arr, value):
49
  return arr.attributes[value].value
50
51
# |----------------- Sub Functions
52
53
for Device in GetDevice(XMLOut, "PhysicalDrive"):
54
  TextToPrint = GetValue(Device,"marketingName") + " - ID: " + GetValue(Device,"id")
55
  for MetaStructure in GetMetaStructure(Device, "Physical Drive Status"):
56
    for MetaProperty in GetMetaProperty(MetaStructure, "Drive Serial Number"):
57
      TextToPrint = TextToPrint + " - SN: " + GetValue(MetaProperty,"value")
58
      print(TextToPrint)

Bekomme ich dann folgende Ausgabe:
1
root@node-1:~/hddoverview# python3 test.py
2
Physical Drive (4 TB SAS HDD) 2I:1 - ID: AC:2799134361,PD:0 - SN: Z1ZA22C5
3
Physical Drive (4 TB SAS HDD) 2I:2 - ID: AC:2799134361,PD:1 - SN: Z1ZA23TW
4
Physical Drive (1.2 TB SAS HDD) 1I:1 - ID: AC:1715036241,PD:0 - SN: 001415DGSPLF        KZHGSPLF
5
Physical Drive (1.2 TB SAS HDD) 1I:2 - ID: AC:1715036241,PD:1 - SN: 001351DUTS6E        KZGUTS6E
6
Physical Drive (1.2 TB SAS HDD) 1I:4 - ID: AC:1715036241,PD:3 - SN: 001415DGTK7F        KZHGTK7F
7
Physical Drive (1.2 TB SAS HDD) 2I:5 - ID: AC:1715036241,PD:4 - SN: 001351DUVYKE        KZGUVYKE
8
Physical Drive (1.2 TB SAS HDD) 2I:7 - ID: AC:1715036241,PD:6 - SN: 001415DEX3PF        KZHEX3PF

Ich danke euch schonmal vielmals für die hilfreichen Denkanstöße, das 
hilft mir schon erheblichst weiter. :-D

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Rene K. schrieb:
> Super, ja das mit den einzelnen Funktionen ist natürlich so ne Sache wie
> mit den Bäumen und den Wald. :-D

Das Modul bs4 [1,2] wird zwar meistens zum Parsen und 
Auseinanderpflücken von HTML benutzt, kommt aber auch mit XML prima klar 
und hat den Vorteil, daß es dort sehr leistungsfähige Suchfunktionen für 
die Inhalte gibt.

[1] https://pypi.org/project/beautifulsoup4/
[2] https://www.crummy.com/software/BeautifulSoup/

von Oliver (imonbln)


Lesenswert?

Traditionell wird auch gerne das etree Element in Python verwendet, 
unter der Annahme, dass dein XML den Name "bla.xml" hat, sollte 
folgender Code dir ein Hinweis geben, wie man xml parsen könnte.
1
import xml.etree.ElementTree as ET
2
3
4
def show_child(xnode):
5
    for item in xnode:
6
        print(item.tag, item.attrib)
7
        show_child(item)
8
9
10
def show_by_xpath(xnode, path):
11
    for item in xnode.findall(path):
12
        print(item.tag, item.attrib)
13
14
15
def main():
16
    tree = ET.parse('bla.xml')
17
    root = tree.getroot()
18
    show_child(root)
19
    input("by xpath all errors (press Return)")
20
    show_by_xpath(root, "./Device/Errors/Message")
21
22
if __name__ == "__main__":
23
    main()

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Oliver schrieb:
> Traditionell wird auch gerne das etree Element in Python verwendet,
> unter der Annahme, dass dein XML den Name "bla.xml" hat,

Schick... aber mein Vorschlag wäre:
1
from argparse import ArgumentParser
2
import xml.etree.ElementTree as ET
3
4
def main():
5
    parser = ArgumentParser(description='...')
6
    parser.add_argument('filename', help='file to parse')
7
    args = parser.parse_args()
8
9
    tree = ET.parse(args.filename)
10
    # ...
11
12
if __name__ == "__main__":
13
    main()

von Oliver (imonbln)


Lesenswert?

Ein T. schrieb:
> Schick... aber mein Vorschlag wäre:

Auch fein, aber das Beispiel ist auf das Wesentliche reduziert. Es fehlt 
auch noch ein wenig Fehlerbehandlung, wenn die Datei nicht da ist oder 
nicht erwarteten Inhalt hat.

Ein T. schrieb:
> from argparse import ArgumentParser
> import xml.etree.ElementTree as ET

Wenn man pedantisch ist, könnte man hier anmerken, dass es gelebte 
Praxis ist, erst Import und dann from zu verwenden, aber das ist nicht 
durch Pep8 abgedeckt, sondern mehr sowas wie ein Gentlemans-Agreement 
unter Entwicklern und daher keine wirkliche Kritik.

von Ein T. (ein_typ)


Lesenswert?

Oliver schrieb:
> Auch fein, aber das Beispiel ist auf das Wesentliche reduziert. Es fehlt
> auch noch ein wenig Fehlerbehandlung, wenn die Datei nicht da ist oder
> nicht erwarteten Inhalt hat.

Dann gibt es eine Exception, und das Programm steigt mit Fehlermeldung 
und Traceback aus...

> Wenn man pedantisch ist, könnte man hier anmerken, dass es gelebte
> Praxis ist, erst Import und dann from zu verwenden, aber das ist nicht
> durch Pep8 abgedeckt, sondern mehr sowas wie ein Gentlemans-Agreement
> unter Entwicklern und daher keine wirkliche Kritik.

Ja, manche Entwickler halten das so, und ich auch -- allerdings mit 
einer anderen Regel: bei mir wird zuerst aus den Standardmodulen 
inkludiert, die Python mitliefert, danach wird aus externen Modulen 
inkludiert, die etwa mit pip oder Poetry installiert wurden, zuletzt 
kommen dann Includes aus meinen eigenen, selbstimplementierten Modulen. 
Die Include-Blöcke werden dabei mit Leerzeilen getrennt, so daß man 
sofort und auf den ersten Blick sieht, woher welcher Import stammt. 
Innerhalb der Blöcke befolge ich dann die genannte Regel, plus 
natürlich: niemals "from bla import *".

von Christian M. (likeme)


Lesenswert?

Was ist Parsen? Frage für mich, der jetzt keine Ahnung hat...

von Xanthippos (xanthippos)


Lesenswert?

> Wie kann ich da am dümmsten durch die einzelnen MetaStructures bei
> Device parsen?

Bei XML musst du nicht unbedingt DOM nehmen. Gibt auch Libraries wie 
xmltodict, die normale Python Objekte liefern. Aber schon alleine dass 
es dutzende davon gibt, zeigt - die sind auch nicht wesentlich besser 
als DOM.

von Heinz B. (Firma: Privat) (hbrill)


Lesenswert?

Christian M. schrieb:
> Was ist Parsen? Frage für mich, der jetzt keine Ahnung hat...

Parsen = auseinander dröseln und auf Korrektheit prüfen.

von Xanthippos (xanthippos)


Lesenswert?

> Was ist Parsen?

Mathematiker haben da eine klare Definition.

Scannen ist, wenn du ein Wort oder ein Zeichen nach dem anderen 
abarbeiten kannst.

Parsen ist, wenn du Rekursion brauchst. Wenn du z.B. zuerst alle 
<MetaProperty> Elemente parsen musst, bevor du das 
<ADUReport>...</ADUReport> abarbeiten kannst

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.