# 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()