Módulo de Python ElementTree: Cómo ignorar el espacio de nombres de los archivos XML para ubicar el elemento coincidente cuando se usa el método “buscar”, “buscar todo”

Quiero usar el método de “findall” para localizar algunos elementos del archivo xml de origen en el módulo ElementTree.

Sin embargo, el archivo xml de origen (test.xml) tiene espacio de nombres. Trunco ​​parte del archivo xml como muestra:

  Updates 9/26/2012 10:30:34 AM All Rights Reserved. newlicense.htm  N   

El código de ejemplo de python está debajo:

 from xml.etree import ElementTree as ET tree = ET.parse(r"test.xml") el1 = tree.findall("DEAL_LEVEL/PAID_OFF") # Return None el2 = tree.findall("{http://www.test.com}DEAL_LEVEL/{http://www.test.com}PAID_OFF") # Return  

Aunque puede funcionar, porque hay un espacio de nombres “{http://www.test.com}”, es muy inconveniente agregar un espacio de nombres delante de cada etiqueta.

¿Cómo puedo ignorar el espacio de nombres cuando uso el método de “encontrar”, “buscar todo” y así sucesivamente?

En lugar de modificar el documento XML en sí, es mejor analizarlo y luego modificar las tags en el resultado. De esta manera puede manejar múltiples espacios de nombres y alias de espacios de nombres:

 from StringIO import StringIO import xml.etree.ElementTree as ET # instead of ET.fromstring(xml) it = ET.iterparse(StringIO(xml)) for _, el in it: if '}' in el.tag: el.tag = el.tag.split('}', 1)[1] # strip all namespaces root = it.root 

Esto se basa en la discusión aquí: http://bugs.python.org/issue18304

Si elimina el atributo xmlns del xml antes de analizarlo, entonces no habrá un espacio de nombres ante cada etiqueta en el árbol.

 import re xmlstring = re.sub(' xmlns="[^"]+"', '', xmlstring, count=1) 

Las respuestas hasta ahora ponen explícitamente el valor del espacio de nombres en el script. Para una solución más genérica, prefiero extraer el espacio de nombres del xml:

 import re def get_namespace(element): m = re.match('\{.*\}', element.tag) return m.group(0) if m else '' 

Y utilízalo en el método de búsqueda:

 namespace = get_namespace(tree.getroot()) print tree.find('./{0}parent/{0}version'.format(namespace)).text 

Aquí hay una extensión de la respuesta de nonagon, que también elimina los espacios de nombres de los atributos:

 from StringIO import StringIO import xml.etree.ElementTree as ET # instead of ET.fromstring(xml) it = ET.iterparse(StringIO(xml)) for _, el in it: if '}' in el.tag: el.tag = el.tag.split('}', 1)[1] # strip all namespaces for at in el.attrib.keys(): # strip namespaces of attributes too if '}' in at: newat = at.split('}', 1)[1] el.attrib[newat] = el.attrib[at] del el.attrib[at] root = it.root 

Mejorando la respuesta por ericspod:

En lugar de cambiar el modo de análisis global, podemos envolverlo en un objeto que admita el constructo with.

 from xml.parsers import expat class DisableXmlNamespaces: def __enter__(self): self.oldcreate = expat.ParserCreate expat.ParserCreate = lambda encoding, sep: self.oldcreate(encoding, None) def __exit__(self, type, value, traceback): expat.ParserCreate = self.oldcreate 

Esto puede ser utilizado como sigue

 import xml.etree.ElementTree as ET with DisableXmlNamespaces(): tree = ET.parse("test.xml") 

La belleza de esta manera es que no cambia ningún comportamiento para el código no relacionado fuera del bloque with. Terminé creando esto después de obtener errores en bibliotecas no relacionadas después de usar la versión de ericspod que también usaba expatriado.

También puede utilizar la construcción de formato de cadena elegante:

 ns='http://www.test.com' el2 = tree.findall("{%s}DEAL_LEVEL/{%s}PAID_OFF" %(ns,ns)) 

o, si está seguro de que PAID_OFF solo aparece en un nivel en el árbol:

 el2 = tree.findall(".//{%s}PAID_OFF" % ns) 

Si está utilizando ElementTree y no cElementTree , puede forzar a Expat para que ignore el procesamiento del espacio de nombres reemplazando ParserCreate() :

 from xml.parsers import expat oldcreate = expat.ParserCreate expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None) 

ElementTree intenta usar Expat llamando a ParserCreate() pero no ofrece ninguna opción para no proporcionar una cadena de separador de espacio de nombres, el código anterior hará que se ignore, pero se advierte que esto podría romper otras cosas.

Puede que sea tarde para esto, pero no creo que re.sub sea ​​una buena solución.

Sin embargo, la reescritura xml.parsers.expat no funciona para las versiones de Python 3.x,

El principal culpable es xml/etree/ElementTree.py Vea la parte inferior del código fuente.

 # Import the C accelerators try: # Element is going to be shadowed by the C implementation. We need to keep # the Python version of it accessible for some "creative" by external code # (see tests) _Element_Py = Element # Element, SubElement, ParseError, TreeBuilder, XMLParser from _elementtree import * except ImportError: pass 

Que es un poco triste.

La solución es deshacerse de él primero.

 import _elementtree try: del _elementtree.XMLParser except AttributeError: # in case deleted twice pass else: from xml.parsers import expat # NOQA: F811 oldcreate = expat.ParserCreate expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None) 

Probado en Python 3.6.

La instrucción Try try es útil en caso de que en algún lugar del código recargues o importes un módulo dos veces obtengas errores extraños como

  • Profundidad máxima de recursión excedida
  • AttributeError: XMLParser

Por cierto, el código fuente etree parece muy desordenado.