XML-Datei ein- und auslesen / UTF-8

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
calo
User
Beiträge: 52
Registriert: Freitag 8. Dezember 2006, 21:35
Wohnort: Stuttgart

Hallo,

ich habe zwei Funktionen zur Umwandlung einer kleinen XML-Datei in ein Dictionary (und umgekehrt) geschrieben. Leider habe ich ein kleines Problem mit der sauberen Kodierung/Dekodierung von UTF-8 zu darstellbarem String. Die erste XML-Datei ("sample-in.xml") habe ich mir dabei "von Hand" erstellt. Das Ergebnis wird vom Skript erstellt ("sample-out.xml"). Leider schaffe ich es nicht "sample-out.xml" wieder so ein zu lesen, dass alle Sonderzeichen richtig interpretiert werden. Vielleicht kann mir einer von euch ein Tipp geben?

Hier der Code:

Code: Alles auswählen

from xml.dom import minidom

def xml2dict(xml_fn):
    """
    Description:
    ~~~~~~~~~~~~
    parses and converts a XML-file (robodoc-format) to an python-dictionary 
    format.
    """
    with open(xml_fn, "rb") as f:
        xml_file = minidom.parse(f)    #.toxml('utf-8')   .decode('utf-8')
        rdoc = xml_file.documentElement
        xml_robo = {}
            
        node_l = rdoc.firstChild
        while node_l:
            if node_l.nodeType == node_l.ELEMENT_NODE:
                lang = node_l.getAttribute("lang")
                xml_robo[lang] = {}
                node_k = node_l.firstChild
                while node_k:
                    if node_k.nodeType == node_k.ELEMENT_NODE:
                        attr = node_k.getAttribute("name")
                        val = node_k.firstChild.data
                        xml_robo[lang][attr] = val    ; print(val, type(val)) #.decode('utf-8')
                    node_k = node_k.nextSibling      
            node_l = node_l.nextSibling
        
    return xml_robo

def dict2xml(dxml, new_fn, base_fn):
    """
    Description:
    ~~~~~~~~~~~~
    converts a dictionary (robodoc-format) to a XML-file with robodoc-format.
    """
    base_xml_file = minidom.parse(base_fn)
    base_doc = base_xml_file.documentElement
    
    node_l = base_doc.firstChild
    while node_l:
        if node_l.nodeType == node_l.ELEMENT_NODE:
            lang = node_l.getAttribute("lang")
            node_k = node_l.firstChild
            while node_k:
                if node_k.nodeType == node_k.ELEMENT_NODE:
                    attr = node_k.getAttribute("name")
                    node_k.firstChild.data = dxml[lang][attr].encode()
                node_k = node_k.nextSibling      
        node_l = node_l.nextSibling
    
    # export xml-file to new_fn
    with open(new_fn, "w") as f:
        f.write(base_xml_file.toxml()) #.toxml('utf-8'))


# ##############################################################################
if __name__ == "__main__":
    from pprint import pprint
    
#    dxml = xml2dict(xml_fn="sample-in.xml")
    dxml = xml2dict(xml_fn="sample-out.xml")
#    pprint(dxml)
#    
#    dict2xml(dxml=dxml, new_fn="sample-out.xml", base_fn="sample-in.xml")
#    print("...done")
#
#    print(dxml["en"]["material_description"])   #.decode())
"sample-in.xml":

Code: Alles auswählen

<?xml version="1.0" encoding="utf-8"?>
<roboxml>
    <language lang="en">
        <key name="product">air-intake-manifold</key>
        <key name="material_description">Material:
  Durethan AKV35 , cond.
Mechanical Properties:
  σB =  98 MPa
  εB =  1.2 %
Strength Criteria:
  σlim =  78 MPa
  εlim =  0.9 %</key>
    </language>
    <language lang="de">
        <key name="product">ladeluftsammler</key>
        <key name="material_description">Material:
  Durethan AKV35 , cond.
Mechanical Properties:
  σB =  98 MPa
  εB =  1.2 %
Strength Criteria:
  σlim =  78 MPa
  εlim =  0.9 %</key>
    </language>
</roboxml>
"sample-out.xml":

Code: Alles auswählen

<?xml version="1.0" ?><roboxml>
    <language lang="en">
        <key name="product">b'air-intake-manifold'</key>
        <key name="material_description">b'Material:\n  Durethan AKV35 , cond.\nMechanical Properties:\n  \xcf\x83B =  98 MPa\n  \xce\xb5B =  1.2 %\nStrength Criteria:\n  \xcf\x83lim =  78 MPa\n  \xce\xb5lim =  0.9 %'</key>
    </language>
    <language lang="de">
        <key name="product">b'ladeluftsammler'</key>
        <key name="material_description">b'Material:\n  Durethan AKV35 , cond.\nMechanical Properties:\n  \xcf\x83B =  98 MPa\n  \xce\xb5B =  1.2 %\nStrength Criteria:\n  \xcf\x83lim =  78 MPa\n  \xce\xb5lim =  0.9 %'</key>
    </language>
</roboxml>

Vielen Dank im Voraus, Calo
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das encode hat da nichts verloren. XML sorgt selbst für seine binäre Repräsentation. Der XML Header ist deshalb verpflichtend, weil er entweder ein encoding anhubt, oder ohne UTF8 verwandt wird.

Mit minidom zu arbeiten ist übrigens nicht ratsam. Das ist uralt und schwierig zu handhaben. Nutze die mitgelieferte element-tree Implementierung. Ggf macht die auch dein Problem einfacher.
calo
User
Beiträge: 52
Registriert: Freitag 8. Dezember 2006, 21:35
Wohnort: Stuttgart

Hallo __deets__, vielen Dank für die Info. Aber in der Zwischenzeit habe ich es selber hinbekommen. Hier der Code der funktioniert:

Code: Alles auswählen

from xml.dom import minidom

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def xml2dict(xml_fn):
    """
    Description:
    ~~~~~~~~~~~~
    parses and converts a XML-file (robodoc-format) to an python-dictionary 
    format.
    """
    with open(xml_fn, "r", encoding="utf-8") as f:
        xml_file = minidom.parse(f)
        rdoc = xml_file.documentElement
        xml_robo = {}
            
        node_l = rdoc.firstChild
        while node_l:
            if node_l.nodeType == node_l.ELEMENT_NODE:
                lang = node_l.getAttribute("lang")
                xml_robo[lang] = {}
                node_k = node_l.firstChild
                while node_k:
                    if node_k.nodeType == node_k.ELEMENT_NODE:
                        attr = node_k.getAttribute("name")
                        val = node_k.firstChild.data
                        xml_robo[lang][attr] = val
                    node_k = node_k.nextSibling      
            node_l = node_l.nextSibling
        
    return xml_robo


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def dict2xml(dxml, new_fn, base_fn):
    """
    Description:
    ~~~~~~~~~~~~
    converts a dictionary (robodoc-format) to a XML-file with robodoc-format.
    """
    base_xml_file = minidom.parse(base_fn)
    base_doc = base_xml_file.documentElement
    
    node_l = base_doc.firstChild
    while node_l:
        if node_l.nodeType == node_l.ELEMENT_NODE:
            lang = node_l.getAttribute("lang")
            node_k = node_l.firstChild
            while node_k:
                if node_k.nodeType == node_k.ELEMENT_NODE:
                    attr = node_k.getAttribute("name")
                    node_k.firstChild.data = dxml[lang][attr]
                node_k = node_k.nextSibling      
        node_l = node_l.nextSibling
    
    # export xml-file to new_fn
    with open(new_fn, "wb") as f:
        f.write(base_xml_file.toxml('utf-8'))


# ##############################################################################
if __name__ == "__main__":
    from pprint import pprint
    
    dxml = xml2dict(xml_fn="sample-in.xml")
    pprint(dxml)
    
    dict2xml(dxml=dxml, new_fn="sample-out.xml", base_fn="sample-in.xml")

    print(dxml["en"]["material_description"]) 

    dxml = xml2dict(xml_fn="sample-out.xml")
    pprint(dxml)
Entscheidend ist vermutlich (hab' zu viel auf einmal geändert!) der Export im binary-mode am Ende von dict2xml() .

Müsste ich von vorne beginnen, würde ich vermutlich mit elementTree arbeiten. Jetzt muss das aber so gehen.

Nochmals vielen Dank, Calo
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das ist aber kaputt, denn der Code geht davon aus, dass die XML-Datei als UTF-8 kodiert ist. Das ist aber nicht garantiert. Genau dafür gibt es ja die XML-Deklaration, damit man in der Datei die tatsächliche Kodierung angeben kann. Das kodieren und dekodieren sollte man komplett der XML-Bibliothek überlassen. Die Kodierung ist Teil von der XML-Spezifikation, also fällt das auch in den Aufgabenbereich der Bibliothek und nicht in den eigenen Code, wo man leicht Fehler machen kann, oder Sachen nachprogrammiert, welche die Bibliothek bereits leistet.

Keine Ahnung was Minidom da macht, das verwende ich nicht, und wohl keiner der nicht auf Schmerzen steht oder Java toll findet. (Hohe Schnittmenge! 😉)

Du solltest bessere Namen verwenden. Kein kryptischen Abkürzungen. Nicht `something_fn` wenn eigentlich `something_filename` gemeint ist. `fn` ist auch eine übliche Abkürzunge für „function“.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
calo
User
Beiträge: 52
Registriert: Freitag 8. Dezember 2006, 21:35
Wohnort: Stuttgart

Hallo __blackjack__, anders habe ich das mit minidom nicht hinbekommen. Jetzt bin ich erstmal froh, dass es funktioniert.

@__deets__, ich bin jetzt echt beeindruckt. Hab den ersten Teil (xml2dict) mit ElementTree versucht und die Umwandlung zu einem dict() funktioniert auf anhieb :-)

Code: Alles auswählen

import xml.etree.ElementTree as ET
from pprint import pprint

tree = ET.parse("sample-in.xml")
root = tree.getroot() 

xml_robo = {}                                         
for child in root:
    lang = child.attrib["lang"]
    xml_robo[lang] = {}
    for key_child in child:
        key = key_child.attrib['name']
        val = key_child.text
        xml_robo[lang][key] = val

pprint(xml_robo)
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Alternativ mit ein paar weniger Namen für Zwischenergebnisse und „dict comprehensions“:

Code: Alles auswählen

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


def main():
    robodoc_data = {
        child.attrib["lang"]: {
            key_child.attrib["name"]: key_child.text for key_child in child
        }
        for child in ET.parse("sample-in.xml").getroot()
    }
    pprint(robodoc_data)


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten