web-dev-qa-db-fra.com

Analyse de XML avec un espace de noms dans Python via 'ElementTree'

J'ai le XML suivant que je veux analyser en utilisant ElementTree de Python:

<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 souhaite rechercher toutes les balises owl:Class, puis extraire la valeur de toutes les instances rdfs:label qui s'y trouvent. J'utilise le code suivant:

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

À cause de l'espace de noms, j'obtiens l'erreur suivante.

SyntaxError: prefix 'owl' not found in prefix map

J'ai essayé de lire le document à l'adresse http://effbot.org/zone/element-namespaces.htm , mais je ne parviens toujours pas à faire fonctionner ce document car le code XML ci-dessus comporte plusieurs espaces de nom imbriqués.

Faites-moi savoir comment changer le code pour trouver toutes les balises owl:Class.

145
Sudar

ElementTree n'est pas trop intelligent sur les espaces de noms. Vous devez donner aux méthodes .find(), findall() et iterfind() un dictionnaire d’espace de nommage explicite. Ce n'est pas très bien documenté:

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

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

Les préfixes sont seulement recherchés dans le paramètre namespaces que vous transmettez. Cela signifie que vous pouvez utiliser n'importe quel préfixe d'espace de nom que vous préférez; l'API sépare la partie owl:, recherche l'URL d'espace de nom correspondante dans le dictionnaire namespaces, puis modifie la recherche pour rechercher l'expression XPath {http://www.w3.org/2002/07/owl}Class. Vous pouvez aussi utiliser la même syntaxe vous-même:

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

Si vous pouvez passer à la bibliothèque lxml , les choses vont mieux; cette bibliothèque prend en charge la même API ElementTree, mais collecte des espaces de noms pour vous dans un attribut .nsmap sur des éléments.

202
Martijn Pieters

Voici comment faire cela avec lxml sans avoir à coder en dur les espaces de noms ni à scanner 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:

Cinq ans plus tard, je rencontre encore des variantes de ce problème. Comme je l'ai montré ci-dessus, lxml aide, mais pas dans tous les cas. Les commentateurs ont peut-être un bon argument en ce qui concerne cette technique lorsqu’il s’agit de fusionner des documents, mais je pense que la plupart des gens ont de la difficulté à chercher simplement 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 tags sans préfixe obtiennent cet espace de noms par défaut. Cela signifie que lorsque vous recherchez Tag2, vous devez inclure l'espace de noms pour le trouver. Cependant, lxml crée une entrée nsmap avec la clé None et je ne trouve aucun moyen de la rechercher. J'ai donc créé un nouveau dictionnaire d'espaces de noms comme celui-ci.

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)
53
Brad Dre

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

Pour extraire les préfixes et l'URI de l'espace de noms à partir de données XML, vous pouvez utiliser la fonction ElementTree.iterparse, en analysant uniquement les événements de démarrage de l'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#'}

Ensuite, le dictionnaire peut être passé en argument aux fonctions de recherche:

root.findall('owl:Class', my_namespaces)
23
Davide Brunato

J'utilise un code similaire à celui-ci et j'ai trouvé qu'il valait toujours la peine 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 TOUS.

Cela vaut peut-être la peine d'essayer de faire en sorte que votre code fonctionne avec ce qui suit, en particulier si vous traitez avec 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! Je pensais que cela valait la peine de s'en souvenir.

root.iter()

ref: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall () ne trouve que les éléments avec une balise qui sont 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: "

6
MJM

Pour obtenir l’espace de noms dans son format, par exemple, {myNameSpace}, vous pouvez effectuer les opérations suivantes:

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

De cette façon, vous pourrez l’utiliser plus tard dans votre code pour rechercher des nœuds, par exemple en utilisant une interpolation de chaîne (Python 3).

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

Je sais que je suis en retard de quelques années, mais je viens de créer un package qui gérera la conversion d'un dictionnaire en XML valide avec des espaces de noms. Le paquet est hébergé sur PyPi @ https://pypi.python.org/pypi/xmler .

En utilisant ce paquet, vous pouvez prendre un dictionnaire qui ressemble à ceci:

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

et obtenez une sortie XML qui ressemble à ceci:

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

J'espère que cela sera utile pour les futurs

1
watzon

Ma solution est basée sur le commentaire de @Martijn Pieters:

register_namespace n'influence que la sérialisation, pas la recherche.

Le truc ici est donc d’utiliser différents dictionnaires pour la sérialisation et la recherche.

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

Maintenant, enregistrez tous les espaces de noms pour l'analyse et l'écriture:

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

Pour la recherche (find(), findall(), iterfind()), nous avons besoin d'un préfixe non vide. Transmettez à ces fonctions un dictionnaire modifié (ici je modifie le dictionnaire d'origine, mais cela ne doit être fait qu'après que les espaces de noms ont été enregistrés).

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

Maintenant, les fonctions de la famille find() peuvent être utilisées avec le préfixe default:

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

mais

tree.write(destination)

n'utilise aucun préfixe pour les éléments de l'espace de noms par défaut.

0
peter.slizik