如何删除lxml中的元素

时间:2011-11-02 14:19:00

标签: python xml 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>"

6 个答案:

答案 0 :(得分:131)

使用xmlElement的remove方法:

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 :(得分:27)

您正在寻找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>

答案 2 :(得分:11)

我遇到了一种情况:

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

div.remove(script)会删除我并不意味着的text here部分。

在回答here之后,我发现etree.strip_elements对我来说是一个更好的解决方案,您可以控制是否删除with_tail=(bool) param背后的文字。

但我仍然不知道这是否可以使用xpath过滤器来标记。只需将其用于通知。

这是doc:

  

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
       )

答案 3 :(得分:1)

如上所述,您可以使用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>

答案 4 :(得分:0)

您也可以使用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>

答案 5 :(得分:0)

remove 函数从树中分离元素,因此删除 XML 节点(元素、PI 或注释)、其内容(后代项)和 tail 文本。在这里,保留 tail 文本是多余的,因为它只包含空格和换行符,可以将其视为可忽略的空格。

要删除元素(及其内容),保留其 tail,您可以使用以下函数:

def remove_node(child, keep_content=False):
    """
    Remove an XML element, preserving its tail text.

    :param child: XML element to remove
    :param keep_content: ``True`` to keep child text and sub-elements.
    """
    parent = child.getparent()
    parent_text = parent.text or u""
    prev_node = child.getprevious()
    if keep_content:
        # insert: child text
        child_text = child.text or u""
        if prev_node is None:
            parent.text = u"{0}{1}".format(parent_text, child_text) or None
        else:
            prev_tail = prev_node.tail or u""
            prev_node.tail = u"{0}{1}".format(prev_tail, child_text) or None
        # insert: child elements
        index = parent.index(child)
        parent[index:index] = child[:]
    # insert: child tail
    parent_text = parent.text or u""
    prev_node = child.getprevious()
    child_tail = child.tail or u""
    if prev_node is None:
        parent.text = u"{0}{1}".format(parent_text, child_tail) or None
    else:
        prev_tail = prev_node.tail or u""
        prev_node.tail = u"{0}{1}".format(prev_tail, child_tail) or None
    # remove: child
    parent.remove(child)

这是一个演示:

from lxml import etree

tree = etree.XML(u"<root>text <bad>before <bad>inner</bad> after</bad> tail</root>")
bad1 = tree.xpath("//bad[1]")[0]
remove_node(bad1)

etree.dump(tree)
# <root>text  tail</root>

如果你想保留内容,你可以这样做:

tree = etree.XML(u"<root>text <bad>before <bad>inner</bad> after</bad> tail</root>")
bad1 = tree.xpath("//bad[1]")[0]
remove_node(bad1, keep_content=True)

etree.dump(tree)
# <root>text before <bad>inner</bad> after tail</root>