Как преобразовать строку xml в словарь в Python?

У меня есть программа, которая читает XML-документ из сокета. У меня есть документ xml, который хранится в строке, которую я хотел бы преобразовать прямо в словарь Python, так же, как это делается в simplejson библиотеке Django.

Возьмем в качестве примера:

 str ="<?xml version="1.0" ?><person><name>john</name><age>20</age></person" dic_xml = convert_to_dic(str) 

Тогда dic_xml будет выглядеть как {'person' : { 'name' : 'john', 'age' : 20 } }

Заранее спасибо,
Ze Maria

15 Solutions collect form web for “Как преобразовать строку xml в словарь в Python?”

Это отличный модуль, созданный кем-то. Я использовал его несколько раз. http://code.activestate.com/recipes/410469-xml-as-dictionary/

Вот код с сайта, если ссылка идет плохо.

 import cElementTree as ElementTree class XmlListConfig(list): def __init__(self, aList): for element in aList: if element: # treat like dict if len(element) == 1 or element[0].tag != element[1].tag: self.append(XmlDictConfig(element)) # treat like list elif element[0].tag == element[1].tag: self.append(XmlListConfig(element)) elif element.text: text = element.text.strip() if text: self.append(text) class XmlDictConfig(dict): ''' Example usage: >>> tree = ElementTree.parse('your_file.xml') >>> root = tree.getroot() >>> xmldict = XmlDictConfig(root) Or, if you want to use an XML string: >>> root = ElementTree.XML(xml_string) >>> xmldict = XmlDictConfig(root) And then use xmldict for what it is... a dict. ''' def __init__(self, parent_element): if parent_element.items(): self.update(dict(parent_element.items())) for element in parent_element: if element: # treat like dict - we assume that if the first two tags # in a series are different, then they are all different. if len(element) == 1 or element[0].tag != element[1].tag: aDict = XmlDictConfig(element) # treat like list - we assume that if the first two tags # in a series are the same, then the rest are the same. else: # here, we put the list in dictionary; the key is the # tag name the list elements all share in common, and # the value is the list itself aDict = {element[0].tag: XmlListConfig(element)} # if the tag has attributes, add those to the dict if element.items(): aDict.update(dict(element.items())) self.update({element.tag: aDict}) # this assumes that if you've got an attribute in a tag, # you won't be having any text. This may or may not be a # good idea -- time will tell. It works for the way we are # currently doing XML configuration files... elif element.items(): self.update({element.tag: dict(element.items())}) # finally, if there are no child tags and no attributes, extract # the text else: self.update({element.tag: element.text}) 

Пример использования:

 tree = ElementTree.parse('your_file.xml') root = tree.getroot() xmldict = XmlDictConfig(root) 

// Или, если вы хотите использовать строку XML:

 root = ElementTree.XML(xml_string) xmldict = XmlDictConfig(root) 

xmltodict (полное раскрытие: я написал) делает именно это:

 xmltodict.parse(""" <?xml version="1.0" ?> <person> <name>john</name> <age>20</age> </person>""") # {u'person': {u'age': u'20', u'name': u'john'}} 

Следующий фрагмент XML-to-Python-dict анализирует сущности, а также атрибуты, соответствующие этой спецификации XML-to-JSON . Это наиболее общее решение для обработки всех случаев XML.

 from collections import defaultdict def etree_to_dict(t): d = {t.tag: {} if t.attrib else None} children = list(t) if children: dd = defaultdict(list) for dc in map(etree_to_dict, children): for k, v in dc.items(): dd[k].append(v) d = {t.tag: {k:v[0] if len(v) == 1 else v for k, v in dd.items()}} if t.attrib: d[t.tag].update(('@' + k, v) for k, v in t.attrib.items()) if t.text: text = t.text.strip() if children or t.attrib: if text: d[t.tag]['#text'] = text else: d[t.tag] = text return d 

Это использовано:

 from xml.etree import cElementTree as ET e = ET.XML(''' <root> <e /> <e>text</e> <e name="value" /> <e name="value">text</e> <e> <a>text</a> <b>text</b> </e> <e> <a>text</a> <a>text</a> </e> <e> text <a>text</a> </e> </root> ''') from pprint import pprint pprint(etree_to_dict(e)) 

Результат этого примера (согласно вышеописанной «спецификации») должен быть:

 {'root': {'e': [None, 'text', {'@name': 'value'}, {'#text': 'text', '@name': 'value'}, {'a': 'text', 'b': 'text'}, {'a': ['text', 'text']}, {'#text': 'text', 'a': 'text'}]}} 

Не обязательно красиво, но это однозначно, а более простые XML-входы приводят к более простому JSON. 🙂


Обновить

Если вы хотите сделать обратное , испустите строку XML из JSON / dict , вы можете использовать:

 try: basestring except NameError: # python3 basestring = str def dict_to_etree(d): def _to_etree(d, root): if not d: pass elif isinstance(d, basestring): root.text = d elif isinstance(d, dict): for k,v in d.items(): assert isinstance(k, basestring) if k.startswith('#'): assert k == '#text' and isinstance(v, basestring) root.text = v elif k.startswith('@'): assert isinstance(v, basestring) root.set(k[1:], v) elif isinstance(v, list): for e in v: _to_etree(e, ET.SubElement(root, k)) else: _to_etree(v, ET.SubElement(root, k)) else: raise TypeError('invalid type: ' + str(type(d))) assert isinstance(d, dict) and len(d) == 1 tag, body = next(iter(d.items())) node = ET.Element(tag) _to_etree(body, node) return ET.tostring(node) pprint(dict_to_etree(d)) 

Эта облегченная версия, хотя и не настраиваемая, довольно легко адаптируется по мере необходимости и работает на старых питонах. Также он является жестким – это означает, что результаты одинаковы независимо от наличия атрибутов.

 import xml.etree.ElementTree as ET from copy import copy def dictify(r,root=True): if root: return {r.tag : dictify(r, False)} d=copy(r.attrib) if r.text: d["_text"]=r.text for x in r.findall("./*"): if x.tag not in d: d[x.tag]=[] d[x.tag].append(dictify(x,False)) return d 

Так:

 root = ET.fromstring("<erik><ax='1'>v</a><ay='2'>w</a></erik>") dictify(root) 

Результаты в:

 {'erik': {'a': [{'x': '1', '_text': 'v'}, {'y': '2', '_text': 'w'}]}} 

Самые последние версии библиотек PicklingTools (1.3.0 и 1.3.1) поддерживают инструменты для преобразования из XML в Python dict.

Загрузка доступна здесь: PicklingTools 1.3.1

Здесь есть довольно много документации для конвертеров: в документации подробно описываются все решения и проблемы, возникающие при конвертации между словарями XML и Python (существует ряд краевых случаев: атрибуты, списки, анонимные списки, анонимные dicts, eval и т. д., которые большинство конвертеров не обрабатывают). В целом, однако, конвертеры просты в использовании. Если «example.xml» содержит:

 <top> <a>1</a> <b>2.2</b> <c>three</c> </top> 

Затем, чтобы преобразовать его в словарь:

 >>> from xmlloader import * >>> example = file('example.xml', 'r') # A document containing XML >>> xl = StreamXMLLoader(example, 0) # 0 = all defaults on operation >>> result = xl.expect XML() >>> print result {'top': {'a': '1', 'c': 'three', 'b': '2.2'}} 

Есть инструменты для конвертации как в C ++, так и в Python: C ++ и Python делают indentical conversion, но C ++ примерно в 60 раз быстрее

Отказ от ответственности: этот модифицированный синтаксический анализатор XML был вдохновлен Адамом Кларком . Исходный синтаксический анализатор XML работает для большинства простых случаев. Однако это не сработало для некоторых сложных XML-файлов. Я отлаживал код за строкой и, наконец, исправлял некоторые проблемы. Если вы найдете некоторые ошибки, сообщите мне. Я рад это исправить.

 class XmlDictConfig(dict): ''' Note: need to add a root into if no exising Example usage: >>> tree = ElementTree.parse('your_file.xml') >>> root = tree.getroot() >>> xmldict = XmlDictConfig(root) Or, if you want to use an XML string: >>> root = ElementTree.XML(xml_string) >>> xmldict = XmlDictConfig(root) And then use xmldict for what it is... a dict. ''' def __init__(self, parent_element): if parent_element.items(): self.updateShim( dict(parent_element.items()) ) for element in parent_element: if len(element): aDict = XmlDictConfig(element) # if element.items(): # aDict.updateShim(dict(element.items())) self.updateShim({element.tag: aDict}) elif element.items(): # items() is specialy for attribtes elementattrib= element.items() if element.text: elementattrib.append((element.tag,element.text )) # add tag:text if there exist self.updateShim({element.tag: dict(elementattrib)}) else: self.updateShim({element.tag: element.text}) def updateShim (self, aDict ): for key in aDict.keys(): # keys() includes tag and attributes if key in self: value = self.pop(key) if type(value) is not list: listOfDicts = [] listOfDicts.append(value) listOfDicts.append(aDict[key]) self.update({key: listOfDicts}) else: value.append(aDict[key]) self.update({key: value}) else: self.update({key:aDict[key]}) # it was self.update(aDict) 

Проще всего использовать синтаксический анализатор XML для Python – ElementTree (начиная с 2.5x и выше он находится в стандартной библиотеке xml.etree.ElementTree). Я не думаю, что есть что-то, что делает именно то, что вы хотите из коробки. Было бы довольно тривиально писать что-то, чтобы делать то, что вы хотите, используя ElementTree, но зачем конвертировать в словарь, и почему бы просто не использовать ElementTree напрямую.

Вот ссылка на решение ActiveState – и код, если он снова исчезнет.

 ================================================== xmlreader.py: ================================================== from xml.dom.minidom import parse class NotTextNodeError: pass def getTextFromNode(node): """ scans through all children of node and gathers the text. if node has non-text child-nodes, then NotTextNodeError is raised. """ t = "" for n in node.childNodes: if n.nodeType == n.TEXT_NODE: t += n.nodeValue else: raise NotTextNodeError return t def nodeToDic(node): """ nodeToDic() scans through the children of node and makes a dictionary from the content. three cases are differentiated: - if the node contains no other nodes, it is a text-node and {nodeName:text} is merged into the dictionary. - if the node has the attribute "method" set to "true", then it's children will be appended to a list and this list is merged to the dictionary in the form: {nodeName:list}. - else, nodeToDic() will call itself recursively on the nodes children (merging {nodeName:nodeToDic()} to the dictionary). """ dic = {} for n in node.childNodes: if n.nodeType != n.ELEMENT_NODE: continue if n.getAttribute("multiple") == "true": # node with multiple children: # put them in a list l = [] for c in n.childNodes: if c.nodeType != n.ELEMENT_NODE: continue l.append(nodeToDic(c)) dic.update({n.nodeName:l}) continue try: text = getTextFromNode(n) except NotTextNodeError: # 'normal' node dic.update({n.nodeName:nodeToDic(n)}) continue # text node dic.update({n.nodeName:text}) continue return dic def readConfig(filename): dom = parse(filename) return nodeToDic(dom) def test(): dic = readConfig("sample.xml") print dic["Config"]["Name"] print for item in dic["Config"]["Items"]: print "Item's Name:", item["Name"] print "Item's Value:", item["Value"] test() ================================================== sample.xml: ================================================== <?xml version="1.0" encoding="UTF-8"?> <Config> <Name>My Config File</Name> <Items multiple="true"> <Item> <Name>First Item</Name> <Value>Value 1</Value> </Item> <Item> <Name>Second Item</Name> <Value>Value 2</Value> </Item> </Items> </Config> ================================================== output: ================================================== My Config File Item's Name: First Item Item's Value: Value 1 Item's Name: Second Item Item's Value: Value 2 

Код из http://code.activestate.com/recipes/410469-xml-as-dictionary/ работает хорошо, но если есть несколько элементов, которые одинаковы в данном месте в иерархии, они просто переопределяют их.

Я добавил прокладку между этим, чтобы посмотреть, существует ли элемент уже до self.update (). Если это так, выдает существующую запись и создает списки из существующих и новых. Любые последующие дубликаты добавляются в список.

Не уверен, что это можно обработать более изящно, но он работает:

 import xml.etree.ElementTree as ElementTree class XmlDictConfig(dict): def __init__(self, parent_element): if parent_element.items(): self.updateShim(dict(parent_element.items())) for element in parent_element: if len(element): aDict = XmlDictConfig(element) if element.items(): aDict.updateShim(dict(element.items())) self.updateShim({element.tag: aDict}) elif element.items(): self.updateShim({element.tag: dict(element.items())}) else: self.updateShim({element.tag: element.text.strip()}) def updateShim (self, aDict ): for key in aDict.keys(): if key in self: value = self.pop(key) if type(value) is not list: listOfDicts = [] listOfDicts.append(value) listOfDicts.append(aDict[key]) self.update({key: listOfDicts}) else: value.append(aDict[key]) self.update({key: value}) else: self.update(aDict) 

Вы можете сделать это довольно легко с помощью lxml. Сначала установите его:

 [sudo] pip install lxml 

Вот рекурсивная функция, которую я написал, что делает тяжелый подъем для вас:

 from lxml import objectify as xml_objectify def xml_to_dict(xml_str): """ Convert xml to dict, using lxml v3.4.2 xml processing library """ def xml_to_dict_recursion(xml_object): dict_object = xml_object.__dict__ if not dict_object: return xml_object for key, value in dict_object.items(): dict_object[key] = xml_to_dict_recursion(value) return dict_object return xml_to_dict_recursion(xml_objectify.fromstring(xml_str)) xml_string = """<?xml version="1.0" encoding="UTF-8"?><Response><NewOrderResp> <IndustryType>Test</IndustryType><SomeData><SomeNestedData1>1234</SomeNestedData1> <SomeNestedData2>3455</SomeNestedData2></SomeData></NewOrderResp></Response>""" print xml_to_dict(xml_string) 

В приведенном ниже варианте сохраняется родительский ключ / элемент:

 def xml_to_dict(xml_str): """ Convert xml to dict, using lxml v3.4.2 xml processing library, see http://lxml.de/ """ def xml_to_dict_recursion(xml_object): dict_object = xml_object.__dict__ if not dict_object: # if empty dict returned return xml_object for key, value in dict_object.items(): dict_object[key] = xml_to_dict_recursion(value) return dict_object xml_obj = objectify.fromstring(xml_str) return {xml_obj.tag: xml_to_dict_recursion(xml_obj)} 

Если вы хотите только вернуть поддерево и преобразовать его в dict, вы можете использовать Element.find (), чтобы получить поддерево, а затем преобразовать его:

 xml_obj.find('.//') # lxml.objectify.ObjectifiedElement instance 

См. Документы lxml здесь . Надеюсь, это поможет!

Из ответа @ K3 — rnc (лучшее для меня) Я добавил небольшие изменения, чтобы получить OrderedDict из XML-текста (в некоторых случаях порядок вопросов):

 def etree_to_ordereddict(t): d = OrderedDict() d[t.tag] = OrderedDict() if t.attrib else None children = list(t) if children: dd = OrderedDict() for dc in map(etree_to_ordereddict, children): for k, v in dc.iteritems(): if k not in dd: dd[k] = list() dd[k].append(v) d = OrderedDict() d[t.tag] = OrderedDict() for k, v in dd.iteritems(): if len(v) == 1: d[t.tag][k] = v[0] else: d[t.tag][k] = v if t.attrib: d[t.tag].update(('@' + k, v) for k, v in t.attrib.iteritems()) if t.text: text = t.text.strip() if children or t.attrib: if text: d[t.tag]['#text'] = text else: d[t.tag] = text return d 

Следуя примеру @ K3 — rnc, вы можете использовать его:

 from xml.etree import cElementTree as ET e = ET.XML(''' <root> <e /> <e>text</e> <e name="value" /> <e name="value">text</e> <e> <a>text</a> <b>text</b> </e> <e> <a>text</a> <a>text</a> </e> <e> text <a>text</a> </e> </root> ''') from pprint import pprint pprint(etree_to_ordereddict(e)) 

Надеюсь, поможет 😉

В какой-то момент мне пришлось анализировать и писать XML, который состоял только из элементов без атрибутов, поэтому сопоставление 1: 1 из XML в dict было легко. Это то, что я придумал, если кому-то еще не нужны атрибуты:

 def xmltodict(element): if not isinstance(element, ElementTree.Element): raise ValueError("must pass xml.etree.ElementTree.Element object") def xmltodict_handler(parent_element): result = dict() for element in parent_element: if len(element): obj = xmltodict_handler(element) else: obj = element.text if result.get(element.tag): if hasattr(result[element.tag], "append"): result[element.tag].append(obj) else: result[element.tag] = [result[element.tag], obj] else: result[element.tag] = obj return result return {element.tag: xmltodict_handler(element)} def dicttoxml(element): if not isinstance(element, dict): raise ValueError("must pass dict type") if len(element) != 1: raise ValueError("dict must have exactly one root key") def dicttoxml_handler(result, key, value): if isinstance(value, list): for e in value: dicttoxml_handler(result, key, e) elif isinstance(value, basestring): elem = ElementTree.Element(key) elem.text = value result.append(elem) elif isinstance(value, int) or isinstance(value, float): elem = ElementTree.Element(key) elem.text = str(value) result.append(elem) elif value is None: result.append(ElementTree.Element(key)) else: res = ElementTree.Element(key) for k, v in value.items(): dicttoxml_handler(res, k, v) result.append(res) result = ElementTree.Element(element.keys()[0]) for key, value in element[element.keys()[0]].items(): dicttoxml_handler(result, key, value) return result def xmlfiletodict(filename): return xmltodict(ElementTree.parse(filename).getroot()) def dicttoxmlfile(element, filename): ElementTree.ElementTree(dicttoxml(element)).write(filename) def xmlstringtodict(xmlstring): return xmltodict(ElementTree.fromstring(xmlstring).getroot()) def dicttoxmlstring(element): return ElementTree.tostring(dicttoxml(element)) 
 def xml_to_dict(node): u''' @param node:lxml_node @return: dict ''' return {'tag': node.tag, 'text': node.text, 'attrib': node.attrib, 'children': {child.tag: xml_to_dict(child) for child in node}} 

@dibrovsd: Решение не будет работать, если xml имеет более одного тега с тем же именем

По вашему мнению, я немного изменил код и написал его для общего узла вместо root:

 from collections import defaultdict def xml2dict(node): d, count = defaultdict(list), 1 for i in node: d[i.tag + "_" + str(count)]['text'] = i.findtext('.')[0] d[i.tag + "_" + str(count)]['attrib'] = i.attrib # attrib gives the list d[i.tag + "_" + str(count)]['children'] = xml2dict(i) # it gives dict return d 

У меня есть рекурсивный метод для получения словаря из элемента lxml

  def recursive_dict(element): return (element.tag.split('}')[1], dict(map(recursive_dict, element.getchildren()), **element.attrib)) 
Interesting Posts

Загружайте данные через лист Excel, используя django-excel

Это ошибка? Переменные – это одинаковые ссылки на одну и ту же строку в этом примере (Python)

Python – Ceil в datetime до следующей четверти часа

varargs в лямбда-функциях в Python

Почему pow (a, d, n) намного быстрее, чем ** d% n?

Подключение к PostgreSQL с помощью pyodbc

Возможно ли изменить переменную в python, которая находится во внешней, но не глобальной области?

Как добавить вторую ось x в matplotlib

Bulbflow: разница между neo4jserver Graph и neo4jserver Neo4jclient

Текстовые размеры Matplotlib

Итератор файла Python по двоичному файлу с более новой идиомой

Обновление Django до 1,9 ошибки «AppRegistryNotReady: приложения еще не загружены».

Для Pylint возможно ли иметь другой файл pylintrc для каждого проекта Eclipse?

Как я могу эффективно обрабатывать массив numpy в блоках, подобных функции blkproc (blockproc) Matlab

Сохраняет ли функция «открыть» Python ее содержимое в памяти или в временном файле?

Python - лучший язык программирования в мире.