¿Una forma realmente sencilla de tratar con XML en Python?

Reflexionando sobre una pregunta recientemente hecha, comencé a preguntarme si hay una manera realmente simple de tratar con documentos XML en Python. Una forma pythonica, por así decirlo.

Tal vez pueda explicar mejor si doy un ejemplo: digamos lo siguiente, que creo que es un buen ejemplo de cómo se usa (mal) XML en los servicios web, es la respuesta que obtengo de la solicitud http a http://www.google .com / ig / api? weather = 94043

                   ...          

Después de cargar / analizar dicho documento, me gustaría poder acceder a la información tan simple como digamos

 >>> xml['xml_api_reply']['weather']['forecast_information']['city'].data 'Mountain View, CA' 

o

 >>> xml.xml_api_reply.weather.current_conditions.temp_f['data'] '68' 

Por lo que vi hasta ahora, parece que ElementTree es lo más cercano a lo que sueño. Pero no está ahí, todavía hay mucho que hacer cuando se consume XML. OTOH, lo que estoy pensando no es tan complicado (probablemente solo una capa delgada sobre un analizador) y, sin embargo, puede reducir la molestia de tratar con XML. ¿Hay tal magia? (Y si no, ¿por qué?)

PD. Tenga en cuenta que ya he probado BeautifulSoup y, aunque me gusta su enfoque, tiene problemas reales con s vacíos: consulte a continuación en los comentarios para ver ejemplos.

¿Quieres una chapa delgada? Eso es fácil de cocinar. Pruebe el siguiente envoltorio trivial alrededor de ElementTree como un comienzo:

 # geetree.py import xml.etree.ElementTree as ET class GeeElem(object): """Wrapper around an ElementTree element. a['foo'] gets the attribute foo, a.foo gets the first subelement foo.""" def __init__(self, elem): self.etElem = elem def __getitem__(self, name): res = self._getattr(name) if res is None: raise AttributeError, "No attribute named '%s'" % name return res def __getattr__(self, name): res = self._getelem(name) if res is None: raise IndexError, "No element named '%s'" % name return res def _getelem(self, name): res = self.etElem.find(name) if res is None: return None return GeeElem(res) def _getattr(self, name): return self.etElem.get(name) class GeeTree(object): "Wrapper around an ElementTree." def __init__(self, fname): self.doc = ET.parse(fname) def __getattr__(self, name): if self.doc.getroot().tag != name: raise IndexError, "No element named '%s'" % name return GeeElem(self.doc.getroot()) def getroot(self): return self.doc.getroot() 

Lo invocas así:

 >>> import geetree >>> t = geetree.GeeTree('foo.xml') >>> t.xml_api_reply.weather.forecast_information.city['data'] 'Mountain View, CA' >>> t.xml_api_reply.weather.current_conditions.temp_f['data'] '68' 

lxml ha sido mencionado. También puede revisar lxml.objectify para una manipulación realmente simple.

 >>> from lxml import objectify >>> tree = objectify.fromstring(your_xml) >>> tree.weather.attrib["module_id"] '0' >>> tree.weather.forecast_information.city.attrib["data"] 'Mountain View, CA' >>> tree.weather.forecast_information.postal_code.attrib["data"] '94043' 

Recomiendo altamente lxml.etree y xpath para analizar y analizar sus datos. Aquí hay un ejemplo completo. He truncado el xml para que sea más fácil de leer.

 import lxml.etree s = """               """ tree = lxml.etree.fromstring(s) for weather in tree.xpath('/xml_api_reply/weather'): print weather.find('forecast_information/city/@data')[0] print weather.find('forecast_information/forecast_date/@data')[0] print weather.find('forecast_conditions/low/@data')[0] print weather.find('forecast_conditions/high/@data')[0] 

Echa un vistazo a Amara 2, particularmente la parte de Bindery de este tutorial .

Funciona de una manera bastante similar a la que usted describe.

Por otra parte. Los métodos find * () de ElementTree le pueden dar el 90% de eso y están empaquetados con Python.

Si no le importa usar una biblioteca de terceros, entonces BeautifulSoup hará casi exactamente lo que pide:

 >>> from BeautifulSoup import BeautifulStoneSoup >>> soup = BeautifulStoneSoup('''''') >>> soup.xml_api_reply.weather.current_conditions.temp_f['data'] u'68' 

Creo que el módulo xml de python integrado hará el truco. Mira a “xml.parsers.expat”

xml.parsers.expat

Encontré el siguiente módulo python-simplexml , que en los bashs del autor de obtener algo parecido a SimpleXML desde PHP es, de hecho, un small wrapper around ElementTree . Tiene menos de 100 líneas pero parece hacer lo que se solicitó:

 >>> import SimpleXml >>> x = SimpleXml.parse(urllib.urlopen('http://www.google.com/ig/api?weather=94043')) >>> print x.weather.current_conditions.temp_f['data'] 58 

El proyecto de suds proporciona una biblioteca cliente de servicios web que funciona casi exactamente como lo describe, le proporciona un wsdl y luego usa los métodos de fábrica para crear los tipos definidos (¡y procesa las respuestas también!).

Si aún no lo has hecho, te sugiero que busques en la API DOM para Python . DOM es un sistema de interpretación XML bastante utilizado, por lo que debería ser bastante robusto.

Probablemente sea un poco más complicado de lo que describe, pero eso se debe a sus bashs de preservar toda la información implícita en el marcado XML en lugar de un mal diseño.