tutorial - ¿Cómo convertir una cadena xml a un diccionario en Python?
xml etree elementtree example (15)
@dibrovsd: la solución no funcionará si el xml tiene más de una etiqueta con el mismo nombre
En su línea de pensamiento, modifiqué un poco el código y lo escribí para nodo general en lugar de raíz:
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
Tengo un programa que lee un documento xml de un socket. Tengo el documento xml almacenado en una cadena que me gustaría convertir directamente a un diccionario de Python, de la misma manera que lo simplejson
biblioteca simplejson
de Django.
Tomemos como ejemplo:
str ="<?xml version="1.0" ?><person><name>john</name><age>20</age></person"
dic_xml = convert_to_dic(str)
Entonces dic_xml
se vería como {''person'' : { ''name'' : ''john'', ''age'' : 20 } }
Aquí hay un enlace a una solución de ActiveState , y el código en caso de que desaparezca nuevamente.
==================================================
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
Descargo de responsabilidad: Este analizador XML modificado fue inspirado por Adam Clark. El analizador XML original funciona para la mayoría de los casos simples. Sin embargo, no funcionó para algunos archivos XML complicados. Depuré el código línea por línea y finalmente solucioné algunos problemas. Si encuentra algunos errores, hágamelo saber. Estoy contento de arreglarlo.
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)
Desde @ K3 --- rnc response (lo mejor para mí) He agregado pequeñas modificaciones para obtener un OrderedDict de un texto XML (algunas veces el orden importa):
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
Siguiendo el ejemplo de @ K3 --- rnc, puedes usarlo:
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))
Espero eso ayude ;)
El analizador XML más fácil de usar para Python es ElementTree (a partir de 2.5x, está en la biblioteca estándar xml.etree.ElementTree). No creo que haya nada que haga exactamente lo que quieres de la caja. Sería bastante trivial escribir algo para hacer lo que quiera usando ElementTree, pero ¿por qué convertirlo a un diccionario y por qué no simplemente usar ElementTree directamente?
El código de http://code.activestate.com/recipes/410469-xml-as-dictionary/ funciona bien, pero si hay varios elementos que son iguales en un lugar determinado de la jerarquía, simplemente los anula.
Agregué un calce entre eso para ver si el elemento ya existe antes de self.update (). Si es así, aparece la entrada existente y crea una lista de la existente y la nueva. Cualquier duplicado subsiguiente se agrega a la lista.
No estoy seguro si esto se puede manejar con más gracia, pero funciona:
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)
El siguiente fragmento XML-to-Python-dict analiza entidades así como atributos que siguen a esta "especificación" XML-to-JSON . Es la solución más general que maneja todos los casos de 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
Esta usado:
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))
El resultado de este ejemplo (según la "especificación" anteriormente vinculada) debería ser:
{''root'': {''e'': [None,
''text'',
{''@name'': ''value''},
{''#text'': ''text'', ''@name'': ''value''},
{''a'': ''text'', ''b'': ''text''},
{''a'': [''text'', ''text'']},
{''#text'': ''text'', ''a'': ''text''}]}}
No necesariamente es bonito, pero no es ambiguo, y las entradas XML más simples dan como resultado un JSON más simple. :)
Actualizar
Si quiere hacer lo contrario , emita una cadena XML desde un JSON / dict , puede usar:
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))
En un momento dado tuve que analizar y escribir XML que solo consistía en elementos sin atributos, por lo que era posible realizar un mapeo 1: 1 de XML a dict fácilmente. Esto es lo que se me ocurrió en caso de que alguien más tampoco necesite atributos:
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))
Esta versión ligera, aunque no configurable, es bastante fácil de adaptar según sea necesario y funciona en pitones viejas. También es rígido, lo que significa que los resultados son los mismos independientemente de la existencia de atributos.
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
Asi que:
root = ET.fromstring("<erik><a x=''1''>v</a><a y=''2''>w</a></erik>")
dictify(root)
Resultados en:
{''erik'': {''a'': [{''x'': ''1'', ''_text'': ''v''}, {''y'': ''2'', ''_text'': ''w''}]}}
Este es un gran módulo que alguien creó. Lo he usado varias veces http://code.activestate.com/recipes/410469-xml-as-dictionary/
Aquí está el código del sitio web en caso de que el enlace se estropee.
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})
Ejemplo de uso:
tree = ElementTree.parse(''your_file.xml'')
root = tree.getroot()
xmldict = XmlDictConfig(root)
// O, si quieres usar una cadena XML:
root = ElementTree.XML(xml_string)
xmldict = XmlDictConfig(root)
Las versiones más recientes de las bibliotecas de PicklingTools (1.3.0 y 1.3.1) admiten herramientas para convertir de XML a un dict de Python.
La descarga está disponible aquí: PicklingTools 1.3.1
Aquí hay bastante documentación para los convertidores: la documentación describe en detalle todas las decisiones y problemas que surgirán al convertir entre diccionarios XML y Python (existen varios casos extremos: atributos, listas, listas anónimas, anónimos dicts, eval, etc. que la mayoría de los convertidores no manejan). En general, sin embargo, los convertidores son fáciles de usar. Si un ''example.xml'' contiene:
<top>
<a>1</a>
<b>2.2</b>
<c>three</c>
</top>
Luego para convertirlo a un diccionario:
>>> 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''}}
Hay herramientas para convertir tanto en C ++ como en Python: el C ++ y el Python realizan una conversión idéntica, pero el C ++ es aproximadamente 60 veces más rápido
Puede hacerlo fácilmente con lxml. Primero instálalo:
[sudo] pip install lxml
Aquí hay una función recursiva que escribí que hace el trabajo pesado por usted:
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)
La variante siguiente conserva la clave / elemento principal:
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)}
Si solo desea devolver un subárbol y convertirlo a dict, puede usar Element.find () para obtener el subárbol y luego convertirlo:
xml_obj.find(''.//'') # lxml.objectify.ObjectifiedElement instance
Vea los documentos lxml here . ¡Espero que esto ayude!
Tengo un método recursivo para obtener un diccionario de un elemento lxml
def recursive_dict(element):
return (element.tag.split(''}'')[1],
dict(map(recursive_dict, element.getchildren()),
**element.attrib))
xmltodict (divulgación completa: lo escribí) hace exactamente eso:
xmltodict.parse("""
<?xml version="1.0" ?>
<person>
<name>john</name>
<age>20</age>
</person>""")
# {u''person'': {u''age'': u''20'', u''name'': u''john''}}
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}}