Как удалить элемент в 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>"
Ответы
Ответ 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>