如果我有一组给定的元素要保留,如何从 lxml 树中删除项目?

How to remove items from lxml tree if I have a given set of elements I want to keep?

提问人:Maciej 提问时间:10/19/2023 最后编辑:Maciej 更新时间:10/24/2023 访问量:79

问:

我正在编写一个Python xml(netconf)解析器,目标是从服务器获取rpc-reply xml,修改一些项目,并生成一个最小的配置.xml,然后可以发送到服务器。

在 GUI 中修改值时,我将修改后的元素以及它们的祖先元素和不包含子元素的同级元素添加到集合中,因为这将是“最小可行”结果文件的内容

我正在处理的示例(缩短的)xml:

<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:a1cfef75-dba4-4fdf-81eb-8d5f65d35511">
  <data>
    <bridges xmlns="urn:ieee:std:802.1Q:yang:ieee802-dot1q-bridge">
      <bridge>
        (...)
      </bridge>
    </bridges>
    <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
      <interface>
        <name>PORT_0</name>
        <description>random</description>
        <type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type>
        <bridge-port xmlns="urn:ieee:std:802.1Q:yang:ieee802-dot1q-bridge">
            (...)
        </bridge-port>
      </interface>
      <interface>
        (...)
      </interface>
    </interfaces>
    <keystore xmlns="urn:ietf:params:xml:ns:yang:ietf-keystore">
        (...)
    </keystore>
  </data>
</rpc-reply>

我在一起使用和一起使用时发现了一个问题,即当我想修改例如<描述>时,它只删除了 <bridge> 分支,然后循环不会返回到 <interfaces>,或者回到它的直接祖先,很可能是因为我已经删除了有关祖先的所有信息。换句话说,循环在第一次遇到“最后一片叶子”元素时停止。.iter().remove().iter().iter().

我正在使用以下代码来删除项目,是一组要保留的self.itemstokeepetree.Element

for item in treecopy.iter():
    if not item in self.itemstokeep:
        if not item.getparent() == None:
            item.getparent().remove(item)
    else:
        continue

您能推荐任何解决此问题或完全解决问题的好方法吗?
到目前为止,我在这里找到的答案的最大区别是,我不知道要删除哪些项目,只知道要保留哪些项目,而且除了 2 个顶级元素之外,我不会总是具有相同的输入结构,这使得通常的“xpath”方法变得复杂......

我还考虑过在修改元素时不再创建集合并基本上重建一棵树,但是如果它看起来是一个非优化的解决方案,因为我需要始终检查祖先之间的重复项并大量迭代树 - 但也许我也错过了一些东西。itemstokeep

python lxml netconf

评论

0赞 Hermann12 10/19/2023
你可以深度复制树的一部分,更改内容并再次插入,我在这里分享了一个例子
0赞 Hermann12 10/20/2023
遗憾的是,命名空间定义在共享的 xml 部分中并不是唯一的。

答:

0赞 Adrián Prestamo 10/19/2023 #1

这是一个解决方案。你基本上需要创建一个新的空树,因为你将添加所有你想保留的项目,而不是那些与条件不匹配的项目

# Parse the XML
root = ET.fromstring(xml_data)

# Set of elements to keep (you have this already)
items_to_keep = set()

# A function to recursively copy elements you want to keep
def copy_elements(element):
    if element in items_to_keep:
        # Clone the element and its attributes
        new_element = ET.Element(element.tag, element.attrib)
        # Copy the text (if any)
        new_element.text = element.text
        new_element.tail = element.tail
        # Recursively copy child elements
        for child in element:
            new_child = copy_elements(child)
            new_element.append(new_child)
        return new_element
    else:
        # If not in the items to keep, return None
        return None

# Create a new XML tree, starting from the root
new_root = copy_elements(root)

# Create a new XML tree and add the new root
new_tree = ET.ElementTree(new_root)

# Serialize the new tree to XML
new_xml = ET.tostring(new_root, encoding='unicode')

print(new_xml)
0赞 Momin Ali 10/19/2023 #2

解决此问题的一种方法是创建一个新的 XML 树,从根元素开始,然后仅添加要保留的元素。这样,可以避免从现有树中删除元素的问题,并确保生成的树仅包含所需的元素。这是你如何做到的:

from xml.etree import ElementTree as ET

# Assuming your original XML is stored in the 'xml_string' variable
root = ET.fromstring(xml_string)

# Create a new XML tree with the root element
new_tree = ET.Element(root.tag, nsmap=root.nsmap)

# A set of elements to keep
elements_to_keep = {"interfaces", "interface", "name", "description"}

# Initialize a stack for ancestors
ancestors = [new_tree]

# Iterate through the original tree
for elem in root.iter():
    if elem.tag in elements_to_keep:
        # Add the element to the current ancestor
        current_ancestor = ancestors[-1]
        current_ancestor.append(elem)

        # If the element has children, push it onto the stack of ancestors
        if list(elem):
            ancestors.append(elem)
    elif elem.tag == ancestors[-1].tag:
        # If we encounter an element with the same tag as the current ancestor,
        # pop it from the stack to move back up the tree
        ancestors.pop()

# Convert the new tree to a string
new_xml_string = ET.tostring(new_tree).decode()

# Print the resulting XML
print(new_xml_string)
0赞 Maciej 10/24/2023 #3

张贴的答案不起作用。如果将来有人遇到类似的问题,我通过使用 2 个循环的解决方法解决了这个问题:

  1. 第一个循环创建项目集的“负数”,即我要删除的项目集,首先定义为空deleteset = set()

  2. 第 2 次循环遍历创建的集合并删除先前定义的元素

        for item in treecopy.iter():
           if not item in self.copyitems_to_keep:                 
              if not item.getparent() == None:                     
                 deleteset.add(item)
              else:                     
                 continue
    
        for item in deleteset:
           item.getparent().remove(item)
    

感谢 Hermann12 对原始问题的评论,我也意识到我在代码的另一部分中的错误 - 最初我没有用于创建根元素,这导致了应用程序中的另一类问题。deepcopy()treecopy

如果将来有人偶然发现这个线程,我仍然想知道是否有办法在删除包含子元素的元素后强制不进入不再存在的树枝。.iter()