Python:如何从扁平数据结构创建嵌套XML

2 投票
3 回答
3001 浏览
提问于 2025-04-16 11:43

我想用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 个回答

0
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

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

1

假设你知道怎么创建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 下,我们列出了嵌套的节点。

4

这里有一个不那么复杂的解决方案,使用的是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)

撰写回答