Как найти XML-элементы через XPath в Python в пространстве имен-агностическим способом?
Поскольку у меня была эта раздражающая проблема во второй раз, я думал, что просьба поможет.
Иногда мне приходится получать элементы из XML-документов, но способы сделать это неудобны.
Я хотел бы знать библиотеку python, которая делает то, что я хочу, элегантный способ формулировки моих XPath, способ автоматической регистрации пространств имен в префиксах или скрытое предпочтение во встроенных реализациях XML или в lxml для полного удаления пространств имен. Разъяснение следует, Если вы уже знаю, чего хочу:)
Пример-doc:
<root xmlns="http://really-long-namespace.uri"
xmlns:other="http://with-ambivalent.end/#">
<other:elem/>
</root>
Что я могу сделать
API ElementTree-единственный встроенный (я знаю о нем), предоставляющий запросы XPath. Но он требует, чтобы я использовал " UNames."Это выглядит так: /{http://really-long-namespace.uri}root/{http://with-ambivalent.end/#}elem
Как видите, они довольно многословны. Я могу сократить их, сделав следующее:
default_ns = "http://really-long-namespace.uri"
other_ns = "http://with-ambivalent.end/#"
doc.find("/{{{0}}}root/{{{1}}}elem".format(default_ns, other_ns))
Но это одновременно {{{уродливо}}} и хрупко, так как http…end/#
≃ http…end#
≃ http…end/
≃ http…end
, и кто я такой, чтобы знать, какой вариант будет используется?
Кроме того, lxml поддерживает префиксы пространств имен, но не использует их в документе и не предоставляет автоматизированный способ работы с пространствами имен по умолчанию. Мне все равно придется получить один элемент каждого пространства имен, чтобы извлечь его из документа. Атрибуты пространства имен не сохраняются, поэтому нет возможности автоматически извлекать их из них.
Существует также способ XPath-запросов, не зависящий от пространства имен, но он является одновременно многословным / уродливым и недоступным в builtin реализация: /*[local-name() = 'root']/*[local-name() = 'elem']
Что я хочу сделать
Я хочу найти библиотеку, опцию или универсальную функцию XPath-морфинга, чтобы достичь вышеуказанных примеров, введя немного больше, чем следующее ...
- безымянный:
/root/elem
- пространство имен-префиксы из документа:
/root/other:elem
...плюс, возможно, некоторые утверждения, что я действительно хочу использовать префиксы документа или лишить пространства имен.
Дальнейшее уточнение: хотя мой текущий вариант использования так же прост поэтому в будущем мне придется использовать более сложные.
Спасибо за чтение!
Решено
Пользователь samplebias направил мое внимание наpy-dom-xpath ; именно то, что я искал. Мой реальный код теперь выглядит так:
#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)
Согласовано с документом, просто, с учетом пространства имен; идеально.
2 ответов:
Синтаксис
*[local-name() = "elem"]
должен работать, но чтобы упростить его, вы можете создать функцию для упрощения построения частичного или полного "подстановочного пространства имен" выражений XPath.Я использую python-lxml 2.2.4 на Ubuntu 10.04 , и приведенный ниже скрипт работает для меня. Вам нужно будет настроить поведение в зависимости от того, как вы хотите указать пространства имен по умолчанию для каждого элемента, а также обрабатывать любой другой синтаксис XPath, который вы хотите свернуть в выражение:
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')
Вывод:
[<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: Если вы обнаружите, что вам действительно нужно проанализировать XPath, вы можете проверить такие проекты, как py-dom-xpath, который является чистой реализацией Python (большей части) XPath 1.0. По крайней мере, это даст вам некоторое представление о сложности синтаксического анализа XPath.
Во-первых, о том, что вы хотите сделать:
- Unnamespaced:
/root/elem
-> никаких проблем здесь я предполагаю- пространство имен-префиксы из документа:
/root/other:elem
- > Ну, это немного проблема, вы не можете просто использовать "пространство имен-префиксы из документа". Даже в пределах одного документа:
- элементы пространства имен не обязательно даже имеют префикс
- один и тот же префикс не обязательно всегда сопоставляется с одним и тем же URI пространства имен
- одно и то же пространство имен uri не обязательно всегда имеет тот же префикс
FYI: если вы хотите получить префиксные отображения в области видимости для определенного элемента, попробуйте
elem.nsmap
в lxml. Кроме того, методом iterparse, показанный и iterwalk методы В помощью lxml.etree можно использовать для "уведомления" об объявлениях пространства имен.
Comments