2013-02-13 13 views
102

mam następujące XML, który chcę analizować przy użyciu Pythona ElementTree:Przetwarzanie XML z nazw w Pythonie poprzez „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 owl:Class tagi i następnie wyodrębnić wartość wszystkich rdfs:label przypadkach w nich. Korzystam z następującego kodu:

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

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

SyntaxError: prefix 'owl' not found in prefix map 

Próbowałem czytania dokumentu w http://effbot.org/zone/element-namespaces.htm ale nadal nie jestem w stanie uzyskać tej pracy, ponieważ powyżej XML ma wiele nazw zagnieżdżonych.

Prosimy o informację, jak zmienić kod, aby znaleźć wszystkie znaczniki owl:Class.

Odpowiedz

153

ElementTree nie jest zbyt inteligentny jeśli chodzi o przestrzenie nazw. Musisz podać metody jawnego słownika przestrzeni nazw. . To nie jest bardzo dobrze udokumentowane:

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

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

Prefiksy są tylko spojrzał w parametrze namespaces możesz przekazać w ten sposób można użyć dowolnego namespace prefix chcesz; API dzieli część owl:, wyszukuje odpowiedni adres URL przestrzeni nazw w słowniku namespaces, a następnie zmienia wyszukiwanie, aby wyszukać wyrażenie XPath {http://www.w3.org/2002/07/owl}Class. Można użyć tej samej składni siebie zbyt przedmiotu:

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

Jeśli można przełączyć do lxml library rzeczy są lepsze; ta biblioteka obsługuje ten sam interfejs ElementTree API, ale zbiera obszary nazw w atrybucie .nsmap na elementach.

+0

Dzięki. Zwłaszcza w drugiej części, w której można bezpośrednio nadać przestrzeń nazw. – Sudar

+5

Dziękuję. Masz pomysł, jak uzyskać przestrzeń nazw bezpośrednio z XML-a, bez twardego kodowania? Lub jak mogę to zignorować? Próbowałem findall ("{*} Class"), ale to nie zadziała w moim przypadku. – Kostanos

+6

Musisz samemu przeskanować drzewo w poszukiwaniu atrybutów 'xmlns'; jak stwierdzono w odpowiedzi, 'lxml' robi to za Ciebie, moduł' xml.etree.ElementTree' nie. Ale jeśli próbujesz dopasować określony (już zakodowany) element, to próbujesz także dopasować określony element do określonego obszaru nazw. Ta przestrzeń nazw nie zmieni się między dokumentami tak jak nazwa elementu. Możesz równie dobrze kodować przy użyciu nazwy elementu. –

36

Oto jak to zrobić z lxml bez konieczności twardego kodeksu nazw lub zeskanować tekst dla nich (jak wspomina Martijn Pieters):

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

to działa dobrze. –

+1

Pełny URL obszaru nazw * jest * identyfikatorem przestrzeni nazw, który ma zostać zakodowany. Lokalny prefiks ('owl') może zmieniać się z pliku na plik. Dlatego robienie tego, co sugeruje ta odpowiedź, jest naprawdę złym pomysłem. –

+1

@MattiVirkkunen dokładnie, jeśli definicja sowy może zmienić się z pliku na plik, czy nie powinniśmy używać definicji zdefiniowanej w każdym pliku zamiast jej kodowania? –

10

Uwaga: Jest to odpowiedź użyteczne dla Biblioteka standardowa ElementTree w standardzie Python bez użycia zakodowanych na stałe przestrzeni nazw.

Aby wyodrębnić przedrostków przestrzeni nazw i URI z danymi XML można użyć ElementTree.iterparse funkcji parsowania tylko nazw rozpoczęcia zdarzenia (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#'} 

Następnie słownika mogą być przekazywane jako argument funkcje wyszukiwania:

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

Jest to przydatne dla tych z nas, którzy nie mają dostępu do lxml i nie chcą tworzyć przestrzeni nazw hardcode. – delrocco

+1

Mam błąd: 'ValueError: write to closed' dla tej linii' filemy_namespaces = dict ([węzeł dla _, węzeł w ET.iterparse (StringIO (my_schema), events = ['start-ns'])]) ' . Każdy pomysł chce źle? – Yuli

+0

Prawdopodobnie błąd jest związany z klasą io.StringIO, która odrzuca ciągi ASCII. Przetestowałem mój przepis za pomocą Python3. Dodając przedrostek "Unicode" do przykładowego ciągu, działa on również w Pythonie 2 (2.7). –

0

wiem, że jestem kilka lat spóźnione, ale ja po prostu stworzył pakiet, który będzie obsługiwać konwersji słownika do ważnego XML z nazw s. Pakiet jest hostowany na PyPi @https://pypi.python.org/pypi/xmler.

Korzystanie z tego pakietu można wziąć ze słownika, który wygląda tak:

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" 
     } 
    } 
} 

i uzyskać dane wyjściowe XML, który wygląda tak:

<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> 

Nadzieja ta jest użyteczna dla ludzi w przyszłości

1

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

findall() znajdzie tylko te elementy, które są bezpośrednie elementy podrzędne bieżącego tagu. Tak naprawdę nie wszystkie.

Być może warto poświęcić chwilę na sprawdzenie, czy Twój kod działa z następującymi elementami, szczególnie jeśli mamy do czynienia z dużymi i złożonymi plikami xml, aby uwzględnić również pod-podelementy (itp.). Jeśli wiesz, gdzie są elementy w twoim xml, to przypuszczam, że 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 o zmiennej, które są bezpośrednimi dzieci bieżącego elementu Element.find() znajduje się pierwszy dziecko określonego znacznika i Element.text uzyskuje dostęp do treści tekstowej elementu Element.get() uzyskuje dostęp do atrybutów elementu: "

Powiązane problemy