2008-09-24 23 views
8

Estoy tratando de generar archivos xml personalizados a partir de un archivo xml de plantilla en python.¿Editando XML como un diccionario en python?

Conceptualmente, quiero leer en la plantilla xml, eliminar algunos elementos, cambiar algunos atributos de texto y escribir el nuevo xml en un archivo. Yo quería que funcione algo como esto:

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) 

ahora quiero escribir en el archivo, pero no veo cómo llegar a ElementTree.ElementTree.write()

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

Es Hay alguna forma de hacer esto, o alguien puede sugerir hacer esto de otra manera?

Respuesta

8

Para una fácil manipulación de XML en Python, me gusta la biblioteca Beautiful Soup. Funciona así:

Muestra de archivo XML:

código
<root> 
    <level1>leaf1</level1> 
    <level2>leaf2</level2> 
</root> 

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' 

Puede utilizar los nombres de los nodos como métodos:

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

También es posible usar expresiones regulares:

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

agregar e insertar nuevos nodos es bastante sencillo:

# 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 convierte todo en minúsculas. Eso realmente apesta. ¡Tengo que preservar los casos de etiquetas y valores! – user236215

+0

El autor de BeautifulSoup dice que hace esto porque HTMLParser lo hace. "Si necesita conservar el caso de etiqueta, intente lxml". – nealmcb

11

No estoy seguro si primero es más fácil convertir el conjunto de información en dictados anidados. Usando elementtree, usted puede hacer esto:

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 fue diseñado para que usted no tiene que convertir sus árboles XML para listas y atributos en primer lugar, ya que utiliza exactamente eso internamente.

También es compatible con un pequeño subconjunto de XPath.

+1

ser que también acaba usar 'find' en el' asignación lvl1', en lugar de 'findall' y obteniendo el primer elemento. –

0

¿Has probado esto?

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

Este ponerlas a un dict menos atributos ... ¡no sé si esto es útil para cualquier persona. Estaba buscando una solución xml to dict cuando se me ocurrió esto.



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 

Este: 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> 

sería igual a esto:


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

muy útil para mí; ¡Gracias! – mellort

+0

Esto era exactamente lo que estaba buscando. Y el uso de 'map' obtiene puntos de bonificación para mí. Bien hecho. –

0

forma más directa de mí:

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

Mi modificación de la respuesta de Daniel, para dar una marginall y más ordenado diccionario:

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 

espacio de nombres es la cadena xmlns, incluyendo los apoyos, que elementtree antepone a todas las etiquetas, así que aquí se han limpiado ya que hay un espacio de nombres para todo el documento

NB que yo ajustó el código XML sin demasiado, por lo que las etiquetas 'vacías' produciría a lo sumo un '' propiedad de texto en la representación elementtree

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

daría por ejemplo

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

que está diseñado para XML específico que es básicamente equivalente a JSON, debe manejar elemento de atributos tales como

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

demasiado

existe la posibilidad de fusionar el diccionario diccionario atributo/etiqueta secundaria de manera similar a subetiquetas cómo repetición se fusionan, aunque anidar las listas parece algo apropiado :-)

0

XML tiene un infoset completo, y se necesitan algunos trucos especiales para representarlo en un diccionario de Python. Los elementos se ordenan, los atributos se distinguen de los elementos, etc.

Un proyecto para manejar viajes de ida y vuelta entre diccionarios XML y Python, con algunas opciones de configuración para manejar las compensaciones de diferentes maneras es XML Support in Pickling Tools. Se requiere la versión 1.3 y más reciente. No es puro Python (y de hecho está diseñado para facilitar la interacción con C++/Python), pero podría ser apropiado para varios casos de uso.

1

La adición de esta línea de

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

en el user247686's code puede tener los atributos de nodo también.

encontró en este post https://stackoverflow.com/a/7684581/1395962

Ejemplo:

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 

Call como

xml_to_dict(root) 
Cuestiones relacionadas