import argparse import os import re import sys from pathlib import Path from configparser import RawConfigParser from fnmatch import fnmatch import ezdxf from openpyxl import Workbook """ Dieses Programm: - liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte ODER liest Texte aus einer TXT Datei (ein Text pro Zeile) - filtert die Texte basierend auf Mustern in translator.cfg - schreibt die Ergebnisse in eine Excel- oder Textdatei - speichert die Ausgabedatei im PROJECT_WORK Verzeichnis """ def check_environment_var(env_str: str) -> Path: """Prüft ob eine Umgebungsvariable gesetzt ist und gibt den Pfad zurück.""" out_path = os.environ.get(env_str) if out_path: return Path(out_path) else: print(f"Umgebungsvariable {env_str} ist nicht gesetzt oder leer.") sys.exit(1) def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]: """ Liest die ignore_pattern aus der translator.cfg Datei. Unterstützt sowohl Wildcard-Muster (*,?) als auch reguläre Ausdrücke. Args: cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG) Returns: Tuple mit (Wildcard-Mustern, Regex-Mustern) """ config_file = cfg_path / "translator.cfg" if not config_file.exists(): print(f"FEHLER: Konfigurationsdatei nicht gefunden: {config_file}") print(f"Bitte erstellen Sie die Datei 'translator.cfg' im Verzeichnis: {cfg_path}") sys.exit(1) # RawConfigParser verhindert dass Backslashes in Regex-Mustern escaped werden config = RawConfigParser() try: config.read(config_file, encoding='utf-8') except Exception as e: print(f"Fehler beim Lesen der Konfigurationsdatei: {e}") sys.exit(1) wildcard_patterns = [] regex_patterns = [] # Lade Wildcard-Muster if config.has_section('ignore_pattern_wildcard'): for key in config['ignore_pattern_wildcard']: pattern = config['ignore_pattern_wildcard'][key].strip() if pattern and not pattern.startswith('#'): wildcard_patterns.append(pattern) # Lade Regex-Muster if config.has_section('ignore_pattern_regex'): for key in config['ignore_pattern_regex']: pattern = config['ignore_pattern_regex'][key].strip() if pattern and not pattern.startswith('#'): regex_patterns.append(pattern) print(f"Geladene Wildcard-Muster: {len(wildcard_patterns)}") for pattern in wildcard_patterns: print(f" - {pattern}") print(f"Geladene Regex-Muster: {len(regex_patterns)}") for pattern in regex_patterns: print(f" - {pattern}") return wildcard_patterns, regex_patterns def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> bool: """ Prüft ob ein Text ignoriert werden soll basierend auf den Mustern. Args: text: Zu prüfender Text wildcard_patterns: Liste der Wildcard-Muster (*,?) regex_patterns: Liste der regulären Ausdrücke debug: Wenn True, gibt Debug-Informationen aus Returns: True wenn Text ignoriert werden soll, False sonst """ # Prüfe Wildcard-Muster for pattern in wildcard_patterns: if fnmatch(text, pattern): if debug: print(f" DEBUG: '{text}' matched wildcard '{pattern}'") return True # Prüfe Regex-Muster for pattern in regex_patterns: try: if re.search(pattern, text): if debug: print(f" DEBUG: '{text}' matched regex '{pattern}'") return True except re.error: # Falls ungültiger Regex, ignoriere dieses Muster print(f"WARNUNG: Ungültiger regulärer Ausdruck: {pattern}") continue return False def extract_text_from_dxf(filename: Path) -> list[str]: """ Extrahiert alle TEXT und MTEXT Objekte aus einer DXF Datei. Verwendet ein Set, um Duplikate zu vermeiden. Filtert leere Texte heraus. Args: filename: Pfad zur DXF Datei Returns: Alphabetisch sortierte Liste aller gefundenen Texte (ohne Duplikate, ohne leere Texte) """ texts_set = set() # Set für eindeutige Texte total_count = 0 empty_count = 0 try: doc = ezdxf.readfile(filename) msp = doc.modelspace() # Sammle alle TEXT Objekte for entity in msp.query('TEXT'): if entity.dxf.text: total_count += 1 text = entity.dxf.text.strip() # Entferne führende/nachfolgende Leerzeichen # Ignoriere leere Texte if not text: empty_count += 1 continue texts_set.add(text) # Sammle alle MTEXT Objekte for entity in msp.query('MTEXT'): if entity.text: total_count += 1 text = entity.text.strip() # Entferne führende/nachfolgende Leerzeichen # Ignoriere leere Texte if not text: empty_count += 1 continue texts_set.add(text) # Konvertiere Set zu sortierter Liste texts = sorted(texts_set) print(f"Anzahl gefundener TEXT/MTEXT Objekte: {total_count}") print(f"Leere Texte ignoriert: {empty_count}") print(f"Duplikate entfernt: {total_count - empty_count - len(texts)}") print(f"Eindeutige Texte extrahiert: {len(texts)}") except Exception as e: print(f"Fehler beim Lesen der DXF Datei: {e}") sys.exit(1) return texts def filter_texts(texts: list[str], wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> tuple[list[str], list[str]]: """ Filtert eine Liste von Texten basierend auf Wildcard- und Regex-Mustern. Diese Funktion ist unabhängig von der Quelle der Texte (DXF oder TXT). Args: texts: Liste der zu filternden Texte wildcard_patterns: Liste der Wildcard-Muster zum Filtern regex_patterns: Liste der Regex-Muster zum Filtern debug: Wenn True, gibt Debug-Informationen aus Returns: Tuple mit (akzeptierte Texte, ignorierte Texte) - beide alphabetisch sortiert """ accepted_texts = [] ignored_texts = [] for text in texts: if should_ignore_text(text, wildcard_patterns, regex_patterns, debug): ignored_texts.append(text) else: accepted_texts.append(text) # Sortiere beide Listen alphabetisch accepted_texts.sort() ignored_texts.sort() print(f"\nFilterung abgeschlossen:") print(f" Akzeptierte Texte: {len(accepted_texts)}") print(f" Ignorierte Texte: {len(ignored_texts)}") return accepted_texts, ignored_texts def read_texts_from_file(filename: Path) -> list[str]: """ Liest Texte aus einer UTF-8 Textdatei (ein Text pro Zeile). Entfernt Duplikate und leere Zeilen. Args: filename: Pfad zur Textdatei Returns: Alphabetisch sortierte Liste aller Texte (ohne Duplikate, ohne leere Zeilen) """ texts_set = set() try: with open(filename, 'r', encoding='utf-8') as f: for line in f: text = line.strip() if text: # Ignoriere leere Zeilen # Konvertiere escaped Zeilenumbrüche zurück text = text.replace('\\n', '\n').replace('\\r', '\r') texts_set.add(text) texts = sorted(texts_set) print(f"Texte aus Datei gelesen: {len(texts)}") return texts except Exception as e: print(f"Fehler beim Lesen der Textdatei: {e}") sys.exit(1) def write_texts_to_excel(texts: list[str], output_path: Path): """ Schreibt die Texte in die erste Spalte einer Excel-Datei. Args: texts: Liste der zu schreibenden Texte output_path: Pfad zur Ausgabe-Excel-Datei """ wb = Workbook() ws = wb.active ws.title = "Texte" # Kopfzeile ws['A1'] = "Text" # Texte in die erste Spalte schreiben for idx, text in enumerate(texts, start=2): ws[f'A{idx}'] = text # Spaltenbreite anpassen ws.column_dimensions['A'].width = 50 try: wb.save(output_path) print(f"Excel-Datei erfolgreich erstellt: {output_path}") except Exception as e: print(f"Fehler beim Schreiben der Excel-Datei: {e}") sys.exit(1) def write_texts_to_textfile(texts: list[str], ignored_texts: list[str], output_path: Path): """ Schreibt die Texte in eine UTF-8 Textdatei. Jeder Text wird in einer eigenen Zeile geschrieben (auch Multiline-Texte). Die Datei enthält zwei Teile: akzeptierte und ignorierte Texte. Args: texts: Liste der akzeptierten Texte ignored_texts: Liste der ignorierten Texte output_path: Pfad zur Ausgabe-Textdatei """ try: with open(output_path, 'w', encoding='utf-8') as f: # Schreibe akzeptierte Texte for text in texts: # Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen) text_single_line = text.replace('\n', '\\n').replace('\r', '\\r') f.write(text_single_line + '\n') # Trennzeile f.write('\n-- durch translator.cfg ausgeschlossen --\n\n') # Schreibe ignorierte Texte for text in ignored_texts: # Multiline-Texte werden in einer Zeile geschrieben (Zeilenumbrüche ersetzen) text_single_line = text.replace('\n', '\\n').replace('\r', '\\r') f.write(text_single_line + '\n') print(f"Textdatei erfolgreich erstellt: {output_path}") print(f" Akzeptierte Texte: {len(texts)}") print(f" Ignorierte Texte: {len(ignored_texts)}") except Exception as e: print(f"Fehler beim Schreiben der Textdatei: {e}") sys.exit(1) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Extrahiert TEXT und MTEXT Objekte aus einer DXF Datei oder filtert Texte aus einer TXT Datei', prog='translate' ) # Mutually exclusive group: entweder DXF-Extraktion oder TXT-Filterung input_group = parser.add_mutually_exclusive_group(required=True) input_group.add_argument( '-e', '--extract', action='store_true', help='Extrahiert Texte aus DXF/DWG Datei (benötigt --filename)' ) input_group.add_argument( '--fromtxt', action='store', metavar='FILE.txt', help='Filtert Texte aus einer TXT Datei (ein Text pro Zeile)' ) parser.add_argument( '-f', '--filename', action='store', help='DXF/DWG Datei (.dxf, .dwg) die verarbeitet werden soll (nur mit --extract)', metavar='myfile.dxf' ) parser.add_argument( '-o', '--outname', action='store', help='Name der Ausgabedatei (ohne Pfad, mit oder ohne .xlsx/.txt Endung)', metavar='output.xlsx' ) parser.add_argument( '-d', '--debug', action='store_true', help='Aktiviert Debug-Ausgabe für Pattern-Matching' ) parser.add_argument( '-t', '--export-type', action='store', default='excel', choices=['excel', 'text'], help='Exportformat: excel (Standard) oder text (UTF-8 Textdatei)' ) args = parser.parse_args() # Validierung: --extract benötigt --filename if args.extract and not args.filename: parser.error("--extract benötigt --filename") # Validierung: --filename nur mit --extract if args.filename and not args.extract: parser.error("--filename kann nur mit --extract verwendet werden") # Prüfe Umgebungsvariablen work_dir = check_environment_var('PROJECT_WORK') cfg_dir = check_environment_var('PROJECT_CFG') # Lade Ignore-Muster aus Konfigurationsdatei wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir) if args.debug: print("DEBUG-Modus aktiviert") # Bestimme Input-Modus und Datei if args.extract: # DXF/DWG-Modus input_filename = args.filename file_extension = input_filename.lower() if not file_extension.endswith(('.dxf', '.dwg')): print("Mit --extract nur .dxf oder .dwg Dateien erlaubt") sys.exit(1) # Versuche zuerst Datei im Work-Ordner zu finden filename = work_dir / input_filename if not filename.exists(): # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist filename = Path(input_filename) if not filename.exists(): print(f"Datei nicht gefunden: {input_filename}") print(f" Geprüft in Work-Ordner: {work_dir / input_filename}") print(f" Geprüft als vollständiger Pfad: {filename}") sys.exit(1) print(f"Lese DXF Datei: {filename}") all_texts = extract_text_from_dxf(filename) else: # args.fromtxt # TXT-Modus input_filename = args.fromtxt if not input_filename.lower().endswith('.txt'): print("Mit --fromtxt nur .txt Dateien erlaubt") sys.exit(1) # Versuche zuerst Datei im Work-Ordner zu finden filename = work_dir / input_filename if not filename.exists(): # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist filename = Path(input_filename) if not filename.exists(): print(f"Datei nicht gefunden: {input_filename}") print(f" Geprüft in Work-Ordner: {work_dir / input_filename}") print(f" Geprüft als vollständiger Pfad: {filename}") sys.exit(1) print(f"Lese Textdatei: {filename}") all_texts = read_texts_from_file(filename) # Schritt 2: Texte filtern texts, ignored_texts = filter_texts(all_texts, wildcard_patterns, regex_patterns, args.debug) # Erstelle Output-Dateinamen basierend auf Export-Typ if args.outname: # Verwende benutzerdefinierten Namen output_filename = args.outname # Füge Endung hinzu falls nicht vorhanden if args.export_type == 'excel': if not output_filename.endswith('.xlsx'): output_filename += '.xlsx' else: # text if not output_filename.endswith('.txt'): output_filename += '.txt' else: # Standardname basierend auf Eingabedatei if args.export_type == 'excel': output_filename = filename.stem + "_texts.xlsx" else: # text output_filename = filename.stem + "_texts.txt" output_path = work_dir / output_filename # Schreibe Texte in gewähltem Format if args.export_type == 'excel': write_texts_to_excel(texts, output_path) else: # text write_texts_to_textfile(texts, ignored_texts, output_path) print("Verarbeitung erfolgreich abgeschlossen.")