wie man ein Element in lxml entfernt

84

Ich muss Elemente basierend auf dem Inhalt eines Attributs mit Pythons lxml vollständig entfernen. Beispiel:

import lxml.etree as et

xml="""
<groceries>
  <fruit state="rotten">apple</fruit>
  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="rotten">mango</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>
"""

tree=et.fromstring(xml)

for bad in tree.xpath("//fruit[@state=\'rotten\']"):
  #remove this element from the tree

print et.tostring(tree, pretty_print=True)

Ich möchte dies drucken:

<groceries>
  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>

Gibt es eine Möglichkeit, dies zu tun, ohne eine temporäre Variable zu speichern und manuell darauf zu drucken:

newxml="<groceries>\n"
for elt in tree.xpath('//fruit[@state=\'fresh\']'):
  newxml+=et.tostring(elt)

newxml+="</groceries>"
ewok
quelle

Antworten:

151

Verwenden Sie die removeMethode eines xmlElement:

tree=et.fromstring(xml)

for bad in tree.xpath("//fruit[@state=\'rotten\']"):
  bad.getparent().remove(bad)     # here I grab the parent of the element to call the remove directly on it

print et.tostring(tree, pretty_print=True, xml_declaration=True)

Wenn ich mit der @ Ancorn-Version vergleichen müsste, funktioniert meine auch dann, wenn sich die zu entfernenden Elemente nicht direkt unter dem Stammknoten Ihrer XML befinden.

Cédric Julien
quelle
1
Können Sie die Unterschiede zwischen dieser Antwort und der von Acorn kommentieren?
Ewok
Es ist eine Schande, dass die Element-Klasse keine Pop-Methode hat.
Pumazi
29

Sie suchen die removeFunktion. Rufen Sie die Methode remove des Baums auf und übergeben Sie ihm ein zu entfernendes Unterelement.

import lxml.etree as et

xml="""
<groceries>
  <fruit state="rotten">apple</fruit>
  <fruit state="fresh">pear</fruit>
  <punnet>
    <fruit state="rotten">strawberry</fruit>
    <fruit state="fresh">blueberry</fruit>
  </punnet>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="rotten">mango</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>
"""

tree=et.fromstring(xml)

for bad in tree.xpath("//fruit[@state='rotten']"):
    bad.getparent().remove(bad)

print et.tostring(tree, pretty_print=True)

Ergebnis:

<groceries>
  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>
Eichel
quelle
Sie haben gerade alle lxml-bezogenen Antworten für mich, nicht wahr? ;-)
ewok
Können Sie die Unterschiede zwischen dieser und der von Cedric bereitgestellten Antwort kommentieren?
Ewok
3
Ah, ich habe die Tatsache übersehen, dass .remove()das Element ein Kind des Elements sein muss, auf das Sie es aufrufen. Sie müssen es also auf dem übergeordneten Element des Elements aufrufen, das Sie entfernen möchten. Antwort korrigiert.
Eichel
@Acorn: Das war's, wenn das zu entfernende Element nicht direkt unter dem Wurzelknoten wäre, wäre es fehlgeschlagen.
Cédric Julien
17
@ewok: Geben Sie Cédric die Annahme, als er 1 Sekunde früher als ich antwortete , und was noch wichtiger ist, seine Antwort war richtig :)
Acorn
13

Ich habe eine Situation getroffen:

<div>
    <script>
        some code
    </script>
    text here
</div>

div.remove(script)wird den text hereTeil entfernen, den ich nicht wollte.

Nach der Antwort hier fand ich, dass dies etree.strip_elementseine bessere Lösung für mich ist, mit der Sie steuern können, ob Sie den Text dahinter mit with_tail=(bool)param entfernen oder nicht .

Aber ich weiß immer noch nicht, ob dies den xpath-Filter für Tags verwenden kann. Setzen Sie dies einfach zur Information.

Hier ist das Dokument:

strip_elements (tree_or_element, * tag_names, with_tail = True)

Löschen Sie alle Elemente mit den angegebenen Tag-Namen aus einem Baum oder Teilbaum. Dadurch werden die Elemente und ihr gesamter Teilbaum entfernt, einschließlich aller Attribute, Textinhalte und Nachkommen. Außerdem wird der Endtext des Elements entfernt, sofern Sie die with_tailOption für das Schlüsselwortargument nicht explizit auf False setzen.

Tag-Namen können Platzhalter wie in enthalten _Element.iter.

Beachten Sie, dass dadurch das übergebene Element (oder ElementTree-Stammelement) nicht gelöscht wird, auch wenn es übereinstimmt. Es wird nur seine Nachkommen behandeln. Wenn Sie das Stammelement einschließen möchten, überprüfen Sie dessen Tag-Namen direkt, bevor Sie diese Funktion aufrufen.

Anwendungsbeispiel ::

   strip_elements(some_element,
       'simpletagname',             # non-namespaced tag
       '{http://some/ns}tagname',   # namespaced tag
       '{http://some/other/ns}*'    # any tag from a namespace
       lxml.etree.Comment           # comments
       )
Zephor
quelle
2

Wie bereits erwähnt, können Sie mit dieser remove()Methode (Unter-) Elemente aus dem Baum löschen:

for bad in tree.xpath("//fruit[@state=\'rotten\']"):
  bad.getparent().remove(bad)

Das Element einschließlich seiner Elemente wird jedoch entfernt. Dies tailist ein Problem, wenn Sie Dokumente mit gemischtem Inhalt wie HTML verarbeiten:

<div><fruit state="rotten">avocado</fruit> Hello!</div>

Wird

<div></div>

Welches ist ich nehme an, was Sie nicht immer wollen :) Ich habe eine Hilfsfunktion erstellt, um nur das Element zu entfernen und seinen Schwanz zu behalten:

def remove_element(el):
    parent = el.getparent()
    if el.tail.strip():
        prev = el.getprevious()
        if prev:
            prev.tail = (prev.tail or '') + el.tail
        else:
            parent.text = (parent.text or '') + el.tail
    parent.remove(el)

for bad in tree.xpath("//fruit[@state=\'rotten\']"):
    remove_element(bad)

Auf diese Weise bleibt der Endtext erhalten:

<div> Hello!</div>
Messa
quelle
1
Überprüfen Sie das el.tail is not None, da es einen solchen Fall geben könnte.
Eivydas Vilčinskas
1

Sie können auch HTML von lxml verwenden, um das zu lösen:

from lxml import html

xml="""
<groceries>
  <fruit state="rotten">apple</fruit>
  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="rotten">mango</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>
"""

tree = html.fromstring(xml)

print("//BEFORE")
print(html.tostring(tree, pretty_print=True).decode("utf-8"))

for i in tree.xpath("//fruit[@state='rotten']"):
    i.drop_tree()

print("//AFTER")
print(html.tostring(tree, pretty_print=True).decode("utf-8"))

Es sollte dies ausgeben:

//BEFORE
<groceries>
  <fruit state="rotten">apple</fruit>
  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="rotten">mango</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>


//AFTER
<groceries>

  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>

  <fruit state="fresh">peach</fruit>
</groceries>
Guven Degirmenci
quelle