python - from - xml.etree.elementtree example
Módulo Python ElementTree: cómo ignorar el espacio de nombres de los archivos XML para localizar el elemento coincidente al usar el método "find", "findall" (6)
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
Quiero utilizar el método de "findall" para localizar algunos elementos del archivo xml fuente en el módulo ElementTree.
Sin embargo, el archivo xml fuente (test.xml) tiene espacio de nombres. Trunco parte del archivo xml como muestra:
<?xml version="1.0" encoding="iso-8859-1"?>
<XML_HEADER xmlns="http://www.test.com">
<TYPE>Updates</TYPE>
<DATE>9/26/2012 10:30:34 AM</DATE>
<COPYRIGHT_NOTICE>All Rights Reserved.</COPYRIGHT_NOTICE>
<LICENSE>newlicense.htm</LICENSE>
<DEAL_LEVEL>
<PAID_OFF>N</PAID_OFF>
</DEAL_LEVEL>
</XML_HEADER>
El código python de muestra está a continuación:
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 <Element ''{http://www.test.com}DEAL_LEVEL/PAID_OFF'' at 0xb78b90>
Aunque puede funcionar, porque hay un espacio de nombres "{http://www.test.com}", es muy inconveniente agregar un espacio de nombres al frente de cada etiqueta.
¿Cómo puedo ignorar el espacio de nombres cuando uso el método de "buscar", "findall" y demás?
En lugar de modificar el documento XML en sí, es mejor analizarlo y luego modificar las etiquetas en el resultado. De esta forma puede manejar múltiples espacios de nombres y alias de espacio 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
Las respuestas hasta ahora explícitamente ponen el valor del espacio de nombres en el script. Para una solución más genérica, preferiría 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 úsala en el método de búsqueda:
namespace = get_namespace(tree.getroot())
print tree.find(''./{0}parent/{0}version''.format(namespace)).text
Si está utilizando ElementTree
y no cElementTree
, puede forzar a Expat a ignorar el procesamiento del espacio de nombres reemplazando a ParserCreate()
:
from xml.parsers import expat
oldcreate = expat.ParserCreate
expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)
ElementTree
intenta utilizar Expat llamando a ParserCreate()
pero no proporciona ninguna opción para no proporcionar una cadena de separador de espacio de nombre, el código anterior hará que se ignore, pero tenga en cuenta que esto podría romper otras cosas.
Si quita el atributo xmlns del xml antes de analizarlo, no habrá un espacio de nombre antes de cada etiqueta en el árbol.
import re
xmlstring = re.sub('' xmlns="[^"]+"'', '''', xmlstring, count=1)
También puede usar la elegante construcción de formato de cadena:
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)