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

100

У меня есть следующий 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.

Теги:
xml-parsing
elementtree

5 ответов

158
Лучший ответ

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 для элементов.

  • 0
    Благодарю. Специально для второй части, где вы можете дать пространство имен напрямую.
  • 6
    Спасибо. Любая идея, как я могу получить пространство имен непосредственно из XML, без жесткого его кодирования? Или как я могу это игнорировать? Я пробовал findall ('{*} Class'), но в моем случае это не сработает.
Показать ещё 8 комментариев
35

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

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)
  • 0
    это работает просто отлично.
  • 2
    Полный URL-адрес пространства имен - это идентификатор пространства имен, который вы должны жестко кодировать. Локальный префикс ( owl ) может меняться от файла к файлу. Поэтому делать то, что предлагает этот ответ, действительно плохая идея.
Показать ещё 2 комментария
10

Примечание. Это ответ, полезный для стандартной библиотеки 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)
  • 1
    Это полезно для тех из нас, кто не имеет доступа к lxml и не хочет жестко кодировать пространство имен.
  • 1
    Я получил сообщение об ошибке: ValueError: write to closed для этой строки filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])]) . Любая идея хочет неправильно?
Показать ещё 3 комментария
1

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

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() доступ к атрибутам элементов:"

0

Я знаю, что я опаздываю на несколько лет, но я создал пакет, который будет обрабатывать преобразование словаря в допустимый 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>

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню