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 <noreply@anthropic.com>
13 KiB
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:
- Entwickler führt benötigte Änderungen an den XSL-Dateien durch
- Entwickler startet die Transformation im DocuMentor und begutachtet die generierte PDF-Diff
- Prüfung: Wurden die richtigen PDF-Dateien geändert?
- 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
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_CHECKINGfü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-Infoslogger.info()für normale Operationenlogger.warning()für Warnungenlogger.error()für Fehler ohne Stack Tracelogger.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()undexpandvars()für Benutzer-/Umgebungspfade verwenden- Projektrelative Pfade werden als relativ gespeichert, zur Laufzeit gegen
project_diraufgelö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
- Linux:
- Enthält Listen von Tools:
java_vms,saxon_jars,apache_fops,diff_pdfs,xsl_dirs,postgresql_dbs
- Wird an plattformspezifischen Orten gespeichert:
-
ProjectData: Projektspezifische Einstellungen, die in
project.yamlim jeweiligen Projektverzeichnis gespeichert werden- Enthält hierarchische Baumstruktur von Transformationsknoten
- Verwendet
TreeNodeundXslFilezur Organisation - Jede
XmlFilehat eine optionalehashsum(blake2b) zur Änderungsverfolgung
Wichtige Datenmodelle
-
Tool-Konfigurationsmodelle (JavaVm, SaxonJar, ApacheFop, DiffPdf, XslDir, PostgreSqlDb):
- Jedes hat eine
idundversion - Speichert Pfade zu Binärdateien/Verzeichnissen
- Jedes hat eine
-
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
-
Baumstruktur (TreeNode → XslFile → XmlFile):
- Hierarchische Organisation von Transformations-Workflows
TreeNode: Organisationseinheit mitxslt_paramsund Kindknoten/-dateienXslFile: XSL-Stylesheet mit zugehörigen XML-Dateien und XSLT-ParameternXmlFile: XML-Eingabedatei mit optionalem blake2b-Hash
UI-Architektur (src/ui/)
Die Anwendung folgt einem spezifischen PySide6-Muster:
-
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
- Diese Dateien definieren die UI-Struktur als Klassen (z.B.
-
Implementierungsdateien (ohne
_uiSuffix): Tatsächliche Dialog-/Fenster-Implementierungen- Importieren und verwenden die entsprechende
*_ui.pyDatei - Enthalten Business-Logik und Signal/Slot-Verbindungen
- Beispiel:
MainWindow.pyverwendetUi_MainWindowausMainWinddow_ui.py
- Importieren und verwenden die entsprechende
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.uiMember - Alle Widgets über
self.ui.widgetNamezugreifen - Signal-Verbindungen immer NACH
setupUi()aufrufen
Hauptfenster (src/ui/MainWindow.py)
Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten:
-
Projektverwaltung:
- Öffnet und verwaltet PDF-Transformationsprojekte
- Lädt/speichert
ProjectDataausproject.yamlDateien
-
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
-
PDF-Vergleichsansicht:
- Drei-Panel-Ansicht (Referenz, Diff, Neu)
- Alpha-Blending für visuellen Vergleich
- Zoom- und Pan-Funktionalität
-
Asynchrone Operationen:
XmlHashCalculatorThread: Hintergrund-blake2b-Hash-Berechnung für XML-DateienDatabaseTestThread(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.yamlinnerhalb jedesXmlFile-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.themegespeichert
Datenbankintegration
PostgreSQL-Integration mit Polars und ConnectorX:
- Konfiguration wird im
PostgreSqlDb-Modell mit SSL-Modus-Unterstützung gespeichert - SQL-Abfragen werden asynchron via
DatabaseQueryThreadimDatabaseMixinausgefü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
- Modell zu
conf.pyhinzufügen (ähnlich wieJavaVm,SaxonJar) - Listenfeld zu
AppSettingshinzufügen - Konfigurationsdialog in
src/ui/erstellen (UI-Datei + Implementierung) - Zu
AppSettings.pyTabs hinzufügen Project-Modell aktualisieren, falls das Tool projektspezifisch sein soll
Neue Baumoperationen hinzufügen
- Aktion zum Kontextmenü in
_create_context_menu_for_type()hinzufügen - Handler-Methode implementieren nach Namensschema
_action_tree_node(),_action_xsl_file(), etc. - Baum nach Änderungen mit
_load_nodes_to_tree()aktualisieren 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:
- Die
project_data.nodesListe modifizieren project_data.writeSettings()aufrufen um zu persistieren- Baum mit
_load_nodes_to_tree()neu laden um Änderungen in der UI zu reflektieren