Skip to main content
 首页 » 编程设计

python之如何将空树节点作为空字符串写入xml文件

2025年05月04日40jackei

我想删除某个标记值的元素,然后写出 .xml 文件,不包含那些已删除元素的任何标记;是我创建新树的唯一选择吗?

有两个选项可以移除/删除一个元素:

clear() Resets an element. This function removes all subelements, clears all attributes, and sets the text and tail attributes to None.

起初我使用它,它的目的是从元素中删除数据,但我仍然留下一个空元素:

# Remove all elements from the tree that are NOT "job" or "make" or "build" elements 
log = open("debug.log", "w") 
for el in root.iter(*): 
 
    if el.tag != "job" and el.tag != "make" and el.tag != "build": 
        print("removed = ", el.tag, el.attrib, file=log) 
        el.clear() 
    else: 
        print("NOT", el.tag, el.attrib, file=log) 
 
log.close() 
tree.write("make_and_job_tree.xml", short_empty_elements=False) 

问题是 xml.etree.ElementTree.ElementTree.write() still writes out empty tags no matter what:

...The keyword-only short_empty_elements parameter controls the formatting of elements that contain no content. If True (the default), they are emitted as a single self-closed tag, otherwise they are emitted as a pair of start/end tags.

为什么没有不打印那些空标签的选项!随便。

然后我想我可以试试

remove(subelement) Removes subelement from the element. Unlike the find* methods this method compares elements based on the instance identity, not on tag value or contents.

但这只对子元素起作用。

所以我必须do something like :

for el in root.iter(*): 
    for subel in el: 
        if subel.tag != "make" and subel.tag != "job" and subel.tag != "build": 
            el.remove(subel) 

但是这里有一个大问题:我通过删除元素使迭代器无效,对吧?

通过添加 if subel 来简单地检查元素是否为空就足够了吗?:

if subel and subel.tag != "make" and subel.tag != "job" and subel.tag != "build" 

或者我是否必须在每次使它无效时都为树元素获取一个新的迭代器?

记住:我只是想写出没有空元素标签的 xml 文件。

这是一个例子。

<?xml version="1.0"?> 
<data> 
    <country name="Liechtenstein"> 
        <rank>1</rank> 
        <year>2008</year> 
        <gdppc>141100</gdppc> 
        <neighbor name="Austria" direction="E"/> 
        <neighbor name="Switzerland" direction="W"/> 
    </country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
        <neighbor name="Malaysia" direction="N"/> 
    </country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
        <neighbor name="Costa Rica" direction="W"/> 
        <neighbor name="Colombia" direction="E"/> 
    </country> 
</data> 

假设我想删除对neighbor 的任何提及。 理想情况下,我希望在删除后得到以下输出:

<?xml version="1.0"?> 
<data> 
    <country name="Liechtenstein"> 
        <rank>1</rank> 
        <year>2008</year> 
        <gdppc>141100</gdppc> 
    </country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
    </country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
    </country> 
</data> 

问题是,当我使用 clear() 运行代码(参见上面的第一个代码块)并将其写入文件时,我得到了这个:

<data> 
    <country name="Liechtenstein"> 
        <rank>1</rank> 
        <year>2008</year> 
        <gdppc>141100</gdppc> 
        <neighbor></neighbor><neighbor></neighbor></country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
        <neighbor></neighbor></country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
        <neighbor></neighbor><neighbor></neighbor></country> 
</data> 

注意 neighbor 仍然出现。

我知道我可以轻松地在输出上运行正则表达式,但必须有一种方法(或另一个 Python api)可以即时执行此操作,而不是要求我再次触摸我的 .xml 文件.

请您参考如下方法:

import lxml.etree as et 
 
xml  = et.parse("test.xml") 
 
for node in xml.xpath("//neighbor"): 
    node.getparent().remove(node) 
 
 
xml.write("out.xml",encoding="utf-8",xml_declaration=True) 

使用 elementTree,我们需要找到邻居节点的父节点,然后找到该父节点内的邻居节点 并删除它们:

from xml.etree import ElementTree as et 
 
xml  = et.parse("test.xml") 
 
 
for parent in xml.getroot().findall(".//neighbor/.."): 
      for child in parent.findall("./neighbor"): 
          parent.remove(child) 
 
 
xml.write("out.xml",encoding="utf-8",xml_declaration=True) 

两者都会给你:

<?xml version='1.0' encoding='utf-8'?> 
<data> 
    <country name="Liechtenstein"> 
        <rank>1</rank> 
        <year>2008</year> 
        <gdppc>141100</gdppc> 
        </country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
        </country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
        </country> 
</data> 

使用您的属性逻辑并修改 xml,如下所示:

x = """<?xml version="1.0"?> 
<data> 
    <country name="Liechtenstein"> 
        <rank>1</rank> 
        <year>2008</year> 
        <gdppc>141100</gdppc> 
        <neighbor name="Austria" direction="E"/> 
        <neighbor name="Switzerland" direction="W"/> 
    </country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
           <neighbor name="Costa Rica" direction="W" make="foo" build="bar" job="blah"/> 
        <neighbor name="Malaysia" direction="N"/> 
    </country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
        <neighbor name="Costa Rica" direction="W" make="foo" build="bar" job="blah"/> 
        <neighbor name="Colombia" direction="E"/> 
    </country> 
</data>""" 

使用 lxml:

import lxml.etree as et 
 
xml = et.fromstring(x) 
 
for node in xml.xpath("//neighbor[not(@make) and not(@job) and not(@make)]"): 
    node.getparent().remove(node) 
print(et.tostring(xml)) 

会给你:

 <data> 
    <country name="Liechtenstein"> 
        <rank>1</rank> 
        <year>2008</year> 
        <gdppc>141100</gdppc> 
        </country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
        <neighbor name="Costa Rica" direction="W" make="foo" build="bar" job="blah"/> 
        </country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
        <neighbor name="Costa Rica" direction="W" make="foo" build="bar" job="blah"/> 
        </country> 
</data> 

ElementTree 中同样的逻辑:

from xml.etree import ElementTree as et 
 
xml = et.parse("test.xml").getroot() 
 
atts = {"build", "job", "make"} 
 
for parent in xml.findall(".//neighbor/.."): 
    for child in parent.findall(".//neighbor")[:]: 
        if not atts.issubset(child.attrib): 
            parent.remove(child) 

如果您正在使用 iter:

from xml.etree import ElementTree as et 
 
xml = et.parse("test.xml") 
 
for parent in xml.getroot().iter("*"): 
    parent[:] = (child for child in parent if child.tag != "neighbor") 

你可以看到我们得到了完全相同的输出:

In [30]: !cat /home/padraic/untitled6/test.xml 
<?xml version="1.0"?> 
<data> 
    <country name="Liechtenstein"># 
      <neighbor name="Austria" direction="E"/> 
        <rank>1</rank> 
        <neighbor name="Austria" direction="E"/> 
        <year>2008</year> 
      <neighbor name="Austria" direction="E"/> 
        <gdppc>141100</gdppc> 
        <neighbor name="Austria" direction="E"/> 
        <neighbor name="Switzerland" direction="W"/> 
    </country> 
    <country name="Singapore"> 
        <rank>4</rank> 
        <year>2011</year> 
        <gdppc>59900</gdppc> 
        <neighbor name="Malaysia" direction="N"/> 
    </country> 
    <country name="Panama"> 
        <rank>68</rank> 
        <year>2011</year> 
        <gdppc>13600</gdppc> 
        <neighbor name="Costa Rica" direction="W"/> 
        <neighbor name="Colombia" direction="E"/> 
    </country> 
</data> 
In [31]: paste 
def test(): 
    import lxml.etree as et 
    xml = et.parse("/home/padraic/untitled6/test.xml") 
    for node in xml.xpath("//neighbor"): 
        node.getparent().remove(node) 
    a = et.tostring(xml) 
    from xml.etree import ElementTree as et 
    xml = et.parse("/home/padraic/untitled6/test.xml") 
    for parent in xml.getroot().iter("*"): 
        parent[:] = (child for child in parent if child.tag != "neighbor") 
    b = et.tostring(xml.getroot()) 
    assert  a == b 
 
## -- End pasted text -- 
 
In [32]: test()