xml.etree.ElementTree vergisst namespaces

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Hase
User
Beiträge: 106
Registriert: Donnerstag 1. Oktober 2009, 15:17
Wohnort: Bremer Speckgürtel

Hallo, ich arbeite eigentlich des öfteren mit XML-Dateien, aber so ein Problem hatte ich noch nie.
Die Aufgabe ist ganz einfach: ich möchte eine in.xml einlesen, dort ein Element hinzufügen und als out.xml speichern.

Code: Alles auswählen

def get_root():
    filename = 'in.xml'
    ns = dict([node for _, node in ET.iterparse(filename, events=['start-ns'])])
    tree = ET.parse(filename)
    root = tree.getroot()
    for key in ns.keys():
        ET.register_namespace(key, ns[key])

    return root, ns, tree
die Ausgabe nach dem Hinzufügen des Elements ist dann:

Code: Alles auswählen

def writeXML(root, ns, tree):
    prettify(root)
    tree.write('out.xml', xml_declaration=True, encoding='UTF-8', method='xml')
    return
in.xml sieht wie folgt aus:

Code: Alles auswählen

<?xml version='1.0' encoding='UTF-8'?>
<gml:FeatureCollection
xmlns:okstra="http://www.okstra.de/okstra/2.022"
xmlns:okstra-basis="http://www.okstra.de/okstra/2.022/okstra-basis"
xmlns:okstra-typen="http://www.okstra.de/okstra/2.022/okstra-typen"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://www.okstra.de/okstra/2.020 http://www.okstra.de/schema/2022/okstra.xsd"
gml:id="ID_666">
  <gml:metaDataProperty>
    <okstra-basis:OKSTRAMetaDaten>
      <okstra-basis:timestamp>2025-07-16T10:45:28</okstra-basis:timestamp>
    </okstra-basis:OKSTRAMetaDaten>
  </gml:metaDataProperty>
  <gml:featureMember>
    <okstra:Testelement gml:id="Testelement_000001" />
  </gml:featureMember>
</gml:FeatureCollection>
out.xml sieht wie folgt aus:

Code: Alles auswählen

<gml:FeatureCollection
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:okstra="http://www.okstra.de/okstra/2.022"
xmlns:okstra-basis="http://www.okstra.de/okstra/2.022/okstra-basis"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.okstra.de/okstra/2.020 http://www.okstra.de/schema/2022/okstra.xsd"
gml:id="ID_666">
  <gml:metaDataProperty>
    <okstra-basis:OKSTRAMetaDaten>
      <okstra-basis:timestamp>2025-07-16T10:45:28</okstra-basis:timestamp>
    </okstra-basis:OKSTRAMetaDaten>
  </gml:metaDataProperty>
  <gml:featureMember>
    <okstra:Testelement gml:id="Testelement_000001">
      <okstra:Dokument Objektklasse="Dokument" xlink:href="#Dokument_2" />
    </okstra:Testelement>
  </gml:featureMember>
  <gml:FeatureMember>
    <okstra:Dokument gml:id="Dokument_2">
      <okstra:Name>meinname</okstra:Name>
      <okstra:zu_Testelement Objektklasse="Testelement" xlink:href="#Testelement_000001" />
    </okstra:Dokument>
  </gml:FeatureMember>
</gml:FeatureCollection>

Die Datei "out.xml" ist keine gültige XML und kann nicht mehr von einem Parser gelesen werden. Fehler ist, dass der Ausdruck "xlink:href" nicht interpretiert werden kann, da der zugehörige Namespace fehlt.
Wie man vielleicht sieht, sind in "in.xml" insgesamt 8 Namespaces definiert. In "out.xml" kommen aber nur 6 an. Meine Vermutung: Namespaces, die in der Datei "in.xml" zwar definiert sind, jedoch im Body nicht benutzt werden, werden bei bei der Funktion tree.write() "wegoptimiert" und kommen somit in "out.xml" nicht an.

Wenn ich nämlich z.B. in "in.xml" das Element <okstra-basis:OKSTRAMetaDaten> weglasse, kommt auch der zugehörige Namespace nicht in "out.xml" an.

Wenn das so ist, wie kann ich denn tree.write() dazu zwingen den vollen originalen Namespace zu schreiben? Oder ist das ein anderer bölder Fehler, hab ich nur irgendwo ein Komma falsch gesetzt? Ein nochmaliges Registrieren des kompletten Namespace eine Zeile vor tree.write() bringt nichts.

Grüße
I.H.
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Hase: Kann ich hier nicht nachvollziehen. Ich habe folgenden Code verwendet um einen Teil (mit http://www.w3.org/1999/xlink Namensraum) einzufügen und da wird auch die xmlns-Deklaration geschrieben:

Code: Alles auswählen

#!/usr/bin/env python3
from xml.etree import ElementTree as ET


def get_root():
    filename = "in.xml"
    ns = dict(node for _, node in ET.iterparse(filename, events=["start-ns"]))
    for prefix, uri in ns.items():
        ET.register_namespace(prefix, uri)
    tree = ET.parse(filename)
    return tree.getroot(), ns, tree


def write_xml(_root, _ns, tree):
    # prettify(root)
    tree.write("out.xml", xml_declaration=True, encoding="UTF-8", method="xml")


def main():
    root, ns, tree = get_root()
    ET.SubElement(
        tree.find(".//{http://www.okstra.de/okstra/2.022}Testelement"),
        "{http://www.okstra.de/okstra/2.022}Dokument",
        {
            "Objektklasse": "Dokument",
            "{http://www.w3.org/1999/xlink}href": "#Dokument_2",
        },
    )
    write_xml(root, ns, tree)


if __name__ == "__main__":
    main()
Was ich hier nicht drin habe ist Dein `prettify()`, vielleicht liegt das Problem dort?

Edit: Die Art wie `ns` aufgebaut wird ist übrigens nicht robust. Prefixes können wiederverwendet werden, das heisst man endet auf diese Weise bei einer Abbildung von Prefix auf zuletzt für diesen Prefix ”gestartete” URI. Das wird in vielen Fällen so funktionieren, muss es aber nicht. Man sollte an der Stelle immer eine eigene Abbildung in den Code schreiben, wenn man das irgendwie steuern möchte.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Hase
User
Beiträge: 106
Registriert: Donnerstag 1. Oktober 2009, 15:17
Wohnort: Bremer Speckgürtel

Hallo, an prettify liegt's nicht.

Auch mit deinem Code kommen bei mir nur 7 der 8 Namespaces an. Allerdings kommt jetzt auch der xlink:href mit rüber, den du ja benutzt.

xmlns:okstra-typen="http://www.okstra.de/okstra/2.022/okstra-typen" fehlt weiterhin.

Mein System:
xml.etree.ElementTree (Python 3.12.3 ) on Linux-6.8.0-63-generic-x86_64-with-glibc2.39

Danke für deine Mühe.

I.H.
Benutzeravatar
__blackjack__
User
Beiträge: 14030
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Hase: Wenn das nicht benutzt wird, fehlt es ja nicht. Es ist zwar nicht verboten xmlns-Deklarationen einzufügen, die dann nicht verwendet werden, aber es macht auch nicht wirklich Sinn das zu tun.

Wenn man einfach nur das ``in.xml`` einliest und wieder schreibt können zwei Dinge passieren, ohne dass das am Dokument selbst etwas ändert: a) zwei xmlns können verschwinden, weil sie nicht genutzt werden, und b) bei allen anderen kann sich der Präfix ändern, denn der ist variabel und ”unwichtig”, solange er (lokal) eindeutig ist. Theoretisch kann auch einer der Präfixe zum Default-Namespace werden, und/oder die xmlns-Deklarationen können auf andere Elemente wandern, solange sich dadurch das Dokument nicht ändert.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Antworten