From e329dc9f3aa3e48c65756f4161e851a33c3e8d58 Mon Sep 17 00:00:00 2001 From: lertlmaier Date: Thu, 3 Jul 2025 15:00:01 +0200 Subject: [PATCH] Excel Export Funktionen implementiert. WSCAD mit Bezug noch mit Bearbeitungsbedarf --- lib/portalexport.py | 316 ++++++++++++++++++++++++++++---------------- 1 file changed, 201 insertions(+), 115 deletions(-) diff --git a/lib/portalexport.py b/lib/portalexport.py index 4e40fd1..3b210d0 100644 --- a/lib/portalexport.py +++ b/lib/portalexport.py @@ -157,9 +157,9 @@ class ExcelConverter: return neue_liste - def excel_datei_beschreiben_simpel(self, ursprungdatei, prefix_eingang, prefix_ausgang): + def _prep_simpel(self, ursprungdatei, prefix_eingang="E", prefix_ausgang="A"): """ - Konvertiert Daten aus der Ursprungsdatei und schreibt sie in eine einfache Excel-Datei. + Konvertiert Daten aus der Ursprungsdatei Eingänge und Ausgänge in seperaten dataframes; Fehler nicht enthalten """ eingaenge = [] @@ -186,7 +186,7 @@ class ExcelConverter: ausgaenge.append({'Name': name, 'Kommentar': kommentar, 'Zeile': i}) else: name = bmk_formatted - fehler.append({'Name': name, 'Kommentar': kommentar, 'Zeile': i}) + fehler.append({'Name': name, 'Kommentar': f"Fehlerhafte BMK '{bmk_zahl_raw}'", 'Zeile': i}) # Fehler-Dataframe erzeugen df_fehler = pd.DataFrame(fehler).sort_values(by="Name") if fehler else pd.DataFrame() @@ -204,7 +204,7 @@ class ExcelConverter: return df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge - def excel_datei_beschreiben_tia(self, ursprungdatei, prefix_eingang, prefix_ausgang): + def _prep_tia(self, ursprungdatei, prefix_eingang="E", prefix_ausgang="A"): """ Konvertiert Daten für TIA Portal und schreibt sie in eine Excel-Datei mit zwei Blättern ("Constants" und "PLC Tags"). Eingänge und Ausgänge gemeinsam jeweils in consts und tags (Prefix cIn, cOut); doppelte Namen markiert und ausgeschlossen; Fehler mit Prefix 'Err'. @@ -273,13 +273,13 @@ class ExcelConverter: df_gleiche_eingaenge = pd.DataFrame(gleiche_eingaenge).sort_values(by="Zahl") if gleiche_eingaenge else pd.DataFrame() df_gleiche_ausgaenge = pd.DataFrame(gleiche_ausgaenge).sort_values(by="Zahl") if gleiche_ausgaenge else pd.DataFrame() - df_consts, df_tags = self.create_tia_dataframes(df_fehler, df_eingaenge, df_ausgaenge) + df_consts, df_tags = self._create_tia_dataframes(df_fehler, df_eingaenge, df_ausgaenge) return df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags - def create_tia_dataframes(self, df_fehler, df_eingaenge, df_ausgaenge): + def _create_tia_dataframes(self, df_fehler, df_eingaenge, df_ausgaenge): """ - Hilfsfunktion für 'excel_datei_beschreiben_tia', die dataframes für spätere excel Ausgabe erzeugt ("Constants" und "PLC Tags"). + Hilfsfunktion für 'prep_tia', die dataframes für spätere excel Ausgabe erzeugt ("Constants" und "PLC Tags"). Eingänge und Ausgänge gemeinsam jeweils in consts und tags; Fehler ebenfalls mit Prefix 'Err' enthalten. """ df_constants_data = [] @@ -358,67 +358,7 @@ class ExcelConverter: return (df_constants, df_plc_tags) - def excel_datei_beschreiben_wscad(self, ursprungdatei, prefix_eingang, prefix_ausgang): - """ - Konvertiert Daten für WSCAD und schreibt sie in eine Excel-Datei. - Entspricht der PowerShell-Funktion ExcelDateiBeschreibenWSCAD. - """ - eingaenge = [] - ausgaenge = [] - fehler = [] - - for i, eintrag in enumerate(ursprungdatei): - eintragszeile = eintrag.strip().split("'") - bmk_zahl_raw = eintragszeile[1].strip() - kommentar = eintragszeile[3].strip() - - bmk_alpha = ''.join(filter(str.isalpha, bmk_zahl_raw)) - bmk_numeric = ''.join(filter(str.isdigit, bmk_zahl_raw)) - - bmk_formatted = bmk_numeric - if len(bmk_numeric) > 0: - bmk_formatted = bmk_numeric[:-1] + "." + bmk_numeric[-1] if len(bmk_numeric) > 1 else "0." + bmk_numeric - - # Remove leading zeros - if len(bmk_formatted) >= 4 and bmk_formatted.startswith("0") and bmk_alpha in self.eingaenge_identifier: - bmk_formatted = bmk_formatted[1:] - if len(bmk_formatted) >= 4 and bmk_formatted.startswith("0"): - bmk_formatted = bmk_formatted[1:] - elif len(bmk_formatted) >= 3 and bmk_formatted.startswith("0") and bmk_alpha in self.ausgaenge_identifier: - bmk_formatted = bmk_formatted[1:] - if len(bmk_formatted) >= 3 and bmk_formatted.startswith("0"): - bmk_formatted = bmk_formatted[1:] - - if bmk_alpha in self.eingaenge_identifier: - name = prefix_eingang + bmk_formatted - eingaenge.append({'Name': name, 'Kommentar': kommentar, 'Zeile': i}) - elif bmk_alpha in self.ausgaenge_identifier: - name = prefix_ausgang + bmk_formatted - ausgaenge.append({'Name': name, 'Kommentar': kommentar, 'Zeile': i}) - else: - name = bmk_formatted - fehler.append({'Name': name, 'Kommentar': kommentar, 'Zeile': i}) - - eingaenge = self._anschluesse_ergaenzen(eingaenge) - ausgaenge = self._anschluesse_ergaenzen(ausgaenge) - - # Fehler-Dataframe erzeugen - df_fehler = pd.DataFrame(fehler).sort_values(by="Name") if fehler else pd.DataFrame() - - # Duplikate verarbeiten & markieren - gleiche_eingaenge, eingaenge = self._verarbeite_io_liste(eingaenge) - gleiche_ausgaenge, ausgaenge = self._verarbeite_io_liste(ausgaenge) - - # DataFrames erzeugen u. sortieren - df_eingaenge = pd.DataFrame(eingaenge).sort_values(by="Name") - df_ausgaenge = pd.DataFrame(ausgaenge).sort_values(by="Name") - - df_gleiche_eingaenge = pd.DataFrame(gleiche_eingaenge).sort_values(by="Name") if gleiche_eingaenge else pd.DataFrame() - df_gleiche_ausgaenge = pd.DataFrame(gleiche_ausgaenge).sort_values(by="Name") if gleiche_ausgaenge else pd.DataFrame() - - return df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge - - def excel_datei_beschreiben_wscad_bezug(self, ursprungdatei, prefix_eingang, prefix_ausgang): + def _prep_wscad(self, ursprungdatei, prefix_eingang="E", prefix_ausgang="A"): """ Konvertiert Daten für WSCAD mit Bezugsinformationen und schreibt sie in eine Excel-Datei. Entspricht der PowerShell-Funktion ExcelDateiBeschreibenWSCAD_Bezug. @@ -479,6 +419,163 @@ class ExcelConverter: return df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge + def _prep_data(self, data, export_type): + """ + Aufbereiter der Daten je nach gewünschtem Export-Typ + """ + if export_type == "Simpel": + return self._prep_simpel(data) + elif export_type == "TIA": + return self._prep_tia(data) + elif export_type == "WSCAD" or "WSCAD mit Bezug": + return self._prep_wscad(data) + else: + raise ValueError("Unbekannter Export-Typ") + + def export_excel(self, data, export_type, export_path): + """ + Wrapper, der aufgerufen wird und in sich die Aufbereitung der Daten und das Abspeichern als Excel abruft. + """ + # Daten vorbereiten + dataframes = self._prep_data(data, export_type) + + # Excel Datei schreiben und speichern + self._write_excel(dataframes, export_type, export_path) + + def _write_excel(self, dataframes, export_type, export_path): + """ + Erzeugung der Excel-Datei im gewünschten Muster + """ + if export_type == "Simpel": + # Format für Simpel-Ausgabe + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = dataframes + + with pd.ExcelWriter(export_path, engine='openpyxl') as writer: + # Sheet 1: Ein / Ausgänge + start_row = 0 + df_eingaenge.to_excel(writer, sheet_name='Ein-, Ausgaenge', index=False, startrow=start_row) + start_row += len(df_eingaenge) + 2 # Leerzeile dazwischen + + # Ausgaenge direkt drunter schreiben (ohne Header) + df_ausgaenge.to_excel(writer, sheet_name='Ein-, Ausgaenge', index=False, startrow=start_row, header=False) + + # Sheet mit Duplikaten + start_row = 0 + # Gleiche Eingänge + if not df_gleiche_eingaenge.empty: + df_gleiche_eingaenge.to_excel(writer, sheet_name='Duplikate', index=False, startrow=start_row) + start_row += len(df_gleiche_eingaenge) + 2 # Leerzeile + # Gleiche Ausgänge + if not df_gleiche_ausgaenge.empty: + df_gleiche_ausgaenge.to_excel(writer, sheet_name='Duplikate', index=False, startrow=start_row, header=False) + + # Fehler in eigenem Sheet + if not df_fehler.empty: + df_fehler.to_excel(writer, sheet_name='Fehler', index=False) + + elif export_type == "TIA": + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags = dataframes + + with pd.ExcelWriter(export_path, engine='openpyxl') as writer: + # 1. Constants Sheet mit Header. Spalten: Name, Path, Data Type, Value, Comment + df_consts.to_excel(writer, sheet_name='Constants', index=False) + + # 2. PLC Tags Sheet mit Header. Spalten: Name, Path, Data Type, Logical Address, Comment, Hmi Visible, Hmi Accessible, Hmi Writeable, Typeobject ID, Version ID + df_tags.to_excel(writer, sheet_name='PLC Tags', index=False) + + # Duplikate in "Duplikate" Sheet + start_row = 0 + if not df_gleiche_eingaenge.empty or not df_gleiche_ausgaenge.empty: + # Gleiche Eingänge + if not df_gleiche_eingaenge.empty: + df_gleiche_eingaenge.to_excel(writer, sheet_name='Duplikate', index=False, startrow=start_row) + start_row += len(df_gleiche_eingaenge) + 2 + # Gleiche Ausgänge + if not df_gleiche_ausgaenge.empty: + df_gleiche_ausgaenge.to_excel(writer, sheet_name='Duplikate', index=False, startrow=start_row, header=False) + + # Fehler in eigenem Sheet + if not df_fehler.empty: + df_fehler.to_excel(writer, sheet_name='Fehler', index=False) + + elif export_type == "WSCAD": + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = dataframes + + # Basis-Template + df_result = pd.DataFrame( + columns=[ + "Anschluss", + "Kommentar", + "Symboltext", + "Bezug", + "Sondertext 1", + "Sondertext 2", + "Sondertext 3", + "Sondertext 4", + "Sondertext 5", + "Sondertext 6", + "Sondertext 7", + "Sondertext 8", + ] + ) + + df_result = self._append_df(df_result, df_fehler) + df_result = self._append_df(df_result, df_eingaenge) + df_result = self._append_df(df_result, df_ausgaenge) + + with pd.ExcelWriter(export_path, engine="openpyxl") as writer: + df_result.to_excel(writer, index=False, sheet_name="WSCAD") + + elif export_type == "WSCAD mit Bezug": + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = dataframes + + # Basis-Template + df_result = pd.DataFrame( + columns=[ + "Anschluss", + "Kommentar", + "Symboltext", + "Bezug", + "Sondertext 1", + "Sondertext 2", + "Sondertext 3", + "Sondertext 4", + "Sondertext 5", + "Sondertext 6", + "Sondertext 7", + "Sondertext 8", + ] + ) + + df_result = self._append_df(df_result, df_fehler) + df_result = self._append_df(df_result, df_eingaenge) + df_result = self._append_df(df_result, df_ausgaenge) + + with pd.ExcelWriter(export_path, engine="openpyxl") as writer: + df_result.to_excel(writer, index=False, sheet_name="WSCAD") + + def _append_df(self, target_df, source_df, include_bezug=False): + """ + Hilfsfunktion, die Dataframes für WSCAD Export aneinander reiht und entweder Bezug inkludiert oder nicht + """ + + data = { + "Anschluss": source_df["Name"], + "Kommentar": source_df["Kommentar"], + "Symboltext": "", + "Bezug": source_df["Bezug"] if include_bezug else "", + "Sondertext 1": "", + "Sondertext 2": "", + "Sondertext 3": "", + "Sondertext 4": "", + "Sondertext 5": "", + "Sondertext 6": "", + "Sondertext 7": "", + "Sondertext 8": "", + } + tmp = pd.DataFrame(data) + return pd.concat([target_df, tmp], ignore_index=True) + class IOConverterApp: def __init__(self, master): self.master = master @@ -886,19 +983,19 @@ class IOConverterApp: fehler = [] if konvertierungsart == "Simpel": - gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.excel_datei_beschreiben_simpel( + gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.prep_simpel( self.selected_txt_file, prefix_eingang, prefix_ausgang, ziel_datei ) elif konvertierungsart == "TIA": - gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.excel_datei_beschreiben_tia( + gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.prep_tia( self.selected_txt_file, prefix_eingang, prefix_ausgang, ziel_datei ) elif konvertierungsart == "WSCAD": - gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.excel_datei_beschreiben_wscad( + gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.prep_wscad( self.selected_txt_file, prefix_eingang, prefix_ausgang, ziel_datei ) elif konvertierungsart == "WSCAD mit Bezug": - gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.excel_datei_beschreiben_wscad_bezug( + gleiche_eingaenge, gleiche_ausgaenge, fehler = self.excel_converter.prep_wscad_bezug( self.selected_txt_file, prefix_eingang, prefix_ausgang, ziel_datei ) @@ -1002,26 +1099,20 @@ def call_portal_to_excel(self, textlines, konvertierungsart): if konvertierungsart == "Simpel": # df_eingaenge, df_ausgaenge wird in excel Datei geschrieben. Hier zu Ausgabe hinzugefügt, damit unittest befüllt werden kann - df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = ec.excel_datei_beschreiben_simpel( + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = ec._prep_simpel( textlines, prefix_eingang, prefix_ausgang ) return df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge elif konvertierungsart == "TIA": # df_consts und df_tags wird in excel Datei geschrieben. Hier zu Ausgabe hinzugefügt, damit unittest befüllt werden kann - df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags = ec.excel_datei_beschreiben_tia( + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags = ec._prep_tia( textlines, prefix_eingang, prefix_ausgang ) return(df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags) - elif konvertierungsart == "WSCAD": - df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = ec.excel_datei_beschreiben_wscad( - textlines, prefix_eingang, prefix_ausgang - ) - return(df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge) - - elif konvertierungsart == "WSCAD mit Bezug": - df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = ec.excel_datei_beschreiben_wscad_bezug( + elif konvertierungsart == "WSCAD" or "WSCAD mit Bezug": + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = ec._prep_wscad( textlines, prefix_eingang, prefix_ausgang ) return(df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge) @@ -1056,10 +1147,10 @@ class TestPlant(unittest.TestCase): df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = call_portal_to_excel(self, textarray, "Simpel") - # print("\n Eingänge:\n") - # print(df_eingaenge) - # print("\n Ausgänge:\n") - # print(df_ausgaenge) + print("\n Eingänge:\n") + print(df_eingaenge) + print("\n Ausgänge:\n") + print(df_ausgaenge) # print("\n gleiche Eingänge:\n") # print(df_gleiche_eingaenge) @@ -1071,6 +1162,8 @@ class TestPlant(unittest.TestCase): self.assertEqual(len(df_eingaenge), 4 - len(df_gleiche_eingaenge)) # 4 Eingänge in Liste, einer davon als Duplikat self.assertEqual(len(df_ausgaenge), 5 - len(df_gleiche_ausgaenge)) # 5 Ausgänge in Liste, einer davon Duplikat + + def test_TIA(self): textlines = """'DI0023','STEUERSPANNUNG 24V UC200 OK','24V OK','=A01+UH000-KF02i','STEUERSPANNUNG 24V UC200 OK','CONTROL VOLTAGE 24V UC200 OK','TENSION DE MANIOBRA 24V UC200 OK','' 'DI0020','STEUERSPANNUNG 24V UH000 OK','24V OK UH000','=A01+UH000-KF02i','STEUERSPANNUNG 24V UH000 OK','CONTROL VOLTAGE 24V UH000 OK','TENSION DE MANIOBRA 24V UH000 OK','' @@ -1124,20 +1217,34 @@ class TestPlant(unittest.TestCase): '''Testcase: 10 Zeilen mit I/O: Davon 4 Eingänge (1 Duplikat), 5 Ausgänge (1 Duplikat), 1 Fehlerhafte BMK''' - df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = call_portal_to_excel(self, textarray, "WSCAD") + df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = call_portal_to_excel(self, textarray, "WSCAD mit Bezug") # print("\n Eingänge:\n") # print(df_eingaenge) # print("\n Ausgänge:\n") # print(df_ausgaenge) + # print("\n Fehler") + # print(df_fehler) + # print("\n gleiche Eingänge:\n") # print(df_gleiche_eingaenge) # print("\n gleiche Ausgänge:\n") # print(df_gleiche_ausgaenge) - def test_WSCAD_bezug(self): - textlines = """'DI0023','STEUERSPANNUNG 24V UC200 OK','24V OK','=A01+UH000-KF02i','STEUERSPANNUNG 24V UC200 OK','CONTROL VOLTAGE 24V UC200 OK','TENSION DE MANIOBRA 24V UC200 OK','' + +if __name__ == "__main__": + + suite = unittest.TestSuite() + suite.addTest(TestPlant('test_Simpel')) + suite.addTest(TestPlant('test_TIA')) + suite.addTest(TestPlant('test_WSCAD')) + + runner = unittest.TextTestRunner() + runner.run(suite) + + export_type_arr = ("Simpel", "TIA", "WSCAD", "WSCAD mit Bezug") # oder "WSCAD", etc. + textlines = """'DI0023','STEUERSPANNUNG 24V UC200 OK','24V OK','=A01+UH000-KF02i','STEUERSPANNUNG 24V UC200 OK','CONTROL VOLTAGE 24V UC200 OK','TENSION DE MANIOBRA 24V UC200 OK','' 'DI0020','STEUERSPANNUNG 24V UH000 OK','24V OK UH000','=A01+UH000-KF02i','STEUERSPANNUNG 24V UH000 OK','CONTROL VOLTAGE 24V UH000 OK','TENSION DE MANIOBRA 24V UH000 OK','' 'DI0020','STEUERSPANNUNG 24V UH000 OK','24V OK UH000','=A01+UH000-KF02i','STEUERSPANNUNG 24V UH000 OK','CONTROL VOLTAGE 24V UH000 OK','TENSION DE MANIOBRA 24V UH000 OK','' 'XY0022','STEUERSPANNUNG 24V UC103 ','24V OK','=A01+UH000-KF02i','STEUERSPANNUNG 24V UC103 OK','CONTROL VOLTAGE 24V UC103 OK','TENSION DE MANIOBRA 24V UC103 OK','' @@ -1148,30 +1255,9 @@ class TestPlant(unittest.TestCase): 'MA1020','ANTRIEB PUFFER F0101 FLYER 1','M-BI-LE','=A01+UC101-KF02Q','','','','' 'MA1020','ANTRIEB PUFFER F0101 FLYER 1','M-BI-LE','=A01+UC101-KF02Q','','','','' """ - textarray = textlines.splitlines() - - '''Testcase: 10 Zeilen mit I/O: Davon 4 Eingänge (1 Duplikat), 5 Ausgänge (1 Duplikat), 1 Fehlerhafte BMK''' + textarray = textlines.splitlines() - df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = call_portal_to_excel(self, textarray, "WSCAD mit Bezug") - - print("\n Eingänge:\n") - print(df_eingaenge) - print("\n Ausgänge:\n") - print(df_ausgaenge) - - print("\n gleiche Eingänge:\n") - print(df_gleiche_eingaenge) - print("\n gleiche Ausgänge:\n") - print(df_gleiche_ausgaenge) - - -if __name__ == "__main__": - - suite = unittest.TestSuite() - suite.addTest(TestPlant('test_Simpel')) - suite.addTest(TestPlant('test_TIA')) - suite.addTest(TestPlant('test_WSCAD')) - suite.addTest(TestPlant('test_WSCAD_bezug')) - - runner = unittest.TextTestRunner() - runner.run(suite) \ No newline at end of file + exporter = ExcelConverter() + for etype in export_type_arr: + export_path = f"work/excel_{etype}.xlsx" + exporter.export_excel(textarray, etype, export_path) \ No newline at end of file