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]}
- Unnamespaceed:
/root/elem
- 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.
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.
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ć":
- Unnamespaceed:
/root/elem
- > no problem here I assume - 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.
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