2008-09-24 13 views
8

J'essaie de générer des fichiers XML personnalisés à partir d'un fichier xml de modèle en python.Modification de XML en tant que dictionnaire en python?

D'un point de vue conceptuel, je souhaite lire le modèle XML, supprimer certains éléments, modifier certains attributs de texte et écrire le nouveau fichier XML dans un fichier. Je voulais que cela fonctionne comme ceci:

conf_base = ConvertXmlToDict('config-template.xml') 
conf_base_dict = conf_base.UnWrap() 
del conf_base_dict['root-name']['level1-name']['leaf1'] 
del conf_base_dict['root-name']['level1-name']['leaf2'] 

conf_new = ConvertDictToXml(conf_base_dict) 

maintenant je veux écrire dans le fichier, mais je ne vois pas comment se rendre à ElementTree.ElementTree.write()

conf_new.write('config-new.xml') 

Est y a-t-il moyen de le faire, ou quelqu'un peut-il suggérer de le faire différemment?

Répondre

8

Pour une manipulation aisée de XML en python, j'aime la bibliothèque Beautiful Soup. Il fonctionne comme ceci:

fichier XML Exemple:

<root> 
    <level1>leaf1</level1> 
    <level2>leaf2</level2> 
</root> 

code Python:

from BeautifulSoup import BeautifulStoneSoup, Tag, NavigableString 

soup = BeautifulStoneSoup('config-template.xml') # get the parser for the xml file 
soup.contents[0].name 
# u'root' 

Vous pouvez utiliser les noms de noeud en tant que méthodes:

soup.root.contents[0].name 
# u'level1' 

Il est également possible d'utiliser des regex:

import re 
tags_starting_with_level = soup.findAll(re.compile('^level')) 
for tag in tags_starting_with_level: print tag.name 
# level1 
# level2 

Ajout et l'insertion de nouveaux nœuds est assez simple:

# build and insert a new level with a new leaf 
level3 = Tag(soup, 'level3') 
level3.insert(0, NavigableString('leaf3') 
soup.root.insert(2, level3) 

print soup.prettify() 
# <root> 
# <level1> 
# leaf1 
# </level1> 
# <level2> 
# leaf2 
# </level2> 
# <level3> 
# leaf3 
# </level3> 
# </root> 
+3

BeautifulSoup convertit tout en minuscules. C'est vraiment nul. Je dois préserver les cas d'étiquettes et de valeurs! – user236215

+0

L'auteur de BeautifulSoup dit qu'il fait cela parce que HTMLParser le fait. "Si vous devez conserver le cas de tag, essayez lxml". – nealmcb

11

Je ne suis pas sûr si la conversion de l'ensemble d'informations en dictes imbriqués est d'abord plus facile. En utilisant ElementTree, vous pouvez le faire:

import xml.etree.ElementTree as ET 
doc = ET.parse("template.xml") 
lvl1 = doc.findall("level1-name")[0] 
lvl1.remove(lvl1.find("leaf1") 
lvl1.remove(lvl1.find("leaf2") 
# or use del lvl1[idx] 
doc.write("config-new.xml") 

ElementTree a été conçu de telle sorte que vous ne devez convertir vos arbres XML à des listes et des attributs d'abord, car il utilise exactement cela en interne.

Il prend également en charge un petit sous-ensemble de XPath.

+1

pourrait tout aussi bien utiliser simplement '' find' sur la cession lvl1', plutôt que 'findall' et obtenir le premier élément. –

0

Avez-vous essayé?

print xml.etree.ElementTree.tostring(conf_new) 
19

Cela va vous obtenez un dict moins les attributs ... je sais pas si cela est utile à tout le monde. Je cherchais moi-même une solution xml to dict quand je suis arrivé avec ça.



import xml.etree.ElementTree as etree 

tree = etree.parse('test.xml') 
root = tree.getroot() 

def xml_to_dict(el): 
    d={} 
    if el.text: 
    d[el.tag] = el.text 
    else: 
    d[el.tag] = {} 
    children = el.getchildren() 
    if children: 
    d[el.tag] = map(xml_to_dict, children) 
    return d 

Ce: http://www.w3schools.com/XML/note.xml

<note> 
<to>Tove</to> 
<from>Jani</from> 
<heading>Reminder</heading> 
<body>Don't forget me this weekend!</body> 
</note> 

égaleraient ceci:


{'note': [{'to': 'Tove'}, 
      {'from': 'Jani'}, 
      {'heading': 'Reminder'}, 
      {'body': "Don't forget me this weekend!"}]} 
+0

très utile pour moi; Merci! – mellort

+0

C'était exactement ce que je cherchais. Et l'utilisation de 'map' obtient des points bonus pour moi. Bien joué. –

0

façon la plus directe pour moi:

root  = ET.parse(xh) 
data  = root.getroot() 
xdic  = {} 
if data > None: 
    for part in data.getchildren(): 
     xdic[part.tag] = part.text 
4

Ma modification de la réponse de Daniel, pour donner un marginall y plus propre dictionnaire:

def xml_to_dictionary(element): 
    l = len(namespace) 
    dictionary={} 
    tag = element.tag[l:] 
    if element.text: 
     if (element.text == ' '): 
      dictionary[tag] = {} 
     else: 
      dictionary[tag] = element.text 
    children = element.getchildren() 
    if children: 
     subdictionary = {} 
     for child in children: 
      for k,v in xml_to_dictionary(child).items(): 
       if k in subdictionary: 
        if (isinstance(subdictionary[k], list)): 
         subdictionary[k].append(v) 
        else: 
         subdictionary[k] = [subdictionary[k], v] 
       else: 
        subdictionary[k] = v 
     if (dictionary[tag] == {}): 
      dictionary[tag] = subdictionary 
     else: 
      dictionary[tag] = [dictionary[tag], subdictionary] 
    if element.attrib: 
     attribs = {} 
     for k,v in element.attrib.items(): 
      attribs[k] = v 
     if (dictionary[tag] == {}): 
      dictionary[tag] = attribs 
     else: 
      dictionary[tag] = [dictionary[tag], attribs] 
    return dictionary 
espace de noms

est la chaîne xmlns, y compris des accolades, qui elementtree précèder à tous les tags, alors voici j'ai effacé comme il y a un espace de noms pour le document entier

NB que je ajusté les données XML brutes aussi, de sorte que les étiquettes « vides » produirait au plus un « » propriété de texte dans la représentation elementTree

spacepattern = re.compile(r'\s+') 
mydictionary = xml_to_dictionary(ElementTree.XML(spacepattern.sub(' ', content))) 

donnerait par exemple

{'note': {'to': 'Tove', 
     'from': 'Jani', 
     'heading': 'Reminder', 
     'body': "Don't forget me this weekend!"}} 

il est conçu pour XML spécifique qui est essentiellement équivalente à JSON, doit gérer attributs d'éléments tels que

<elementName attributeName='attributeContent'>elementContent</elementName> 

trop

il y a la possibilité de fusionner le dictionnaire dictionnaire d'attributs/sous-élément de façon similaire à la façon dont répéter subtags sont fusionnés, bien que l'imbrication des listes semble plutôt appropriée :-)

0

XML a un riche jeu d'infos, et il faut des astuces spéciales pour le représenter dans un dictionnaire Python. Les éléments sont triés, les attributs sont distingués des corps des éléments, etc.

Un projet permettant de gérer les allers-retours entre les dictionnaires XML et Python, avec des options de configuration pour gérer les compromis de différentes manières est XML Support in Pickling Tools. La version 1.3 et plus récente est requise. Ce n'est pas pur Python (et en fait est conçu pour faciliter l'interaction C++/Python), mais il pourrait être approprié pour divers cas d'utilisation.

1

L'ajout de cette ligne

d.update(('@' + k, v) for k, v in el.attrib.iteritems()) 

dans le user247686's code vous pouvez avoir noeud attributs aussi.

Je l'ai trouvé dans ce post https://stackoverflow.com/a/7684581/1395962

Exemple:

import xml.etree.ElementTree as etree 
from urllib import urlopen 

xml_file = "http://your_xml_url" 
tree = etree.parse(urlopen(xml_file)) 
root = tree.getroot() 

def xml_to_dict(el): 
    d={} 
    if el.text: 
     d[el.tag] = el.text 
    else: 
     d[el.tag] = {} 
    children = el.getchildren() 
    if children: 
     d[el.tag] = map(xml_to_dict, children) 

    d.update(('@' + k, v) for k, v in el.attrib.iteritems()) 

    return d 

Appel comme

xml_to_dict(root)