替换 Python 中的整个 XML 树

Replacing whole XML tree in Python

如何替换整个 XML 树? 假设我有一个如下所示的文件:

<root>
  <folder>
    <elem1>'something here'</elem1>
    <elem2>'more stuff here'</elem2>   
    <elem3>
       <sub1>'something else here'</sub1>
       <sub2>'blablabla'</sub2>
    </elem3>
    <elem4>'even more stuff here with subelements too'</elem4> 
  </folder>
</root>

我还有另一个 xml 文件,可以替代 elem3,看起来像:

    <NewElem>
       <Difsub1>'something else here, but different'</Difsub1>
       <Difsub2>'all sorts of different blablabla'</Difsub2>
    </NewElem>

我需要用 NewElem 替换 elem3,结果是:

<root>
  <folder>
    <elem1>'something here'</elem1>
    <elem2>'more stuff here'</elem2>   
    <NewElem>
       <Difsub1>'something else here, but different'</Difsub1>
       <Difsub2>'all sorts of different blablabla'</Difsub2>
    </NewElem>
    <elem4>'even more stuff here with subelements too'</elem4>  
  </folder>
</root>

我正在使用 xml.etree.ElementTree 并尝试 append 但我最终得到 NewElem,在 </folder> 之后。我无法删除更新的 xml 文件的 elem3

我试着用这个附加它:

import xml.etree.ElementTree as ET

tree = ET.parse('base.xml')
baseroot = tree.getroot()
tree2 = ET.parse('new.xml')
newroot = tree2.getroot()

old_element = baseroot.findall('.//elem3')
baseroot.append(newroot)
baseroot.remove(old_element)

因此 NewElem 被附加到文件夹之后,我需要在 elem3 所在的同一位置,或者至少在 <folder> 内 另外,删除时出现错误: TypeError: remove() argument must be xml.etree.ElementTree.Element, not list 如果我改成

old_element = baseroot.find('.//elem3')
baseroot.append(newroot)
baseroot.remove(old_element)

我得到非常相似的错误:ValueError: list.remove(x): x not in list

下面使用的是 lxml 包,比 ElementTree 更高级的 XML 包。 您可以使用父元素的 index(element) 方法找到要替换的节点的索引。

一旦你得到它,你可以使用来自父节点的 insert(index, element) 并将新节点插入旧节点的位置。

接下来是通过remove(element)方法删除旧节点

例子'p'是父元素,a是要替换的p的子节点,b是新的子节点:

p.insert(p.index(a), b) # insert b before a
p.remove(a)

使用ElementTree,您需要先找到旧元素的索引:

p = et.Element('parent') # parent node
a = et.Element('child1') # this is the child node to be replaced
b = et.Element('child2') # this is the new child node
p.append(a) 
index = list(p).index(a)
p[index] = b

如果你坚持使用'ElementTree',首先你应该意识到擦除只对直接包含你要删除的节点的节点有效。

所以在这样搜索的时候

old_element = baseroot.find('.//elem3')

很好,你不能从 baseroot 中删除它,你需要获取它所在的位置,然后从那里删除它。最简单的一定是得到它的父

old_element_parent = xml.find('.//elem3/..')
old_element_parent.remove(old_element )

可以使用 SubElement 添加新元素

a = ET.SubElement(old_element_parent, 'NewElement')

首先感谢@egur和@Henrik的指导,特意指点不同的库。我正在使用 lxml (and this documentation),所以我可以使用以下代码做我想做的事:

from lxml import etree

tree = etree.parse('base.xml')
new = etree.tostring(etree.parse('new.xml')) # parsing to a string so it can be appended later

for elem in tree.xpath(".//elem3"): # finds the parent where the element to be replaced are
    elem.getparent().append(etree.fromstring(new)) #append in the end of parent, the fromstring() is because append don't like elementTree
    elem.getparent().remove(elem)

print(etree.tostring(tree, encoding="utf-8").decode('utf-8'))

以上代码产生以下结果:

<root>
  <folder>
    <elem1>'something here'</elem1>
    <elem2>'more stuff here'</elem2>   
    <elem4>'even more stuff here with subelements too'</elem4> 
  <NewElem>
       <Difsub1>'something else here, but different'</Difsub1>
       <Difsub2>'all sorts of different blablabla'</Difsub2>
    </NewElem></folder>
</root>

请注意,NewElemelem4 之后,不完全是 elem3 所在的位置,而是在 folder 之内,所以我认为这解决了我想要的问题。