วิธีลบองค์ประกอบใน lxml


85

ฉันต้องการลบองค์ประกอบทั้งหมดตามเนื้อหาของแอตทริบิวต์โดยใช้ lxml ของ python ตัวอย่าง:

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

คำตอบ:


155

ใช้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 ของฉันจะใช้งานได้แม้ว่าองค์ประกอบที่จะลบจะไม่ได้อยู่ภายใต้โหนดรูทของ xml ของคุณโดยตรง


1
คุณสามารถแสดงความคิดเห็นเกี่ยวกับความแตกต่างระหว่างคำตอบนี้กับคำตอบของ Acorn ได้หรือไม่?
ewok

1
เป็นเรื่องน่าเสียดายที่คลาส Element ไม่มีเมธอด 'ป๊อป'
pumazi

มันน่าเสียดายที่ xpath สามารถใช้เพื่อเลือกองค์ประกอบเท่านั้น มันเหมือนกับ SQL ที่มีเฉพาะคำสั่ง select
Eric Chow

29

คุณกำลังมองหา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

คุณสามารถแสดงความคิดเห็นเกี่ยวกับความแตกต่างระหว่างคำตอบนี้กับคำตอบที่เซดริกได้หรือไม่?
ewok

3
อ่าฉันมองข้ามความจริงที่.remove()ต้องการให้องค์ประกอบเป็นลูกขององค์ประกอบที่คุณเรียกมัน ดังนั้นคุณต้องเรียกมันบนพาเรนต์ขององค์ประกอบที่คุณต้องการลบ แก้ไขคำตอบแล้ว
Acorn

@Acorn: นั่นแหล่ะถ้าองค์ประกอบที่จะลบไม่ได้อยู่ภายใต้โหนดรูทโดยตรงมันจะล้มเหลว
Cédric Julien

17
@ewok: ให้Cédricยอมรับในขณะที่เขาตอบเร็วกว่าฉัน1 วินาทีและที่สำคัญคำตอบของเขาถูกต้อง :)
Acorn

14

ฉันเจอสถานการณ์หนึ่ง:

<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ตัวเลือกอาร์กิวเมนต์คำหลักเป็น 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เนื่องจากอาจมีกรณีดังกล่าว
Eivydas Vilčinskas

1

คุณยังสามารถใช้ html จาก lxml เพื่อแก้ปัญหานั้น:

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.