如何使用 Python 中的 ElementTree 删除带有 parents 的 iterparse 的 XML 部分?

How can I remove XML parts with iterparse with parents included using ElementTree in Python?

我有多个大文件需要导入并遍历它们 - 所有文件都是 xml 并且具有相同的树结构。 结构是这样的,除了 ID 之外还有一些额外的文本,所以在 Start 下有更多的 children 元素标签: 我想做的是输入一个我知道是错误的 ID 列表,然后从整个 XML 文件中删除该报告。一份报告介于两个 "T" 之间。

<Header>
        <Header2>
           <Header3>
           <T>
              <Start> 
                <Id>abcd</Id>
              </Start>
           </T>
           <T>
              <Start> 
                <Id>qrlf</Id>
              </Start>
           </T>
           </Header3>
        </Header2>
</Header>

我目前拥有的:

from xml.etree import cElementTree as ET

file_path = '/path/to/my_xml.xml'
to_remove = []
root = None
for event, elem in ET.iterparse(file_path, events=("start", "end")):
if event == 'end':
    if elem.tag == 'Id':
        new_root = elem
        #print([elem.tag for elem in new_root.iter()])
        for elem2 in new_root.iter('Id'):
             id = elem2.text
             if id =='abcd':
                print(id)
                to_remove.append(new_root)
root = elem
for item in to_remove:
    root.remove(item)

所以上面的代码显然不起作用,因为根是以 Header 开头的整个 xml 文件,它无法准确找到我要删除的子元素,因为它的 parent 是 Header3 而不是 Header。

所以期望的输出是:

<Header>
        <Header2>
           <Header3>
           <T>
              <Start> 
                <Id>qrlf</Id>
              </Start>
           </T>
           </Header3>
        </Header2>
</Header>

展望未来,我要删除的不是单个值,而是数千个值,因此将成为一个列表,我只是认为以这种方式表示问题更容易。 感谢任何帮助。

由于您的 XML 结构很简单,因此使用 Xpath 可能更容易(大约下降 https://docs.python.org/3/library/xml.etree.elementtree.html 的 1/3)。以下是文档页面该部分的用法示例:

import xml.etree.ElementTree as ET

root = ET.fromstring(countrydata)

# Top-level elements
root.findall(".")

# All 'neighbor' grand-children of 'country' children of the top-level
# elements
root.findall("./country/neighbor")

# Nodes with name='Singapore' that have a 'year' child
root.findall(".//year/..[@name='Singapore']")

# 'year' nodes that are children of nodes with name='Singapore'
root.findall(".//*[@name='Singapore']/year")

# All 'neighbor' nodes that are the second child of their parent
root.findall(".//neighbor[2]")

可以在文档页面的顶部找到用于示例的 XML 结构。

第二个示例显示了一种简单的方法来 select 您想要删除的子元素("T" 在您的情况下)但在您的情况下,倒数第二个情况可能更有用。但是请参阅示例下方出现的 Xpath 语法部分中的 [tag='text'] 操作。
将该操作的结果发送到删除操作(页面下方 ~3/4),然后是 XML 树写入操作(页面下方 ~4/5)以获取清理后的 XML。

上面假设你传递的是一个字符串,你必须使用解析从文件输入,例如:

import xml.etree.ElementTree as ET
tree = ET.parse('country_data.xml')
root = tree.getroot()

** 免责声明 *** 我正在做类似的工作,但我实际上并没有尝试这样做。因此,将此视为灵感,而不是完整的解决方案。

顺便说一句,我正在使用 python 3.7.4。对于那些还不知道的人,您可以使用版本 select 或在文档页面的左上角 select 您正在使用的版本。

我觉得你可以用

ids_to_remove = ['abcd']

elements_to_remove = []

for event, element in ET.iterparse('file.xml'):
    if element.tag == 'T' and element.find('Start/Id').text in ids_to_remove:
        elements_to_remove.append(element)
    if element.tag == 'Header3':
        for el in elements_to_remove:
            element.remove(el)
            el.clear()
    if element.tag == 'Header':
        root = element

ET.dump(root)

我还没有测试过它是如何处理大文件的,显然它首先收集所有要删除的元素,最后删除它们,我不确定 ElementTree API 中是否有分离 elementif element.tag == 'T' and element.find('Start/Id').text in ids_to_remove: 分支中,也许以下会更早地释放元素:

ids_to_remove = ['abcd', 'baz', 'bar']


for event, element in ET.iterparse('file.xml', events = ['start', 'end']):
    if event == 'end' and element.tag == 'T' and element.find('Start/Id').text in ids_to_remove:
        header3.remove(element)
        element.clear()
    if event == 'start' and element.tag == 'Header3':
        header3 = element;
    if element.tag == 'Header':
        root = element


ET.dump(root)