【问题标题】:Python lxml - How to remove empty repeated tagsPython lxml - 如何删除空的重复标签
【发布时间】:2012-10-02 16:09:15
【问题描述】:

我有一些由可能有也可能没有空元素的脚本生成的 XML。有人告诉我,现在 XML 中不能有空元素。这是一个例子:

<customer>  
    <govId>
       <id>@</id>
       <idType>SSN</idType>
           <issueDate/>
           <expireDate/>
           <dob/>
           <state/>
           <county/>
           <country/>
    </govId>
    <govId>
        <id/>
        <idType/>
        <issueDate/>
        <expireDate/>
        <dob/>
        <state/>
        <county/>
        <country/>
    </govId>
</customer>

输出应如下所示:

<customer>  
    <govId>
       <id>@</id>
       <idType>SSN</idType>        
    </govId>        
</customer>

我需要删除所有空元素。您会注意到,我的代码取出了“govId”子元素中的空白内容,但第二次没有取出任何内容。我目前正在使用 lxml.objectify。

这基本上是我正在做的事情:

root = objectify.fromstring(xml)
for customer in root.customers.iterchildren():
    for e in customer.govId.iterchildren():
        if not e.text:
            customer.govId.remove(e)

有没有人知道用 lxml objectify 做到这一点的方法,或者有更简单的方法吗?如果它的所有元素都是空的,我还想完全删除第二个“govId”元素。

【问题讨论】:

  • 您说“您会注意到我的代码取出了“govId”子元素中的空内容,但第二次没有取出任何内容。 ” 你指的是什么“空的东西”?我很困惑;您显示输入或输出的 XML 是什么?
  • @mzjn 我也对此感到困惑。我认为这是他已经得到的输出,但还不正确。
  • @mzjn - 我指的是空元素。我应该删除我找到的任何空元素。我刚刚更新它以显示输入和输出。我会放入整个示例,但由于隐私问题以及真正的 XML 有数百行长这一事实,我不允许这样做。

标签: python xml lxml


【解决方案1】:

首先,您的代码的问题在于您正在迭代customers,而不是govIds。在第三行,您为每个客户获取 first govId,并迭代其子代。因此,您需要另一个 for 循环才能使代码按您的预期工作。

问题末尾的这个小句子使问题变得更加复杂:如果第二个“govId”元素的所有元素都是空的,我还想将其全部删除。

这意味着,除非你想硬编码只检查一层嵌套,否则你需要递归检查一个元素及其子元素是否为空。比如这样:

def recursively_empty(e):
   if e.text:
       return False
   return all((recursively_empty(c) for c in e.iterchildren()))

注意:Python 2.5+ 因为使用了all() builtin

然后,您可以将代码更改为类似这样的内容,以删除文档中一直为空的所有元素。

# Walk over all elements in the tree and remove all
# nodes that are recursively empty
context = etree.iterwalk(root)
for action, elem in context:
    parent = elem.getparent()
    if recursively_empty(elem):
        parent.remove(elem)

样本输出:

<customer>
  <govId>
    <id>@</id>
    <idType>SSN</idType>
  </govId>
</customer>

您可能想做的一件事是在递归函数中优化条件if e.text:。目前,这会将None 和空字符串视为空,但不会将空格和换行符视为空白。如果这是您对“空”的定义的一部分,请使用 str.strip()


编辑:正如@Dave 所指出的,递归函数可以通过使用generator expression 来改进:

return all((recursively_empty(c) for c in e.getchildren()))

这不会同时为所有孩子评估recursively_empty(c),而是懒惰地评估每个孩子。由于all() 将在第一个False 元素上停止迭代,这可能意味着显着的性能提升。

编辑 2:表达式可以通过使用e.iterchildren() 而不是e.getchildren() 进一步优化。这适用于lxml etree APIobjectify API

【讨论】:

  • 我认为这看起来不太对。我实际上并没有看到你使用 any() 内置函数,所以我猜你的意思是 all() 内置函数。我知道 str.strip() 部分,但是应该已经删除了这些数据。感谢您的帮助!
  • @Mike Oops,当然我的意思是all(这就是我在代码中使用并链接到的),感谢您指出这一点。
  • 当我在我的代码上运行 for 循环时,它确实会在其为空时删除整个 govId 部分,但如果其中有一两个元素有数据,它会将这些元素全部保留空的也是。
  • @Mike 啊,我以为这就是它应该做的?你说如果第二个“govId”元素的所有元素都是空的,我也想将其全部删除。所以我假设如果它不为空,它应该保持不变。
  • all() 的列表理解参数可以省略吗?这将创建整个列表,而生成器表达式 return all(recursively_empty(c) for c in e.getchildren())(与上面相同,但没有方括号)将懒惰地一次计算一个 c。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-31
  • 2016-06-21
  • 2011-02-26
  • 1970-01-01
  • 2013-06-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多