рефакторинг этого словаря в XML-конвертер в Python - PullRequest
3 голосов
/ 30 января 2009

На самом деле это небольшая вещь: у меня есть эта функция, которая преобразует объекты dict в xml.

Вот функция:

def dictToXml(d):
    from xml.sax.saxutils import escape

    def unicodify(o):
        if o is None:
            return u'';
        return unicode(o)

    lines = []
    def addDict(node, offset):
        for name, value in node.iteritems():
            if isinstance(value, dict):
                lines.append(offset + u"<%s>" % name)
                addDict(value, offset + u" " * 4)
                lines.append(offset + u"</%s>" % name)
            elif isinstance(value, list):
                for item in value:
                    if isinstance(item, dict):
                        lines.append(offset + u"<%s>" % name)
                        addDict(item, offset + u" " * 4)
                        lines.append(offset + u"</%s>" % name)
                    else:
                        lines.append(offset + u"<%s>%s</%s>" % (name, escape(unicodify(item)), name))
            else:
                lines.append(offset + u"<%s>%s</%s>" % (name, escape(unicodify(value)), name))

    addDict(d, u"")
    lines.append(u"")
    return u"\n".join(lines)

Например, он преобразует этот словарь

{ 'site': { 'name': 'stackoverflow', 'blogger': [ 'Jeff', 'Joel' ] } }

до:

<site>
    <name>stackoverflow</name>
    <blogger>jeff</blogger>
    <blogger>joel</blogger>
</site>

Это работает, но функция addDict выглядит слишком повторяющейся. Я уверен, что есть способ преобразовать его в 3 ко-рекурсивные функции с именами addDict, addList и addElse, но мой мозг застрял. Любая помощь?

Кроме того, любой способ избавиться от вещи offset + в каждой строке был бы хорош.

ПРИМЕЧАНИЕ : я выбрал эту семантику, потому что я пытаюсь сопоставить поведение конвертера json-xml в org.json , Я использую в другой части моего проекта. Если вы попали на эту страницу, просто ища конвертер словаря в xml, в некоторых ответах есть несколько действительно хороших вариантов. (Особенно pyfo ).

Ответы [ 6 ]

9 голосов
/ 30 января 2009
>>> from pyfo import pyfo
>>> d = ('site', { 'name': 'stackoverflow', 'blogger': [ 'Jeff', 'Joel' ] } )
>>> result = pyfo(d, pretty=True, prolog=True, encoding='ascii')
>>> print result.encode('ascii', 'xmlcharrefreplace')
<?xml version="1.0" encoding="ascii"?>
<site>
  <blogger>
    Jeff
    Joel
  </blogger>
  <name>stackoverflow</name>
</site>

Для установки pyfo :

$ easy_install pyfo
4 голосов
/ 30 января 2009

Я заметил, что у вас есть общие черты в добавлении предметов. Используя эту общность, я бы реорганизовал добавление элемента в отдельную функцию.

def addItem(item, name, offset):
          if isinstance(item, dict):
                lines.append(offset + u"<%s>" % name)
                addDict(item, offset + u" " * 4)
                lines.append(offset + u"</%s>" % name)
          else:
                lines.append(offset + u"<%s>%s</%s>" % (name, escape(unicodify(item)), name))

def addList(value,name, offset):
        for item in value:
            addItem(item, name, offset)

def addDict(node, offset):
        for name, value in node.iteritems():
            if isinstance(value, list):
                addList(value, name, offset)
            else:
                addItem(value, name, offset)

Консультативное предупреждение: этот код не тестируется и не написан никем, кто на самом деле использует Python.

1 голос
/ 30 января 2009

Ваш исходный код создает некорректный XML и может создавать один и тот же XML для двух разных словарей (не инъективно , говоря математически).

Например, если у вас есть список в качестве значения единственного ключа в словаре:

 d = { 'list': [1,2,3] }

Я ожидаю, что ваш код выдаст

 <list>1</list><list>2</list><list>3</list>

и нет корневого элемента. Любой XML должен иметь один и только один корневой элемент.

Тогда, учитывая XML, созданный вашим кодом, невозможно сказать, является ли этот XML

 <tag>1</tag>

был произведен из { 'tag': 1 } или из { 'tag': [1] }.

Итак, я предлагаю

  • всегда начинается с корневого элемента
  • представляют списки с двумя специальными тегами (например, <list/> и <item/>) или помечают их как таковые в атрибутах

Затем, после принятия решения об этих концептуальных недостатках, мы можем генерировать правильный и однозначный XML. Я решил использовать атрибуты для разметки списков и использовал ElementTree для автоматического создания дерева XML. Также помогает рекурсия (add_value_to_xml вызывается рекурсивно):

from xml.etree.ElementTree import Element, SubElement, tostring

def is_scalar(v):
    return isinstance(v,basestring) or isinstance(v,float) \
        or isinstance(v,int) or isinstance(v,bool)

def add_value_to_xml(root,v):
    if type(v) == type({}):
        for k,kv in v.iteritems():
            vx = SubElement(root,unicode(k))
            vx = add_value_to_xml(vx,kv)
    elif type(v) == list:
        root.set('type','list')
        for e in v:
            li = SubElement(root,root.tag)
            li = add_value_to_xml(li,e)
            li.set('type','item')
    elif is_scalar(v):
        root.text = unicode(v)
    else:
        raise Exception("add_value_to_xml: unsuppoted type (%s)"%type(v))
    return root

def dict_to_xml(d,root='dict'):
    x = Element(root)
    x = add_value_to_xml(x,d)
    return x

d = { 'float': 5194.177, 'str': 'eggs', 'int': 42,
        'list': [1,2], 'dict': { 'recursion': True } }
x = dict_to_xml(d)
print tostring(x)

Результат преобразования тестового дикта:

<dict><int>42</int><dict><recursion>True</recursion></dict><float>5194.177</float><list type="list"><list type="item">1</list><list type="item">2</list></list><str>eggs</str></dict>
1 голос
/ 30 января 2009

Чтобы избавиться от повторного «смещения +»:

offset = 0
def addLine(str):
    lines.append(u" " * (offset * 4) + str

тогда

...
    addLine(u"<%s>" % name)
    offset = offset + 1
    addDict(value)
    offset = offset - 1
    addLine(u"</%s>" % name)

У меня нет доступа к переводчику, поэтому возьмите это с крошкой соли: (

0 голосов
/ 30 января 2009

Вот что я считаю полезным при работе с XML. Фактически сначала создайте структуру узла XML, а затем переведите ее в текстовый.

Это разделяет две несвязанные проблемы.

  1. Как мне преобразовать мою структуру Python в объектную модель XML?

  2. Как мне отформатировать эту объектную модель XML?

Трудно, когда вы объединяете эти две вещи в одну функцию. С другой стороны, если вы разделяете их, у вас есть две вещи. Во-первых, у вас есть значительно более простая функция для «обхода» структуры Python и возврата узла XML. Ваши XML-узлы могут быть преобразованы в текст с применением некоторых предпочтительных правил кодирования и форматирования.

from xml.sax.saxutils import escape

class Node( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def toXml( self, indent ):
        if len(self.children) == 0:
            return u"%s<%s/>" % ( indent*4*u' ', self.name )
        elif len(self.children) == 1:
            child= self.children[0].toXml(0)
            return u"%s<%s>%s</%s>" % ( indent*4*u' ', self.name, child, self.name )
        else:
            items = [ u"%s<%s>" % ( indent*4*u' ', self.name ) ]
            items.extend( [ c.toXml(indent+1) for c in self.children ] )
            items.append( u"%s</%s>" % ( indent*4*u' ', self.name ) )
            return u"\n".join( items )

class Text( Node ):
    def __init__( self, value ):
        self.value= value
    def toXml( self, indent ):
        def unicodify(o):
            if o is None:
                return u'';
            return unicode(o)
        return "%s%s" % ( indent*4*u' ', escape( unicodify(self.value) ), )

def dictToXml(d):

    def dictToNodeList(node):
        nodes= []
        for name, value in node.iteritems():
            if isinstance(value, dict):
                n= Node( name, *dictToNodeList( value ) )
                nodes.append( n )
            elif isinstance(value, list):
                for item in value:
                    if isinstance(item, dict):
                        n= Node( name, *dictToNodeList( value ) )
                        nodes.append( n )
                    else:
                        n= Node( name, Text( item ) )
                        nodes.append( n )
            else:
                n= Node( name, Text( value ) )
                nodes.append( n )
        return nodes

    return u"\n".join( [ n.toXml(0) for n in dictToNodeList(d) ] )
0 голосов
/ 30 января 2009

Вот мой короткий набросок для решения: иметь общую функцию addSomething(), которая отправляет в зависимости от типа значения addDict(), addList() или addElse(). Эти функции рекурсивно вызывают addSomething() снова.

В основном вы выделяете части в предложении if и добавляете рекурсивный вызов.

...