как удалить элемент в lxml

84

Мне нужно полностью удалить элементы на основе содержимого атрибута, используя lxml python. Пример:

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)

Я хочу напечатать это:

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

Есть ли способ сделать это, не сохраняя временную переменную и не печатая ее вручную, например:

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

newxml+="</groceries>"
эвок
источник

Ответы:

155

Используйте removeметод 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)

Если бы мне пришлось сравнивать с версией @Acorn, моя будет работать, даже если удаляемые элементы не находятся непосредственно под корневым узлом вашего xml.

Седрик Жюльен
источник
1
Можете ли вы прокомментировать различия между этим ответом и ответом Acorn?
ewok
Жаль, что у класса Element нет метода pop.
pumazi
29

Вы ищете removeфункцию. Вызовите метод удаления дерева и передайте ему подэлемент для удаления.

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)

Результат:

<groceries>
  <fruit state="fresh">pear</fruit>
  <fruit state="fresh">starfruit</fruit>
  <fruit state="fresh">peach</fruit>
</groceries>
Желудь
источник
Вы только что получили для меня ответы на все вопросы, связанные с lxml, не так ли? ;-)
ewok
Можете ли вы прокомментировать различия между этим ответом и ответом Седрика?
ewok
3
Ах, я упустил из виду тот факт, что .remove()элемент должен быть дочерним по отношению к элементу, который вы его вызываете. Поэтому вам нужно вызвать его у родителя элемента, который вы хотите удалить. Ответ исправлен.
Acorn
@Acorn: вот и все, если бы удаляемый элемент не находился непосредственно под корневым узлом, он бы потерпел неудачу.
Cédric Julien
17
@ewok: дайте Седрику принять, поскольку он ответил на 1 секунду раньше меня, и, что более важно, его ответ был правильным :)
Acorn
14

Встречал одну ситуацию:

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

div.remove(script) удалит text here часть, которую я не хотел.

после ответа здесь я обнаружил, что etree.strip_elementsэто лучшее решение для меня, которое вы можете контролировать, удаляете ли вы текст позади с помощью with_tail=(bool)параметра.

Но все же я не знаю, можно ли использовать фильтр xpath для тега. Просто поставьте это для информирования.

Вот документ:

strip_elements (tree_or_element, * tag_names, with_tail = True)

Удалите все элементы с указанными именами тегов из дерева или поддерева. Это удалит элементы и все их поддерево, включая все их атрибуты, текстовое содержимое и потомков. Он также удалит хвостовой текст элемента, если вы явно не установитеwith_tail параметра аргумента ключевого слова значение False.

Имена тегов могут содержать подстановочные знаки, как в _Element.iter.

Обратите внимание, что это не приведет к удалению переданного вами элемента (или корневого элемента ElementTree), даже если он совпадает. Он будет лечить только своих потомков. Если вы хотите включить корневой элемент, проверьте имя его тега непосредственно перед вызовом этой функции.

Пример использования:

   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
       )
зефор
источник
2

Как уже упоминалось, вы можете использовать этот remove()метод для удаления (под) элементов из дерева:

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

Но он удаляет элемент, включая его tail, что является проблемой, если вы обрабатываете документы со смешанным содержимым, такие как HTML:

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

Становится

<div></div>

Я полагаю, что это то, чего вы не всегда хотите :) Я создал вспомогательную функцию, чтобы удалить только элемент и сохранить его хвост:

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)

Таким образом, он сохранит текст хвоста:

<div> Hello!</div>
Месса
источник
1
Проверьте el.tail is not None, как может быть такой случай.
Эйвидас Вилчинскас
1

Вы также можете использовать html из lxml, чтобы решить эту проблему:

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"))

Он должен вывести это:

//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>
Гювен Дегирменчи
источник