Подтвердить что ты не робот

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

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

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>"
4b9b3361

Ответ 1

Используйте 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, моя работа будет работать, даже если элементы для удаления не находятся непосредственно под корневым node вашего xml.

Ответ 2

Вы ищете функцию 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>

Ответ 3

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

<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 аргумента ключевого слова 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
       )

Ответ 4

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

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>