Python: как создать вложенный XML из плоской структуры данных - PullRequest
2 голосов
/ 12 февраля 2011

Я хотел бы создать вложенный XML (в виде строки) из списка dicts с python:

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'},]

уровень означает уровень вложенности , и в моем распоряжении может быть более 2 уровней. Ток имеет фиксированный порядок (по entryno). Уровень может увеличиваться только на один от одного входа к другому, но может уменьшаться более чем на один. Вот вложенный пример 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 ]

4 голосов
/ 13 февраля 2011

Не уродливое решение с использованием 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)
1 голос
/ 12 февраля 2011

Предположим, что вы знаете, как создавать XML.

Давайте предположим, что «уровень» в ваших данных увеличивается, если данные вложены в предыдущий узел, и увеличивается только на 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 мы перечисляем вложенные узлы.

0 голосов
/ 13 февраля 2011
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
...