web-dev-qa-db-ja.com

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

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のルートノードの直下にない場合でも、私のものは動作します。

135
Cédric Julien

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>
28
Acorn

私は1つの状況に出会った:

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

div.remove(script)は、意図していないtext here部分を削除します。

答え 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
       )
11
zephor

すでに述べたように、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>
2
Messa