Python:如何从扁平数据结构创建嵌套XML
我想用Python从一个字典列表创建嵌套的XML字符串:
toc = [
{'entryno': 1, 'level': 1, 'pageno': 17, 'title': 'title a'},
{'entryno': 2, 'level': 2, 'pageno': 19, 'title': 'title b'},
{'entryno': 3, 'level': 1, 'pageno': 25, 'title': 'title c'},]
level指的是嵌套层级,我的字典可能会有超过两个层级。目录的顺序是固定的(根据entryno排序)。层级只能从一个条目到下一个条目增加1,但可以减少超过1。
这是我想要创建的嵌套XML示例:
<entry id="1">
<pageno>17</pageno>
<title>title a</title>
<entry id="2">
<pageno>19</pageno>
<title>title b</title>
</entry>
</entry>
<entry id="3">
<pageno>25</pageno>
<title>title c</title>
</entry>
我尝试用string.Template()和遍历目录来解决这个问题,但在创建XML的嵌套部分时遇到了困难。我怀疑解决方案可能需要用到递归。
作为一个编程初学者,我不仅想要一个解决方案,还想了解你是如何思考来解决这个问题的!
3 个回答
toc = [
{'entryno': 1, 'level': 1, 'pageno': 17, 'title': 'title a'},
{'entryno': 2, 'level': 2, 'pageno': 18, 'title': 'title d'},
{'entryno': 3, 'level': 3, 'pageno': 19, 'title': 'title e'},
{'entryno': 4, 'level': 4, 'pageno': 20, 'title': 'title b'},
{'entryno': 5, 'level': 5, 'pageno': 25, 'title': 'title c'},]
blevel=0
ret=""
for i in toc:
while blevel >= i['level']:
ret += "%s</entry>\n" % (" " * blevel)
blevel-=1
blevel=i['level']
ident=" " * i['level']
ret += "%s<entry id=\"%i\">\n" % (ident, i['entryno'])
ident+=" "
for a in i:
if not a in ['entryno','level']:
ret += "%s<%s>%s</%s>\n" % (ident,a,i[a],a)
while blevel > 0:
ret += "%s</entry>\n" % (" " * blevel)
blevel-=1
print ret
当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。
假设你知道怎么创建XML文件。
假设在你的数据中,如果某个数据嵌套在前一个节点里,它的“层级”就会增加,而且每次只增加1。如果层级减少,说明你不再关注当前节点,而是要看上面的某个节点;层级为1意味着“在文档级别上附加”。
处理增加层级时,你只需要记住前一个节点。如果层级增加1,你就创建一个新节点,并把它作为前一个节点的子节点。
处理相同层级时,你需要记住之前创建的节点的父节点。因为新节点和之前的节点是同级的,所以你把新节点附加到那个父节点上。
处理减少层级时,你需要从前一个节点往回退几步,以确保你在正确的层级上。你能看出这个规律吗?
实际上,你需要记住从文档级别到之前创建的节点的整个链条。如果 next_node.level == previous_node.level + 1
,你就把它附加到链条的末尾。否则,你就要往回退 previous_node.level - next_node.level + 1
步,找到那个节点作为父节点。我们假设层级0是文档级别。
下面是一段代码来说明这个过程:
def nest(input):
ret = {'level': 0} # 'document level'
path = [ret]
for item in input:
node = dict(item) # a copy of item, lest we alter input
old_level = path[-1]['level'] # last element's
new_level = node['level']
delta = new_level - old_level - 1
if delta < 0:
path = path[:delta]
children_list = path[-1].get('_children', None) or []
children_list.append(node)
path[-1]['_children'] = children_list
path.append(node)
return ret
from pprint import PrettyPrinter
pr = PrettyPrint(indent=2).pprint
pr(nest(toc))
然后你会看到
{ '_children': [ { '_children': [ { 'entryno': 2,
'level': 2,
'pageno': 19,
'title': 'title b'}],
'entryno': 1,
'level': 1,
'pageno': 17,
'title': 'title a'},
{ 'entryno': 3, 'level': 1, 'pageno': 25, 'title': 'title c'}],
'level': 0}
在 _children
下,我们列出了嵌套的节点。
这里有一个不那么复杂的解决方案,使用的是ElementTree这个API。Python自带了一个实现,叫做xml.etree.[c]ElementTree。还有一个叫lxml.etree的库,它提供了更多的功能,包括可以让输出的内容看起来更美观。
# import xml.etree.cElementTree as et
import lxml.etree as et
import sys
toc = [
{'entryno': 1, 'level': 1, 'pageno': 17, 'title': 'title a'},
{'entryno': 2, 'level': 2, 'pageno': 19, 'title': 'title b'},
{'entryno': 3, 'level': 1, 'pageno': 25, 'title': 'Smith & Wesson'},
{'entryno': 4, 'level': 2, 'pageno': 27, 'title': '<duct tape>'},
{'entryno': 5, 'level': 2, 'pageno': 29, 'title': u'\u0404'},
]
root = et.Element("root")
tree = et.ElementTree(root)
parent = {0: root}
for entry in toc:
level = entry['level']
entryno = entry['entryno']
# create the element and link it to its parent
elem = et.SubElement(parent[level - 1], "entry", {'id': str(entryno)})
# create children to hold the other data items
for k, v in entry.iteritems():
if k in ('entryno', 'level'): continue
child = et.SubElement(elem, k)
child.text = unicode(v)
# record current element as a possible parent
parent[level] = elem
# tree.write(sys.stdout)
tree.write(sys.stdout, pretty_print=True)