Как найти XML-элементы через XPath в Python независимым от пространства имен способом? - PullRequest
18 голосов
/ 06 апреля 2011

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

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

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

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

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

Но это {{{{ugly}}} и хрупкое, так как http…end/#http…end#http…end/http…end, иКто я такой, чтобы знать, какой вариант будет использоваться?

Кроме того, lxml поддерживает префиксы пространств имен, но не использует ни те, что в документе, ни обеспечивает автоматический способ обработки пространств имен по умолчанию.Мне все равно придется получить один элемент каждого пространства имен, чтобы извлечь его из документа.Атрибуты пространства имен не сохраняются, поэтому также нет способа их автоматического извлечения из них.

Существует также способ, не зависящий от пространства имен, для запросов XPath, но он является как подробным, так и уродливым и недоступным во встроенной реализации.: /*[local-name() = 'root']/*[local-name() = 'elem']

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

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

  1. Пространство без имени: /root/elem
  2. Префиксы пространства имен из документа: /root/other:elem

… плюс, возможно, некоторые утверждения, которые я действительно хочу использовать префиксы документа илиУдалите пространства имен.

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

Спасибо за чтение!


Решено

Пользовательские выборки обратили мое внимание на 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 ]

13 голосов
/ 07 апреля 2011

Синтаксис *[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>]

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

1 голос
/ 08 апреля 2011

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

  1. Unnamespaced: /root/elem -> здесь нет проблем, я предполагаю
  2. Префиксы пространства имен из документа: /root/other:elem-> Ну, это небольшая проблема, вы не можете просто использовать «префиксы пространства имен из документа».Даже в пределах одного документа:
    • элементы пространства имен не обязательно даже имеют префикс
    • один и тот же префикс не обязательно всегда отображается в одном и том же пространстве имен uri
    • в одном и том же пространстве имен uriне обязательно всегда иметь одинаковый префикс

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

...