我有一些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”元素都是空的,我还想将其全部删除。

最佳答案

首先,您的代码的问题是您在迭代customers,而不是在govIds。在第三行,对每个客户取第一个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

07-27 13:26