199 votes

Analyse syntaxique de XML avec espace de noms en Python via 'ElementTree'.

J'ai le XML suivant que je veux analyser à l'aide de la méthode 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>

Je veux trouver tous owl:Class puis extraire la valeur de toutes les balises rdfs:label des instances en leur sein. J'utilise le code suivant :

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

À cause de l'espace de noms, je reçois l'erreur suivante.

SyntaxError: prefix 'owl' not found in prefix map

J'ai essayé de lire le document à http://effbot.org/zone/element-namespaces.htm mais je n'arrive toujours pas à le faire fonctionner car le XML ci-dessus comporte plusieurs espaces de noms imbriqués.

Veuillez m'indiquer comment modifier le code pour trouver tous les éléments suivants owl:Class tags.

265voto

Martijn Pieters Points 271458

Vous devez donner à la .find() , findall() et iterfind() méthodes un dictionnaire d'espace de noms explicite :

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

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

Les préfixes sont seulement regardé en haut dans le namespaces que vous passez dans le paramètre. Cela signifie que vous pouvez utiliser le préfixe d'espace de nom de votre choix ; l'API sépare le préfixe d'espace de nom de l'espace de nom. owl: recherche l'URL de l'espace de nom correspondant dans la partie namespaces puis modifie la recherche pour rechercher l'expression XPath {http://www.w3.org/2002/07/owl}Class au lieu de. Vous pouvez bien sûr utiliser la même syntaxe vous-même :

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

Voir aussi le Analyser le XML avec des espaces de noms section de la documentation ElementTree.

Si vous pouvez passer à la lxml bibliothèque les choses sont meilleures ; cette bibliothèque supporte la même API ElementTree, mais collecte les espaces de noms pour vous dans les fichiers .nsmap sur les éléments et offre généralement une meilleure prise en charge des espaces de noms.

68voto

Brad Dre Points 11

Voici comment le faire avec lxml sans avoir à coder en dur les espaces de noms ou à les rechercher dans le texte (comme le mentionne Martijn Pieters) :

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

UPDATE :

5 ans plus tard, je rencontre toujours des variations de ce problème. lxml aide comme je l'ai montré ci-dessus, mais pas dans tous les cas. Les commentateurs ont peut-être un point valable concernant cette technique lorsqu'il s'agit de fusionner des documents, mais je pense que la plupart des gens ont des difficultés à simplement rechercher des documents.

Voici un autre cas et comment je l'ai traité :

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

xmlns sans préfixe signifie que les balises sans préfixe obtiennent cet espace de nom par défaut. Cela signifie que lorsque vous recherchez Tag2, vous devez inclure l'espace de nom pour le trouver. Cependant, lxml crée une entrée nsmap avec None comme clé, et je n'ai pas trouvé de moyen de la rechercher. J'ai donc créé un nouveau dictionnaire d'espace de noms comme ceci

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)

46voto

Davide Brunato Points 584

Note : Il s'agit d'une réponse utile pour la bibliothèque standard ElementTree de Python sans utiliser les espaces de noms codés en dur.

Pour extraire les préfixes et les URI des espaces de noms à partir de données XML, vous pouvez utiliser la méthode suivante ElementTree.iterparse en analysant uniquement les événements de début d'espace de nom ( 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#'}

Le dictionnaire peut ensuite être transmis comme argument aux fonctions de recherche :

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

7voto

MJM Points 128

J'ai utilisé un code similaire à celui-ci et j'ai constaté qu'il est toujours utile de lire la documentation... comme d'habitude !

findall() ne trouvera que les éléments qui sont enfants directs de la balise courante . Donc, pas vraiment TOUT.

Cela pourrait valoir la peine d'essayer de faire fonctionner votre code avec ce qui suit, surtout si vous avez affaire à des fichiers xml volumineux et complexes, de sorte que les sous-sous-éléments (etc.) soient également inclus. Si vous savez vous-même où se trouvent les éléments dans votre xml, alors je suppose que tout ira bien ! J'ai juste pensé que cela valait la peine de s'en souvenir.

root.iter()

réf : https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall() trouve uniquement les éléments avec une balise qui sont des enfants directs de l'élément actuel. Element.find() trouve le premier enfant avec une balise particulière, et Element.text accède au contenu textuel de l'élément. Element.get() accède aux attributs de l'élément :"

7voto

Bram Vanroy Points 4460

Pour obtenir l'espace de nom dans son format d'espace de nom, par ex. {myNameSpace} vous pouvez faire ce qui suit :

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

De cette façon, vous pouvez l'utiliser plus tard dans votre code pour trouver des nœuds, par exemple en utilisant l'interpolation de chaînes (Python 3).

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X