Parsowanie XML z przestrzenią nazw w Pythonie za pomocą 'ElementTree'

Mam następujący XML, który chcę przeanalizować używając Pythona 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>

Chcę znaleźć wszystkie znaczniki owl:Class, a następnie wyodrębnić wartość wszystkich instancji rdfs:label wewnątrz nich. Używam następującego kodu:

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

Z powodu przestrzeni nazw, dostaję następujący błąd.

SyntaxError: prefix 'owl' not found in prefix map

Próbowałem przeczytać dokument na http://effbot.org/zone/element-namespaces.htm ale nadal nie jestem w stanie tego uruchomić, ponieważ powyższy XML ma wiele zagnieżdżone przestrzenie nazw.

Uprzejmie daj mi znać, jak zmienić kod, aby znaleźć wszystkie tagi owl:Class.

Author: stovfl, 2013-02-13

6 answers

ElementTree nie jest zbyt inteligentny w zakresie przestrzeni nazw. Musisz dać .find(), findall() and iterfind() methods an explicit namespace dictionary. Nie jest to dobrze udokumentowane:

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

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

Prefiksy są tylko wyszukane w parametrze namespaces, który podajesz. Oznacza to, że możesz użyć dowolnego prefiksu przestrzeni nazw; API dzieli część owl:, wyszukuje odpowiedni adres URL przestrzeni nazw w słowniku namespaces, a następnie zmienia wyszukiwanie, aby szukać wyrażenia XPath {http://www.w3.org/2002/07/owl}Class. Możesz oczywiście użyć tej samej składni:

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

Jeśli możesz przełączyć się na lxml biblioteka rzeczy są lepsze; ta biblioteka obsługuje ten sam ElementTree API, ale zbiera przestrzenie nazw dla Ciebie w atrybucie .nsmap na elementach.

 232
Author: Martijn Pieters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-30 15:20:49

Oto Jak to zrobić z lxml bez konieczności kodowania przestrzeni nazw lub skanowania ich tekstu (jak wspomina Martijn Pieters):

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

UPDATE :

5 lat później wciąż mam do czynienia z wariacjami tego problemu. lxml pomaga jak pokazałem powyżej, ale nie w każdym przypadku. Komentatorzy mogą mieć ważny punkt dotyczący tej techniki, jeśli chodzi o łączenie dokumentów, ale myślę, że większość ludzi ma trudności z prostym wyszukiwaniem dokumentów.

Oto kolejna sprawa i jak sobie z nią poradziłem:

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

Xmlns bez prefiksu oznacza, że znaczniki bez prefiksu otrzymują domyślną przestrzeń nazw. Oznacza to, że szukając Tag2, musisz dołączyć przestrzeń nazw, aby ją znaleźć. Jednak lxml tworzy wpis nsmap z None jako kluczem i nie mogłem znaleźć sposobu, aby go wyszukać. Tak więc, stworzyłem nowy słownik przestrzeni nazw jak ten

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)
 58
Author: Brad Dre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-30 18:47:24

Uwaga: jest to odpowiedź przydatna dla standardowej biblioteki ElementTree Pythona bez używania zakodowanych na twardo przestrzeni nazw.

Aby wyodrębnić prefiksy przestrzeni nazw i URI z danych XML można użyć funkcji ElementTree.iterparse, analizującej tylko zdarzenia start przestrzeni nazw (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#'}

Wtedy słownik może być przekazany jako argument do funkcji wyszukiwania:

root.findall('owl:Class', my_namespaces)
 32
Author: Davide Brunato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-21 08:15:53

Aby uzyskać Przestrzeń nazw w jej formacie, np. {myNameSpace}, możesz wykonać następujące czynności:

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

W ten sposób możesz użyć go później w kodzie, aby znaleźć węzły, np. za pomocą interpolacji łańcuchów (Python 3).

link = root.find(f"{ns}link")
 7
Author: Bram Vanroy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-11 08:33:15

Używałem podobnego kodu do tego i stwierdziłem, że zawsze warto przeczytać dokumentację... jak zwykle!

Findall() znajdzie tylko elementy, które są bezpośrednimi potomkami bieżącego znacznika . Więc nie wszystkie.

Może warto spróbować, aby Twój kod działał z następującymi, zwłaszcza jeśli masz do czynienia z dużymi i złożonymi plikami xml, dzięki czemu pod-podelementy (itp.). Jeśli wiesz, gdzie znajdują się elementy w Twoim xml, więc będzie dobrze! Pomyślałem, że warto o tym pamiętać.

root.iter()

Ref: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall () znajduje tylko elementy ze znacznikiem, które są bezpośrednimi potomkami bieżącego elementu. Element.find() znajduje pierwsze dziecko z określonym znacznikiem i elementem.tekst uzyskuje dostęp do zawartości tekstowej elementu. Element.get () uzyskuje dostęp do atrybutów elementu:"

 6
Author: MJM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-16 09:51:36

Moje rozwiązanie opiera się na komentarzu @ Martijn Pieters:

register_namespace wpływa tylko na serializację, a nie wyszukiwanie.

Sztuczka polega na używaniu różnych słowników do serializacji i wyszukiwania.

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

Teraz zarejestruj wszystkie przestrzenie nazw do parsowania i pisania:

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

Do wyszukiwania (find(), findall(), iterfind()) potrzebujemy niepustego prefiksu. Przekazać te funkcje zmodyfikowanym słownikiem (tutaj modyfikuję oryginalny słownik, ale to musi być wykonane dopiero po zarejestrowaniu przestrzeni nazw).

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

Teraz funkcje z rodziny find() mogą być używane z prefiksem default:

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

Ale

tree.write(destination)

Nie używa prefiksów dla elementów w domyślnej przestrzeni nazw.

 0
Author: peter.slizik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-06-04 00:49:07