Files
info 789fb5d77f docs: Projektnamen bereinigen, README und Web aktualisieren
Entfernt den Zusatz "ehemals xsl-validator" aus CLAUDE.md und README.
README mit korrekten Infos zu Theme, Worker-Pool und externen Tools ergänzt.
Download-Links auf Web-Seite auf Version 1.7.3 aktualisiert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 19:37:17 +02:00

13 KiB

CLAUDE.md

Spreche mit mir auf Deutsch! (Communicate with me in German!)

Projektübersicht

DocuMentor 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):

uv sync                    # Abhängigkeiten installieren
uv run python src/main.py  # Anwendung starten

Linting

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:

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):

# 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:

# 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

# 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:

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:

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:

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

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