如何将xmlstring转换为Python中的字典?
我有一个程序,从套接字读取一个XML文件。 我有xml文档存储在一个string,我想直接转换成一个Python字典,就像在Django的simplejson
库中完成一样。
举一个例子:
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 } }
提前致谢,
泽玛丽亚
这是有人创build的一个很好的模块。 我用了好几次 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)
//或者,如果你想使用一个XMLstring:
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代码段parsingXML-to-JSON“规范”之后的实体和属性。 这是处理XML所有情况的最通用的解决scheme。
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'}]}}
不一定很漂亮,但它是明确的,简单的XMLinput导致更简单的JSON。 🙂
更新
如果你想做相反的事情, 从JSON / Dict发出一个XMLstring ,你可以使用:
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))
这个轻量级的版本,虽然不可configuration,但是很容易根据需要进行裁剪,并且可以在老的python中使用。 它也是刚性的 – 意味着无论属性的存在如何,结果都是一样的。
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字典的工具。
下载可在这里find: PicklingTools 1.3.1
这里有很多关于转换器的文档:文档详细描述了在XML和Python字典之间转换时会出现的所有决定和问题(有许多边界情况:属性,列表,匿名列表,匿名字典,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可以进行转换,但是C ++速度要快60倍
你可以很容易地用lxml来做到这一点。 首先安装它:
[sudo] pip install lxml
下面是我写的一个recursion函数:
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)}
如果只想返回一个子树并将其转换为字典,可以使用Element.find()获取子树,然后将其转换为:
xml_obj.find('.//') # lxml.objectify.ObjectifiedElement instance
请参阅这里的lxml文档。 我希望这有帮助!
免责声明:这个修改后的XMLparsing器受到Adam Clark的启发原始的XMLparsing器适用于大多数简单的情况。 但是,它并不适用于一些复杂的XML文件。 我一行一行地debugging代码,最后修复了一些问题。 如果你发现一些错误,请让我知道。 我很高兴解决这个问题。
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)
Python的最简单的XMLparsing器是ElementTree(2.5x及以上,它在标准库xml.etree.ElementTree中)。 我不认为有任何东西是你想要的东西。 使用ElementTree编写一些东西来做你想要的东西是相当简单的,但为什么要转换成字典,为什么不直接使用ElementTree。
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}}
这里是一个ActiveState解决scheme的链接 – 以及代码,以防万一它消失了。
================================================== 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
我之间添加了一个垫片,看看在self.update()之前该元素是否已经存在。 如果是这样,popup现有的条目,并创build一个列表中的现有和新的。 任何后续的重复项都被添加到列表中。
不知道这是否可以更优雅地处理,但它的工作原理:
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)
从@ K3 — rnc 响应 (最适合我)我已经添加了一些小的修改,以从XML文本(有时顺序重要)获得OrderedDict:
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))
希望能帮助到你 ;)
有一次,我不得不parsing和编写只包含没有属性的元素的XML,所以可以很容易地从XML到字典的1:1映射。 这是我想出来的情况下,别人也不需要属性:
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))
@dibrovsd:如果xml具有多个具有相同名称的标记,解决scheme将不起作用
根据你的想法,我修改了一下代码,并把它写成了一般节点而不是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
我有一个recursion的方法来从lxml元素获取字典
def recursive_dict(element): return (element.tag.split('}')[1], dict(map(recursive_dict, element.getchildren()), **element.attrib))