ElementTree write liefert SystemError

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
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Liebe alle,
mir ist kein besserer Titel für das Thema eingefallen, für Verbesserungsvorschläge bin ich zu haben.

Ich habe mir vor einiger Zeit eine Funktion geschrieben, die eine Dateiliste mit XML-Dateien einliest und die Dateien nach einem bestimmten XML-Knoten durchsucht. Die Ergebnisse schreibe ich in einen neuen XML-Tree, denn ich dann mittels tree.write() in eine neue Datei geschrieben habe. Das hatte auch alles soweit funktioniert. Seit gestern bekomme ich dabei allerdings den folgenden Fehler:

Code: Alles auswählen

In [58]: tree.write('xxxxxxxxxxxxx\Analyse_docAuthor_02.04.2019.xml')
---------------------------------------------------------------------------
SystemError                               Traceback (most recent call last)
<ipython-input-58-e7b673c0da16> in <module>()
----> 1 tree.write('xxxxxxxxxxxxx\Analyse_docAuthor_02.04.2019.xml')

c:\python\python36-32\lib\xml\etree\ElementTree.py in write(self, file_or_filena
me, encoding, xml_declaration, default_namespace, method, short_empty_elements)
    771                 _serialize_text(write, self._root)
    772             else:
--> 773                 qnames, namespaces = _namespaces(self._root, default_namespace)
    774                 serialize = _serialize[method]
    775                 serialize(write, self._root, qnames, namespaces,

c:\python\python36-32\lib\xml\etree\ElementTree.py in _namespaces(elem, default_
namespace)
    884         elif tag is not None and tag is not Comment and tag is not PI:
    885             _raise_serialization_error(tag)
--> 886         for key, value in elem.items():
    887             if isinstance(key, QName):
    888                 key = key.text

SystemError: ..\Objects\dictobject.c:2708: bad argument to internal function
Ich verstehe den Fehler so, dass beim Versuch den Baum in die Datei zu schreiben ein fehlerhafter Wert aus dem Dictionary, welches die Elemente enthält, an die Funktion zum Schreiben an die Datei übergeben wird. Meine Versuche den Fehler weiter einzugrenzen haben mich leider nicht weiter gebracht, auch online bin ich nicht fündig geworden. Mich irritiert, dass eben dieses Prozedere mit denselben Dateien als Ausgangsbasis schon fehlerfrei funktioniert hat.

Die Funktion, die ich zum erzeugen der Analyse.xml nutze:

Code: Alles auswählen

def findInstances(fileList, pattern):
    '''Sucht in mehreren Dateien nach einem Knoten und liefert die Ergebnisse als neues XML zurück'''
    
    from xml.etree import ElementTree as ET
    from datetime import datetime as DT
    
    nodename = input('Bitte Name des gesuchten Knoten eingeben:')
    newroot = ET.Element('results')
    newroot.attrib = {'date': DT.now().date().isoformat()}
    
    d = input('Mit dem Dictonary fortfahren? y/n: ')
    if d.lower() != 'y':
        print('Bitte neues Pattern angeben')
        pattern = subPattern()
    for file in fileList:
        docTag = file.split('/')[-1]
        res = re.compile('[\)\(\+]')
        docTag = re.sub(res, '-', docTag)
        docTag = docTag.replace(' ', '')
        if docTag.startswith(tuple(str(1234567890))):
            docTag = '_'+docTag
        doc = ET.SubElement(newroot, docTag)
        doc.attrib = {"loc", file}
        print(str(fileList.index(file)) + ': ' + docTag)
        if nodename in file:
            doc.attrib = {'error':'tagInFileName'}
        else:
            try:
                with open(file, mode="r") as fin:
                    fText = fin.read()
                    fText = cleanUp(fText, pattern) #Austausch div. Zeichen, die beim Einlesen des Baums Probleme verursacht hatten 
                    try: 
                        tree = ET.ElementTree(ET.fromstring(fText))
                        root = tree.getroot()
                        for child in root.findall('.//'+nodename):
                            ancestorNode = ET.SubElement(doc, root.findall('.//'+nodename+'/../..')[0].tag)
                            ancestorNode.attrib = root.findall('.//'+nodename+'/../..')[0].attrib
                            ancestorNode.text = root.findall('.//'+nodename+'/../..')[0].text
                            ancestorNode.tail = root.findall('.//'+nodename+'/../..')[0].tail
                            for parent in root.findall('.//'+nodename+'/..'):
                                parentNode = ancestorNode.append(parent)
                    except:
                        doc.attrib = {'error':'notRead'}
            except: 
                doc.attrib = {'error':'notOpened'}
    return(ET.ElementTree(newroot))
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist ein Interner Fehler, weil Du irgendwie Deine Python-Installation zerschossen hast. Probier mal, Python frisch zu installieren.

Zum Code: Die Namenskonvention ist, dass Funktionsnamen klein_mit_unterstrich geschrieben werden. Variablen auch: file_list oder besser gleich files, denn es muß sich ja nur um irgendein iterierbares Objekt handeln und nicht unbedingt um eine Liste.
Importe gehören an den Anfang der Datei.
datetime als DT abzukürzen macht man nicht, richtig wäre DateTime, weil es sich um eine Klasse handelt. ET sollte eigentlich et sein, aber aus historischen Gründen bekommt man das ET wahrscheinlich nie weg.
Den Namen einer Datei bekommt man über os.path.basename oder pathlib.Path.name, aber nicht per split.
Das startswith wäre klarer als docTag[0].isdigit() (doc_tag!) ausgedrückt.
Attribute kann man bei SubElement gleich als Keywort-Argumente angeben.
Wenn man einen Index zu einer for-Schleife braucht, nimmt man enumerate und sucht nicht per index den Index.
Keine nackten Excepts, immer konkrete Fehlerklassen angeben, hier wohl irgendwas mit IOError.
Die vielen findall sind unsinnig. Wenn Du einmal ein Element brauchst, dann speicherst Du das in einer lokalen Variable und rufst nicht 4 mal das selbe auf.
Was willst Du überhaupt mit den for-Schleifen erreichen? Ich kann nicht verstehen, wie das richtig sein kann.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Zunächst danke für die Antwort, ich geh mal unsere IT nerven, dass sie meine Python-Installation erneuert.

Gibt es eigentlich eine Möglichkeit zu verhindern, dass mich das Forum auslogt, während ich eine Antwort tippe? Es ist super ärgerlich, gerade bei längeren Antworten, wenn diese verschwinden.
Sirius3 hat geschrieben: Dienstag 2. April 2019, 10:07 Zum Code: Die Namenskonvention ist, dass Funktionsnamen klein_mit_unterstrich geschrieben werden. Variablen auch: file_list oder besser gleich files, denn es muß sich ja nur um irgendein iterierbares Objekt handeln und nicht unbedingt um eine Liste.
datetime als DT abzukürzen macht man nicht, richtig wäre DateTime, weil es sich um eine Klasse handelt. ET sollte eigentlich et sein, aber aus historischen Gründen bekommt man das ET wahrscheinlich nie weg.
Hier haben eindeutig eigene Faulheit/Gewohnheit die Oberhand. Ich werde mir das für Code, der zur Verteilung gedacht ist, auf jeden Fall merken. Dass es ein Liste ist, kann ich in dem Fall garantieren, weil ich sie selbst in einem vorhergehenden Schritt erzeugt habe.
Sirius3 hat geschrieben: Dienstag 2. April 2019, 10:07 Den Namen einer Datei bekommt man über os.path.basename oder pathlib.Path.name, aber nicht per split.
Das startswith wäre klarer als docTag[0].isdigit() (doc_tag!) ausgedrückt.
Dieser Padawan muss noch viel lernen und sieht manchmal den Wald vor lauter Bäumen nicht, natürlich sind 0-9 alles Zahlen.
Sirius3 hat geschrieben: Dienstag 2. April 2019, 10:07 Keine nackten Excepts, immer konkrete Fehlerklassen angeben, hier wohl irgendwas mit IOError.
Importe gehören an den Anfang der Datei.
Ich dachte Importe, die nur in einer Funktion verwendet werden, kommen dort an den Anfang? Zumindest meine ich, etwas derartiges gelernt/gelesen zu haben?
Bei den Excepts hast du natürlich prinzipiell recht, aber der genau Fehler interessiert mich an der Stelle gar nicht. Hier reicht es aus, wenn ich später in der Analyse.xml einfach sehen kann, wie oft ein Abbruch auf einer bestimmten Stufe vorkommt und welche Dateien betroffen sind. Daher reicht mir die entsprechende "custom"-Fehlermeldung im Attribut des entsprechenden Dokumenten-Knotens. Dann kann ich entweder nach der Fehlerquelle suchen (und mir die exakte Fehlerklasse ausgeben lassen) oder mir dir entsprechende Datei händisch anschauen.
Sirius3 hat geschrieben: Dienstag 2. April 2019, 10:07 Attribute kann man bei SubElement gleich als Keywort-Argumente angeben.
Wenn man einen Index zu einer for-Schleife braucht, nimmt man enumerate und sucht nicht per index den Index.
Die vielen findall sind unsinnig. Wenn Du einmal ein Element brauchst, dann speicherst Du das in einer lokalen Variable und rufst nicht 4 mal das selbe auf.
Was willst Du überhaupt mit den for-Schleifen erreichen? Ich kann nicht verstehen, wie das richtig sein kann.
Die erste for-Schleife liest die Dateipfade aus einer Liste, die in einem anderen Schritt erzeugt worden ist. Lädt den entsprechenden Text, ersetzt Zeichen, die beim Parsen in einen xml-Baum Probleme verursachen (in erster Linie Html-"Namen" für Sonderzeichen) und erstellt einen neuen Knoten im Baum der Analyse.xml, mit dem Dateinamen als Tag. Der Rest bezieht sich vermutlich auf die (vor)letzte for-Schleife?

Code: Alles auswählen

for child in root.findall('.//'+nodename):
    ancestorNode = ET.SubElement(doc, root.findall('.//'+nodename+'/../..')[0].tag)
    ancestorNode.attrib = root.findall('.//'+nodename+'/../..')[0].attrib
    ancestorNode.text = root.findall('.//'+nodename+'/../..')[0].text
    ancestorNode.tail = root.findall('.//'+nodename+'/../..')[0].tail
    for parent in root.findall('.//'+nodename+'/..'):
        parentNode = ancestorNode.append(parent) 
Ich nehme hier nicht den Index der for-Schleife, sondern greife auf das jeweils erste Element der Liste zu, die mir root.findall(XPath) zurückgibt. Mir ist nicht ganz klar, was du mit deiner Bemerkung zu den Attributen bei SubElement meinst? Ich erzeuge hier ja nicht neue Attribute, sondern kopiere sie aus dem ursprünglichen Baum in den Analyse-Baum. Vielleicht hilft hier ein schematischer Aufbau der Dokumente, in welchen in nach bestimmten Knoten suche:

Code: Alles auswählen

<original>
    <!-- mehrere unwichtige Knoten -->
    <ancestor>
         <!-- mehrere unwichtige Knoten -->
        <parent>
             <!-- mögliche Geschwister -->
            <gesuchter_Knoten/>
            <!-- weitere Geschwister -->
        </parent>
       <!-- mehrere unwichtige Knoten -->
    </ancestor>
    <!-- mehrere unwichtige Knoten -->
</original>
Die XML-Dateien sind nicht gleichförmig und können sowohl vor als auch nach dem gesuchten Knoten mehrere Hundert weitere Knoten auf verschiedenen Ebenen enthalten und der gesuchte Knoten kann mehr als einmal vorkommen. Für meine Analyse brauch ich für jede Datei alle Vorkommen des gesuchten Knotens, seiner unmittelbaren Geschwister, sowie den Elternknoten "parent" - aber eben nicht die Geschwister des "parents" - und den Elternknoten des Elternknotens "ancestor".
Ich suche daher im ersten Schritt alle Vorkommen des Knotens "child". Mit ElementTree gibt es außer dem XPath keine Möglichkeit vom "child" zum "ancestor" zu kommen. Daher muss ich den Ancestor wieder mit root.findall(XPath[child]) ermitteln. Jetzt wäre es einfach, wenn ich hier den kompletten Knoten "ancestor" einfach in meinen Analyse-Baum übernehmen könnte, aber dieser kann immer noch mehrere Äste mit einigen 100 Knoten haben, so dass ich am Ende nichts gewonnen habe.
Daher baue ich für meinen Analyse-Baum jeden "ancestor" aus den für mich wichtigen Teilen (Tag, Attribute und Textbestandteile) neu auf. Ich hätte natürlich statt faulerweise mit copy&paste die root.findall(XPath)-Funktion zu kopieren auch das "ancestor"-Element in einer Variable speichern können und dann über die entsprechenden Descriptoren darauf zugreifen können, aber das macht den code auch nicht leserlicher. Oder täusche ich mich:

Code: Alles auswählen

for child in root.findall('.//'+nodename):
    origAncestor = root.findall('.//'+nodename+'/../..')[0]
    ancestorNode = ET.SubElement(doc, origAncestor.tag)
    ancestorNode.attrib = origAncestor.attrib
    ancestorNode.text = origAncestor.text
    ancestorNode.tail = origAncestor.tail
    for parent in origAncestor.findall('.//'+nodename+'/..'):
        parentNode = ancestorNode.append(parent) 
Wobei man "nodename" noch durch child.tag austauschen könnte. Aber da "nodename" die Variable für die "Benutzereingabe" zum gesuchten Tag/Knoten ist, fand ich das hier sinnvoller. Zum Schluss hänge ich den gesamten "parent" mit allen Kindern an den neuen "ancestor". Das Ergebnis ist ein XML-Baum mit der folgenden Struktur:

Code: Alles auswählen

 
<analyseBaum>
    <Datei1>
        <ancestor>
            <parent>
                <Geschwister/>
                <gesuchterKnoten/>
                <Geschwister/>
            </parent>
        </ancestor>
    </Datei1>
    <Datei2>
        <ancestor1>
            <parent>
                <Geschwister/>
                <gesuchterKnoten1/>
                <Geschwister/>
            </parent>
        </ancestor1>
        <ancestor2>
            <parent>
                <Geschwister/>
                <gesuchterKnoten2/>
                <Geschwister/>
            </parent>
        </ancestor2>
    </Datei2>
<!-- usw -->
</analyseBaum>
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Marku5W hat geschrieben: Dienstag 2. April 2019, 13:17 Ich dachte Importe, die nur in einer Funktion verwendet werden, kommen dort an den Anfang?
Nein.
Marku5W hat geschrieben: Dienstag 2. April 2019, 13:17 Dass es ein Liste ist, kann ich in dem Fall garantieren, weil ich sie selbst in einem vorhergehenden Schritt erzeugt habe.
Es kommt nicht darauf an, wie Du das konkret benutzt, sondern was die Funktion fordert.
Marku5W hat geschrieben: Dienstag 2. April 2019, 13:17 aber der genau Fehler interessiert mich an der Stelle gar nicht.
Es interessiert Dich sehr wohl, ob Du im try-Block einen Programmierfehler gemacht hast. Jeder nicht von Dir erwartete Fehler ist ein solcher Programmierfehler.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

@sirius3 Ich schätze deine schnellen, prompten und meistens hilfreichen Antworten sehr, aber deine letzte Antwort ist im Ton sowas von daneben. Wie kommst du überhaupt dazu mir zu erklären, was wann für mich wichtig ist?

Ich kann weder garantieren, dass die XML-Dateien valide sind, noch weiß ich wie sie kodiert sind oder wie die Sonderzeichen umgesetzt sind. Das sind die beiden Fehler, die ich abfange - und erwarte! - und diese sind mir - und ich wiederhole mich - herzlich schnuppe, so lange sie nicht zu häufig auftreten.

Aber danke, dass du auf meine Fragen eingegangen bist.
Marku5W hat geschrieben: Dienstag 2. April 2019, 13:17
  • Gibt es eigentlich eine Möglichkeit zu verhindern, dass mich das Forum auslogt, während ich eine Antwort tippe?
  • Mir ist nicht ganz klar, was du mit deiner Bemerkung zu den Attributen bei SubElement meinst?
  • Ich hätte [..] das "ancestor"-Element in einer Variable speichern können und dann über die entsprechenden Descriptoren darauf zugreifen können, aber das macht den code auch nicht leserlicher. Oder täusche ich mich?
[/code]
Wen das Wort nicht schlägt, den schlägt auch der Stock nicht.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Sirius3 hat geschrieben: Dienstag 2. April 2019, 10:07 Das ist ein Interner Fehler, weil Du irgendwie Deine Python-Installation zerschossen hast. Probier mal, Python frisch zu installieren.
Nach der Neuinstallation bekomme ich denselben oder zumindest eine sehr ähnlichen Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "cleanUp.py", line 167, in <module>
    tree.write(os.path.join(pfad, filename))
  File "C:\Python\Python37-32\lib\xml\etree\ElementTree.py", line 774, in write
    qnames, namespaces = _namespaces(self._root, default_namespace)
  File "C:\Python\Python37-32\lib\xml\etree\ElementTree.py", line 887, in _namespaces
    for key, value in elem.items():
SystemError: c:\_work\5\s\objects\dictobject.c:2636: bad argument to internal function
Benutzeravatar
__blackjack__
User
Beiträge: 14044
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marku5W: Du fängst *alle* Ausnahmen ab und behandelst die so als wären es die beiden von Dir erwarteten Probleme, auch wenn es ein ganz anderes Problem ist! Das ist genau das Problem, weil Du dann gar nicht mitbekommst wenn es ein nicht von Dir erwartetes Problem ist, und vor allem welches Problem das dann ist.

Ich verstehe auch nicht ganz wie Kodierung ein Problem sein kann? Da kümmert sich doch der XML-Parser drum. Und wenn es kein valides XML ist – und da gehört die Kodierungsfrage dazu, dann sollte man es nicht als XML verarbeiten. Was ein Problem darstellt ist das Du die Datei einfach ohne explizite Kodierung als Textdatei öffnest, ohne zu wissen welche Kodierung in der Datei tatsächlich verwendet wird. Kann es sein, dass Dein `cleanUp()` überhaupt nur deshalb notwendig ist, weil Du das nicht dem XML-Parser überlässt?
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Zeige doch mal Deinen gesamten Code, der das Problem reproduziert. Die Zeile, die im Traceback angegeben ist, sehen wir nämlich gar nicht.
Ich vermute mal, dass Du da lxml und cElementTree mischst.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

__blackjack__ hat geschrieben: Dienstag 2. April 2019, 17:11 @Marku5W: Du fängst *alle* Ausnahmen ab und behandelst die so als wären es die beiden von Dir erwarteten Probleme, auch wenn es ein ganz anderes Problem ist! Das ist genau das Problem, weil Du dann gar nicht mitbekommst wenn es ein nicht von Dir erwartetes Problem ist, und vor allem welches Problem das dann ist.

Ich verstehe auch nicht ganz wie Kodierung ein Problem sein kann? Da kümmert sich doch der XML-Parser drum. Und wenn es kein valides XML ist – und da gehört die Kodierungsfrage dazu, dann sollte man es nicht als XML verarbeiten. Was ein Problem darstellt ist das Du die Datei einfach ohne explizite Kodierung als Textdatei öffnest, ohne zu wissen welche Kodierung in der Datei tatsächlich verwendet wird. Kann es sein, dass Dein `cleanUp()` überhaupt nur deshalb notwendig ist, weil Du das nicht dem XML-Parser überlässt?
Vielleicht verwende ich "Kodierung" hier bei dem, was ich sagen will, nicht richtig. :?:

Ich arbeite mit einem Satz von rund 5.000 Dateien, die über die letzten 10 Jahre von mindestens 8 verschiedenen Personen meist händisch erstellt und teilweise von bis zu 3 verschiedenen Fremdfirmen weiter bearbeitet worden sind. Fehler im XML sind deshalb eine Tatsache, wenn auch nur in geringem Ausmaß. Derzeit sitze ich mit einem weiteren Dienstleister daran, diese alle in das gleiche Format/Schema zu bringen, muss aber ab und zu Einzelheiten überprüfen, bevor ich eine Entscheidung fällen kann, wie mit einzelnen Elementen bei der Transformation verfahren werden soll. Wenn ich bei der Analyse rund 10% nicht eingelesene Dateien habe, ist es daher idR zu verschmerzen, weil ich dennoch ein gutes Bild bekomme, wie einzelne Elemente von den einzelnen Verantwortlichen behandelt worden sind.

Wenn ich feststelle, dass ich eine der nicht eingelesenen Dateien doch brauche oder eine Großteil der Fehler beim gleichen Bearbeiter/Zeitabschnitt auftaucht, schau ich mir den Fall gesondert an. Dafür reicht mir aber das Wissen, ob ich bereits beim Öffnen der Dateien nach Problemen suchen muss oder erst beim Parsen. Deshalb reichen mir diese kurzen "Custom-Fehler", die Exceptions waren mal ausführlicher.

Auf diesem Weg ist auch die Funktion cleanUp() entstanden: ein wesentlicher Teil der XML-Dateien eines Zeitraums enthält Sonderzeichen, die als Html-Namen kodiert/aufgelöst sind (&auml; etc.). Diese haben den XML-Parser immer zum Abbruch gebracht (vermutlich hätte man - nicht ich - das auch irgendwie dem Parser beibiegen können?). Deshalb lese ich die Datei immer zuerst als Text ein und ersetze die Html-Namen durch die passenden Umlaut-Zeichen.
Die Ausnahmebehandlung beim Einlesen als Text ist entstanden, weil die Dateien teilweise ANSII/Latin-1 kodiert sind, teilweise in UTF-8 und in sehr geringen Mengen UTF-8 mit BOM und irgendetwas anderes. Die letzten beiden haben dabei beim Öffnen/Einlesen immer zum Abbruch geführt, UTF-8 mit BOM, weil .read() nicht mit dem BOM klar kam, das andere, weil es schon bei with open() as... eine Exception warf. Beides öffnet problemlos, wenn ich es mit dem Editor meiner Wahl anschaue und validiert auch gegen das jeweils zugehörige Schema. Daher kann ich das Problem ignorieren und dem Dienstleister überlassen.
Wen das Wort nicht schlägt, den schlägt auch der Stock nicht.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Sirius3 hat geschrieben: Dienstag 2. April 2019, 19:15 Zeige doch mal Deinen gesamten Code, der das Problem reproduziert. Die Zeile, die im Traceback angegeben ist, sehen wir nämlich gar nicht.
Ich vermute mal, dass Du da lxml und cElementTree mischst.
Die Zeile und die davor gehende lautet:

Code: Alles auswählen

tree, filename = findInstances(files, pattern)
tree.write(os.path.join(pfad, filename))
Die Verwirrung hab ich sicher selbst zu verantworten; sowohl die .py als auch eine Funktion in der .py hießen gleich. Die .py heißt jetzt anders.

LXML ist nicht (mehr) Teil meiner Python-Installation, da ich die IT maximal von der Minimalinstallation von Python überzeugen konnte und ich mit pip nicht (mehr) an unserer Firewall oder der des übergeordneten Landesbetriebs vorbeikomme. (Und ich keine Chance habe mir da eine Ausnahme eintragen zu lassen). Ich habe insgesamt aber auch keine Funktionen aus lxml verwendet?!
Wen das Wort nicht schlägt, den schlägt auch der Stock nicht.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Aber nochmal der ganze Code (Und ich bin mir sicher, jetzt schlagen einige die Hände über dem Kopf zusammen):

Code: Alles auswählen

import os, re
from xml.etree import ElementTree as ET
from datetime import datetime as DT

pfad = 'xxxxxxx' #ist hier nur ein Platzhalter
#KVsub = [('&auml;', 'ä'), ('&uuml;', 'ü'), ('&ouml;','ö'), ('&Auml;','Ä'), ('&Ouml;', 'Ö'), ('&Uuml;', 'Ü'), ('&szlig;', 'ß'), ('&sect;', '§')]

def foldersList(pfad):
    '''make a list of list of subfolders, starting with a path'''
    from copy import deepcopy
    
    reComp = re.compile('\.[a-z]{3}')
    volume = input('----------\nErstelle Ordenerliste\nBitte den Namen des Hauptordners eingeben: ')
    volPath = os.path.join(pfad, volume)
    print('Starte Ordnerliste für '+volPath)
    folderList, oldList = [], []
    for item in os.listdir(volPath):
        if not re.search(reComp, item.lower()):
            folderList.append(os.path.join(volPath, item))
    print('----\nUnterordner in '+volume+': ')
    print(os.listdir(volPath))
    while len(folderList) > len(oldList):
        oldList = deepcopy(folderList)
        for folder in oldList:
            for item in os.listdir(folder):
                if not re.search(reComp, item.lower()) and not os.path.join(folder, item) in oldList:
                    folderList.append(os.path.join(folder, item))
    else: 
        print('Anzahl der Ordner in '+volPath+': '+str(len(folderList))+'\n-----')
        return(folderList)

def fileType(fileList):
    '''delete all filetypes except one from a list of files'''
    delList = []
    keepString = input('Bitte zu erhaltende Dateiendung eingeben: ')
    for file in fileList:
        if not file.lower().endswith(keepString.lower()):
            delList.append(file)
    for item in delList:
            fileList.remove(item)
    return(fileList)

def filesList(folderList):
    '''make a list of files in a given list of folders'''
    fileList = []
    print('--------------\nStarte Sammlung der Dateien')
    for folder in folderList:
        for file in os.listdir(folder):
            fileList.append(os.path.join(folder, file))
    print('Dateiliste mit '+str(len(fileList))+' Dateien erstellt')
    for file in fileList:
         newfile = file.replace('\\\\', '/')
         newfile = newfile.replace('\\', '/')
         fileList[fileList.index(file)] = newfile
    print('Zeichen bereinigt')
    print('Länge der Liste: '+str(len(fileList)))
    fileList = fileType(fileList)
    print('Nicht xml-Dateien entfernt.')
    print('Länge der Liste: '+str(len(fileList)))
    return(fileList)

def nonEmptyFilesList(fileList):
    '''remove empty files and files which cause an error while opening from the list'''
    testLen = len(fileList)
    startLen = testLen
    for item in fileList:
        try:
            with open(item) as f:
                if f.read() == '':
                    fileList.remove(item)
        except:
            fileList.remove(item)
    finalLen = len(fileList)
    while finalLen < testLen:
        testLen = finalLen 
        for item in fileList:
            try:
                with open(item) as f:
                    if f.read() == '':
                        fileList.remove(item)
            except:
                fileList.remove(item)
            finalLen = len(fileList)
    print('Dateiliste von '+str(startLen)+' Dateien auf '+str(finalLen)+' reduziert.\n-----')
    return(fileList)

def cleanUp(text, pattern):
    ''' Nimmt eine Dateiliste files, liest deren text ein Text und ersetzt die in "pattern" angegeben Muster'''
    for key in pattern.keys():
        text = re.sub(str(key), pattern[key], text)
    return(text)

def findInstances(fileList, pattern):
    '''Sucht in mehreren Dateien nach einem Knoten und liefert die Ergebnisse als neues XML zurück'''
    
    nodename = input('Bitte Name des gesuchten Knoten eingeben:')
    newroot = ET.Element('results')
    newroot.attrib = {'date': DT.now().date().isoformat()}
    
    d = input('Mit dem Dictonary fortfahren? y/n: ')
    if d.lower() != 'y':
        print('Bitte neues Pattern angeben')
        pattern = subPattern()
    for file in fileList:
        docTag = file.split('/')[-1]
        res = re.compile('[\)\(\+]')
        docTag = re.sub(res, '-', docTag)
        docTag = docTag.replace(' ', '')
        if docTag.startswith(tuple(str(1234567890))):
            docTag = '_'+docTag
        doc = ET.SubElement(newroot, docTag)
        doc.attrib = {"loc", file}
        print(str(fileList.index(file)) + ': ' + docTag)
        if nodename in file:
            doc.attrib = {'error':'tagInFileName'}
        else:
            try:
                with open(file, mode="r") as fin:
                    fText = fin.read()
                    fText = cleanUp(fText, pattern) #Austausch div. Zeichen, die beim Einlesen des Baums Probleme verursacht hatten 
                    try: 
                        tree = ET.ElementTree(ET.fromstring(fText))
                        root = tree.getroot()
                        for child in root.findall('.//'+nodename):
                            ancestorNode = ET.SubElement(doc, root.findall('.//'+nodename+'/../..')[0].tag)
                            ancestorNode.attrib = root.findall('.//'+nodename+'/../..')[0].attrib
                            ancestorNode.text = root.findall('.//'+nodename+'/../..')[0].text
                            ancestorNode.tail = root.findall('.//'+nodename+'/../..')[0].tail
                            for parent in root.findall('.//'+nodename+'/..'):
                                parentNode = ancestorNode.append(parent)
                    except:
                        doc.attrib = {'error':'notRead'}
            except: 
                doc.attrib = {'error':'notOpened'}
    results = 'Analyse_'+nodename+'_'+DT.now().date().isoformat()
    return(ET.ElementTree(newroot), results)


def subPattern():
    csvFile = 'xxxxxxxx/html_special_chars_w-o_XML.csv' #auch hier nur zur Verschleierung des Pfads
    print ('Ersetzungen stammen aus: '+csvFile)
    csvInput = input('Damit fortfahren? y/n: ')
    if csvInput.lower() != 'y':
        csvFile = input('Bitte neues csvFile angeben!: ')
    with open(csvFile, mode="r", encoding='UTF-8') as csvin:
        lines = csvin.readlines()
        print('Die csv hat folgendes Format:')
        for line in lines[:10]:
            print(line)
        delimiter = input('Bitte das Trennzeichen eingeben: ')
        keynr =  int(input('Bitte Spaltennummer für Suchbegriffe eingeben. '))
        valuenr = int(input('Bitte Spaltennummer für Ersetzungen eingeben '))
        pattern = {line.split(delimiter)[keynr]: line.split(delimiter)[valuenr] for line in lines}
    return(pattern)

print('Es wurde '+pfad+' als Arbeitsort festgelegt.')
pfadInput = input ('Damit fortfahren? y/n: ')
if pfadInput.lower() != 'y':
    pfad = input('Bitte neuen Arbeitsort angeben!: ')
pattern = subPattern()
folders = foldersList(pfad)
files = filesList(folders)
#testFiles = sorted(files)[:100]
#nonEmptyFiles = nonEmptyFilesList(files)
tree, filename = findInstances(files, pattern)
tree.write(os.path.join(pfad, filename))
Wen das Wort nicht schlägt, den schlägt auch der Stock nicht.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Und nochmals die Fehlermeldung mit dem neuen Namen der .py:

Code: Alles auswählen

Traceback (most recent call last):
  File "analyse_KP-alt_XML.py", line 166, in <module>
    tree.write(os.path.join(pfad, filename))
  File "C:\Python\Python37-32\lib\xml\etree\ElementTree.py", line 774, in write
    qnames, namespaces = _namespaces(self._root, default_namespace)
  File "C:\Python\Python37-32\lib\xml\etree\ElementTree.py", line 887, in _namespaces
    for key, value in elem.items():
SystemError: c:\_work\5\s\objects\dictobject.c:2636: bad argument to internal function
Wen das Wort nicht schlägt, den schlägt auch der Stock nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 14044
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marku5W: Hm, also irgendwie habe ich das Gefühl Du verstehst das Problem mit der Ausnahmebehandlung immer noch nicht. Wenn in so einen ``except:``-Zweig gegangen wird, muss das nicht daran liegen das etwas mit der XML-Datei formal oder inhaltlich nicht stimmt. Die kann völlig in Ordnung sein.

Beim öffnen der Datei kann es keinen Fehler geben der etwas mit dem Dateiinhalt zu tun hat. Und zwischen öffnen und parsen liegt ja normalerweise nichts. Das sehe ich bei Deinem Code auch als Fehler an, das Du die XML-Dateien als *Textdateien* öffnest. Du weisst doch gar nicht wie die kodiert sind, also sollte man sie als Binärdatei öffnen und dann erst einmal ermitteln wie die kodiert sind. Wobei das aber auch der Parser schon macht, also warum das noch mal selbst nachbasteln.

Für „character entity references“ jenseits der paar die in XML selbst schon definiert sind, braucht mein DTD wo die definiert sind. Oder man sagt dem Parser das Entities nicht aufgelöst werden sollen falls das eine Option wäre. Braucht man dann aber `lxml.etree` für.

Die Namen in dem Programm halten sich nicht an die Konventionen. Also alles klein_mit_unterstrichen, ausser Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Siehe auch den Style Guide for Python Code.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Zeichenkettenteile und Werte mit `str()` und ``+`` zusammenstückeln ist eher BASIC als Python. Python hat dafür Zeichenkettenformatierung mit der `format()`-Methode und seit Python 3.6 f-Zeichenkettenliterale.

`foldersList()` ist kein guter Name für eine Funktion. Funktions- und Methodennamen beschreiben üblicherweise die Tätigkeit die sie durchführern. `foldersList` wäre dagegen ein Name für eine Liste mit Ordnern. Wobei der konkrete Basistyp nicht in den Namen gehört. Dann kann man den nämlich nicht mehr ändern ohne das man entweder einen irreführenden Namen im Programm hat oder überall alle davon betroffenen Namen anpassen muss.

Das ``else`` zu der ``while``-Schleife macht keinen Sinn, weil in der Schleife nirgends ein ``break`` vorkommt.

`copy.deepcopy()` auf eine Liste die ausschliesslich Zeichenketten enthält ist unnötig. Da braucht man nicht einmal `copy.copy()` für, weil man einfach mit `list()` eine flache Kopie erstellen kann. Letztlich ist aber die ganze Lösung da ziemlich umständlich und ineffizient. Du baust da letztendlich `os.walk()` nicht-rekursiv nach. Und wohl auch nicht so wirklich robust wenn Du '\.[a-z]{3}' tatsächlich als regulären Ausdruck verwendest um Dateien von Ordnern zu unterscheiden. Es kann auch Ordner geben auf die dieses Muster zutrifft, und es kann auch Dateien geben auf die das Muster *nicht* zutrifft. Zudem suchst Du das Muster nicht nur am Ende des Namens sondern das passt ja auch wenn irgendwo *im* Namen ein Punkt gefolgt von drei Buchstaben von 'a' bis 'z' vorkommt.

Vor der ``while``-Schleife steht Code der auch noch einmal *in* der Schleife vorkommt. Das hätte man leicht vermeiden können in dem man nicht mit einer leeren Liste anfängt, sondern den Ausgangspfad vor dem Eintritt in die Schleife als einzelnes Element in die Liste eingegtragen hätte.

``return`` ist eine Anweisung und keine Funktion, das sollte man also auch nicht so schreiben das es wie ein Funktionsaufruf aussieht. Die Klammern gehören da nicht um den Ausdruck und zwischen Schlüsselworten und einen folgenden Ausdruck steht ein Leerzeichen.

Von `foldersList()` bleibt dann am Ende nur noch das hier übrig:

Code: Alles auswählen

def get_folders(pfad):
    """Make a list of list of subfolders, starting with a path."""
    
    volume = input(
        '----------\n'
        'Erstelle Ordenerliste\n'
        'Bitte den Namen des Hauptordners eingeben: '
    )
    folders = list()
    for root, dirnames, filenames in os.walk(os.path.join(pfad, volume)):
        folders.extend(os.path.join(root, dirname) for dirname in dirnames)
    return folders
Wobei Benutzerinteraktion in solche Funktionen eher nicht hinein gehört, weil man die dann nicht wiederverwenden oder leicht automatisiert oder auch manuell testen kann.

`fileType()` ist wieder ein schlechter und irreführender Name für eine Funktion. Und auch diese Funktion ist umständlich und ineffizient geschrieben. Statt erst alle zu löschenden Namen in einer Liste zu sammeln um dann die Namen einzeln mit `remove()` aus der ursprünglichen Liste zu entfernen, würde man einfach eine neue Liste ohne die unerwünschten Namen erstellen. Die Funktion verändert die übergebene Liste, was an sich unschön ist, *und* gibt sie als Ergebnis zurück, was unsinnig ist, denn der Aufrufer hat die Liste ja bereits – sonst hätte er sie nicht übergeben können.

Das ersetzen der Backslashes durch Schrägstriche ist dann wieder extremst ineffizient gelöst. Ich würde diesen Schritt komplett streichen. Der ist auch nicht portabel.

`nonEmptyFilesList()` ist, Überraschung, extrem umständlich und ineffizient. Auch hier ist wieder etwas vor der ``while``-Schleife was auch *in* der ``while``-Schleife steht und das nur weil die Startwerte ungünstig gesetzt sind. Aber dieses ganze wiederholen ist unsinnig und überhaupt nur deswegen nötig weil man aus einer Datenstruktur über die man gerade iteriert, nichts löschen darf, ohne das man Probleme bekommt das Elemente übersprungen werden, weil durch das löschen ja alle Elemente nach dem gelöschten Element einen Platz nach vorne rücken. Das umgeht man in dem man einfach nichts verändert, sondern eine neue Liste ohne die unerwünschten Elemente erstellt. Auch hier ist wieder ein nacktes ``except:`` ohne konkrete Ausnahme(n). Ein Fehler hier ist auch wieder das öffnen der Datei als Textdatei. Wenn man die als Binärdatei öffnen würde, könnte man zum Beispiel gar keine Ausnahemen bezüglich der Kodierung bekommen. Zudem könnte man auch einfach die Grösse der Datei prüfen wenn man wissen möchte ob da tatsächlich gar nichts drin steht. Damit spart man sich das öffnen der Datei. Da bleibt ein Einzeiler übrig:

Code: Alles auswählen

def remove_empty_files(filenames):
    """Remove empty files and files which cause an error while opening
    from the list.
    """
    return [filename for filename in filenames if os.path.getsize(filename)]
Bei `cleanUp()` verstehe ich nicht so recht warum `key` von einer Zeichenketten mit `str()` in eine Zeichenkette umgewandelt wird. Das ist eine Nulloperation.

Der Docstring der Funktion geht ziemlich am tatsächlichen Inhalt der Funktion vorbei.

`pattern` ist als Einzahl kein guter Name für ein Wörterbuch mit Muster und Ersetzung.

Wenn man über die Schlüssel/Wert-Paare iterieren möchte, sollte man das tun, und nicht nur über die Schlüssel: `items()` statt `keys()`.

Wobei ein Wörterbuch hier auch gar nicht wirklich Sinn macht. Eine Sequenz aus Suchausdruck/Ersetzungs-Paaren würde ausreichen.

`file` ist kein guter Name für einen Dateinamen, denn bei `file` würde man als Leser ein Dateiobjekt erwarten das zum Beispiel eine `close()`-Methode besitzt.

Das Datum sollte man nur *einmal* erstellen, sonst kann es passieren, dass zwischen den beiden Aufrufen Mitternacht liegt und die Datei im Inhalt ein anderes Datum enthält als im Dateinamen.

Der reguläre Ausdruck mit der Zeichenklasse die Klammern und '+' enthält ist recht umständlich geschrieben. Die ganzen Backslashes sind überflüssig weil weder runde Klammern noch das '+' innerhalb einer Zeichenklasse eine besondere Bedeutung haben.

Einen XML-Elementnamen aus einem Dateinamen zu erstellen ist keine gute Idee, denn das ist eher ein Datum, Elementnamen sind aber statisch, sonst kann man keine Schemata dafür erstellen.

Mit `index()` die Position eines Elementes zu suchen wenn man gerade über die Liste iteriert und das Element das aktuelle Element der aktuellen Iteration ist, macht keinen Sinn. Da würde man einfach mit `enumerate()` die Indizes parallel zu den Elementen in der Schleife aufzählen, statt jedes Element wieder von vorne in der Liste zu suchen.

Im ``with``-Block steht viel zu viel Code, und das äussere ``try`` umfasst auch zu viel, weil deutlich mehr als das öffnen/einlesen der Datei.

Textdateien öffnen ohne eine Kodierung anzugeben ist ein Fehler.

Du erstellst da ein `ElementTree`-Objekt aus einem `Element` nur um danach dieses `Element`-Objekt mit `getroot()` wieder abzufragen und nichts mit dem `tree` zu machen. Den Schritt kann man sich sparen.

Was dann in der Schleife veranstaltet wird sieht einfach nur falsch aus. Du suchst nach den `nodename`-Elementen, machst dann aber überhaupt gar nichts mit denen‽ `child` wird nirgends im Code verwendet. Innerhalb der Schleife findest Du immer und immer wieder das *selbe* Element relativ zum ersten `child`. Das macht keinen Sinn. Ebenso macht es wenig Sinn viermal das gleiche `findall()` zu machen, statt das nur einmal zu machen und sich das Ergebnis zu merken. Und wenn man nur ein Ergebnis/das erste braucht, dann ist das auch `find()` und nicht `findall()`.

`findall()` und eine Schleife ist bei Pfaden die auf Übergeordnete Elemente führen auch überflüssig, denn es kann da ja nur *ein* Element geben. (Oder keines wenn man über die Wurzel hinaus geht.)

`parentNode` wird definiert, aber nirgends verwendet.

CSV-Dateien sind komplexer als einfach an einem Trennzeichen aufzuteilen. Dafür gibt es das `csv`-Modul in der Standardbibliothek.

Ich lande dann (ungetestet) ungefähr bei so etwas hier als Zwischenschritt:

Code: Alles auswählen

#!/usr/bin/env python3
import csv
from datetime import date as Date
import os
import re
from xml.etree import ElementTree as ET

PFAD = 'xxxxxxx' #ist hier nur ein Platzhalter


def get_foldernames(pfad):
    """Make a list of list of subfolders, starting with a path."""
    
    volume = input(
        '----------\n'
        'Erstelle Ordenerliste\n'
        'Bitte den Namen des Hauptordners eingeben: '
    )
    foldernames = list()
    for root, dirnames, _filenames in os.walk(os.path.join(pfad, volume)):
        foldernames.extend(os.path.join(root, dirname) for dirname in dirnames)
    return foldernames
    

def remove_filenames(filenames):
    """Delete all filetypes except one from a list of files."""
    
    extension = input('Bitte zu erhaltende Dateiendung eingeben: ')
    if not extension.startswith('.'):
        extension = '.' + extension
    return [name for name in filenames if not name.lower().endswith(extension)]


def get_filenames(foldernames):
    """Make a list of files in a given list of folders."""
    print('--------------\nStarte Sammlung der Dateien')
    filenames = list()
    for foldername in foldernames:
        filenames.extend(
            os.path.join(foldername, filename)
            for filename in os.listdir(foldername)
        )
    print(f'Dateiliste mit {len(filenames)} Dateien erstellt')
    filenames = remove_filenames(filenames)
    print('Nicht xml-Dateien entfernt.')
    print(f'Länge der Liste: {len(filenames)}')
    return filenames


def remove_empty_files(filenames):
    """Remove empty files and files which cause an error while opening
    from the list.
    """
    return [filename for filename in filenames if os.path.getsize(filename)]
    

def clean_up(text, patterns):
    for old, new in patterns:
        text = re.sub(old, new, text)
    return text


def find_instances(filenames, patterns):
    """Sucht in mehreren Dateien nach einem Knoten und liefert die Ergebnisse
    als neues XML zurück.
    """
    nodename = input('Bitte Name des gesuchten Knoten eingeben:')
    today = Date.today()
    newroot = ET.Element('results')
    newroot.attrib = {'date': today.isoformat()}
    
    answer = input('Mit dem Dictonary fortfahren? y/n: ')
    if answer.lower() != 'y':
        print('Bitte neues Pattern angeben')
        patterns = load_patterns()
    
    for i, path in enumerate(filenames):
        filename = os.path.basename(path)
        doc_tag = re.sub('[()+]', '-', filename).replace(' ', '')
        if doc_tag[:1] in '0123456789':
            doc_tag = '_' + doc_tag
        doc = ET.SubElement(newroot, doc_tag)
        doc.attrib = {'loc', path}
        print(f'{i}: {doc_tag}')
        if nodename in path:
            doc.attrib = {'error': 'tagInFileName'}
        else:
            try:
                with open(path, 'r', encoding='UTF-8') as file:
                    text = file.read()
            except (UnicodeDecodeError, OSError): 
                doc.attrib = {'error': 'notRead'}
            else:
                try: 
                    text = clean_up(text, patterns)
                    root = ET.fromstring(text)
                    grandparent = root.find(f'.//{nodename}/../..')
                    if grandparent is None:
                        raise ValueError
                    # 
                    # TODO Das hier macht keinen Sinn.
                    # 
                    for _ in root.findall('.//' + nodename):
                        ancestor_node = ET.SubElement(doc, grandparent.tag)
                        ancestor_node.attrib = grandparent.attrib
                        ancestor_node.text = grandparent.text
                        ancestor_node.tail = grandparent.tail
                        ancestor_node.append(root.find(f'.//{nodename}/..'))
                except (re.error, ET.ParseError, ValueError):
                    doc.attrib = {'error': 'notProcessed'}
    
    results = f'Analyse_{nodename}_{today.isoformat()}'
    return (ET.ElementTree(newroot), results)


def load_patterns():
    csv_filename = 'xxxxxxxx/html_special_chars_w-o_XML.csv'
    print('Ersetzungen stammen aus:', csv_filename)
    answer = input('Damit fortfahren? y/n: ')
    if answer.lower() != 'y':
        csv_filename = input('Bitte neues csvFile angeben!: ')
    
    with open(csv_filename, mode='r', encoding='UTF-8') as csv_file:
        delimiter = input('Bitte das Trennzeichen eingeben: ')
        reader = csv.reader(csv_file, delimiter=delimiter)
        rows = list(reader)
        
    old_index = int(input('Bitte Spaltennummer für Suchbegriffe eingeben. '))
    new_index = int(input('Bitte Spaltennummer für Ersetzungen eingeben '))
    return [(row[old_index], row[new_index]) for row in rows]


def main():
    pfad = PFAD
    print(f'Es wurde {pfad} als Arbeitsort festgelegt.')
    answer = input('Damit fortfahren? y/n: ')
    if answer.lower() != 'y':
        pfad = input('Bitte neuen Arbeitsort angeben!: ')
    pattern = load_patterns()
    foldernames = get_foldernames(pfad)
    filenames = get_filenames(foldernames)
    #test_filenames = sorted(filenames)[:100]
    #non_empty_filenames = remove_empty_files(filenames)
    tree, filename = find_instances(filenames, pattern)
    tree.write(os.path.join(pfad, filename))


if __name__ == '__main__':
    main()
Zwischenschritt weil es umständlich ist erst die Ordnerliste zu erstellen, dann da alle Dateien zu suchen (und eigentlich ja auch noch mal alle Ordner!), und daraus dann nur die Namen mit einer bestimmten Endung heraus zu filtern. Das hätte man auch alles gleich beim ersten `os.walk()` erledigen können. Oder noch kompakter mit `pathlib.Path.glob()`.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Marku5W
User
Beiträge: 20
Registriert: Montag 8. Januar 2018, 14:21
Kontaktdaten:

Zunächst einmal vielen Dank an @__blackjack__ für diese ausführliche Antwort, in der soviel drin steckt, dass ich tatsächlich einige Zeit gebraucht habe um mich mir ihr auseinander setzen zu können. Ich kann daher nur hoffen, dass diese Thema noch jemand verfolgt.
Es zeigt sich, wie häufig ich immer noch von meiner menschlichen Arbeitsweise her denke und nicht von einer "maschinennahen". Wie die Ausführungen zeigen ist da noch jede Menge Luft, um mein Geschreibsel effektiver und kürzer zu machen - und auch viele Fehler zu vermeiden.
Beim öffnen der Datei kann es keinen Fehler geben der etwas mit dem Dateiinhalt zu tun hat. Und zwischen öffnen und parsen liegt ja normalerweise nichts. Das sehe ich bei Deinem Code auch als Fehler an, das Du die XML-Dateien als *Textdateien* öffnest. Du weisst doch gar nicht wie die kodiert sind, also sollte man sie als Binärdatei öffnen und dann erst einmal ermitteln wie die kodiert sind. Wobei das aber auch der Parser schon macht, also warum das noch mal selbst nachbasteln.
Ich habe hier noch Verständnisfragen:
  • Zum einen habe ich nie wirklich verstanden, was der Sinn hinter dem Öffnen als Binärdatei ist, bzw das "Konzept" "Binärdatei" ist mir unverständlich und ich habe nichts gefunden, dass mir das erklären könnte. Eventuell gibt es da etwas, wo man es für Laien verständlich nachlesen kann? Idealerweise gleich so, dass ich auch verstehe, wie ich damit dann die Kodierung von Dateien ermitteln kann?
  • Zum anderen in wie weit übernimmt der Parser beim Einlesen das Ermitteln der Kodierung? Die ganze Routine an der Stelle hatte ich erstellt, weil der Parser mit den Dateien nicht zurecht gekommen ist. Wenn ich das beim Parsen gleich mit einer entsprechenden Fehlerbehandlung abfangen könnte, wäre es natürlich um ein vielfaches einfacher/besser/kürzer.....
Ich hab noch viel zu lernen und bin froh, dass es hier jede Menge geduldige und streitbare Geister gibt, die sich die Zeit für so ausführliche Antworten nehmen!
Grüße, Markus
Wen das Wort nicht schlägt, den schlägt auch der Stock nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 14044
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Marku5W: Binärdatei ist eigentlich der Basisfall, wo keine der beteiligten Stellen versucht irgend etwas an den Daten zu ändern. Die Datei wird dann einfach als Bytewerte zwischen 0 und 255 angesehen und 1:1 so gelesen und weitergereicht.

Wenn man die Datei als Textdatei öffnet, dann verändern einige Betriebssysteme schon die Daten bevor Python sie zu sehen bekommt (Windows wandelt Bytefolgen mit 13, 10 in eine einzelnes Byte mit dem Wert 10 um und hört beim Bytewert 26 einfach auf zu lesen, auch wenn danach noch Daten in der Datei stehen. Ein Überbleibsel von DOS, was dieses Verhalten wiederum von CP/M ”geerbt” hat.

Und Python dekodiert die Binärdaten zu Zeichen(ketten) wenn man eine Datei als Textdatei öffnet. Dazu muss man aber wissen wie die Daten kodiert sind, also welcher Bytewert oder welche Folge von Bytewerten welchem Zeichen entsprechen. Wenn man keine explizite Kodierung angibt nimmt Python die ”Systemkodierung”, die von System zu System verschieden sein kann, weshalb man die besser immer explizit angibt, damit das Programm auf jedem System das gleiche macht.

XML-Dateien sind keine normalen Textdateien und XML sieht vor, das man die Kodierung des Dokuments im Dokument selbst angeben kann und hat auch spezifiziert welche Kodierung gilt wenn keine in der Datei hinterlegt ist. Deshalb sollte man das dem XML-Parser überlassen, denn der weiss ja wie er aus dem XML die Kodierung auslesen kann.

Ganz allgemein kann man die Kodierung von Textdateien übrigens nicht ermitteln, das ist immer nur ein raten und kann damit auch falsch sein. Man sollte das bei Textdateien die man verarbeitet also immer *wissen* und explizit angeben.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Antworten