webentwicklung-frage-antwort-db.com.de

Syntaxanalyse von XML mit Namespace in Python über 'ElementTree'

Ich habe das folgende XML, das ich mit Pythons ElementTree analysieren möchte:

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

Ich möchte alle finden owl:Class markiert und extrahiert dann den Wert aller rdfs:label Instanzen in ihnen. Ich benutze den folgenden Code:

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

Aufgrund des Namespace erhalte ich die folgende Fehlermeldung.

SyntaxError: prefix 'owl' not found in prefix map

Ich habe versucht, das Dokument unter http://effbot.org/zone/element-namespaces.htm zu lesen, kann dies aber immer noch nicht zum Laufen bringen, da die oben genannte XML-Datei mehrere verschachtelte Namespaces enthält.

Bitte lassen Sie mich wissen, wie Sie den Code ändern können, um alle owl:Class Stichworte.

145
Sudar

In Bezug auf Namespaces ist ElementTree nicht besonders intelligent. Sie müssen den Methoden .find(), findall() und iterfind() ein explizites Namespace-Wörterbuch zuweisen. Dies ist nicht sehr gut dokumentiert:

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

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

Präfixe werden nur in dem von Ihnen übergebenen namespaces -Parameter nachgeschlagen . Dies bedeutet, dass Sie ein beliebiges Namespace-Präfix verwenden können. Die API teilt den Teil owl: auf, sucht die entsprechende Namespace-URL im Wörterbuch namespaces und ändert die Suche, um stattdessen nach dem XPath-Ausdruck {http://www.w3.org/2002/07/owl}Class zu suchen. Sie können die gleiche Syntax natürlich auch selbst verwenden:

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

Wenn Sie zur lxml Bibliothek wechseln können sind die Dinge besser; Diese Bibliothek unterstützt dieselbe ElementTree-API, sammelt jedoch Namespaces für Sie in einem .nsmap - Attribut für Elemente.

202
Martijn Pieters

So machen Sie das mit lxml, ohne die Namespaces fest zu codieren oder den Text danach zu scannen (wie Martijn Pieters erwähnt):

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

UPDATE :

5 Jahre später stoße ich immer noch auf Variationen dieser Ausgabe. lxml hilft, wie ich oben gezeigt habe, aber nicht in jedem Fall. Die Kommentatoren mögen einen guten Punkt in Bezug auf diese Technik haben, wenn es darum geht, Dokumente zusammenzuführen, aber ich denke, die meisten Leute haben Schwierigkeiten, einfach Dokumente zu suchen.

Hier ist ein weiterer Fall und wie ich damit umgegangen bin:

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

xmlns ohne Präfix bedeutet, dass nicht vorfixierte Tags diesen Standardnamespace erhalten. Dies bedeutet, dass Sie bei der Suche nach Tag2 den Namespace einschließen müssen, um ihn zu finden. Lxml erstellt jedoch einen nsmap-Eintrag mit None als Schlüssel, und ich konnte keine Möglichkeit finden, danach zu suchen. Also habe ich ein neues Namespace-Wörterbuch wie dieses erstellt

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

Hinweis : Dies ist eine nützliche Antwort für Pythons ElementTree-Standardbibliothek ohne Verwendung von fest codierte Namespaces

Um die Präfixe und den URI des Namespace aus XML-Daten zu extrahieren, können Sie ElementTree.iterparse Funktion, die nur Namespace-Startereignisse analysiert ( 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#'}

Dann kann das Wörterbuch als Argument an die Suchfunktionen übergeben werden:

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

Ich habe ähnlichen Code verwendet und festgestellt, dass es sich immer lohnt, die Dokumentation zu lesen ... wie immer!

findall () findet nur Elemente, die es sind direkte Kinder des aktuellen Tags. Also nicht wirklich ALL.

Es kann sich lohnen, sich mit dem folgenden Code vertraut zu machen, insbesondere wenn Sie mit großen und komplexen XML-Dateien arbeiten, sodass auch Unter-Unter-Elemente (usw.) enthalten sind. Wenn Sie selbst wissen, wo sich Elemente in Ihrer XML-Datei befinden, ist dies vermutlich in Ordnung. Ich dachte, das ist es wert, in Erinnerung zu bleiben.

root.iter()

ref: https://docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall () findet nur Elemente mit einem Tag, die direkte untergeordnete Elemente des aktuellen Elements. Element.find () findet das erste untergeordnete Element mit einem bestimmten Tag und Element.text greift auf den Textinhalt des Elements zu. Element.get () greift auf die Attribute des Elements zu:

6
MJM

Um den Namespace in seinem Namespace-Format abzurufen, z. {myNameSpace} können Sie Folgendes tun:

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

Auf diese Weise können Sie es später in Ihrem Code verwenden, um Knoten zu finden, z. B. mithilfe der Zeichenfolgeninterpolation (Python 3).

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

Ich weiß, dass ich ein paar Jahre zu spät bin, aber ich habe gerade ein Paket erstellt, mit dem ein Wörterbuch in gültiges XML mit Namespaces konvertiert werden kann. Das Paket wird auf PyPi @ https://pypi.python.org/pypi/xmler gehostet.

Mit diesem Paket können Sie ein Wörterbuch erstellen, das so aussieht:

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

und erhalte eine XML-Ausgabe, die so aussieht:

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

Hoffe, das ist nützlich für die Menschen in der Zukunft

1
watzon

Meine Lösung basiert auf dem Kommentar von @Martijn Pieters:

register_namespace Beeinflusst nur die Serialisierung, nicht die Suche.

Der Trick hier besteht also darin, verschiedene Wörterbücher für die Serialisierung und für die Suche zu verwenden.

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

Registrieren Sie jetzt alle Namespaces zum Parsen und Schreiben:

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

Für die Suche (find(), findall(), iterfind()) benötigen wir ein nicht leeres Präfix. Übergeben Sie diesen Funktionen ein geändertes Wörterbuch (hier ändere ich das ursprüngliche Wörterbuch, dies muss jedoch erst gemacht werden, nachdem die Namespaces registriert wurden).

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

Jetzt können die Funktionen aus der find() -Familie mit dem Präfix default verwendet werden:

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

aber

tree.write(destination)

verwendet keine Präfixe für Elemente im Standardnamespace.

0
peter.slizik