Problem mit lxml

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.
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

Hi,

hab da ein Problem mit lxml, hab jetzt schon zweimal die Docu durchgelesen und rumprobiert aber ich bekomms einfach nicht gebacken :oops:
Und zwar hab ich eine test.xml:

Code: Alles auswählen

 <test name = "Test1"> 
   <datensatz ID = "1"> 
       <daten 
              Vorname="hans" 
              Nachname = "mustermann"/> 
   </datensatz> 
</test> 
Dazu noch ein Python File:

Code: Alles auswählen

from lxml import etree

XML = etree.parse("test.xml")
temp = etree.tostring(XML)
print temp
Jo, dieses parsed mir meine XML, packts als String in die Variable temp und printed das ganze Zeugs raus.
Nun raff ich aber einfach nicht, wie ich z.B. den Vornamen von "hans" auf "otto" ändern kann.
Könnte mir jemand auf die Sprünge helfen?
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Wurde Dir diese Frage nicht schon hier beantwortet?
MfG
HWK
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

ja nicht direkt, weil ich ja erstmal keinen String als Quelle hab, sondern ein XML File. Aber habs nun hinbekommen.
XML einlesen -> zu string konvertieren -> als string wieder einlesen und dann die Schleife von BlackJack nehmen.
Keine Ahnung, ob das von der Logik her so konsequent ist, aber es funktioniert.
Ganz kapiert hab ich das LXML noch nicht wirklich.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ob aus einem String oder einer Datei ist für lxml unerheblich. Das ändert nur die Initialisierung. lxml.etree operiert immer auf einem elementtree, den erzeugst du entweder aus einem String oder aus einer Datei.

Code: Alles auswählen

from StringIO import StringIO
from lxml import etree
xml_string = """<test name = "Test1">
   <datensatz ID = "1">
       <daten
              Vorname="hans"
              Nachname = "mustermann"/>
   </datensatz>
</test>"""
parsed = etree.parse(StringIO(xml_string))
print etree.tostring(parsed)
StringIO simuliert ein File-Object aus einem String. Vielleicht geht dir das Licht ja damit auf ;)
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

hm ok, wie ich grad festgestellt habe, komm ich doch nicht weiter.
Denn meine XML enthält natürlich mehrere Datensätze (mit Namen und Vornamen) und ich möchte bei

Code: Alles auswählen

<Gesamt zeit = "heute">
	<test name = "Test1"> 
	   <datensatz ID = "1"> 
	       <daten 
	              Vorname="otto" 
	              Nachname = "mustermann"/> 
	   </datensatz> 
	   <datensatz ID = "5"> 
	       <daten 
	              Vorname="hans" 
	              Nachname = "lummelmann"/> 
	   </datensatz> 
	</test>
	<test name = "Test2">
	</test>
	<test name = "Test3">
	</test>
</Gesamt>
zum Beispiel NUR den Namen hans nach otto ändern, und nicht ALLE Vornamen....
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Entweder könntest du die daten deiner datensätze traversieren und dabei die `Vorname'-Attribute vergleichen, oder du schaust dir XPath und den entsprechenden Teil der lxml-Dokumentation an.
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

naja, ist ja nicht so, als ob ich mir das nicht schon durchgelesen und rumprobiert hätte...
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

Kann man eigentlich direkt ein Attribut setzen? Also ohne, dass man irgendwelche Schleifen durchlaufen muss?
Denn mir ist quasi alles bekannt, also ich weis zum Beispiel:

zeit = "heute"
name = "Test1"
ID = "5"
Vorname = "hans"

Mir ist also grundsätzlich der komplette Pfad zum "Zielattribut" das ich ändern möchte bekannt.
Das müsste dann doch auch ohne Schleife gehen oder?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

blubber hat geschrieben:Mir ist also grundsätzlich der komplette Pfad zum "Zielattribut" das ich ändern möchte bekannt.
Das müsste dann doch auch ohne Schleife gehen oder?
Logisch, cofi hat ja XPath bereits erwähnt. Alternativ kann man da sicher auch mit CSS-Selektoren etwas machen, wenn einem XPath zu kompliziert ist.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

Hab mir die Doku zu Xpath ja durchgelesen, aber wirklich gecheckt hab ich das nicht. Bzw. das was in der Doku steht war (aus meiner Sicht) für mein Problem nicht hilfreich, vielleicht fehlt mir da aber auch die Weitsicht die man benötigt, keine Ahnung.
Könnte mir denn jemand mit einem Beispiel weiterhelfen? Scheint ja nur eine Art Einzeiler zu sein wenn man direkt auf ein Attribut zugreifen kann...
lunar

cofi hat geschrieben:Ob aus einem String oder einer Datei ist für lxml unerheblich. Das ändert nur die Initialisierung. lxml.etree operiert immer auf einem elementtree, den erzeugst du entweder aus einem String oder aus einer Datei.

Code: Alles auswählen

from StringIO import StringIO
from lxml import etree
xml_string = """<test name = "Test1">
   <datensatz ID = "1">
       <daten
              Vorname="hans"
              Nachname = "mustermann"/>
   </datensatz>
</test>"""
parsed = etree.parse(StringIO(xml_string))
print etree.tostring(parsed)
StringIO simuliert ein File-Object aus einem String. Vielleicht geht dir das Licht ja damit auf ;)
"etree.fromstring()" existiert, das StringIO-Objekt ist unnötig.
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Ich glaube, cofi wollte damit zeigen, wie es mit einem File bzw. Filelike-Object funktioniert, da blubber seine Datei erst als String geladen und dann den String weiter verarbeitet hat.
MfG
HWK
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Code: Alles auswählen

from lxml import etree as ET
from lxml.cssselect import CSSSelector

root = ET.fromstring("""<Gesamt zeit = "heute">
    <test name = "Test1">
       <datensatz ID = "1">
           <daten
                  Vorname="otto"
                  Nachname = "mustermann"/>
       </datensatz>
       <datensatz ID = "5">
           <daten
                  Vorname="hans"
                  Nachname = "lummelmann"/>
       </datensatz>
    </test>
    <test name = "Test2">
    </test>
    <test name = "Test3">
    </test>
</Gesamt> """)
CSSSelector('daten[Vorname="hans"]')(root)[0].attrib['Vorname'] = 'otto'
print ET.tostring(root)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

Das ist jetzt aber kein gutes Beispiel für "wenn einem XPath zu kompliziert ist":

Code: Alles auswählen

root.xpath('//daten[@Vorname="hans"]')[0].attrib['Vorname'] = 'otto'
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

BlackJack hat geschrieben:Das ist jetzt aber kein gutes Beispiel für "wenn einem XPath zu kompliziert ist"
Nein, eher nicht. Aber ich finde den CSS-Selektor trotzdem marginal einfacher zu verstehen, eben weil da kein ``//`` und ``@`` vorkommt, wo man erst nachschauen müsste was das bedeutet. Insbesondere da es ja wie der CSS-Selektor vormacht, auch ohne geht. Aber ich finde generell CSS-Selektoren angenehm, bin wohl schon zu sehr jQuery-geschädigt, von daher halte ich den Einsatz davon durchaus vertretbar.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
lunar

Ich muss die Bedeutung nicht nachsehen. Auch würde ich bei jemandem, der sich mit XML beschäftigt, eher XPath Kenntnisse erwarten, insofern ist der Einsatz von XPath letztlich sogar lesbarer, wenn der Code auch von Nicht-Webentwicklern gelesen wird.

Außerdem ist CSS verglichen mit XPath doch eher limitiert. Mit XPath kann man z.B. komfortabel Textknoten extrahieren, was mit CSS nicht möglich ist. Außerdem kann man mit XPath auf sichere Weise dynamische Ausdrücke erstellen, da XPath Variablen kennt, was bei CSS ebenfalls nicht der Fall ist. Da wird ein Anführungszeichen im Namen einer Person mitunter zum Problem ;)
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

Hallo zusammen,

erstmal danke, dass ihr versucht mir zu helfen und mein "Unverständnis" zu beseitigen.
Die angegebene Lösung

Code: Alles auswählen

root.xpath('//daten[@Vorname="hans"]')[0].attrib['Vorname'] = 'egon'
funktioniert zwar, aber ist jetzt nicht wirklich eindeutig oder?
Also es können natürlich mehrere "Hans" vorhanden sein, mit teils unterschiedlichen aber teils sogar gleichen Nachnamen.
Eine absolute Eindeutigkeit, welcher Vorname und Nachname nun wirklich gemeint ist, erreicht man nur über den kompletten Pfad. Daher gab ich an, dass alles bekannt ist (und meiner Meinung auch sein muss).
Sprich, die Lösung muss auch folgende Informationen beinhalten:

Gesamt zeit = heute
test name = Test1
datensatz ID = 5
Vorname = hans

Nur dann wird wirklich der "hans" getroffen, der gemeint ist oder? Vielleicht könnt ihr mir diesbezüglich nochmals helfen...

Grüße
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

Ok, ich glaub sogar, ich habs n bissl gecheckt und diese Lösung hier gefunden:

Code: Alles auswählen

from lxml import etree as ET 

root = ET.fromstring("""<Gesamt zeit = "heute"> 
    <test name = "Test1"> 
       <datensatz ID = "1"> 
           <daten 
                  Vorname="otto" 
                  Nachname = "mustermann"/> 
       </datensatz>
       <datensatz ID = "4"> 
           <daten 
                  Vorname="hans" 
                  Nachname = "stachelmann"/> 
       </datensatz>        
       <datensatz ID = "5"> 
           <daten 
                  Vorname="hans" 
                  Nachname = "lummelmann"/> 
       </datensatz>
    </test> 
    <test name = "Test2"> 
    </test> 
    <test name = "Test3"> 
    </test> 
</Gesamt> """) 

root.xpath('/Gesamt[@zeit="heute"]/test[@name="Test1"]/datensatz[@ID="4"]/daten')[0].attrib['Vorname'] = 'Manni'
print ET.tostring(root)
blubber
User
Beiträge: 123
Registriert: Montag 19. März 2007, 09:08

Bin noch auf zwei weitere, kleine Probleme gestoßen, die mir grad Kopfzerbrechen bereiten. Wie ich festgestellt habe, kann meine XML pro Datensatz mehrere Daten enthalten, also so:

Code: Alles auswählen

from lxml import etree as ET 

root = ET.fromstring("""<Gesamt zeit = "heute"> 
    <test name = "Test1"> 
       <datensatz ID = "1"> 
           <daten 
                  Vorname="otto" 
                  Nachname = "waldmann"/> 
       </datensatz>
       <datensatz ID = "4"> 
           <daten 
                  Vorname="hans" 
                  Nachname = "stachelmann"/> 
           <daten 
                  Vorname="hans" 
                  Nachname = "mustermann"/> 
       </datensatz>        
    </test> 
    <test name = "Test2"> 
    </test> 
    <test name = "Test3"> 
    </test> 
</Gesamt> """) 

root.xpath('/Gesamt[@zeit="heute"]/test[@name="Test1"]/datensatz[@ID="4"]/daten')[0].attrib['Vorname'] = 'Manni'
print ET.tostring(root)
Problem1: Dieses [0] am Ende der XPath Anweisung bedeutet ja, dass er den Vornamen von "hans stachelmann" in "Manni" ändern würde, also das 0-te Element. Nun ist es aber so, dass mir nicht die Nummer bekannt ist, sondern eben alle Attribute.
Also ich weis, ich möchte den Vornamen "hans" ändern, welcher den Nachnamen "stachelmann" besitzt. Kann man diese Bedingung irgendwie einbauen und stattdessen quasi auf die Nummerierung verzichten?

Problem2: Ich hab die ganzen Informationen natürlich in Variablen stehen. Also zum Beispiel bei

Code: Alles auswählen

.../Gesamt[@zeit="heute"]/...
steht das "heute" in der Variable Zeitpunkt.
Wenn ich nun aber

Code: Alles auswählen

Zeitpunkt = "heute"
.../Gesamt[@zeit=Zeitpunkt]/...
schreibe, dann bekomm ich einen Fehler. List index out of range.
Wie ist die Syntax, um Variablen anstatt direkt Strings nehmen zu können?


Grüße
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

blubber hat geschrieben:Wie ist die Syntax, um Variablen anstatt direkt Strings nehmen zu können?
Das sind ganz normale Strings, also so wie man das in Strings immer machen kann

Code: Alles auswählen

zeitpunkt = "heute"
print ".../Gesamt[@zeit=%(zeitpunkt)]/..." % {'zeitpunkt' : zeitpunkt}
Zu deiner anderen Frage, es geht und ist trivial. Keine Ahnung warum du das nicht einfach ausprobiert hast:

Code: Alles auswählen

root.xpath('/Gesamt[@zeit="heute"]/test[@name="Test1"]/datensatz[@ID="4"]/daten[@Nachname="stachelmann"]')[0].attrib['Vorname'] = 'Manni'
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten