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

回答:


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 2011年

Elementクラスに「pop」メソッドがないのは残念です。
pumazi 2015

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 2011年

この回答とセドリックが提供した回答の違いについてコメントできますか?
ewok 2011年

3
ああ、私.remove()は要素があなたがそれを呼んでいる要素の子である必要があるという事実を見落としました。したがって、削除する要素の親で呼び出す必要があります。回答が修正されました。
どんぐり

@Acorn:それだけです。削除する要素がルートノードの直下になかった場合、失敗します。
セドリックジュリアン

17
@ewok:Cédricが私より1秒早く答えたので、受け入れてください。さらに重要なことに、彼の答えは正しかったです:)
Acorn

13

私は1つの状況に遭遇しました:

<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činskas

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>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.