Разбор XML с пространством имен в Python через 'ElementTree'

У меня есть следующий XML, который я хочу проанализировать с помощью Python ElementTree:

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

Я хочу найти все теги owl:Class, а затем извлечь из них все экземпляры rdfs:label. Я использую следующий код:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

Из-за пространства имен я получаю следующую ошибку.

SyntaxError: prefix 'owl' not found in prefix map

Я пробовал читать документ http://effbot.org/zone/element-namespaces.htm, но я все еще не могу получить эту работу, поскольку указанный выше XML имеет несколько вложенных пространств имен.

Пожалуйста, дайте мне знать, как изменить код, чтобы найти все теги owl:Class.

Ответ 1

ElementTree не слишком разбирается в пространствах имен. Вы должны дать методам .find(), findall() и iterfind() явный словарь пространства имен. Это не очень хорошо документировано:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

Префиксы просматриваются только в параметре namespaces, который вы передаете. Это означает, что вы можете использовать любой префикс пространства имен, который вам нравится; API отделяет часть owl:, просматривает соответствующий URL-адрес пространства имен в словаре namespaces, а затем изменяет поиск, чтобы искать выражение XPath {http://www.w3.org/2002/07/owl}Class. Вы тоже можете использовать тот же синтаксис:

root.findall('{http://www.w3.org/2002/07/owl#}Class')

Если вы можете переключиться на lxml library, все будет лучше; эта библиотека поддерживает один и тот же API ElementTree, но собирает пространства имен для вас в атрибуте .nsmap для элементов.

Ответ 2

Вот как это сделать с помощью lxml без необходимости жесткого кодирования пространств имен или сканирования их текста (как упоминает Мартин Питерс):

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

UPDATE:

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

Вот еще один случай и как я с этим справился:

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

xmlns без префикса означает, что теги без префиксов получают это пространство имен по умолчанию. Это означает, что при поиске Tag2 вам необходимо включить пространство имен, чтобы найти его. Однако lxml создает запись nsmap с ключом None, и я не смог найти способ найти его. Итак, я создал новый словарь пространства имен, как этот

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)

Ответ 3

Примечание. Это ответ, полезный для стандартной библиотеки Python ElementTree без использования жестко заданных пространств имен.

Чтобы извлечь префиксы пространства имен и URI из данных XML, вы можете использовать функцию ElementTree.iterparse, анализируя только события запуска пространства имен (start-ns):

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

Затем словарь может быть передан как аргумент в функции поиска:

root.findall('owl:Class', my_namespaces)

Ответ 4

Я использовал подобный код для этого и нашел, что всегда стоит прочитать документацию... как обычно!

findall() найдет только элементы, которые являются прямыми потомками текущего тега. Так что не совсем ВСЕ.

Возможно, стоит попытаться заставить ваш код работать со следующим, особенно если вы имеете дело с большими и сложными XML файлами, чтобы эти подэлементы (и т.д.) Также были включены. Если вы сами знаете, где находятся элементы в вашем xml, тогда, я думаю, все будет хорошо! Просто подумал, что это стоит запомнить.

root.iter()

ref: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall() находит только элементы с тегом, которые являются прямыми потомками текущего элемента. Element.find() находит первого потомка с определенным тегом, а Element.text обращается к текстовому содержимому элементов. Element.get() обращается к атрибутам элементов: "

Ответ 5

Чтобы получить пространство имен в его формате пространства имен, например, {myNameSpace}, вы можете сделать следующее:

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

Таким образом, вы можете использовать его позже в своем коде для поиска узлов, например, используя интерполяцию строк (Python 3).

link = root.find(f"{ns}link")

Ответ 6

Я знаю, что я опаздываю на несколько лет, но я создал пакет, который будет обрабатывать преобразование словаря в допустимый XML с пространствами имен. Пакет размещен на PyPi @https://pypi.python.org/pypi/xmler.

Используя этот пакет, вы можете взять словарь, который выглядит так:

myDict = {
    "RootTag": {                        # The root tag. Will not necessarily be root. (see #customRoot)
        "@ns": "soapenv",           # The namespace for the RootTag. The RootTag will appear as <soapenv:RootTag ...>
        "@attrs": {                     # @attrs takes a dictionary. each key-value pair will become an attribute
            { "xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/" }
        },
        "childTag": {
            "@attrs": {
                "someAttribute": "colors are nice"
            },
            "grandchild": "This is a text tag"
        }
    }
}

и получить XML-вывод, который выглядит следующим образом:

<soapenv:RootTag xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <childTag someAttribute="colors are nice">
        <grandchild>This is a text tag</grandchild>
    </childTag>
</soapenv:RootTag>

Надеюсь, это полезно для людей в будущем.

Ответ 7

Мое решение основано на комментарии @Martijn Pieters:

register_namespace влияет только на сериализацию, а не на поиск.

Таким образом, хитрость в том, чтобы использовать разные словари для сериализации и поиска.

namespaces = {
    '': 'http://www.example.com/default-schema',
    'spec': 'http://www.example.com/specialized-schema',
}

Теперь зарегистрируйте все пространства имен для анализа и записи:

for name, value in namespaces.iteritems():
    ET.register_namespace(name, value)

Для поиска (find(), findall(), iterfind()) нам нужен непустой префикс. Передайте этим функциям измененный словарь (здесь я изменяю исходный словарь, но это необходимо сделать только после регистрации пространств имен).

self.namespaces['default'] = self.namespaces['']

Теперь функции из семейства find() можно использовать с префиксом по default:

print root.find('default:myelem', namespaces)

но

tree.write(destination)

не использует префиксы для элементов в пространстве имен по умолчанию.