Files
kabellaengen/lib/translate.py
T

455 lines
15 KiB
Python

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.")