我有一个 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
我有办法做到这一点吗?
谢谢
您可以使用相应的
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所述。
我知道这是一个旧线程,但当我试图找出类似的任务时,它不断弹出。我不喜欢接受的答案有两个原因:
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)
您可以使用 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 作为第一个子元素的元素,但这可能没有多大意义,具体取决于您的架构。
使用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
利用每个孩子都必须有父母这一事实,我将简化@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)
我也使用 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)
我喜欢使用 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)
我只想对已接受的答案添加评论,但我缺乏声誉不允许我这样做。我想补充一点,将
.findall("*")
添加到迭代器以避免出现问题非常重要,如 文档中所述:
请注意,迭代时并发修改可能会导致问题,就像迭代和修改 Python 列表或字典时一样。因此,该示例首先使用 root.findall() 收集所有匹配元素,然后才迭代匹配列表。
因此,在接受的答案中,迭代应该是
for child in root.findal("*"):
而不是for child in root:
。不这样做会使我的代码跳过列表中的一些元素。
如果您偶然发现这个问题是因为您想使用 ElementTree 搜索和删除元素
那么这个功能可能会有所帮助。它构建并使用从元素到其父元素的映射。
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)
可以进行进一步的扩展,例如为删除条件提供元素及其父元素。