forked from AG_QGIS/Plugin_SN_Basis
92 lines
4.0 KiB
Python
92 lines
4.0 KiB
Python
# sn_plan41/modules/excel_importer.py
|
|
import os
|
|
from typing import Optional, Iterable, Mapping, Any, List, cast
|
|
|
|
from openpyxl import load_workbook
|
|
from openpyxl.workbook.workbook import Workbook
|
|
from openpyxl.worksheet.worksheet import Worksheet
|
|
|
|
from sn_basis.modules.Dateipruefer import Dateipruefer
|
|
from sn_basis.modules.Pruefmanager import Pruefmanager
|
|
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
|
|
|
|
|
class ExcelImporter:
|
|
"""
|
|
Excel-Importer für Linklisten, verwendet Dateipruefer und Pruefmanager zur Meldungsbehandlung.
|
|
|
|
- Der Aufrufer übergibt einen konkreten Dateipfad.
|
|
- Vor dem Öffnen wird der Pfad mit Dateipruefer geprüft.
|
|
- Link- und Stilprüfungen erfolgen nicht hier, sondern im DataGrabber.
|
|
- Nach dem Ladevorgang wird die Arbeitsmappe geschlossen, damit die Datei vom OS freigegeben wird.
|
|
"""
|
|
|
|
def __init__(self, filepath: str, pruefmanager: Pruefmanager):
|
|
if not filepath:
|
|
raise ValueError("ExcelImporter benötigt einen gültigen Dateipfad.")
|
|
if pruefmanager is None:
|
|
raise ValueError("ExcelImporter benötigt einen Pruefmanager.")
|
|
self.filepath = filepath
|
|
self.pruefmanager = pruefmanager
|
|
|
|
def import_xlsx(self) -> List[Mapping[str, Any]]:
|
|
"""
|
|
Liest die Excel-Datei und gibt eine Liste von Dicts (Zeilen) zurück.
|
|
Bei Prüf- oder Leseproblemen wird der Pruefmanager zur Verarbeitung des pruef_ergebnis aufgerufen.
|
|
Im Fehlerfall wird eine leere Liste zurückgegeben.
|
|
"""
|
|
# 1) Dateiprüfung über Dateipruefer
|
|
datei_pruefer = Dateipruefer(pfad=self.filepath, temporaer_erlaubt=False)
|
|
ergebnis: pruef_ergebnis = datei_pruefer.pruefe()
|
|
ergebnis = self.pruefmanager.verarbeite(ergebnis)
|
|
|
|
if not ergebnis.ok:
|
|
return []
|
|
|
|
workbook: Optional[Workbook] = None
|
|
try:
|
|
workbook = load_workbook(filename=self.filepath, data_only=True)
|
|
|
|
# workbook.active kann typmäßig als Optional angesehen werden; cast/prüfen, damit Pylance weiß, dass sheet ein Worksheet ist
|
|
sheet = workbook.active
|
|
if sheet is None:
|
|
pe = pruef_ergebnis(ok=False, meldung=f"Kein aktives Blatt in der Arbeitsmappe: {self.filepath}", aktion="kein_arbeitsblatt", kontext=self.filepath)
|
|
self.pruefmanager.verarbeite(pe)
|
|
return []
|
|
|
|
# Typengranularität für den Linter
|
|
sheet = cast(Worksheet, sheet)
|
|
|
|
# Header aus erster Zeile (als Werte)
|
|
header_row = next(sheet.iter_rows(min_row=1, max_row=1, values_only=True), None)
|
|
if not header_row:
|
|
pe = pruef_ergebnis(ok=False, meldung=f"Excel-Datei enthält keine Header-Zeile: {self.filepath}", aktion="kein_header", kontext=self.filepath)
|
|
self.pruefmanager.verarbeite(pe)
|
|
return []
|
|
|
|
header = list(header_row)
|
|
if not header or all(h is None for h in header):
|
|
pe = pruef_ergebnis(ok=False, meldung=f"Excel-Header ist leer oder ungültig: {self.filepath}", aktion="kein_header", kontext=self.filepath)
|
|
self.pruefmanager.verarbeite(pe)
|
|
return []
|
|
|
|
ergebnis_list: List[Mapping[str, Any]] = []
|
|
# Werte-only lesen für Performance und Einfachheit
|
|
for row in sheet.iter_rows(min_row=2, values_only=True):
|
|
if row is None:
|
|
continue
|
|
# zip stoppt bei kürzerer Länge; das ist beabsichtigt
|
|
attributes = dict(zip(header, row))
|
|
ergebnis_list.append(attributes)
|
|
|
|
return ergebnis_list
|
|
|
|
except Exception as exc:
|
|
pe = pruef_ergebnis(ok=False, meldung=f"Fehler beim Lesen der Excel-Datei '{self.filepath}': {exc}", aktion="read_error", kontext=self.filepath)
|
|
self.pruefmanager.verarbeite(pe)
|
|
return []
|
|
|
|
finally:
|
|
if workbook is not None:
|
|
workbook.close()
|