як видалити елемент у


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

Відповіді:


153

Використовуйте 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: ось і все, якби елемент, який потрібно видалити, не знаходився безпосередньо під кореневим вузлом, він би провалився.
Седрик Жульєн,

17
@ewok: дайте Седрику прийняти, оскільки він відповів на 1 секунду раніше за мене, і що ще важливіше, його відповідь була правильною :)
Жолудь

13

Я зустрів одну ситуацію:

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

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

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

Але все одно я не знаю, чи може це використовувати фільтр xpath для тегу. Просто поставте це для інформування.

Ось документ:

strip_elements (дерево_або_елемент, * теги_назви, 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, оскільки може бути такий випадок.
Eivydas Vilčinskas

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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.