Подтвердить что ты не робот

Как найти XML-элементы через XPath в Python в пространстве имен - агностик?

так как у меня была эта неприятная проблема во второй раз, я подумал, что просьба поможет.

Иногда мне приходится получать элементы из XML-документов, но способы сделать это неудобны.

Мне нравится знать библиотеку python, которая делает то, что я хочу, элегантный способ сформулировать мои XPaths, способ автоматически регистрировать пространства имен в префиксах или скрытые предпочтения во встроенных реализациях XML или в lxml для полного заполнения пространств имен. Уточнение следует, если вы уже не знаете, чего я хочу:)

Пример-документ:

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

Что я могу сделать

API ElementTree является единственным встроенным (я знаю), предоставляющим запросы XPath. Но это требует от меня использовать "имена". Это выглядит так: /{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, но он также является подробным/уродливым и недоступным во встроенной реализации: /*[local-name() = 'root']/*[local-name() = 'elem']

Что я хочу сделать

Я хочу найти библиотеку, опцию или общую функцию XPath-morphing для достижения вышеприведенных примеров, набрав немного больше, чем следующее...

  • Unnamespaced: /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)

В соответствии с документом, простым, с поддержкой пространства имен; совершенны.

4b9b3361

Ответ 1

Синтаксис *[local-name() = "elem"] должен работать, но для упрощения создания функции можно упростить создание частичного или полного выражения подстановочных имен XPath.

Я использую python-lxml 2.2.4 на Ubuntu 10.04, а ниже script работает для меня. Вам нужно будет настроить поведение в зависимости от того, как вы хотите указать пространства имен по умолчанию для каждого элемента, а также обработать любой другой синтаксис 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>]

Обновление. Если вы выясните, что вам нужно разобрать XPaths, вы можете проверить проекты, такие как py-dom-xpath, который является чистой реализацией Python (большей части) XPath 1.0. По крайней мере, это даст вам некоторое представление о сложности разбора XPath.

Ответ 2

Во-первых, о том, что вы хотите сделать:

  • Unnamespaced: /root/elem → никаких проблем здесь я предполагаю
  • Префикс пространства имен из документа: /root/other:elem → хорошо, что это проблема, вы не можете просто использовать "namespace-prefixes from document". Даже внутри одного документа:
    • Элементы с именами не обязательно имеют префикс
    • тот же префикс не обязательно всегда отображается в одно и то же пространство имен uri
    • одно и то же пространство имен uri не обязательно всегда имеет один и тот же префикс

FYI: если вы хотите перейти к префиксным сопоставлениям в области для определенного элемента, попробуйте elem.nsmap в lxml. Кроме того, iterparse и iterwalk методы в lxml.etree могут быть использованы для уведомления об именах пространств имен.