我有一些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 API和objectify API。