¿Cómo encontrar elementos XML a través de XPath en Python de una manera independiente del espacio de nombres?

ya que tuve este problema molesto por segunda vez, pensé que preguntar ayudaría.

A veces tengo que obtener elementos de documentos XML, pero las formas de hacerlo son incómodas.

Me gustaría conocer una biblioteca de Python que haga lo que quiero, una forma elegante de formular mis XPaths, una forma de registrar los espacios de nombres en los prefijos automáticamente o una preferencia oculta en las implementaciones XML integradas o en lxml para eliminar los espacios de nombres completamente. Sigue una aclaración a menos que ya sepas lo que quiero 🙂

Ejemplo-doc:

   

Que puedo hacer

La API de ElementTree es la única integrada (que conozco) que proporciona consultas XPath. Pero me obliga a usar “UNames”. Esto se ve así: /{http://really-long-namespace.uri}root/{http://with-ambivalent.end/#}elem

Como puedes ver, estos son bastante verbosos. Puedo acortarlos haciendo lo siguiente:

 default_ns = "http://really-long-namespace.uri" other_ns = "http://with-ambivalent.end/#" doc.find("/{{{0}}}root/{{{1}}}elem".format(default_ns, other_ns)) 

Pero esto es tanto {{{fevado}}} como frágil, ya que http…end/#http…end# http…end/ http…end , ¿y quién soy yo para saber qué variante se usará?

Además, lxml admite prefijos de espacio de nombres, pero no utiliza los del documento ni proporciona una forma automatizada de tratar los espacios de nombres predeterminados. Todavía tendría que obtener un elemento de cada espacio de nombres para recuperarlo del documento. Los atributos del espacio de nombres no se conservan, por lo que tampoco hay forma de recuperarlos automáticamente de estos.

También hay una forma agnóstica de espacio de nombres de las consultas XPath, pero es detallada / fea y no está disponible en la implementación integrada: /*[local-name() = 'root']/*[local-name() = 'elem']

Lo que quiero hacer

Quiero encontrar una biblioteca, una opción o una función genérica de cambio de XPath para lograr los ejemplos anteriores escribiendo poco más que lo siguiente …

  1. Unnamespaced: /root/elem
  2. Los prefijos de espacio de nombres del documento: /root/other:elem

… y quizás algunas afirmaciones de que realmente quiero usar los prefijos del documento o quitar los espacios de nombres.

Más aclaraciones: aunque mi caso de uso actual es tan simple como eso, tendré que usar otros más complejos en el futuro.

¡Gracias por leer!


Resuelto

Las samplebias de usuario dirigieron mi atención a py-dom-xpath ; Exactamente lo que estaba buscando. Mi código actual ahora se ve así:

 #parse the document into a DOM tree rdf_tree = xml.dom.minidom.parse("install.rdf") #read the default namespace and prefix from the root node context = xpath.XPathContext(rdf_tree) name = context.findvalue("//em:id", rdf_tree) version = context.findvalue("//em:version", rdf_tree) # inherits the default RDF namespace resource_nodes = context.find("//Description/following-sibling::*", rdf_tree) 

Consistente con el documento, simple, consciente del espacio de nombres; Perfecto.

La syntax *[local-name() = "elem"] debería funcionar, pero para que sea más fácil, puede crear una función para simplificar la construcción de las expresiones XPath parciales o de “espacio de nombre de comodín”.

Estoy usando python-lxml 2.2.4 en Ubuntu 10.04 y el script a continuación funciona para mí. Deberá personalizar el comportamiento dependiendo de cómo desee especificar los espacios de nombre predeterminados para cada elemento, además de manejar cualquier otra syntax de XPath que desee incluir en la expresión:

 import lxml.etree def xpath_ns(tree, expr): "Parse a simple expression and prepend namespace wildcards where unspecified." qual = lambda n: n if not n or ':' in n else '*[local-name() = "%s"]' % n expr = '/'.join(qual(n) for n in expr.split('/')) nsmap = dict((k, v) for k, v in tree.nsmap.items() if k) return tree.xpath(expr, namespaces=nsmap) doc = '''  ''' tree = lxml.etree.fromstring(doc) print xpath_ns(tree, '/root') print xpath_ns(tree, '/root/elem') print xpath_ns(tree, '/root/other:elem') 

Salida:

 [] [] [] 

Actualización : Si descubre que necesita analizar XPaths, puede revisar proyectos como py-dom-xpath, que es una implementación pura de Python de (la mayoría de) XPath 1.0. En lo más mínimo, eso te dará una idea de la complejidad del análisis de XPath.

Primero, sobre “lo que quieres hacer”:

  1. Unnamespaced: /root/elem -> no hay problema aquí, supongo
  2. Los prefijos de espacio de nombres del documento: /root/other:elem -> bueno, eso es un problema, no puede usar los “prefijos de espacio de nombres del documento”. Incluso dentro de un documento:
    • Los elementos de espacio de nombre no necesariamente tienen un prefijo
    • el mismo prefijo no siempre está necesariamente asignado al mismo espacio de nombres uri
    • el mismo espacio de nombres uri no necesariamente tiene siempre el mismo prefijo

Para su información: si desea obtener las asignaciones de prefijo en el ámbito de un determinado elemento, intente elem.nsmap en lxml. Además, los métodos iterparse e iterwalk en lxml.etree pueden utilizarse para ser “notificados” de las declaraciones de espacio de nombres.