Seite 1 von 2

Problem mit lxml

Verfasst: Montag 3. November 2008, 11:02
von blubber
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?

Verfasst: Montag 3. November 2008, 11:30
von HWK
Wurde Dir diese Frage nicht schon hier beantwortet?
MfG
HWK

Verfasst: Montag 3. November 2008, 11:35
von blubber
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.

Verfasst: Montag 3. November 2008, 11:57
von cofi
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 ;)

Verfasst: Montag 3. November 2008, 12:37
von blubber
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....

Verfasst: Montag 3. November 2008, 13:30
von cofi
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.

Verfasst: Montag 3. November 2008, 13:38
von blubber
naja, ist ja nicht so, als ob ich mir das nicht schon durchgelesen und rumprobiert hätte...

Verfasst: Montag 3. November 2008, 14:23
von blubber
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?

Verfasst: Montag 3. November 2008, 14:45
von Leonidas
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.

Verfasst: Montag 3. November 2008, 15:11
von blubber
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...

Verfasst: Montag 3. November 2008, 15:18
von 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.

Verfasst: Montag 3. November 2008, 16:25
von HWK
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

Verfasst: Montag 3. November 2008, 18:19
von Leonidas

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)

Verfasst: Montag 3. November 2008, 20:30
von 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'

Verfasst: Montag 3. November 2008, 22:51
von Leonidas
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.

Verfasst: Montag 3. November 2008, 23:32
von 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 ;)

Verfasst: Dienstag 4. November 2008, 07:35
von blubber
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

Verfasst: Dienstag 4. November 2008, 07:40
von blubber
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)

Verfasst: Dienstag 4. November 2008, 10:38
von blubber
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

Verfasst: Dienstag 4. November 2008, 10:46
von Leonidas
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'