XML

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.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

MarcelF6 hat geschrieben: Noch eine Schlussfrage: Was würdest du unter "Text des body-Elements" verstehen? Ist das einfach alles, inklusive Tags (wie z.B. titel) oder nur Text (ohne Tags) des nodes, den man übergibt?
Unter "Text" in Zusammenhang mit XML-Elementen würde ich nur an einen Text-Knoten denken. Dieser beinhaltet nur Text - folgende Tags repräsentieren ja dann neue, separate Knoten. (Bei `lxml` verzichtet man auf Text-Knoten und repräsentiert diese mittels der Properties `element.text` und `element.tail` - das ändert imho aber nichts am Prinzip.)

Allerdings mag es sein, dass sich die Bedeutung je nach Kontext ergibt; in einer dokumentzentrierten XML-Datei (ich denke bei "body" an XHTML...) könnte durchaus der gesamte Ast unterhalb des Elementes gemeint sein.

Steht obige Formulierung in einer Aufgabe? Oder wieso fragst Du das?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@MarcelF6: Falls hier wirklich von (X)HTML die Rede ist, dann kommt ``<title>`` nicht im ``<body>`` vor, sondern im ``<head>``. Die Beschreibung „Text des body-Elements” ist IMHO wie Hyperion schon schrieb in so einem Szenario nicht wirklich eindeutig. Das kann nur die Textknoten meinen, die direkte Kinder von ``<body>`` sind, aber auch rekursiv sämtliche Textknoten.

In letzteren Fall solltest Du noch mal die Methoden von `lxml.Element` anschauen, bevor Du etwas von Hand bastelst was es schon fertig gibt. Falls nur die direkten Kind-Textknoten gemeint sind, kommt man da mit XPath einfacher dran als sich alle `text`- und `tail`-Attribute in einer Schleife zusammen zu sammeln.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Ok, dann liegt das nicht an meinem Verständnis - danke :)
Da man vorher den gesamten Text (inkl. Subelemente) zurückgeben musste, wird wohl das (also der ganze Text, jedoch ohne Tags) gemeint sein. Sonst würde man ja etwas retournieren, obwohl man es dann doch nicht braucht - was etwa so sinnvoll wäre wie tausend roots... (sorry nochmals :) )
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Noch eine letzte Frage: Ich habe soeben festgestellt, dass ich in dem String, den ich retourniere (also in dem Text von 'node' inklusive der Subelemente) viele "None" habe. Wie bringt man die wieder weg; bzw. was müsste ich machen, damit sie gar nicht erst auftauchen?

Ich habs mal mit "replace" versucht, aber so werden natürlich nur jene "None" erwischt, die alleine stehen. "Leider" gibt es auch (vor allem) solche, die an andere Wörter kleben.
Ich hoffe, ihr habt eine gute Idee :)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Kann es sein, dass Du - wovon BlackJack Dir ja *abgeraten* hatte - einfach auf allen Children die `.text` und `.tail`-Properties wahllos aufrufst?

Zeig uns doch mal ein minimales, lauffähiges Beispiel (Mit dem nötigen XML als String vorliegend), mit welchem der Fehler nachvollziehbar wird!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@MarcelF6: `None` kann nicht „an andere Wörter (sic!) kleben”. `None` hat einen ganz anderen Datentyp als Wörter. Und nochmal der Hinweis auf die verschiedenen Methoden die `Element`-Objekte haben. Da ist eine dabei, die so ziemlich genau das macht was Du haben möchtest.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Hm doch, eigentlich verwende ich schon .text und .tail. Vor allem auch dewegen, weil ich die *passende* Methode leider noch nicht gefunden habe...

Code: Alles auswählen

def read(node):
  s = " " 
  for element in doc.iter(node):
    for ele in element.iter():
      s += "".join(" %s %s" % (ele.text, ele.tail))
Die letzte Zeile könnte man natürlich auch einfacher als s += (" %s %s" % (ele.text, ele.tail)) schreiben - kommt ja hier aufs Gleiche raus.
BlackJack

@MarcelF6: Wie hast Du denn gesucht? Zielgerichtetes Vorgehen wäre zum Beispiel sich den Typ von einem Element ausgeben zu lassen und dann in der API-Dokumentation zu dem konkreten Typ die Liste der Methoden durchgehen ob da Eine dabei ist mit der man über Textknoten iterieren kann. Oder man inspiziert so ein Objekt direkt in einer Python-Shell. Lässt sich mit `dir()` die Methoden auflisten und lässt sich von den Methoden die vom Namen her in Frage kommen mit `help()` den DocString anzeigen. Der Name der Methode ist offensichtlich.

Im `lxml`-Tutorial gibt es übrigens noch einen Weg das ganze noch einfacher zu erreichen: Ein Funktionsaufruf der nicht nur die Textknoten liefert, sondern die Texte sogar schon zu einer einzigen Zeichenkette zusammen setzt.

Den `join()`-Aufruf könntest Du in der Tat weg lassen. Wenn Du das weisst, warum schreibst Du solchen Unsinn dann überhaupt?

Das wiederholte ``+=`` sollte man auch vermeiden. Mit einem *sinvollen* `join()`-Aufruf auf einer Liste der Teilzeichenketten, die man vorher gesammelt hat.

In Deiner Funktion wird immer noch auf ein Objekt zugegriffen das nicht als Argument übergeben wurde. `doc` dürfte die Funktion so gar nicht kennen.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Danke für die Antworten!
Darf ich fragen, wo die einfachere Möglichkeit im Tutorial zu finden ist?
BlackJack

@MarcelF6: Arbeite es doch einfach mal durch.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Ok, ich habs. Mit Hilfe von iterparse hats so geklappt, wie gewollt - und ohne "None" ' s :)
Besten Dank für die Hilfe und all die Tipps! :)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Im Abschnitt "Elements contains Text"hättest Du `etree.tostring(html, method="text")` gefunden :roll:

Darunter geht es dann auch gleich weiter mit der XPath-Methode...

und das hast Du nicht gefunden?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

Vor allem ist das alles deutlich *vor* den naheliegenden Lösungen im Tutorial. Und ich sehe auch nicht wie `iterparse()` eine Lösung sein kann, denn auch dort haben die Attribute `text` und `tail` den Wert `None` wenn kein Textknoten dafür vorhanden ist. Eben so wie das bei `Element`-Objekten zu erwarten ist. Was immer MarcelF6 da auch anders gemacht hat, es hat rein gar nichts mit `iterparse()` zu tun.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Stimmt. Mit iterparse() habe ich aber zusätzlich noch Abfragen gemacht, um zu prüfen, ob Text vorhanden ist oder nicht. Falls ja, so konnte der Inhalt dem String hinzugefügt werden, falls nein wurde einfach zum nächsten Element gegangen. Funktioniert super so, obwohl man es offenbar in ein paar wenigen Zeilen machen könnte.

ich habe den Artikel tatsächlich mal auf dem Bildschirm, ihn aber nur rasch überflogen.
Ich habe mir xpath mal angeschaut und wollte es für mein Beispiel anwenden. Allerdings erhalte ich so fast mehr Fehler, als ich es vorher mal hatte :P

Wie wäre es denn richtig?
Analog zum Beispiel im Tutorial habe ich es wie folgt probiert:

Code: Alles auswählen

import lxml.etree as ET

doc = ET.parse("beispiel.xml")

def lesen(node):
  root = ET.Element("root") # neuer Wurzelknoten gemacht.
  # nun müsste man body anfügen per: ET.SubElement(html, doc.xpath(node)) 
  # ABER: dieses doc.xpath(node) kann man so natürlich nicht übergeben. .text würde auch nicht viel bringen
  
Naja, diese Variante wäre im Moment natürlich noch kleiner als meine jetzige. Aber ich denke, dass man - um den Text zu erhalten - durch alle Kindknoten durchiterieren müsste. In einem solchen Fall wäre die Variante nicht mehr viel einfacher...
Oder ginge es anders?
BlackJack

@MarcelF6: Analog zu welchem Beispiel in welchem Tutorial ist der Quelltext‽ Und was soll das werden? Jedenfalls nicht die Textknoten in einem vorhandenen (Unter)Baum ermitteln, denn dann würde mich der Sinn vom erstellen eines neuen Elements nicht erschliessen‽

Es wurde zwar schon mal verlinkt, aber nur zur Sicherheit, das `lxml`-Tutorial ist das hier: http://lxml.de/tutorial.html
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Ich beziehe mich auf den Link von Hyperion (d.h. dasselbe Tutorial).

In dem Beispiel wird ja folgendes gemacht:

Code: Alles auswählen

print(html.xpath("string()")) # lxml.etree only!
TEXTTAIL
wobei vorher die Knoten und Texte erstellt werden:

Code: Alles auswählen

html = etree.Element("html")
body = etree.SubElement(html, "body")
body.text = "TEXT"
Nun habe ich ja aber schon ein existierendes xml-file mit existierenden Knoten und Texte. Der Clue ist ja der erste Code-Ausschnitt, denn dort wird der Text ausgegeben. Sehe ich das richtig, dass ich einfach per Iteration jeweils xpath aufrufen muss (und anstatt "string()" jeweils der Knoten bzw. Kindknoten angeben)?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Probiere solche Kleinigkeiten doch einfach mal in einer Shell aus. Das geht doch viel schneller, als wenn Du hier auf eine Antwort wartest ;-)

Code: Alles auswählen

In [12]: data = """
<root>
Hallo<br/>Welt!. Du bist <b>so</b> schön!
</root>
"""

In [13]: from lxml import etree

In [14]: root = etree.fromstring(data)

In [15]: print(etree.tostring(root, method="text", encoding="utf-8"))
b'\nHalloWelt!. Du bist so sch\xc3\xb6n!\n'
oder auch mit XPath:

Code: Alles auswählen

In [16]: root.xpath("string()")
Out[16]: '\nHalloWelt!. Du bist so schön!\n'
Du musst da nix iterieren! Hättest Du doch leicht selber drauf kommen können, oder? ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Danke für das Beispiel. Aber so ganz überzeugt hast du mich noch nicht ;)
Darf ich dein Beispiel etwas erweitern?

Angenommen, wir haben:

Code: Alles auswählen

data = """
<root>
Hey
<titel>
Hallo<br/>Welt!. Du bist <b>so</b> schön!
</titel>
bye
</root>
"""
Und wir wollen nur das ausgeben, das in 'titel' und den Subelementen steht (d.h. "Hallo Welt! Du bist so schön!"). Dafür ist dann schon eine Iteration nötig, oder doch nicht?
BlackJack

@MarcelF6: Nein. Dazu musst Du nur das Titelelement übergeben statt dem ganzen Baum. Und das *find*et man ohne Schleife.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Ok, mein Beispiel war dumm, bzw. noch nicht auf die Spitze getrieben.
Sagen wir, dass wir folgendes haben:

Code: Alles auswählen

data = """
<root>
Hey
<titel>
Hallo<br/>Welt!. Du bist <b>so</b> schön!
</titel>
<titel> Und ich hoffe <untertitel> das bleibt </untertitel> so! </titel>
bye
</root>
"""
Und wir immernoch am Text von allen 'titel' (inkl. Unterelemente) interessiert sind. In dem Fall kommt man tatsächlich nicht um die Iteration herum, oder?
Zuletzt geändert von MarcelF6 am Sonntag 13. Mai 2012, 20:12, insgesamt 1-mal geändert.
Antworten