Eine XML-Datei mit lxml lesen

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
BlueDogi
User
Beiträge: 30
Registriert: Mittwoch 29. April 2015, 22:25

Hallo in die Runde,

Ich habe bisher wenig mit Python gemacht, und eine XML Auslesen noch nie.
Und zwar würde ich gerne erst : /root/alarm[0]/aktiv auslesen und dan /root/alarm[1]/aktiv. Also beim ersten würde ich das Ergebnis "true" erwarten und beim zweite das Ergebnis "false".

Wenn das was in dem Python Skript steht richtig verstanden habe ist es volgender Ablauf:
1. die Datei wird geparst xmlData = etree.parse(inFile)
2. Erstellen einer Liste mit den Adressen aller gefundenen alarm Tags messages = xmlData.findall("alarm")
3. Inerhalb von alarm nach dem Tag "aktiv" suchen und dessen wert Ausgeben Wert = readXML(messages, "aktiv")
4. Als Wert bekomme ich "false".

Also liest der den zweiten alarm aus.

Meine Idee war es nun da messeges eine Liste enthält mit einem Index zu arbeiten. Also habe ich die Zeile so geändert Wert = readXML(messages[0], "aktiv") um den ersten alarm zu erhalten.

Leider geht das nicht.
Als antwort erhalte ich dann:

Code: Alles auswählen

Ausgabe von  /var/www/data/alarmClock.xml
findall alarm  [<Element alarm at 0x1ca3ad0>, <Element alarm at 0x1ca3af8>, <Element alarm at 0x1ca3b20>, <Element alarm at 0x1ca3b48>, <Element alarm at 0x1ca3b70>, <Element alarm at 0x1ca3b98>, <Element alarm at 0x1ca3bc0>]
messages ist vom Typ <class 'list'>
Ausgabe von  /var/www/data/alarmClock.xml
readXML mode  None
Wert ist vom Typ <class 'NoneType'>
Die XML-Datei:

Code: Alles auswählen

<root>
  <alarm>
    <aktiv>true</aktiv>
    <repeat>false</repeat>
    <mode>standard</mode>
    <monday>false</monday>
    <tuesday>false</tuesday>
    <wednesday>false</wednesday>
    <thursday>false</thursday>
    <friday>false</friday>
    <saturday>false</saturday>
    <sunday>false</sunday>
    <time>00:00</time>
  </alarm>
  <alarm>
    <aktiv>false</aktiv>
    <repeat>false</repeat>
    <mode>standard</mode>
    <monday>true</monday>
    <tuesday>false</tuesday>
    <wednesday>false</wednesday>
    <thursday>false</thursday>
    <friday>false</friday>
    <saturday>false</saturday>
    <sunday>false</sunday>
    <time>00:00</time>
  </alarm>
</root>
Das Python Skript:

Code: Alles auswählen

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

from lxml import etree
import time

def readXML(a, Text):
    for b in a:
        c = b.findtext(Text)
    return c

# Konfiguration
inFile = "/var/www/data/alarmClock.xml"                    # XML-Datei
# XML-Datei einlesen
xmlData = etree.parse(inFile)

messages = xmlData.findall("alarm")
print("Ausgabe von ", inFile)
print("findall alarm ", messages)
print("messages ist vom Typ", type(messages))

Wert = readXML(messages, "aktiv")
print("Ausgabe von ", inFile)
print("readXML mode ", Wert)
print("Wert ist vom Typ", type(Wert)) 
Ich habe da wohl was Falsch verstanden... Kann mich da mal einer aufklären?
Vielen Dank.
BlackJack

@BlueDogi: Du suchst nach dem *Text* 'aktiv' aber so ein *Text* kommt in der gesamten XML-Datei nicht vor. Es gibt Elemente die als Tag 'aktiv' haben und als Text dann 'true' oder 'false'.

Wie bekommst Du eine Liste mit sieben <alarm>-Elementen bei einer XML-Datei die nur zwei davon enthält?

Die `readXML()` macht keinen Sinn. Du gehst da in einer Schleife alle Elemente von `a` durch und bindest jedes mal `c` neu, so das am Ende *immer* das Ergebnis vom letzten Schleifendurchlauf zurückgegeben wird, egal was die Elemente davor als Ergebnis hatten. Für die Namen `a`, `b`, und `c` gehört jemand verhauen. ;-)

In der Beschreibung was Du machen möchtest verwendest Du ja schon so etwas wie XPath-Notation. `lxml` kann das:

Code: Alles auswählen

In [11]: root.xpath('./alarm/aktiv/text()')
Out[11]: ['true', 'false']
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: findtext liefert den Textinhalt des ersten Elements, das gefunden wird.
@BlueDogi: anscheinend hat das letzte <alarm>-Element kein <aktiv>-Element. Mit Deiner for-Schleife gehst Du ja schon die Liste durch. Mit Indizes arbeitet man übrigens in Python eher selten.
So bekommst Du von jedem <alarm>-Element den aktiv-Wert:

Code: Alles auswählen

for alarm in messages:
    print(alarm.findtext('aktiv'))
BlueDogi
User
Beiträge: 30
Registriert: Mittwoch 29. April 2015, 22:25

Danke für die Hilfe meine Lösung sieht jetzt so aus:

Code: Alles auswählen

def readXML(messages, Text):
    Wert = []
    for alarm in messages:
        Wert.append(alarm.findtext(Text))
    return Wert
BlackJack

@BlueDogi: Wobei die Namen deutlich verbesserungswürdig sind. Was soll `readXML()` bedeuten? Die Funktion liest kein XML. Warum ist der gesuchte Tagname an den Namen `Text` gebunden? Warum heissen die <alarm>-Elemente `messages`? Warum ist eine Liste mit Wert*en* (Mehrzahl) an den Namen `Wert` (Einzahl) gebunden? Warum englische und deutsche Namen gemischt? Ausserdem könnte man sich an die Namenskonventionen halten, also `read_xml()`, `text`, und `wert`.
BlueDogi
User
Beiträge: 30
Registriert: Mittwoch 29. April 2015, 22:25

Ich habe die Namen noch einmal geändert. Hoffe das es so verständlicher ist.

Code: Alles auswählen

def find_xml_value(xml_data, node_name, child_name):
    data = xml_data.findall(node_name)
    for alarm in data:
        value = (alarm.findtext(child_name))
    return value
Ich hab noch eine Frage. Wie kann ich Werte in der XML ändern?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlueDogi: die Funktion macht immer noch keinen wirklichen Sinn. Was soll die for-Schleife, wenn doch nur das letzte Element zurückgegeben wird?

Du kannst dem text-Attribut einfach einen Wert zuweisen:

Code: Alles auswählen

alarm.find('aktiv').text="false"
Antworten