From 64408157baad7a6af5c0980e998e4ff6efbeade1 Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Sat, 21 Mar 2026 15:25:10 +0100 Subject: [PATCH] Docs: AGENTS.md mit CLAUDE.md konsolidiert und Einzeldatei entfernt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code-Style-Richtlinien (Imports, Type Annotations, Naming, Logging, Docstrings), UI-Import-Pattern, Thread-Pattern, RAM-Optimierung und Test-Infos aus AGENTS.md übernommen. Veraltete Einträge korrigiert (qdarktheme entfernt, _execute_sql_query → DatabaseQueryThread, XslDependencyDialog dokumentiert). Co-Authored-By: Claude Opus 4.6 --- AGENTS.md | 365 ------------------------------------- CLAUDE.md | 534 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 350 insertions(+), 549 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 8c5c268..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,365 +0,0 @@ -# AGENTS.md - -**Entwicklerrichtlinien für DocuMentor (KI-Coding-Agenten)** - -Diese Datei enthält wichtige Richtlinien für Coding-Agenten, die in diesem Repository arbeiten. - -## Sprache - -**Alle Kommunikation, Code-Kommentare und UI-Texte auf Deutsch!** -- Kommentare: Deutsch -- Docstrings: Deutsch -- Log-Meldungen: Deutsch -- Variablennamen: Deutsch wo kontextuell passend -- UI-Labels: Deutsch - -## Build & Lint Kommandos - -### Paketmanager: `uv` (NICHT pip oder poetry!) - -```bash -# Abhängigkeiten installieren -uv sync - -# Anwendung starten -uv run python src/main.py - -# Code-Style prüfen (Zeilenlänge: 120) -uv run ruff check - -# Code automatisch formatieren -uv run ruff format -``` - -### Tests ausführen - -```bash -# Alle Hash-Tests -uv run python test_hash_implementation.py - -# Duplikatserkennung testen -uv run python test_xml_hash_duplicate_detection.py - -# Einzelnen Test ausführen (direkter Python-Aufruf) -uv run python test_hash_implementation.py -``` - -**Hinweis:** Dieses Projekt verwendet KEINE pytest/unittest-Frameworks. Tests sind standalone Python-Skripte mit if __name__ == "__main__". - -## Code-Style-Richtlinien - -### Import-Organisation - -**Reihenfolge (keine Leerzeilen zwischen Gruppen):** - -```python -# 1. Standard Library -import os -import sys -import logging -from pathlib import Path -from typing import Optional, TYPE_CHECKING - -# 2. Drittanbieter (Third-Party) -from PySide6.QtCore import Qt, QThread, Signal -from PySide6.QtWidgets import QDialog, QMainWindow -from pydantic import BaseModel, Field - -# 3. Lokale Imports (IMMER absolute Imports, KEINE relativen .imports) -from conf import app_settings, TreeNode, XslFile -from ui.MainWindow import MainWindow -from ui.JavaVmConfigDialog_ui import Ui_JavaVmConfigDialog -``` - -**Wichtig:** -- Immer `from pathlib import Path` verwenden -- NIEMALS String-Pfade verwenden, IMMER `Path`-Objekte -- `TYPE_CHECKING` für zirkuläre Import-Vermeidung nutzen -- Keine relativen Imports (`.` oder `..`) - -### Type Annotations - -**Moderne Union-Syntax verwenden:** - -```python -# RICHTIG -def transform(xml_path: Path, params: dict[str, str]) -> tuple[bool, str]: - result: str | None = None - files: list[Path] = [] - -# FALSCH -def transform(xml_path, params): # Keine Annotations - result: Optional[str] = None # Alte Union-Syntax - files: List[Path] = [] # Großgeschriebene Types -``` - -**Pflicht:** -- Alle Funktionsparameter annotieren -- Alle Rückgabewerte annotieren -- Moderne Syntax: `str | None` statt `Optional[str]` -- Container: `list[str]`, `dict[str, str]`, `tuple[int, int]` - -### Naming Conventions - -```python -# Klassen: PascalCase -class SaxonWorkerPool: -class TransformationJob: - -# Funktionen/Methoden: snake_case -def transform_saxon(xml_file: Path) -> bool: -def calculate_hash(content: bytes) -> str: - -# Private Methoden: _snake_case mit Unterstrich -def _create_tree_item(self, node: TreeNode): -def _load_project_data(self): - -# Variablen: snake_case -xml_file_path = Path("test.xml") -diff_pdf_count = 0 -self.current_zoom = 100 - -# Konstanten: UPPER_CASE -SAXON_WORKER_JAVA = """...""" -MAX_RETRY_COUNT = 3 -``` - -### Formatierung & Linting - -- **Zeilenlänge:** 120 Zeichen (via Ruff konfiguriert) -- **Strings:** Bevorzugt Double-Quotes `"..."`, aber konsistent im File -- **Trailing Commas:** Bei mehrzeiligen Strukturen verwenden -- **Ruff:** Alle Warnings beheben vor Commit - -### Error Handling - -**IMMER Logging statt print() verwenden:** - -```python -import logging - -logger = logging.getLogger(__name__) - -def transform(xml_path: Path) -> tuple[bool, str]: - try: - # Operation durchführen - logger.info(f"Transformation gestartet: {xml_path}") - result = do_transform(xml_path) - logger.debug(f"Zwischenergebnis: {result}") - return True, "Erfolg" - - except FileNotFoundError as e: - error_msg = f"XML-Datei nicht gefunden: {xml_path}" - logger.error(error_msg) - return False, error_msg - - except Exception as e: - error_msg = f"Fehler bei Transformation: {str(e)}" - logger.exception(error_msg) # Mit Stack Trace - return False, error_msg -``` - -**Pattern:** -- `logger.debug()` für Debugging-Infos -- `logger.info()` für normale Operationen -- `logger.warning()` für Warnungen -- `logger.error()` für Fehler ohne Stack Trace -- `logger.exception()` für Fehler MIT Stack Trace -- Fehlermeldungen auf Deutsch - -### Docstrings - -**Google-Style auf Deutsch:** - -```python -def transform_xml_to_pdf(xml_path: Path, xsl_path: Path, output_dir: Path) -> tuple[bool, str]: - """ - Transformiert eine XML-Datei mit XSL zu PDF. - - Args: - xml_path: Pfad zur XML-Eingabedatei - xsl_path: Pfad zum XSL-Stylesheet - output_dir: Zielverzeichnis für PDF-Ausgabe - - Returns: - tuple[bool, str]: (Erfolg, Fehlermeldung oder Info-Text) - - Raises: - FileNotFoundError: Wenn XML- oder XSL-Datei nicht existiert - """ -``` - -**Modul-Docstrings am Dateianfang:** - -```python -""" -Saxon Worker Pool - Persistente JVM-Prozesse für schnelle XSLT-Transformationen. - -Eliminiert JVM-Startup-Overhead durch Vorinitialisierung von N Worker-Prozessen. -Verwendet multiprocessing.Queue für Thread-sichere Kommunikation. -""" -``` - -## Pydantic Models - -### Definition - -```python -from pydantic import BaseModel, Field - -class Project(BaseModel): - id: int = Field(..., description="Eindeutige Projekt-ID", gt=0) - name: str = Field(..., description="Projekt-Name", min_length=1, max_length=255) - project_dir: Path = Field(..., description="Pfad zum Projekt-Verzeichnis") - - # Helper-Methoden direkt im Model erlaubt - def getJavaVm(self) -> str: - global app_settings - value = [x.version for x in app_settings.java_vms if x.id == self.java_vm_id] - return value[0] if len(value) else "" -``` - -### Settings speichern - -```python -from conf import app_settings, ProjectData - -# Globale Einstellungen -app_settings.theme = "Fusion" -app_settings.save() # WICHTIG: Nicht vergessen! - -# Projekteinstellungen -project_data = ProjectData.readSettings(project_dir) -project_data.nodes.append(new_node) -project_data.writeSettings(project_dir) # WICHTIG: Persistieren! -``` - -## PySide6 UI-Integration - -### KRITISCH: UI-Dateien nicht manuell bearbeiten! - -``` -src/ui/ -├── MainWindow.ui # Qt Designer Datei (editieren erlaubt) -├── MainWinddow_ui.py # AUTO-GENERIERT (NICHT BEARBEITEN!) -└── MainWindow.py # Implementierung (hier Code schreiben) -``` - -### UI-Import-Pattern - -```python -# In src/ui/JavaVmConfigDialog.py -from PySide6.QtWidgets import QDialog -from ui.JavaVmConfigDialog_ui import Ui_JavaVmConfigDialog - -class JavaVmConfigDialog(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - - # UI einrichten - self.ui = Ui_JavaVmConfigDialog() - self.ui.setupUi(self) - - # Signale NACH setupUi() verbinden - self.ui.browseButton.clicked.connect(self._browse_file) - - def _browse_file(self): - # Widgets über self.ui.widgetName zugreifen - current_path = self.ui.pathLineEdit.text() - ... -``` - -**Wichtig:** -- UI-Klassen NIEMALS direkt erben, nur als `self.ui` Member -- Alle Widgets über `self.ui.widgetName` zugreifen -- Signal-Verbindungen immer NACH `setupUi()` aufrufen - -## Projektstruktur-Änderungen - -Beim Modifizieren der Baumstruktur (TreeNode, XslFile, XmlFile): - -```python -# 1. ProjectData modifizieren -self.project_data.nodes.append(new_node) - -# 2. SOFORT persistieren -self.project_data.writeSettings(self.project.project_dir) - -# 3. UI neu laden -self._load_nodes_to_tree() -``` - -**Pattern:** Immer in dieser Reihenfolge: Modifizieren → Speichern → UI aktualisieren - -## Wichtige Konventionen - -### Pathlib IMMER verwenden - -```python -# RICHTIG -from pathlib import Path - -xml_path = Path("data/test.xml") -xml_path = Path.home() / ".config" / "app" / "config.json" -xml_path = Path(os.path.expandvars("$HOME/data")).expanduser() - -if xml_path.exists(): - content = xml_path.read_text(encoding="utf-8") - -# FALSCH -xml_path = "data/test.xml" # String-Pfad -xml_path = os.path.join("data", "test.xml") # os.path statt pathlib -``` - -### Globale Singletons - -```python -# In conf.py am Modulende -app_settings = AppSettings() - -# In anderen Modulen -from conf import app_settings - -# Verwendung -java_vm = [x for x in app_settings.java_vms if x.id == vm_id][0] -``` - -### Thread-basierte Operationen - -```python -from PySide6.QtCore import QThread, Signal - -class HashCalculatorThread(QThread): - # Signale für Thread-sichere Kommunikation - progress = Signal(int) - finished = Signal(dict) - - def __init__(self, files: list[Path]): - super().__init__() - self.files = files - - def run(self): - for i, file_path in enumerate(self.files): - hash_value = calculate_hash(file_path) - self.progress.emit(i + 1) - self.finished.emit(results) - -# Verwendung -thread = HashCalculatorThread(xml_files) -thread.progress.connect(self._on_progress) -thread.finished.connect(self._on_finished) -thread.start() # NICHT run() direkt aufrufen! -``` - -## RAM-Optimierung - -Da DocuMentor permanent läuft, sparsam mit RAM umgehen: - -- Worker-Pools nach Verwendung herunterfahren -- Große Datenstrukturen frühzeitig freigeben -- Polars DataFrames statt Pandas (geringerer RAM-Verbrauch) -- Lazy Loading wo möglich - ---- - -**Zusammenfassung:** Deutsch sprechen, pathlib verwenden, Typen annotieren, Ruff nutzen, UI-Dateien nicht anfassen! diff --git a/CLAUDE.md b/CLAUDE.md index cb3b505..609d8d7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,184 +1,350 @@ -# CLAUDE.md - -Spreche mit mir auf Deutsch! (Communicate with me in German!) - -## Projektübersicht - -DocuMentor (ehemals xsl-validator) ist eine PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit XML-Dateien. Sie bietet eine GUI zur Konfiguration von Transformations-Toolchains (Saxon, Apache FOP, diff-pdf) und zur Verwaltung von PDF-Generierungsprojekten mit PostgreSQL-Datenbankintegration. - -## Anvisiertes Nutzungsszenario -Der primäre Einsatz ist die kontinuierliche Weiterentwicklung von PDF-Dokumenten in Flexnow (Software zur Prüfungsverwaltung). Dabei handelt es sich beispielsweise um amtliche Urkunden, Zeugnisse und Bescheide. - -Die Basis bilden etwa 100 XSL-Dateien. Die meisten sind mittels `` bzw. `` miteinander verknüpft (ähnlich der Klassen-Vererbung). Daher können sich Änderungen in einer XSL-Datei auf (unerwartet) viele andere auswirken. Um diese Auswirkungen im Auge zu behalten, wird DocuMentor entwickelt. - -**Typischer Workflow:** -1. Entwickler führt benötigte Änderungen an den XSL-Dateien durch -2. Entwickler startet die Transformation im DocuMentor und begutachtet die generierte PDF-Diff -3. Prüfung: Wurden die richtigen PDF-Dateien geändert? -4. Prüfung: Hat die Änderung der XSL-Dateien die erhoffte Änderung in den PDF-Dateien ergeben? - -Diese Schritte können sich mehrfach wiederholen. - -Da der DocuMentor permanent im Hintergrund läuft, ist ein sparsamer Umgang mit RAM wichtig. - -## PySide6-GUI -- Beim Erstellen neuer Dialoge und Fenster sollte immer eine entsprechende UI-Datei erstellt werden -- Der Entwickler sollte später in der Lage sein, den neuen Dialog bzw. Fenster über diese UI-Datei zu gestalten -- Aus der UI-Datei wird in Visual Studio Code über eine Erweiterung automatisch eine .py-Datei erzeugt -- Die automatisch generierte .py-Datei muss in den Code eingebunden und verwendet werden - -## Entwicklungskommandos - -### Paketverwaltung -Dieses Projekt verwendet den `uv` Paketmanager (nicht pip oder poetry): -```bash -uv sync # Abhängigkeiten installieren -uv run python src/main.py # Anwendung starten -uv run python test_hash_implementation.py # Hash-Tests ausführen -``` - -### Linting -```bash -uv run ruff check # Code-Style prüfen (Zeilenlänge: 120) -uv run ruff format # Code formatieren -``` - -## Architektur - -### Konfigurationssystem (src/conf.py) - -Die Anwendung verwendet ein zentralisiertes Konfigurationsmodell mit Pydantic: - -- **AppSettings**: Globales Singleton (`app_settings`), das die gesamte Anwendungskonfiguration speichert - - Wird an plattformspezifischen Orten gespeichert: - - Linux: `~/.config/DocuMentor/config.json` - - Windows: `%APPDATA%\DocuMentor\config.json` - - macOS: `~/Library/Application Support/DocuMentor/config.json` - - Enthält Listen von Tools: `java_vms`, `saxon_jars`, `apache_fops`, `diff_pdfs`, `xsl_dirs`, `postgresql_dbs` - -- **ProjectData**: Projektspezifische Einstellungen, die in `project.yaml` im jeweiligen Projektverzeichnis gespeichert werden - - Enthält hierarchische Baumstruktur von Transformationsknoten - - Verwendet `TreeNode` und `XslFile` zur Organisation - - Jede `XmlFile` hat eine optionale `hashsum` (blake2b) zur Änderungsverfolgung - -### Wichtige Datenmodelle - -1. **Tool-Konfigurationsmodelle** (JavaVm, SaxonJar, ApacheFop, DiffPdf, XslDir, PostgreSqlDb): - - Jedes hat eine `id` und `version` - - Speichert Pfade zu Binärdateien/Verzeichnissen - -2. **Project-Modell**: - - Referenziert Tool-Konfigurationen über ID - - Verlinkt zu einem Projektverzeichnis mit `project.yaml` - - Hat Hilfsmethoden wie `getXsl()`, `getJavaVm()` um IDs in Namen/Versionen aufzulösen - -3. **Baumstruktur** (TreeNode → XslFile → XmlFile): - - Hierarchische Organisation von Transformations-Workflows - - `TreeNode`: Organisationseinheit mit `xslt_params` und Kindknoten/-dateien - - `XslFile`: XSL-Stylesheet mit zugehörigen XML-Dateien und XSLT-Parametern - - `XmlFile`: XML-Eingabedatei mit optionalem blake2b-Hash - -### UI-Architektur (src/ui/) - -Die Anwendung folgt einem spezifischen PySide6-Muster: - -1. **UI-Definitionsdateien** (`*_ui.py`): Automatisch generiert aus UI-Designer-Dateien - - Diese Dateien definieren die UI-Struktur als Klassen (z.B. `Ui_MainWindow`) - - Sollten NICHT manuell bearbeitet werden - -2. **Implementierungsdateien** (ohne `_ui` Suffix): Tatsächliche Dialog-/Fenster-Implementierungen - - Importieren und verwenden die entsprechende `*_ui.py` Datei - - Enthalten Business-Logik und Signal/Slot-Verbindungen - - Beispiel: `MainWindow.py` verwendet `Ui_MainWindow` aus `MainWinddow_ui.py` - -Beim Erstellen neuer Dialoge: -- Immer zuerst eine entsprechende UI-Datei erstellen -- Die UI-Datei wird automatisch als `.py`-Datei von einer VS Code Extension generiert -- Die generierte UI-Klasse in der Implementierungsdatei importieren und verwenden - -### Hauptfenster (src/ui/MainWindow.py) - -Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten: - -1. **Projektverwaltung**: - - Öffnet und verwaltet PDF-Transformationsprojekte - - Lädt/speichert `ProjectData` aus `project.yaml` Dateien - -2. **Tree Widget**: Zeigt hierarchische Struktur von Transformationsknoten an - - Kontextmenüs zum Hinzufügen/Bearbeiten/Löschen von Knoten, XSL-Dateien und XML-Dateien - - Drag-and-Drop-Unterstützung für XML-Dateien - -3. **PDF-Vergleichsansicht**: - - Drei-Panel-Ansicht (Referenz, Diff, Neu) - - Alpha-Blending für visuellen Vergleich - - Zoom- und Pan-Funktionalität - -4. **Asynchrone Operationen**: - - `XmlHashCalculatorThread`: Hintergrund-blake2b-Hash-Berechnung für XML-Dateien - - `DatabaseTestThread` (in PostgreSqlConfigDialog): Asynchrones Testen von Datenbankverbindungen - -### Hash-Berechnungssystem - -Die Anwendung verwendet blake2b-Hashing zur Verfolgung von XML-Dateiänderungen: - -- **Automatisch**: Hashes werden berechnet, wenn Projekte geladen werden (nur für Dateien ohne existierenden Hash) -- **Asynchron**: Hintergrund-Thread (`XmlHashCalculatorThread`) um die UI reaktionsfähig zu halten -- **Format**: `blake2b:<64-Zeichen-Hexdigest>` -- **Speicherung**: Persistiert in `project.yaml` innerhalb jedes `XmlFile`-Objekts -- **Details**: Siehe `docs/blake2b_hash_implementation.md` - -### Theme-System - -Die Anwendung unterstützt mehrere Qt-Themes: -- Theme-Auswahlmenü wird dynamisch aus `QStyleFactory.keys()` befüllt -- Theme-Präferenz wird in `AppSettings.theme` gespeichert -- Dark-Theme-Unterstützung via `qdarktheme` Paket (aktuell in main.py auskommentiert) - -### Datenbankintegration - -PostgreSQL-Integration mit Polars und ConnectorX: -- Konfiguration wird im `PostgreSqlDb`-Modell mit SSL-Modus-Unterstützung gespeichert -- SQL-Abfragen werden via `_execute_sql_query()` im MainWindow ausgeführt -- Ergebnisse werden in Polars DataFrames geladen - -## Wichtige Konventionen - -### Deutsche Sprache -Die Codebasis verwendet Deutsch für: -- UI-Texte und Labels -- Kommentare und Dokumentation -- Variablennamen wo kontextuell passend -- Log-Meldungen - -### Pfadbehandlung -- Immer `pathlib.Path`-Objekte verwenden, keine Strings -- `expanduser()` und `expandvars()` für Benutzer-/Umgebungspfade verwenden -- Projektrelative Pfade werden als relativ gespeichert, zur Laufzeit gegen `project_dir` aufgelöst - -### ID-basierte Lookups -Konfigurationsentitäten (Tools, Datenbanken) werden in Projekten über ID referenziert. Die Hilfsmethoden des `Project`-Modells (`getXsl()`, `getJavaVm()`, etc.) verwenden, um IDs in Anzeigewerte aufzulösen. - -### Einstellungspersistenz -- Globale Einstellungen: `app_settings.save()` nach Änderungen aufrufen -- Projekteinstellungen: `project_data.writeSettings(project_dir)` nach Änderungen aufrufen - -## Arbeiten mit der Codebasis - -### Neue Tool-Konfigurationen hinzufügen -1. Modell zu `conf.py` hinzufügen (ähnlich wie `JavaVm`, `SaxonJar`) -2. Listenfeld zu `AppSettings` hinzufügen -3. Konfigurationsdialog in `src/ui/` erstellen (UI-Datei + Implementierung) -4. Zu `AppSettings.py` Tabs hinzufügen -5. `Project`-Modell aktualisieren, falls das Tool projektspezifisch sein soll - -### Neue Baumoperationen hinzufügen -1. Aktion zum Kontextmenü in `_create_context_menu_for_type()` hinzufügen -2. Handler-Methode implementieren nach Namensschema `_action_tree_node()`, `_action_xsl_file()`, etc. -3. Baum nach Änderungen mit `_load_nodes_to_tree()` aktualisieren -4. `self.project_data.writeSettings(self.project.project_dir)` aufrufen um Änderungen zu persistieren - -### Projektstruktur modifizieren -Das `ProjectData`-Modell ist die Quelle der Wahrheit. Alle Änderungen an der Baumstruktur müssen: -1. Die `project_data.nodes` Liste modifizieren -2. `project_data.writeSettings()` aufrufen um zu persistieren -3. Baum mit `_load_nodes_to_tree()` neu laden um Änderungen in der UI zu reflektieren +# CLAUDE.md + +Spreche mit mir auf Deutsch! (Communicate with me in German!) + +## Projektübersicht + +DocuMentor (ehemals xsl-validator) ist eine PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit XML-Dateien. Sie bietet eine GUI zur Konfiguration von Transformations-Toolchains (Saxon, Apache FOP, diff-pdf) und zur Verwaltung von PDF-Generierungsprojekten mit PostgreSQL-Datenbankintegration. + +## Anvisiertes Nutzungsszenario +Der primäre Einsatz ist die kontinuierliche Weiterentwicklung von PDF-Dokumenten in Flexnow (Software zur Prüfungsverwaltung). Dabei handelt es sich beispielsweise um amtliche Urkunden, Zeugnisse und Bescheide. + +Die Basis bilden etwa 100 XSL-Dateien. Die meisten sind mittels `` bzw. `` miteinander verknüpft (ähnlich der Klassen-Vererbung). Daher können sich Änderungen in einer XSL-Datei auf (unerwartet) viele andere auswirken. Um diese Auswirkungen im Auge zu behalten, wird DocuMentor entwickelt. + +**Typischer Workflow:** +1. Entwickler führt benötigte Änderungen an den XSL-Dateien durch +2. Entwickler startet die Transformation im DocuMentor und begutachtet die generierte PDF-Diff +3. Prüfung: Wurden die richtigen PDF-Dateien geändert? +4. Prüfung: Hat die Änderung der XSL-Dateien die erhoffte Änderung in den PDF-Dateien ergeben? + +Diese Schritte können sich mehrfach wiederholen. + +Da der DocuMentor permanent im Hintergrund läuft, ist ein sparsamer Umgang mit RAM wichtig: +- Worker-Pools nach Verwendung herunterfahren +- Große Datenstrukturen frühzeitig freigeben +- Polars DataFrames statt Pandas (geringerer RAM-Verbrauch) +- Lazy Loading wo möglich + +## Entwicklungskommandos + +### Paketverwaltung +Dieses Projekt verwendet den `uv` Paketmanager (nicht pip oder poetry): +```bash +uv sync # Abhängigkeiten installieren +uv run python src/main.py # Anwendung starten +``` + +### Linting +```bash +uv run ruff check # Code-Style prüfen (Zeilenlänge: 120) +uv run ruff format # Code formatieren +``` + +### Tests +Dieses Projekt verwendet KEINE pytest/unittest-Frameworks. Tests sind standalone Python-Skripte: +```bash +uv run python test_hash_implementation.py # Hash-Tests +uv run python test_xml_hash_duplicate_detection.py # Duplikatserkennung +``` + +## Code-Style-Richtlinien + +### Import-Organisation + +Reihenfolge (keine Leerzeilen zwischen Gruppen): +```python +# 1. Standard Library +import os +import sys +import logging +from pathlib import Path +from typing import TYPE_CHECKING + +# 2. Drittanbieter +from PySide6.QtCore import Qt, QThread, Signal +from PySide6.QtWidgets import QDialog, QMainWindow +from pydantic import BaseModel, Field + +# 3. Lokale Imports (IMMER absolute Imports, KEINE relativen .imports) +from conf import app_settings, TreeNode, XslFile +from ui.MainWindow import MainWindow +``` + +- `TYPE_CHECKING` für zirkuläre Import-Vermeidung nutzen +- Keine relativen Imports (`.` oder `..`) + +### Type Annotations + +Moderne Union-Syntax verwenden: +```python +# RICHTIG +def transform(xml_path: Path, params: dict[str, str]) -> tuple[bool, str]: + result: str | None = None + files: list[Path] = [] + +# FALSCH +def transform(xml_path, params): # Keine Annotations + result: Optional[str] = None # Alte Union-Syntax + files: List[Path] = [] # Großgeschriebene Types +``` + +### Naming Conventions + +```python +# Klassen: PascalCase +class SaxonWorkerPool: + +# Funktionen/Methoden: snake_case +def transform_saxon(xml_file: Path) -> bool: + +# Private Methoden: _snake_case mit Unterstrich +def _create_tree_item(self, node: TreeNode): + +# Konstanten: UPPER_CASE +SAXON_WORKER_JAVA = """...""" +``` + +### Formatierung +- **Zeilenlänge:** 120 Zeichen (via Ruff konfiguriert) +- **Strings:** Bevorzugt Double-Quotes `"..."`, aber konsistent im File +- **Trailing Commas:** Bei mehrzeiligen Strukturen verwenden + +### Error Handling + +IMMER Logging statt `print()` verwenden: +```python +import logging +logger = logging.getLogger(__name__) + +def transform(xml_path: Path) -> tuple[bool, str]: + try: + logger.info(f"Transformation gestartet: {xml_path}") + result = do_transform(xml_path) + return True, "Erfolg" + except FileNotFoundError as e: + error_msg = f"XML-Datei nicht gefunden: {xml_path}" + logger.error(error_msg) + return False, error_msg + except Exception as e: + error_msg = f"Fehler bei Transformation: {str(e)}" + logger.exception(error_msg) # Mit Stack Trace + return False, error_msg +``` + +- `logger.debug()` für Debugging-Infos +- `logger.info()` für normale Operationen +- `logger.warning()` für Warnungen +- `logger.error()` für Fehler ohne Stack Trace +- `logger.exception()` für Fehler MIT Stack Trace +- Fehlermeldungen auf Deutsch + +### Docstrings + +Google-Style auf Deutsch: +```python +def transform_xml_to_pdf(xml_path: Path, xsl_path: Path, output_dir: Path) -> tuple[bool, str]: + """ + Transformiert eine XML-Datei mit XSL zu PDF. + + Args: + xml_path: Pfad zur XML-Eingabedatei + xsl_path: Pfad zum XSL-Stylesheet + output_dir: Zielverzeichnis für PDF-Ausgabe + + Returns: + tuple[bool, str]: (Erfolg, Fehlermeldung oder Info-Text) + + Raises: + FileNotFoundError: Wenn XML- oder XSL-Datei nicht existiert + """ +``` + +### Pfadbehandlung +- Immer `pathlib.Path`-Objekte verwenden, keine Strings +- `expanduser()` und `expandvars()` für Benutzer-/Umgebungspfade verwenden +- Projektrelative Pfade werden als relativ gespeichert, zur Laufzeit gegen `project_dir` aufgelöst + +## Architektur + +### Konfigurationssystem (src/conf.py) + +Die Anwendung verwendet ein zentralisiertes Konfigurationsmodell mit Pydantic: + +- **AppSettings**: Globales Singleton (`app_settings`), das die gesamte Anwendungskonfiguration speichert + - Wird an plattformspezifischen Orten gespeichert: + - Linux: `~/.config/DocuMentor/config.json` + - Windows: `%APPDATA%\DocuMentor\config.json` + - macOS: `~/Library/Application Support/DocuMentor/config.json` + - Enthält Listen von Tools: `java_vms`, `saxon_jars`, `apache_fops`, `diff_pdfs`, `xsl_dirs`, `postgresql_dbs` + +- **ProjectData**: Projektspezifische Einstellungen, die in `project.yaml` im jeweiligen Projektverzeichnis gespeichert werden + - Enthält hierarchische Baumstruktur von Transformationsknoten + - Verwendet `TreeNode` und `XslFile` zur Organisation + - Jede `XmlFile` hat eine optionale `hashsum` (blake2b) zur Änderungsverfolgung + +### Wichtige Datenmodelle + +1. **Tool-Konfigurationsmodelle** (JavaVm, SaxonJar, ApacheFop, DiffPdf, XslDir, PostgreSqlDb): + - Jedes hat eine `id` und `version` + - Speichert Pfade zu Binärdateien/Verzeichnissen + +2. **Project-Modell**: + - Referenziert Tool-Konfigurationen über ID + - Verlinkt zu einem Projektverzeichnis mit `project.yaml` + - Hat Hilfsmethoden wie `getXsl()`, `getJavaVm()` um IDs in Namen/Versionen aufzulösen + +3. **Baumstruktur** (TreeNode → XslFile → XmlFile): + - Hierarchische Organisation von Transformations-Workflows + - `TreeNode`: Organisationseinheit mit `xslt_params` und Kindknoten/-dateien + - `XslFile`: XSL-Stylesheet mit zugehörigen XML-Dateien und XSLT-Parametern + - `XmlFile`: XML-Eingabedatei mit optionalem blake2b-Hash + +### UI-Architektur (src/ui/) + +Die Anwendung folgt einem spezifischen PySide6-Muster: + +1. **UI-Definitionsdateien** (`*_ui.py`): Automatisch generiert aus UI-Designer-Dateien + - Diese Dateien definieren die UI-Struktur als Klassen (z.B. `Ui_MainWindow`) + - Sollten NICHT manuell bearbeitet werden + +2. **Implementierungsdateien** (ohne `_ui` Suffix): Tatsächliche Dialog-/Fenster-Implementierungen + - Importieren und verwenden die entsprechende `*_ui.py` Datei + - Enthalten Business-Logik und Signal/Slot-Verbindungen + - Beispiel: `MainWindow.py` verwendet `Ui_MainWindow` aus `MainWinddow_ui.py` + +Beim Erstellen neuer Dialoge: +- Immer zuerst eine entsprechende UI-Datei erstellen +- Die UI-Datei wird automatisch als `.py`-Datei von einer VS Code Extension generiert +- Die generierte UI-Klasse in der Implementierungsdatei importieren und verwenden + +**UI-Import-Pattern:** +```python +from PySide6.QtWidgets import QDialog +from ui.JavaVmConfigDialog_ui import Ui_JavaVmConfigDialog + +class JavaVmConfigDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.ui = Ui_JavaVmConfigDialog() + self.ui.setupUi(self) + # Signale NACH setupUi() verbinden + self.ui.browseButton.clicked.connect(self._browse_file) +``` + +- UI-Klassen NIEMALS direkt erben, nur als `self.ui` Member +- Alle Widgets über `self.ui.widgetName` zugreifen +- Signal-Verbindungen immer NACH `setupUi()` aufrufen + +### Hauptfenster (src/ui/MainWindow.py) + +Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten: + +1. **Projektverwaltung**: + - Öffnet und verwaltet PDF-Transformationsprojekte + - Lädt/speichert `ProjectData` aus `project.yaml` Dateien + +2. **Tree Widget**: Zeigt hierarchische Struktur von Transformationsknoten an + - Kontextmenüs zum Hinzufügen/Bearbeiten/Löschen von Knoten, XSL-Dateien und XML-Dateien + - Drag-and-Drop-Unterstützung für XML-Dateien + +3. **PDF-Vergleichsansicht**: + - Drei-Panel-Ansicht (Referenz, Diff, Neu) + - Alpha-Blending für visuellen Vergleich + - Zoom- und Pan-Funktionalität + +4. **Asynchrone Operationen**: + - `XmlHashCalculatorThread`: Hintergrund-blake2b-Hash-Berechnung für XML-Dateien + - `DatabaseTestThread` (in PostgreSqlConfigDialog): Asynchrones Testen von Datenbankverbindungen + +### XSL-Abhängigkeitsgraph (src/ui/XslDependencyDialog.py) + +Interaktiver Dialog zur Visualisierung von ``- und ``-Abhängigkeiten zwischen XSL-Dateien: +- Sidebar mit Suchfilter zur Navigation +- Abhängigkeitsgraph-Darstellung via vis.js +- Parsing der XSL-Dateien mit lxml + +### Hash-Berechnungssystem + +Die Anwendung verwendet blake2b-Hashing zur Verfolgung von XML-Dateiänderungen: + +- **Automatisch**: Hashes werden berechnet, wenn Projekte geladen werden (nur für Dateien ohne existierenden Hash) +- **Asynchron**: Hintergrund-Thread (`XmlHashCalculatorThread`) um die UI reaktionsfähig zu halten +- **Format**: `blake2b:<64-Zeichen-Hexdigest>` +- **Speicherung**: Persistiert in `project.yaml` innerhalb jedes `XmlFile`-Objekts +- **Details**: Siehe `docs/blake2b_hash_implementation.md` + +### Theme-System + +Die Anwendung unterstützt mehrere Qt-Themes: +- Theme-Auswahlmenü wird dynamisch aus `QStyleFactory.keys()` befüllt +- Theme-Präferenz wird in `AppSettings.theme` gespeichert + +### Datenbankintegration + +PostgreSQL-Integration mit Polars und ConnectorX: +- Konfiguration wird im `PostgreSqlDb`-Modell mit SSL-Modus-Unterstützung gespeichert +- SQL-Abfragen werden asynchron via `DatabaseQueryThread` im `DatabaseMixin` ausgeführt +- Ergebnisse werden in Polars DataFrames geladen + +### Thread-basierte Operationen + +```python +from PySide6.QtCore import QThread, Signal + +class HashCalculatorThread(QThread): + progress = Signal(int) + finished = Signal(dict) + + def __init__(self, files: list[Path]): + super().__init__() + self.files = files + + def run(self): + for i, file_path in enumerate(self.files): + hash_value = calculate_hash(file_path) + self.progress.emit(i + 1) + self.finished.emit(results) + +# Verwendung +thread = HashCalculatorThread(xml_files) +thread.progress.connect(self._on_progress) +thread.finished.connect(self._on_finished) +thread.start() # NICHT run() direkt aufrufen! +``` + +## Wichtige Konventionen + +### Deutsche Sprache +Die Codebasis verwendet Deutsch für: +- UI-Texte und Labels +- Kommentare und Dokumentation +- Variablennamen wo kontextuell passend +- Log-Meldungen + +### ID-basierte Lookups +Konfigurationsentitäten (Tools, Datenbanken) werden in Projekten über ID referenziert. Die Hilfsmethoden des `Project`-Modells (`getXsl()`, `getJavaVm()`, etc.) verwenden, um IDs in Anzeigewerte aufzulösen. + +### Einstellungspersistenz +- Globale Einstellungen: `app_settings.save()` nach Änderungen aufrufen +- Projekteinstellungen: `project_data.writeSettings(project_dir)` nach Änderungen aufrufen + +## Arbeiten mit der Codebasis + +### Neue Tool-Konfigurationen hinzufügen +1. Modell zu `conf.py` hinzufügen (ähnlich wie `JavaVm`, `SaxonJar`) +2. Listenfeld zu `AppSettings` hinzufügen +3. Konfigurationsdialog in `src/ui/` erstellen (UI-Datei + Implementierung) +4. Zu `AppSettings.py` Tabs hinzufügen +5. `Project`-Modell aktualisieren, falls das Tool projektspezifisch sein soll + +### Neue Baumoperationen hinzufügen +1. Aktion zum Kontextmenü in `_create_context_menu_for_type()` hinzufügen +2. Handler-Methode implementieren nach Namensschema `_action_tree_node()`, `_action_xsl_file()`, etc. +3. Baum nach Änderungen mit `_load_nodes_to_tree()` aktualisieren +4. `self.project_data.writeSettings(self.project.project_dir)` aufrufen um Änderungen zu persistieren + +### Projektstruktur modifizieren +Das `ProjectData`-Modell ist die Quelle der Wahrheit. Alle Änderungen an der Baumstruktur müssen: +1. Die `project_data.nodes` Liste modifizieren +2. `project_data.writeSettings()` aufrufen um zu persistieren +3. Baum mit `_load_nodes_to_tree()` neu laden um Änderungen in der UI zu reflektieren