2013-02-13 14 views
102

ho il seguente codice XML, che voglio analizzare utilizzando Python ElementTree:parsing XML con spazio dei nomi in Python via '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> 

Voglio trovare tutti owl:Class tag e quindi estrarre il valore di tutti rdfs:label istanze dentro di loro. Sto usando il seguente codice:

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

A causa dello spazio dei nomi, ricevo il seguente errore.

SyntaxError: prefix 'owl' not found in prefix map 

ho provato leggendo il documento in http://effbot.org/zone/element-namespaces.htm ma non sono ancora in grado di ottenere questo lavoro in quanto il sopra XML ha più spazi dei nomi nidificati.

Gentilmente fammi sapere come modificare il codice per trovare tutti i tag owl:Class.

risposta

153

ElementTree non è troppo intelligente sui namespace. È necessario fornire i metodi .find(), findall() e iterfind() a un dizionario dello spazio dei nomi esplicito. . Questo non è documentato molto bene:

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

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

prefissi sono solo alzò gli occhi nel parametro namespaces si passa in questo modo è possibile utilizzare qualsiasi prefisso namespace che ti piace; l'API divide la parte owl:, cerca l'URL dello spazio dei nomi corrispondente nel dizionario namespaces, quindi modifica la ricerca per cercare l'espressione XPath {http://www.w3.org/2002/07/owl}Class. È possibile utilizzare la stessa sintassi te troppo, naturalmente:

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

Se è possibile passare alle lxml library le cose vanno meglio; quella libreria supporta la stessa API ElementTree, ma raccoglie gli spazi dei nomi per voi in un attributo .nsmap sugli elementi.

+0

Grazie. Soprattutto per la seconda parte, dove puoi dare direttamente lo spazio dei nomi. – Sudar

+5

Grazie. Qualche idea su come posso ottenere lo spazio dei nomi direttamente da XML, senza codificarlo? O come posso ignorarlo? Ho provato findall ('{*} Class') ma non funzionerà nel mio caso. – Kostanos

+6

Dovresti eseguire la scansione dell'albero per gli attributi 'xmlns'; come indicato nella risposta, 'lxml' lo fa per te, il modulo' xml.etree.ElementTree' no. Ma se stai cercando di abbinare un elemento specifico (già codificato), stai anche cercando di abbinare un elemento specifico in uno spazio dei nomi specifico. Lo spazio dei nomi non cambierà più tra i documenti rispetto al nome dell'elemento. Si può anche hardcode con il nome dell'elemento. –

36

Ecco come fare questo con lxml senza dover codificare gli spazi dei nomi o la scansione del testo per loro (come cita Martijn Pieters):

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

funziona perfettamente. –

+1

L'URL completo dello spazio dei nomi * è * l'identificatore dello spazio dei nomi che si suppone debba codificare. Il prefisso locale ('owl') può cambiare da file a file. Quindi fare ciò che questa risposta suggerisce è una pessima idea. –

+1

@MattiVirkkunen esattamente se la definizione del gufo può cambiare da file a file, non dovremmo usare la definizione definita in ogni file invece di codificarla? –

10

Nota: Questa è una risposta utile per La libreria standard ElementTree di Python senza utilizzare spazi dei nomi codificati.

Per estrarre i prefissi di namespace e URI da dati XML è possibile utilizzare ElementTree.iterparse la funzione, l'analisi solo namespace avviare eventi (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#'} 

Poi il dizionario può essere passato come argomento al funzioni di ricerca:

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

Questo è utile per quelli di noi senza accesso a lxml e senza volere lo spazio dei nomi dell'hardcode. – delrocco

+1

Ho ricevuto l'errore: 'ValueError: write to closed' per questa riga' filemy_namespaces = dict ([nodo per _, nodo in ET.iterparse (StringIO (my_schema), events = ['start-ns'])]) ' . Qualche idea vuole sbagliato? – Yuli

+0

Probabilmente l'errore è correlato alla classe io.StringIO, che rifiuta le stringhe ASCII. Ho provato la mia ricetta con Python3. Aggiungendo il prefisso di stringa unicode 'u' alla stringa di esempio funziona anche con Python 2 (2.7). –

0

so di un paio di anni di ritardo, ma ho solo creato un pacchetto che consente di gestire la conversione di un dizionario per XML valido con spazio dei nomi S. Il pacchetto è ospitato su PyPi @https://pypi.python.org/pypi/xmler.

Con questo pacchetto è possibile prendere un dizionario che assomiglia a questo:

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

e ottenere output XML che assomiglia a questo:

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

Spero che questo è utile per le persone in futuro

1

Ho usato un codice simile a questo e ho trovato che vale sempre la pena leggere la documentazione ... come al solito!

findall() trova solo elementi che sono figli diretti del tag corrente. Quindi, non proprio TUTTO.

Potrebbe valere la pena provare a far funzionare il codice con quanto segue, in particolare se si hanno a che fare con file xml grandi e complessi, in modo da includere anche questi sotto-elementi secondari (ecc.). Se conosci te stesso dove gli elementi sono nel tuo xml, allora suppongo che andrà bene! Pensavo che valesse la pena di ricordare.

root.iter() 

ref:. https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall() trova solo gli elementi con un tag, che sono figli diretti dell'elemento corrente Element.find() trova il primo figlio con una particolare etichetta, e Element.text accede al contenuto del testo dell'elemento. Element.get() accede agli attributi dell'elemento: "

Problemi correlati