Синтаксический анализ 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 ответов


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

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

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

префиксы только посмотрел в . Конечно, вы можете использовать тот же синтаксис:

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 функция, разбор только событий запуска пространства имен (пуск-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() находит первый дочерний элемент с определенным тегом и элементом.text обращается к текстовому содержимому элемента. Элемент.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)

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

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