Files
Plugin_SN_Basis/modules/excel_importer.py

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