Jak znaleźć elementy XML za pomocą XPath w Pythonie w przestrzeni nazw-agnostic sposób?

Ponieważ miałem ten irytujący problem po raz drugi, pomyślałem, że prośba pomoże.

Czasami muszę pobierać elementy z dokumentów XML, ale sposoby na to są niezręczne.

Chciałbym znać bibliotekę Pythona, która robi to, co chcę, elegancki sposób formułowania moich ścieżek XPath, sposób na automatyczną rejestrację przestrzeni nazw w prefiksach lub ukrytą preferencję w wbudowanych implementacjach XML lub w lxml, aby całkowicie usunąć przestrzenie nazw. Wyjaśnienie następuje, chyba że już wiem czego chcę:)

Przykład-doc:

<root xmlns="http://really-long-namespace.uri"
  xmlns:other="http://with-ambivalent.end/#">
    <other:elem/>
</root>

What I can do

ElementTree API jest jedynym wbudowanym (o którym wiem) dostarczającym zapytania XPath. Ale to wymaga ode mnie użycia "UNames."To wygląda tak: /{http://really-long-namespace.uri}root/{http://with-ambivalent.end/#}elem

Jak widzisz, są dość gadatliwe. Mogę je skrócić, wykonując następujące czynności:

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

Ale To jest zarówno {{{brzydkie}}} jak i kruche, ponieważ http…end/#http…end#http…end/http…end, i kim jestem, żeby wiedzieć, który wariant będzie używany?

Lxml obsługuje również prefiksy przestrzeni nazw, ale nie używa prefiksów zawartych w dokumencie, ani nie zapewnia automatycznego sposobu radzenia sobie z domyślnymi przestrzeniami nazw. Nadal musiałbym dostać jeden element każdej przestrzeni nazw, aby pobrać go z dokumentu. Atrybuty przestrzeni nazw nie są zachowane, więc nie ma możliwości automatycznego ich pobierania z nich.

Istnieje również przestrzeń nazw-agnostyczny sposób zapytań XPath, ale jest zarówno wyrazisty/brzydki, jak i niedostępny w wbudowanym realizacja: /*[local-name() = 'root']/*[local-name() = 'elem']

What I want to do

Chcę znaleźć bibliotekę, opcję lub ogólną funkcję XPath-morphing, aby osiągnąć powyższe przykłady, wpisując niewiele więcej niż następujące ... {12]}

  1. Unnamespaceed: /root/elem
  2. Przestrzeń nazw-prefiksy z dokumentu: /root/other:elem

... plus może kilka stwierdzeń, że rzeczywiście chcę użyć prefiksów dokumentu lub usunąć przestrzenie nazw.

Dalsze wyjaśnienie: chociaż mój obecny przypadek użycia jest tak prosty w związku z tym w przyszłości będę musiał użyć bardziej złożonych.

Dzięki za przeczytanie!


Rozwiązany

Użytkownik samplebias skierował moją uwagę na py-dom-xpath ; dokładnie tego szukałem. Mój rzeczywisty kod wygląda teraz tak:

#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)

#<Description/> inherits the default RDF namespace
resource_nodes = context.find("//Description/following-sibling::*", rdf_tree)

Zgodny z dokumentem, prosty, świadomy przestrzeni nazw; doskonały.

Author: flying sheep, 2011-04-06

2 answers

Składnia *[local-name() = "elem"] powinna działać, ale aby to ułatwić, możesz utworzyć funkcję upraszczającą budowę częściowej lub pełnej" wieloznacznej przestrzeni nazw " wyrażeń XPath.

Używam python-lxml 2.2.4 na Ubuntu 10.04 i poniższy skrypt działa dla mnie. Musisz dostosować zachowanie w zależności od tego, jak chcesz określić domyślne przestrzenie nazw dla każdego elementu, a także obsłużyć każdą inną składnię XPath, którą chcesz złożyć wyrażenie:

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 = '''<root xmlns="http://really-long-namespace.uri"
    xmlns:other="http://with-ambivalent.end/#">
    <other:elem/>
</root>'''

tree = lxml.etree.fromstring(doc)
print xpath_ns(tree, '/root')
print xpath_ns(tree, '/root/elem')
print xpath_ns(tree, '/root/other:elem')

Wyjście:

[<Element {http://really-long-namespace.uri}root at 23099f0>]
[<Element {http://with-ambivalent.end/#}elem at 2309a48>]
[<Element {http://with-ambivalent.end/#}elem at 2309a48>]

Update: jeśli okaże się, że musisz parsować ścieżki XPath, możesz sprawdzić projekty takie jak py-dom-xpath, który jest czystą implementacją Pythona (większości) XPath 1.0. Przynajmniej to da ci pojęcie o złożoności parsowania XPath.

 12
Author: samplebias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-07 15:10:41

Po pierwsze, o "co chcesz zrobić":

  1. Unnamespaceed: /root/elem - > no problem here I assume
  2. Przestrzeń nazw-prefiksy z dokumentu: /root/other:elem - > Cóż, to jest mały problem, nie można po prostu użyć "przestrzeń nazw-prefiksy z dokumentu". Nawet w ramach jednego dokumentu:
    • Elementy przestrzeni nazw niekoniecznie mają nawet przedrostek
    • ten sam prefiks nie zawsze jest mapowany do tej samej przestrzeni nazw uri
    • ta sama przestrzeń nazw uri niekoniecznie zawsze ma ten sam przedrostek

FYI: jeśli chcesz dostać się do mapowania prefiksów w zakresie dla określonego elementu, spróbuj elem.nsmap w lxml. Ponadto, metody iterparse i iterwalk w lxml.etree może być używane do "powiadamiania" o deklaracjach przestrzeni nazw.

 1
Author: Steven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-07 21:26:30