[Python] XML Parsen

OP #7431028
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?

#7431071
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?

OP #7431160
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.

#7431214
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.

OP #7431545
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

#7431626
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/

#7432447
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()
#7432467
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()
#7432755
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.

#7433723
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 *".

#7433732
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.

#7433745
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren