如何删除lxml中的元素


84

我需要使用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>"

Answers:


153

使用removexmlElement的方法:

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功能。调用树的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

您能否评论此答案与Cedric提供的答案之间的区别?
ewok

3
啊,我忽略了一个事实,即.remove()要求元素必须是您正在调用的元素的子元素。因此,您需要在要删除的元素的父元素上调用它。答案已更正。
Acorn

@Acorn:就是这样,如果要删除的元素不在根节点的正下方,则它将失败。
塞德里克朱利安

17
@ewok:让塞德里克接受,因为他比我早了1秒钟,更重要的是,他的回答是正确的:)
Acorn

13

我遇到一种情况:

<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

如前所述,您可以使用该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,因为可能会出现这种情况。
EivydasVilčinskas19年

1

您也可以使用lxml中的html来解决该问题:

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.