XML ontleden met naamruimte in Python via ‘ElementTree’

Ik heb de volgende XML die ik wil ontleden met Python’s 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>

Ik wil alle owl:Class-tags vinden en vervolgens de waarde extraheren van alle rdfs:label-instanties erin. Ik gebruik de volgende code:

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

Vanwege de naamruimte krijg ik de volgende foutmelding.

SyntaxError: prefix 'owl' not found in prefix map

Ik heb geprobeerd het document te lezen op http://effbot.org/zone/element-namespaces.htmmaar ik krijg dit nog steeds niet werkend omdat de bovenstaande XML meerdere geneste naamruimten heeft.

Laat me weten hoe ik de code kan wijzigen om alle owl:Class-tags te vinden.


Antwoord 1, autoriteit 100%

Je moet de methoden .find(), findall()en iterfind()een expliciet naamruimtewoordenboek geven:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed
root.findall('owl:Class', namespaces)

Voorvoegsels worden alleenopgezocht in de parameter namespacesdie u invoert. Dit betekent dat u elke gewenste naamruimtevoorvoegsel kunt gebruiken; de API splitst het owl:-gedeelte af, zoekt de corresponderende naamruimte-URL op in het namespaces-woordenboek en wijzigt vervolgens de zoekopdracht om te zoeken naar de XPath-expressie {http://www.w3.org/2002/07/owl}Classin plaats daarvan. U kunt dezelfde syntaxis natuurlijk ook zelf gebruiken:

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

Zie ook de XML ontleden met naamruimtensectievan de ElementTree-documentatie.

Als je kunt overschakelen naar de lxml-bibliotheekis alles beter; die bibliotheek ondersteunt dezelfde ElementTree API, maar verzamelt naamruimten voor u in het kenmerk .nsmapop elementen en heeft over het algemeen superieure ondersteuning voor naamruimten.


Antwoord 2, autoriteit 26%

Hier leest u hoe u dit doet met lxml zonder de naamruimten hard te coderen of de tekst ervoor te scannen (zoals Martijn Pieters vermeldt):

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

UPDATE:

Vijf jaar later kom ik nog steeds varianten van dit probleem tegen. lxml helpt zoals ik hierboven heb laten zien, maar niet in alle gevallen. De commentatoren hebben misschien een goed punt met betrekking tot deze techniek als het gaat om het samenvoegen van documenten, maar ik denk dat de meeste mensen moeite hebben met het eenvoudig doorzoeken van documenten.

Hier is nog een zaak en hoe ik het heb afgehandeld:

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

xmlns zonder prefix betekent dat niet-voorgefixeerde tags deze standaardnaamruimte krijgen. Dit betekent dat wanneer u naar Tag2 zoekt, u de naamruimte moet opnemen om deze te vinden. lxml maakt echter een nsmap-item met None als sleutel en ik kon geen manier vinden om ernaar te zoeken. Dus heb ik een nieuw naamruimtewoordenboek gemaakt zoals dit

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)

Antwoord 3, autoriteit 16%

Opmerking: dit is een nuttig antwoord voor de ElementTree-standaardbibliotheek van Python zonder gebruik te maken van hardgecodeerde naamruimten.

Om de voorvoegsels en URI van de naamruimte uit XML-gegevens te extraheren, kunt u de functie ElementTree.iterparsegebruiken, waarbij alleen startgebeurtenissen van naamruimten worden ontleden (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#'}

Vervolgens kan het woordenboek als argument worden doorgegeven aan de zoekfuncties:

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

Antwoord 4, autoriteit 3%

Ik heb soortgelijke code gebruikt en vond het altijd de moeite waard om de documentatie te lezen… zoals gewoonlijk!

findall() zal alleen elementen vinden die directe onderliggende items zijn van de huidige tag. Dus niet echt ALLES.

Het kan de moeite waard zijn om je code met het volgende te laten werken, vooral als je te maken hebt met grote en complexe xml-bestanden, zodat die sub-sub-elementen (enz.) ook worden opgenomen.
Als je zelf weet waar elementen in je xml staan, dan denk ik dat het wel goed komt! Ik dacht dat dit het onthouden waard was.

root.iter()

ref: https://docs. python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements
“Element.findall() vindt alleen elementen met een tag die directe kinderen zijn van het huidige element. Element.find() vindt het eerste kind met een bepaalde tag en Element.text heeft toegang tot de tekstinhoud van het element. Element.get() heeft toegang tot de attributen van het element:”


Antwoord 5, autoriteit 3%

Om de naamruimte in de naamruimte-indeling te krijgen, b.v. {myNameSpace}, u kunt het volgende doen:

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

Op deze manier kunt u het later in uw code gebruiken om knooppunten te vinden, bijvoorbeeld met behulp van string-interpolatie (Python 3).

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

Antwoord 6

Mijn oplossing is gebaseerd op de opmerking van @Martijn Pieters:

register_namespaceheeft alleen invloed op serialisatie, niet op zoeken.

De truc hier is dus om verschillende woordenboeken te gebruiken voor serialisatie en voor zoeken.

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

Registreer nu alle naamruimten voor het ontleden en schrijven:

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

Voor zoeken (find(), findall(), iterfind()) hebben we een niet-lege prefix nodig. Geef deze functies een aangepast woordenboek (hier wijzig ik het originele woordenboek, maar dit mag pas worden gedaan nadat de naamruimten zijn geregistreerd).

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

Nu kunnen de functies van de find()-familie worden gebruikt met het default-voorvoegsel:

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

maar

tree.write(destination)

gebruikt geen prefixen voor elementen in de standaard naamruimte.


Antwoord 7

Dit is eigenlijk het antwoord van Davide Brunato, maar ik kwam erachter dat zijn antwoord ernstige problemen had, aangezien de standaardnaamruimte de lege string was, althans bij mijn python 3.6-installatie. De functie die ik uit zijn code heb gedestilleerd en die voor mij werkte, is de volgende:

from io import StringIO
from xml.etree import ElementTree
def get_namespaces(xml_string):
    namespaces = dict([
            node for _, node in ElementTree.iterparse(
                StringIO(xml_string), events=['start-ns']
            )
    ])
    namespaces["ns0"] = namespaces[""]
    return namespaces

waarbij ns0slechts een tijdelijke aanduiding is voor de lege naamruimte en u deze kunt vervangen door elke willekeurige tekenreeks die u maar wilt.

Als ik het dan doe:

my_namespaces = get_namespaces(my_schema)
root.findall('ns0:SomeTagWithDefaultNamespace', my_namespaces)

Het produceert ook het juiste antwoord voor tags met gebruik van de standaard naamruimte.

Other episodes