@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()`.