在Python中使用elementTree搜索和删除元素

问题描述 投票:0回答:9

我有一个 XML 文档,我想在其中搜索某些元素以及它们是否符合某些条件 我想删除它们

但是,我似乎无法访问该元素的父元素以便删除它

file = open('test.xml', "r")
elem = ElementTree.parse(file)

namespace = "{http://somens}"

props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
    type = prop.attrib.get('type', None)
    if type == 'json':
        value = json.loads(prop.attrib['value'])
        if value['name'] == 'Page1.Button1':
            #here I need to access the parent of prop
            # in order to delete the prop

我有办法做到这一点吗?

谢谢

python elementtree
9个回答
42
投票

您可以使用相应的

remove
方法删除子元素。要删除一个元素,您必须调用其父元素
remove
方法。不幸的是
Element
没有提供对其父母的引用,因此您需要跟踪父母/孩子关系(这不利于您使用
elem.findall()

建议的解决方案可能如下所示:

root = elem.getroot()
for child in root:
    if child.name != "prop":
        continue
    if True:# TODO: do your check here!
        root.remove(child)

PS:不要使用

prop.attrib.get()
,使用
prop.get()
,如here所述。


8
投票

我知道这是一个旧线程,但当我试图找出类似的任务时,它不断弹出。我不喜欢接受的答案有两个原因:

1)它不处理多个嵌套级别的标签。

2) 如果同一级别中的多个xml标签被相继删除,就会崩溃。由于每个元素都是

Element._children
的索引,因此在向前迭代时不应删除。

我认为更好、更通用的解决方案是这样的:

import xml.etree.ElementTree as et
file = 'test.xml'
tree = et.parse(file)
root = tree.getroot()

def iterator(parents, nested=False):
    for child in reversed(parents):
        if nested:
            if len(child) >= 1:
                iterator(child)
        if True:  # Add your entire condition here
            parents.remove(child)

iterator(root, nested=True)

对于OP来说,这应该可行 - 但我没有你正在使用的数据来测试它是否完美。

import xml.etree.ElementTree as et
file = 'test.xml'
tree = et.parse(file)

namespace = "{http://somens}"
props = tree.findall('.//{0}prop'.format(namespace))

def iterator(parents, nested=False):
    for child in reversed(parents):
        if nested:
            if len(child) >= 1:
                iterator(child)
        if prop.attrib.get('type') == 'json':
            value = json.loads(prop.attrib['value'])
            if value['name'] == 'Page1.Button1':
                parents.remove(child)

iterator(props, nested=True)

6
投票

您可以使用 xpath 来选择元素的父元素。

file = open('test.xml', "r")
elem = ElementTree.parse(file)

namespace = "{http://somens}"

props = elem.findall('.//{0}prop'.format(namespace))
for prop in props:
    type = prop.get('type', None)
    if type == 'json':
        value = json.loads(prop.attrib['value'])
        if value['name'] == 'Page1.Button1':
            # Get parent and remove this prop
            parent = prop.find("..")
            parent.remove(prop)

http://docs.python.org/2/library/xml.etree.elementtree.html#supported-xpath-syntax

除非如果您尝试它不起作用:http://elmpowered.skawaii.net/?p=74

所以你必须:

file = open('test.xml', "r")
elem = ElementTree.parse(file)

namespace = "{http://somens}"
search = './/{0}prop'.format(namespace)

# Use xpath to get all parents of props    
prop_parents = elem.findall(search + '/..')
for parent in prop_parents:
    # Still have to find and iterate through child props
    for prop in parent.findall(search):
        type = prop.get('type', None)
        if type == 'json':
            value = json.loads(prop.attrib['value'])
            if value['name'] == 'Page1.Button1':
                parent.remove(prop)

这是两次搜索和一个嵌套循环。内部搜索仅针对已知包含 props 作为第一个子元素的元素,但这可能没有多大意义,具体取决于您的架构。


3
投票

使用lxml模块的解决方案

from lxml import etree

root = ET.fromstring(xml_str)
for e in root.findall('.//{http://some.name.space}node'):
parent = e.getparent()
for child in parent.find('./{http://some.name.space}node'):
    try:
        parent.remove(child)
    except ValueError:
        pass

2
投票

利用每个孩子都必须有父母这一事实,我将简化@kitsu.eb 的示例。如果使用 findall 命令获取孩子和父母,他们的索引将是相等的。

    file = open('test.xml', "r")
    elem = ElementTree.parse(file)

    namespace = "{http://somens}"
    search = './/{0}prop'.format(namespace)

    # Use xpath to get all parents of props    
    prop_parents = elem.findall(search + '/..')

    props = elem.findall('.//{0}prop'.format(namespace))
    for prop in props:
            type = prop.attrib.get('type', None)
            if type == 'json':
                value = json.loads(prop.attrib['value'])
                if value['name'] == 'Page1.Button1':
                    #use the index of the current child to find
                    #its parent and remove the child
                    prop_parents[props.index[prop]].remove(prop)

2
投票

我也使用 XPath 来解决这个问题,但方式不同:

root = elem.getroot()    
elementName = "YourElement"
#this will find all the parents of the elements with elementName
for elementParent in root.findall(".//{}/..".format(elementName)):
   #this will find all the elements under the parent, and remove them
   for element in elementParent.findall("{}".format(elementName)):
      elementParent.remove(element)

1
投票

我喜欢使用 XPath 表达式进行此类过滤。除非我知道,否则这样的表达式必须在根级别应用,这意味着我不能只获取父级并在该父级上应用相同的表达式。然而,在我看来,有一个很好且灵活的解决方案,它应该适用于任何受支持的 XPath,只要所寻找的节点都不是根节点。事情是这样的:

root = elem.getroot()
# Find all nodes matching the filter string (flt)
nodes = root.findall(flt)
while len(nodes):
    # As long as there are nodes, there should be parents
    # Get the first of all parents to the found nodes
    parent = root.findall(flt+'/..')[0]
    # Use this parent to remove the first node
    parent.remove(nodes[0])
    # Find all remaining nodes
    nodes = root.findall(flt)

1
投票

我只想对已接受的答案添加评论,但我缺乏声誉不允许我这样做。我想补充一点,将

.findall("*")
添加到迭代器以避免出现问题非常重要,如 文档中所述:

请注意,迭代时并发修改可能会导致问题,就像迭代和修改 Python 列表或字典时一样。因此,该示例首先使用 root.findall() 收集所有匹配元素,然后才迭代匹配列表。

因此,在接受的答案中,迭代应该是

for child in root.findal("*"):
而不是
for child in root:
。不这样做会使我的代码跳过列表中的一些元素。


0
投票

如果您偶然发现这个问题是因为您想使用 ElementTree 搜索和删除元素

  • 使用内置的 xml 模块(不是 lxml)
  • 与 ElementTree.findall 一样灵活(使用 xpath 子集)
  • 直接引用要删除的元素,而不是父元素
  • 适用于任何嵌套级别
  • 即使找到的元素嵌套在其他找到的元素中也能工作

那么这个功能可能会有所帮助。它构建并使用从元素到其父元素的映射。


import itertools
from xml.etree import ElementTree

def deleteall(root: ElementTree.Element, match, namespaces=None):
    parent_by_child=dict(itertools.chain.from_iterable(
        ((child, element) for child in element) for element in root.iter()))

    for element in root.findall(match, namespaces):
        parent_by_child[element].remove(element)

原始帖子中要求的附加检查可以通过作为附加参数提供的 Callable 来完成:


import itertools
from typing import Callable
from xml.etree import ElementTree

def deleteall(
    root: ElementTree.Element,
    match,
    namespaces=None,
    deletion_criteria: Callable[[ElementTree.Element], bool]=lambda x: True
):
    parent_by_child=dict(itertools.chain.from_iterable(
        ((child, element) for child in element) for element in root.iter()))
    for element in root.findall(match, namespaces):
        if deletion_criteria(element):
            parent_by_child[element].remove(element)

可以进行进一步的扩展,例如为删除条件提供元素及其父元素。

© www.soinside.com 2019 - 2024. All rights reserved.