Разбор 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 теги, а затем извлечь значение all 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 теги.

6 117

6 ответов:

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 . Вы можете использовать тот же синтаксис и сами, конечно:

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

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

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

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

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

для извлечения префиксов пространства имен и 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)

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

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

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

root.iter()

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

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

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

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

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

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

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