【问题标题】:python - how to write empty tree node as empty string to xml filepython - 如何将空树节点作为空字符串写入xml文件
【发布时间】:2016-06-23 22:37:43
【问题描述】:

我想删除某个标签值的元素,然后写出.xml文件,而这些删除的元素没有任何标签;是我创建新树的唯一选择吗?

移除/删除元素有两种选择:

clear() 重置元素。此功能删除所有子元素,清除所有 属性,并将 text 和 tail 属性设置为 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:

...仅关键字的 short_empty_elements 参数控制 不包含内容的元素的格式。如果为真(默认), 它们作为单个自封闭标签发出,否则它们是 作为一对开始/结束标签发出。

为什么不能选择不打印那些空标签!随便。

所以我想我可以试试

remove(subelement) 从元素中移除子元素。与 find* 方法不同,这 方法基于实例标识而不是标签来比较元素 价值或内容。

但这仅对子元素起作用。

所以我必须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 文件。

【问题讨论】:

  • 你能添加一个你的xml样本和你想要的输出吗?你也愿意使用 lxml 吗?
  • @PadraicCunningham 如果lxml 在 Python 中,是的。我不在乎我使用哪个 API。我会在我要查找的内容之前和之后进行更新。
  • 是否需要python?
  • @vtd-xml-author 没有。我只是选择了 Python,因为调试很简单,而且我已经使用过它。你有什么想法?
  • @PadraicCunningham 如何让this question 链接到我的问题?这是回答我的问题的问题。编辑:实际上没有回答。仅仅解释了一种方法是无效的。

标签: python xml


【解决方案1】:
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,我们需要找到 parents of the neighbor nodes 然后找到 neighbor nodes inside that parent 并删除它们:

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()

【讨论】:

  • 你能更清楚地格式化“邻居”这个词吗?当我第一次阅读您的答案时,我以为您的意思是邻居而不是“称为邻居的标签”。我认为code 格式是合适的。我会先尝试修改您的帖子,但由于某种原因,我的修改从未获得批准。
  • @Adrian,如果我在一般上下文中使用邻居,我会正确拼写;)
  • 我不知道你可以使用not 并像"//neighbor[not(@make) and not(@job) and not(@make)]" 那样连接东西
  • 是的,lxml 有完整的 xpath 语法支持以及一些额外的 lxml.de/extensions.html#xpath-extension-functions
  • 它是一个生成器表达式,项目被延迟评估,就[:]语法而言,它选择列表/父节点中的所有节点,如果你设置 parent = [... ] 您要做的就是创建名称 parent 到列表的绑定,而不是更改实际上不更改对象/父列表内容
【解决方案2】:

当需要修改 XML 文档时,还要考虑 XSLT,它是 XSL 家族的专用语言部分,其中包括 XPath。 XSLT 专为转换 XML 文件而设计。 Pythoners 不会很快推荐它,但它避免了在通用代码中循环或嵌套 if/then 逻辑的需要。 Python 的 lxml 模块可以使用 libxslt 处理器运行 XSLT 1.0 脚本。

下面的转换运行身份转换以按原样复制文档,然后在 &lt;neighbor&gt; 上运行空模板匹配以将其删除:

XSLT 脚本(另存为.xsl 文件以像源.xml 一样加载,两者都是格式良好的xml 文件)

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>

  <!-- IDENTITY TRANSFORM TO COPY XML AS IS -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- EMPTY TEMPLATE TO REMOVE NEIGHBOR WHEREVER IT EXISTS -->  
  <xsl:template match="neighbor"/>

</xsl:transform>

Python脚本

import lxml.etree as et

# LOAD XML AND XSL DOCUMENTS
xml  = et.parse("Input.xml")
xslt = et.parse("Script.xsl")

# TRANSFORM TO NEW TREE
transform = et.XSLT(xslt)
newdom = transform(xml)

# CONVERT TO STRING
tree_out = et.tostring(newdom, encoding='UTF-8', pretty_print=True,  xml_declaration=True)

# OUTPUT TO FILE
xmlfile = open('Output.xml'),'wb')
xmlfile.write(tree_out)
xmlfile.close()

【讨论】:

    【解决方案3】:

    这里的技巧是找到父节点(国家节点),然后从那里删除邻居。在这个例子中,我使用的是 ElementTree,因为我对它有点熟悉:

    import xml.etree.ElementTree as ET
    
    if __name__ == '__main__':
        with open('debug.log') as f:
            doc = ET.parse(f)
    
            for country in doc.findall('.//country'):
                for neighbor in country.findall('neighbor'):
                    country.remove(neighbor)
    
            ET.dump(doc)  # Display
    

    【讨论】:

      猜你喜欢
      • 2012-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多