# 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 `<xsl:import/>` bzw. `<xsl:include/>` 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
```

### Commit
Jedes mal bei Commit diese Skills nutzen:

- /license-check
- /version-bump

## 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 `<xsl:import/>`- und `<xsl:include/>`-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
