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

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

74 голосов
/ 02 ноября 2011

Мне нужно полностью удалить элементы, основанные на содержимом атрибута, используя 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>"

Ответы [ 4 ]

132 голосов
/ 02 ноября 2011

Используйте 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.

27 голосов
/ 02 ноября 2011

Вы ищете функцию 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>
11 голосов
/ 28 декабря 2016

Я встретил одну ситуацию:

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

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

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

Но все же я не знаю, может ли это использовать фильтр 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 голосов
/ 01 декабря 2018

Как уже упоминалось, вы можете использовать метод 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>
...