chore: OpenWiki-Dokumentation einrichten, AGENTS.md ergänzen

Fügt OpenWiki-Setup (Workflow, Seiten, CLAUDE.md-Verweis) und AGENTS.md
hinzu, damit wiederkehrende Code-Dokumentation automatisch gepflegt wird.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 14:22:57 +02:00
parent 881d890e81
commit 4733ea6b82
12 changed files with 1426 additions and 685 deletions
+51
View File
@@ -0,0 +1,51 @@
name: OpenWiki Update
on:
workflow_dispatch:
schedule:
- cron: "0 8 * * *"
permissions:
contents: write
pull-requests: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install OpenWiki
run: npm install --global openwiki
- name: Run OpenWiki
run: openwiki code --update --print
env:
OPENWIKI_PROVIDER: openrouter
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENWIKI_MODEL_ID: z-ai/glm-5.2
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
LANGCHAIN_PROJECT: openwiki
LANGCHAIN_TRACING_V2: "true"
- name: Create OpenWiki update pull request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
with:
add-paths: |
openwiki
AGENTS.md
CLAUDE.md
.github/workflows/openwiki-update.yml
branch: openwiki/update
commit-message: "docs: update OpenWiki"
title: "docs: update OpenWiki"
body: |
Automated OpenWiki documentation update.
This PR was generated by the scheduled OpenWiki workflow.
+9
View File
@@ -0,0 +1,9 @@
<!-- OPENWIKI:START -->
## OpenWiki
This repository uses OpenWiki for recurring code documentation. Start with `openwiki/quickstart.md`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps.
The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate.
<!-- OPENWIKI:END -->
+10
View File
@@ -420,3 +420,13 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
<!-- OPENWIKI:START -->
## OpenWiki
This repository uses OpenWiki for recurring code documentation. Start with `openwiki/quickstart.md`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps.
The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate.
<!-- OPENWIKI:END -->
+1
View File
@@ -0,0 +1 @@
{"lastUpdate": "2025-07-25T12:00:00Z", "mode": "code", "version": "1.0.0"}
+1
View File
@@ -0,0 +1 @@
A code wiki for this local repository. Prioritize a concise quickstart, architecture overview, source map, key workflows, domain concepts, operations/runbook notes, testing guidance, and integration points. Inspect git history to understand reasoning behind code changes and the progression of the repository. Keep pages grounded in the repository structure and recent code changes. Prefer practical navigation for engineers over generic summaries.
+113
View File
@@ -0,0 +1,113 @@
---
type: Architecture Overview
title: Architektur-Überblick
description: PySide6-Mixin-Architektur, UI-Pattern (generierte UI vs. Implementierung), Thread-Modell und Konfigurationssystem von DocuMentor.
tags: [architektur, pyside6, mixins, threads, konfiguration]
---
# Architektur-Überblick
## Technologie-Stack
| Schicht | Technologie | Zweck |
|---------|------------|------|
| GUI | PySide6 (Qt 6) | Native Desktop-Oberfläche |
| Konfiguration | Pydantic + pydantic-settings | Typsichere Datenmodelle, JSON/YAML-Persistenz |
| Datenverarbeitung | Polars + ConnectorX | PostgreSQL-Abfragen, DataFrames |
| XSL-Parsing | lxml | XSL-Abhängigkeitsgraph |
| Hashing | hashlib (blake2b) | Duplikatserkennung |
| Build | PyInstaller + WiX/Inno Setup | Windows-Distribution |
| Paketverwaltung | uv | Dependency-Management |
## Einstiegspunkt
Die Anwendung startet in `src/main.py`:
1. Logging konfigurieren (Datei + Konsole, 24h Auto-Cleanup)
2. `QApplication` erstellen, App-Icon setzen
3. `MainWindow` instanziieren und anzeigen
4. Bei fehlender Tool-Konfiguration `AppSettingsDlg` modal öffnen
5. Qt-Event-Loop starten
## MainWindow Mixin-Architektur
`MainWindow` (`src/ui/MainWindow.py`) ist die zentrale Klasse und erbt von mehreren Mixins, um Funktionalität zu trennen:
```
MainWindow
├── IconRefreshMixin (src/icons.py) Theme-Wechsel → Icons neu einfärben
├── QMainWindow (PySide6) Qt-Basisfenster
├── TreeManagerMixin (mixins/tree_manager.py) Baum-Widget, Kontextmenüs, Node-Operationen
├── PdfViewerMixin (mixins/pdf_viewer.py) PDF-Rendering, Alpha-Blending, Zoom
├── WorkerPoolMixin (mixins/worker_pool.py) Saxon/FOP Worker-Pool Lebenszyklus
├── DatabaseMixin (mixins/database.py) PostgreSQL-Abfragen, Obsolete-Erkennung
├── DragDropMixin (mixins/drag_drop.py) Drag-and-Drop für XML-Dateien
├── HashCalculationMixin (mixins/hash_calculation.py) blake2b-Hash-Berechnung, Duplikatserkennung
└── TransformationMixin (mixins/transformation.py) XSL-Transformations-Ausführung
```
Jedes Mixin erwartet bestimmte Attribute (`self.ui`, `self.project`, `self.pdf_project`) und Methoden von anderen Mixins. Die Mixins sind in `src/ui/mixins/__init__.py` gebündelt.
### Mixin-Abhängigkeiten
`TransformationMixin` benötigt Methoden aus `WorkerPoolMixin` (`_initialize_saxon_worker_pool`, `_shutdown_saxon_worker_pool`) und `TreeManagerMixin` (`_collect_parent_params`, `_create_centered_progress_bar`). `HashCalculationMixin` und `DragDropMixin` benötigen `_save_project_settings()` und `_load_nodes_to_tree()` von MainWindow.
## UI-Pattern: Generierte UI vs. Implementierung
DocuMentor folgt einem strikten PySide6-Pattern:
1. **UI-Definitionsdateien** (`*_ui.py`, z.B. `MainWinddow_ui.py`): Aus `.ui`-Dateien von einer VS Code Extension generiert. Enthalten nur Widget-Layout-Definitionen als Klasse (`Ui_MainWindow`). **Nicht manuell bearbeiten.**
2. **Implementierungsdateien** (ohne `_ui`-Suffix, z.B. `MainWindow.py`): Importieren die UI-Klasse, instanziieren sie als `self.ui` und fügen Business-Logik hinzu.
```python
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)
```
**Regeln:**
- UI-Klassen niemals direkt erben, nur als `self.ui`-Member verwenden
- Alle Widgets über `self.ui.widgetName` zugreifen
- Signal-Verbindungen immer nach `setupUi()`
## Thread-Modell
Asynchrone Operationen verwenden `QThread`-Subklassen aus `src/ui/threads.py`:
| Thread-Klasse | Zweck | Signale |
|---------------|------|---------|
| `XmlHashCalculatorThread` | blake2b-Hash-Berechnung für XML-Dateien | `hash_calculated`, `calculation_finished`, `error_occurred` |
| `XmlBatchProcessingThread` | Batch-Verarbeitung: Hash + Duplikatserkennung + Dateikopieren | `progress_update`, `file_processed`, `processing_finished` |
| `TransformationThread` | Ausführung der XSL-Transformations-Pipeline | Fortschritts- und Abschluss-Signale |
| `DatabaseQueryThread` | Asynchrone PostgreSQL-Abfragen (in `mixins/database.py`) | `query_completed` (DataFrame), `query_failed` |
**Wichtig:** Threads immer mit `.start()` starten, nie `.run()` direkt aufrufen.
## Konfigurationssystem
Die Konfiguration ist in [Datenmodelle & Konfiguration](../data-models.md) detailliert beschrieben. Zusammenfassung:
- **`AppSettings`** (`src/conf.py`): Globales Singleton (`app_settings`), gespeichert als JSON. Enthält Tool-Listen, UI-Zustand, Worker-Pool-Einstellungen.
- **`ProjectData`** (`src/conf.py`): Projektspezifisch, gespeichert als `project.yaml` im Projektverzeichnis. Enthält die Baumstruktur (`TreeNode → XslFile → XmlFile`).
## Icon-System
Icons werden über `src/icons.py` verwaltet:
- Feather Icons aus dem Qt-Ressource-System (`:/icons/<name>.svg`)
- Einmaliges Rendern bei 64px, Qt skaliert für 16/24/32px
- Theme-reactiv: Färbung nach `QPalette.WindowText`, Cache-Schlüssel enthält Theme-Farbe
- `IconRefreshMixin` erlaubt Theme-Wechsel zur Laufzeit ohne manuellen Cache-Reset
## XSL-Abhängigkeitsgraph
`src/xsl_dependencies.py` (`XslDependencyGraph`) parst XSL-Dateien mit lxml und baut einen Abhängigkeitsgraph für `<xsl:import/>` und `<xsl:include/>`. Der Graph wird über Datei-mtime automatisch invalidiert. Visualisiert wird er im Dialog `src/ui/XslDependencyDialog.py` via vis.js.
Der Abhängigkeitsgraph ist auch für die [Transformations-Pipeline](../workflows/transformation-pipeline.md) relevant: wenn eine importierte XSL-Datei neuer ist als das Output-PDF, gilt die Transformation als veraltet.
## Obsolete-Erkennung
`src/obsolete_detector.py` enthält reine Analyselogik (ohne Qt-Abhängigkeit) zum Finden veralteter `XslFile`-Einträge nach Datenbank-Import. Wird vom `DatabaseMixin` nach dem DB-Laden aufgerufen und im `ObsoleteEntriesDialog` (`src/ui/ObsoleteEntriesDialog.py`) angezeigt.
+178
View File
@@ -0,0 +1,178 @@
---
type: Data Model
title: Datenmodelle & Konfiguration
description: Alle Pydantic-Modelle von DocuMentor AppSettings, Project, ProjectData, TreeNode/XslFile/XmlFile, Tool-Konfigurationen, Hash-System und PostgreSQL-Integration.
tags: [datenmodelle, pydantic, konfiguration, hash, postgresql]
---
# Datenmodelle & Konfiguration
Alle Modelle sind in `src/conf.py` definiert und verwenden Pydantic für Typsicherheit und Validierung.
## Globale Konfiguration: AppSettings
`AppSettings` ist ein `BaseSettings`-Singleton (`app_settings`), das als JSON gespeichert wird. Der Pfad ist plattformabhängig (siehe [Schnellstart](quickstart.md)).
### Tool-Konfigurationen
Jedes Tool-Modell hat eine `id` (int) und eine `version` bzw. `name`:
| Modell | Felder | Zweck |
|--------|--------|------|
| `JavaVm` | `id`, `version`, `path_to_binary_file` | Pfad zur Java-Executable |
| `SaxonJar` | `id`, `version`, `path_to_jar_file`, `output_file_extension` ("fo") | Pfad zur Saxon-JAR |
| `ApacheFop` | `id`, `version`, `path_to_dir`, `output_file_extension` ("pdf") | FOP-Installationsverzeichnis |
| `DiffPdf` | `id`, `version`, `path_to_binary_file`, `default_params`, `output_file_extension` ("pdf") | diff-pdf-Binary + Standardparameter |
| `XslDir` | `id`, `name`, `path_to_root_dir` | Wurzelverzeichnis der XSL-Dateien |
| `PostgreSqlDb` | `id`, `name`, `host`, `port` (5432), `database`, `username`, `password`, `ssl_mode`, `timeout` (10) | Datenbankverbindung |
### AppSettings-Listen
```python
java_vms: list[JavaVm] = []
diff_pdfs: list[DiffPdf] = []
saxon_jars: list[SaxonJar] = []
apache_fops: list[ApacheFop] = []
xsl_dirs: list[XslDir] = []
pdf_projects: list[Project] = []
postgresql_dbs: list[PostgreSqlDb] = []
```
### Worker-Pool-Einstellungen
| Feld | Standard | Beschreibung |
|------|----------|-------------|
| `max_workers` | 8 | Anzahl paralleler Worker pro Pool |
| `use_saxon_worker_pool` | True | Saxon-Pool aktivieren (benötigt JDK) |
| `saxon_xslt_version` | `XSLT_2_0_3_0` | XSLT-Version: `1.0` (JAXP) oder `2.0/3.0` (s9api) |
| `use_fop_worker_pool` | True | FOP-Pool aktivieren (benötigt JDK) |
### UI-Zustand
| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `theme` | `str \| None` | Qt-Theme-Name |
| `window_geometry` | `tuple[int,int,int,int] \| None` | (x, y, width, height) |
| `splitter_sizes` | `list[int] \| None` | Splitter-Positionen |
| `tree_column_widths` | `list[int] \| None` | TreeWidget-Spaltenbreiten |
| `graph_layout_settings` | `GraphLayoutSettings` | vis.js Layout-Parameter |
### Speichern
`app_settings.save()` serialisiert das gesamte Modell als JSON (`model_dump_json(indent=4)`) an den plattformspezifischen Konfigurationspfad.
## Projekt-Modell: Project
`Project` referenziert Tool-Konfigurationen über IDs:
```python
class Project(BaseModel):
id: int
name: str
project_dir: Path
java_vm_id: int # → app_settings.java_vms
diff_pdf_id: int # → app_settings.diff_pdfs
saxon_jar_id: int # → app_settings.saxon_jars
apache_fop_id: int # → app_settings.apache_fops
xsl_dir_id: int # → app_settings.xsl_dirs
postgre_sql_db_id: int # → app_settings.postgresql_dbs
fop_config_dir: Path | None
xslt_params: dict[str, str] # Projektweite XSLT-Parameter
```
Hilfsmethoden (`getXsl()`, `getJavaVm()`, `getSaxon()`, `getApacheFop()`, `getDiffPdf()`, `getPostgreSqlDb()`) lösen IDs in Anzeigewerte auf.
## Projektdaten: ProjectData
`ProjectData` wird pro Projekt in `project.yaml` gespeichert:
```python
class ProjectData(BaseModel):
nodes: list[TreeNode] = []
expanded_nodes: list[tuple] | None = None
```
Die Methode `writeSettings(project_dir)` serialisiert das Modell als YAML in `{project_dir}/project.yaml`.
## Baumstruktur: TreeNode → XslFile → XmlFile
```
TreeNode
├── children: list[TreeNode | XslFile]
├── id: tuple # Eindeutige ID als Tuple
├── bez: str # Bezeichnung
└── xslt_params: dict[str, str] # Knotenspezifische XSLT-Parameter
XslFile
├── id: tuple
├── bez: str
├── xsl_file: Path # Pfad zur XSL-Datei (absolut)
├── xslt_params: dict[str, str]
└── xmls: list[XmlFile]
XmlFile
├── xml: Path # Pfad zur XML-Datei (relativ zu project_dir)
└── hashsum: str | None # blake2b-Hash oder None
```
Die `id` als Tuple ermöglicht hierarchische Adressierung z.B. `(1, 2, 3)` für den dritten XSL-Knoten unter dem zweiten TreeNode unter dem ersten Root-Knoten.
### XSLT-Parameter-Vererbung
XSLT-Parameter werden hierarchisch vererbt: Projektweite Parameter (`Project.xslt_params`) werden von Knoten-Parametern (`TreeNode.xslt_params`) überschrieben, die wiederum von XSL-Datei-Parametern (`XslFile.xslt_params`) überschrieben werden. Die Methode `_collect_parent_params()` im `TreeManagerMixin` sammelt alle Parameter entlang des Pfades.
## Hash-System (blake2b)
### Berechnung
`calculate_blake2b_hash()` in `src/utils.py`:
- Verwendet `hashlib.blake2b()`
- Format: `blake2b:<64-Zeichen-Hexdigest>`
- Wird beim Projekt-Laden für alle XML-Dateien ohne existierenden Hash berechnet
- Asynchron via `XmlHashCalculatorThread` (`src/ui/threads.py`)
### Duplikatserkennung
Beim Zuordnen von XML-Dateien zu XSL-Knoten (Drag-and-Drop oder Dialog):
1. Hash der neuen Datei berechnen
2. Existierender Hash in `XmlFile.hashsum` wird verglichen
3. Bei Übereinstimmung wird die Datei als Duplikat erkannt und nicht erneut zugewiesen
4. Hashes werden in `project.yaml` persistiert
Details: `docs/blake2b_hash_implementation.md` und `docs/xml_hash_duplicate_detection.md`
## PostgreSQL-Integration
### Verbindungsaufbau
`PostgreSqlDb` unterstützt SSL-Modi:
| Enum-Wert | Bedeutung |
|-----------|-----------|
| `DISABLE` | Kein SSL |
| `ALLOW` | SSL optional |
| `PREFER` | SSL bevorzugt (Standard) |
| `REQUIRE` | SSL erforderlich |
| `VERIFY_CA` | SSL mit CA-Zertifikat-Verifikation |
| `VERIFY_FULL` | SSL mit vollständiger Verifikation |
### Abfrage-Ausführung
`DatabaseQueryThread` (`src/ui/mixins/database.py`):
- Verwendet `polars.read_database_uri()` mit `engine="connectorx"`
- Connection-String wird aus `PostgreSqlDb`-Feldern gebaut
- Ergebnis wird als Polars DataFrame sortiert nach `reporttyp_bez`, `report_bez`, `repfile_bez`
### Obsolete-Erkennung
Nach dem DB-Import vergleicht `obsolete_detector.py` die geladenen XslFile-IDs mit den Projekt-Einträgen. Veraltete Einträge (im Projekt, aber nicht mehr in der DB) werden im `ObsoleteEntriesDialog` angezeigt und können entfernt werden.
## Enums
| Enum | Werte | Verwendung |
|------|-------|------------|
| `XsltVersion` | `XSLT_1_0`, `XSLT_2_0_3_0` | Auswahl der Saxon-API (JAXP vs s9api) |
| `SSLMode` | `DISABLE` bis `VERIFY_FULL` | PostgreSQL-SSL-Modus |
| `GraphLayout` | `BARNES_HUT`, `FORCE_ATLAS2`, `REPULSION`, `HIERARCHICAL` | vis.js Layout-Modus |
| `HierarchicalDirection` | `UD`, `DU`, `LR`, `RL` | Hierarchisches Layout: Richtung |
| `HierarchicalSortMethod` | `HUBSIZE`, `DIRECTED` | Hierarchisches Layout: Sortierung |
+157
View File
@@ -0,0 +1,157 @@
---
type: Operations
title: Build & Betrieb
description: Build-Prozess für Windows-Distribution (ZIP, MSI, Setup.exe), Tests, Code-Style-Richtlinien und Lizenzmanagement.
tags: [build, pyinstaller, wix, msi, tests, ruff, lizenzen]
---
# Build & Betrieb
## Paketverwaltung
Das Projekt verwendet **uv** (nicht pip oder poetry):
```bash
uv sync # Abhängigkeiten installieren
uv sync --all-groups # Inkl. Dev-Abhängigkeiten (PyInstaller, Pillow)
uv run python src/main.py # Anwendung starten
```
Python-Version: 3.13+ (max. 3.14), konfiguriert in `pyproject.toml`.
## Code-Qualität
```bash
uv run ruff check # Linting (Zeilenlänge: 120)
uv run ruff format # Formatierung
```
Ruff ist in `pyproject.toml` konfiguriert:
- `line-length = 120`
- `extend-exclude = ["*_ui.py"]` generierte UI-Dateien werden nicht gelintet
### Code-Style-Konventionen
| Bereich | Konvention |
|---------|-----------|
| Imports | 1. Standard Library → 2. Drittanbieter → 3. Lokale (immer absolut, nie relativ) |
| Type Annotations | Moderne Union-Syntax: `str \| None`, `list[Path]`, `dict[str, str]` |
| Klassen | PascalCase |
| Funktionen/Methoden | snake_case |
| Private Methoden | `_snake_case` mit Unterstrich |
| Konstanten | UPPER_CASE |
| Strings | Double-Quotes bevorzugt |
| Error Handling | Stets `logging` statt `print()` |
| Docstrings | Google-Style auf Deutsch |
| Pfade | Immer `pathlib.Path`, nie Strings |
| Sprache | UI-Texte, Kommentare, Log-Meldungen auf Deutsch |
### Zirkuläre Imports
`TYPE_CHECKING` aus `typing` verwenden, um zirkuläre Imports zu vermeiden:
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from saxon_pool import SaxonWorkerPool
```
## Tests
DocuMentor verwendet **keine** pytest/unittest-Frameworks. Tests sind standalone Python-Skripte:
```bash
uv run python test_hash_implementation.py # Hash-Implementierung
uv run python test_xml_hash_duplicate_detection.py # Duplikatserkennung
```
## Build: Windows-Distribution
Die Build-Skripte befinden sich im Repository-Root. Ausführliche Anleitung in `BUILD.md`.
### ZIP-Distribution (empfohlen)
```bash
uv run python build_windows.py
```
Erstellt automatisch:
1. App-Icon (`resources/icon.ico`) via `create_icon.py` (falls nicht vorhanden)
2. Versionsinformationen (`version_info.txt`) via `create_version_info.py`
3. PyInstaller-Build (`DocuMentor.spec``dist/DocuMentor/DocuMentor.exe`)
4. ZIP-Archiv (`dist/DocuMentor-YYYYMMDD-Windows.zip`)
### Manueller PyInstaller-Build
```bash
rm -rf build/ dist/
uv run pyinstaller --clean DocuMentor.spec
```
### MSI-Installer (WiX Toolset)
Voraussetzung: WiX Toolset v6 (`dotnet tool install --global wix --version 6.*`)
```bash
# 1. PyInstaller-Build erstellen
uv run python build_windows.py
# 2. ProductFiles.wxs generieren (WiX v6 hat `heat` entfernt)
uv run python generate_wix_files.py
# 3. MSI kompilieren
wix build DocuMentor.wxs ProductFiles.wxs -o DocuMentor.msi
# 4. Testen
msiexec /i DocuMentor.msi # Installation
msiexec /i DocuMentor.msi /quiet /qn # Silent Installation
msiexec /x DocuMentor.msi # Deinstallation
```
### Setup.exe (Inno Setup)
Alternative zum MSI: `installer.iss` für Inno Setup.
### Build-Skripte im Überblick
| Skript | Zweck |
|--------|------|
| `build_windows.py` | Automatischer Build (Icon, Version, PyInstaller, ZIP) |
| `build_msi.py` | MSI-Build-Wrapper |
| `create_icon.py` | Icon-Generierung mit Pillow |
| `create_version_info.py` | Windows-Versionsinformationen |
| `generate_wix_files.py` | ProductFiles.wxs aus dist/ generieren |
| `generate_guid.py` | GUID-Generierung für WiX |
| `DocuMentor.spec` | PyInstaller-Spezifikation |
| `DocuMentor.wxs` | WiX-Main-Definition |
| `ProductFiles.wxs` | WiX-Datei-Inventar (generiert) |
| `installer.iss` | Inno Setup-Skript |
## Versionierung
- Version in `pyproject.toml` (`version = "1.7.3"`)
- `versions.json` enthält Paketversionen für PyInstaller-Bundles (da `importlib.metadata` im Bundle nicht funktioniert)
- Version-Bump via Skill `/version-bump` (verwendet `uv version --bump`)
## Lizenzmanagement
- Projekt-Lizenz: MIT (`LICENSE`)
- `LICENSES.md`: Vollständige Lizenzanalyse aller Abhängigkeiten
- `THIRD_PARTY_LICENSES.txt`: Third-Party-Lizenztexte (wird ins PyInstaller-Bundle gepackt)
- `src/license_parser.py`: Parst `THIRD_PARTY_LICENSES.txt` und ergänzt mit installierten Paketversionen (via `importlib.metadata` oder `versions.json` im Bundle)
- Bei jedem Commit: Skill `/license-check` ausführen
## Externe Tool-Lizenzen
| Tool | Lizenz |
|------|--------|
| Saxon-HE | Mozilla Public License 2.0 |
| Apache FOP | Apache License 2.0 |
| diff-pdf | GPL |
## CI/CD
GitHub Actions Workflow `.github/workflows/openwiki-update.yml`:
- Täglicher Cron (08:00 UTC) oder manuell
- Führt `openwiki code --update --print` aus
- Erstellt Pull Request mit aktualisierten Wiki-Seiten
+86
View File
@@ -0,0 +1,86 @@
---
type: Quickstart
title: DocuMentor Schnellstart
description: Einstiegspunkt in die Dokumentation. Überblick über DocuMentor, einer PySide6-Desktop-App für XSL-Transformationsverwaltung und PDF-Generierung.
tags: [xsl, pdf, pyside6, desktop-app, transformation]
---
# DocuMentor Schnellstart
**DocuMentor** ist eine PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit automatischer PDF-Generierung. Sie richtet sich an Entwickler, die kontinuierlich XSL-Stylesheets pflegen und die Auswirkungen auf generierte PDF-Dokumente nachvollziehen müssen.
## Was ist DocuMentor?
Der primäre Einsatz ist die Weiterentwicklung von PDF-Dokumenten in **Flexnow** (Prüfungsverwaltungs-Software). Die Basis bilden ca. 100 XSL-Dateien, die über `<xsl:import/>` und `<xsl:include/>` miteinander verknüpft sind. Änderungen an einer XSL-Datei können sich auf viele andere auswirken. DocuMentor hilft, diese Auswirkungen durch automatisierte Transformation und PDF-Vergleich zu kontrollieren.
### Kern-Features
- **Hierarchische Projektverwaltung** Baumstruktur für Transformations-Workflows mit verschachtelten Knoten
- **Asynchrone Batch-Verarbeitung** Große Mengen von XML-Dateien im Hintergrund mit Fortschrittsanzeige
- **Parallele Worker-Pools** Persistente JVM-Prozesse für Saxon (XSLT) und Apache FOP (PDF) eliminieren JVM-Startup-Overhead
- **Duplikatserkennung** Hash-basierte (blake2b) Erkennung identischer XML-Dateien
- **PDF-Vergleichsansicht** Drei-Panel-Ansicht (Referenz, Diff, Neu) mit Alpha-Blending und Zoom
- **PostgreSQL-Integration** Datenbankanbindung mit Polars/ConnectorX
- **XSL-Abhängigkeitsgraph** Visualisierung von `<xsl:import/>`- und `<xsl:include/>`-Beziehungen via vis.js
- **Plattformübergreifend** Linux, Windows, macOS
## Installation
### Voraussetzungen
- Python 3.13+
- [uv](https://github.com/astral-sh/uv) Paketmanager
- OpenJDK/JRE (für Saxon und Apache FOP)
### Abhängigkeiten
```bash
uv sync
```
### Externe Tools
| Tool | Zweck | Download |
|------|-------|----------|
| **Saxon-HE** | XSLT 3.0 Prozessor | [saxonica.com](https://www.saxonica.com/download/) |
| **Apache FOP** | PDF-Generierung aus XSL-FO | [xmlgraphics.apache.org](https://xmlgraphics.apache.org/fop/download.html) |
| **diff-pdf** | PDF-Vergleich | [GitHub](https://github.com/vslavik/diff-pdf) |
| **OpenJDK** | JVM für Saxon/FOP Worker-Pools | [Eclipse Temurin](https://adoptium.net) |
## Anwendung starten
```bash
uv run python src/main.py
```
Beim ersten Start öffnet sich der Einstellungsdialog, in dem die externen Tools (Java VM, Saxon JAR, Apache FOP, diff-pdf, XSL-Verzeichnis, PostgreSQL) konfiguriert werden.
## Typischer Workflow
1. Entwickler ändert XSL-Dateien
2. Transformation in DocuMentor starten
3. PDF-Diff begutachten: Wurden die richtigen PDFs geändert?
4. Prüfen: Entspricht die Änderung der Erwartung?
5. Ggf. zurück zu Schritt 1
## Konfigurationsorte
| Plattform | Pfad |
|-----------|------|
| Linux | `~/.config/DocuMentor/config.json` |
| Windows | `%APPDATA%\DocuMentor\config.json` |
| macOS | `~/Library/Application Support/DocuMentor/config.json` |
Projektdaten werden pro Projekt in `project.yaml` gespeichert.
## Wiki-Struktur
- [Architektur-Überblick](architecture/overview.md) PySide6-Mixin-Architektur, UI-Pattern, Thread-Modell, Konfigurationssystem
- [Transformations-Pipeline](workflows/transformation-pipeline.md) XML→FO→PDF→Diff, Worker-Pools, XSL-Abhängigkeitsgraph, Entscheidungslogik
- [Datenmodelle & Konfiguration](data-models.md) AppSettings, Project, ProjectData, TreeNode/XslFile/XmlFile, Hash-System, PostgreSQL
- [Build & Betrieb](operations.md) Build-Prozess, MSI/ZIP-Distribution, Tests, Code-Style, Lizenzen
## Backlog
- **Web-Seite** (`web/`): Statische HTML-Landingpage mit Datenschutz/Impressum nicht Teil der Anwendungslogik, bei Bedarf separat dokumentieren.
- **Skills** (`skills/`): Nur `write-connector` (OpenWiki-Skill), nicht projektspezifisch.
@@ -0,0 +1,134 @@
---
type: Workflow
title: Transformations-Pipeline
description: Die dreistufige Pipeline XML→FO→PDF→Diff, Worker-Pool-Architektur, Entscheidungslogik für Neutransformation und XSL-Abhängigkeitsgraph.
tags: [transformation, saxon, fop, diff-pdf, worker-pool, pipeline]
---
# Transformations-Pipeline
## Übersicht
Die Kern-Pipeline von DocuMentor besteht aus drei Stufen:
```
XML ──[Saxon XSLT]──→ FO ──[Apache FOP]──→ PDF ──[diff-pdf]──→ Diff-PDF
```
1. **XML → FO**: XSLT-Transformation mit Saxon-HE (XSLT 1.0 via JAXP oder 2.0/3.0 via s9api)
2. **FO → PDF**: PDF-Generierung aus XSL-FO mit Apache FOP
3. **PDF → Diff**: Vergleich von Referenz- und Neu-PDF mit diff-pdf
Die Implementierung befindet sich in `src/transform.py` (`TransformationJob`-Klasse).
## TransformationJob
`TransformationJob` repräsentiert einen einzelnen Transformations-Job. Beim Initialisieren werden folgende Ausgabeverzeichnisse im Projektordner erstellt:
| Verzeichnis | Inhalt |
|-------------|--------|
| `new/` | Neu generierte PDFs und temporäre FO-Dateien |
| `ref/` | Referenz-PDFs (vor Änderung) |
| `diff/` | Diff-PDFs vom Vergleich |
Dateinamen folgen dem Schema `{xml_stem}_xsl_{xsl_id_tuple}.pdf` z.B. `zeugnis_xsl_1_2_3.pdf`.
### Classpath-Caching
`TransformationJob` hat einen klassenweiten Cache (`_classpath_cache: dict[Path, str]`) für Saxon-Classpaths. Beim ersten Job wird der Classpath aus allen JARs im Saxon-Verzeichnis (+ `lib/`-Unterordner) gebaut und für nachfolgende Jobs wiederverwendet.
## Entscheidungslogik: Wann wird transformiert?
`is_up_to_date()` prüft, ob eine Neutransformation nötig ist. Eine Transformation gilt als **aktuell**, wenn:
1. Das `new/`-PDF existiert **und**
2. Das `new/`-PDF neuer ist als:
- Die XML-Eingabedatei
- Die XSL-Stylesheet-Datei
- **Alle transitiv importierten/inkludierten XSL-Dateien** (über `XslDependencyGraph`)
Wenn `force=True` übergeben wird (Menü "Alle neu transformieren"), wird `is_up_to_date()` übersprungen.
Diese Logik stellt sicher, dass Änderungen in importierten XSL-Dateien die durch `<xsl:import/>`/`<xsl:include/>` transitiv abhängig sind automatisch erkannt werden. Siehe auch `docs/Transformation ohne Force - Entscheidungslogik`.
## Worker-Pool-Architektur
Um den JVM-Startup-Overhead zu eliminieren, verwendet DocuMentor persistente JVM-Worker-Pools. Die Basisimplementierung ist `BaseWorkerPool` (`src/worker_pool_base.py`).
### Saxon-Worker-Pool
Zwei Varianten, ausgewählt über `AppSettings.saxon_xslt_version`:
| Variante | Klasse | API | XSLT-Version |
|----------|--------|-----|--------------|
| JAXP | `SaxonWorkerPool` (`src/saxon_pool.py`) | `javax.xml.transform` | 1.0 |
| s9api | `SaxonWorkerPoolS9Api` (`src/saxon_pool_s9api.py`) | `net.sf.saxon.s9api` | 2.0/3.0 |
Beide Varianten:
- Starten N Worker-JVM-Prozesse (konfigurierbar über `AppSettings.max_workers`, Standard: 8)
- Kompilieren Java-Worker-Code zur Laufzeit (Java-Sourcecode als String- Konstante in der Python-Datei)
- Jeder Worker liest Jobs von stdin, schreibt Ergebnisse nach stdout, Logs nach stderr
- Stylesheet-Caching im Worker (HashMap) für wiederholte Transformationen
- Kommunikation über tab-getrennte Zeilen via stdin/stdout
- Fallback auf direkten subprocess-Aufruf, wenn der Pool nicht verfügbar ist
### FOP-Worker-Pool
`FopWorkerPool` (`src/fop_pool.py`) analog zum Saxon-Pool, aber für FO→PDF-Transformation:
- `FopFactory` wird einmal pro Worker erstellt und wiederverwendet
- Unterstützt optionale FOP-Konfigurationsdatei (`fop.xconf`)
- Fallback auf subprocess (`fop.cmd`/`fop`)
### Lebenszyklus
Der Lebenszyklus wird vom `WorkerPoolMixin` (`src/ui/mixins/worker_pool.py`) gesteuert:
1. **Initialisierung** bei Projektwechsel: `_initialize_saxon_worker_pool()` / `_initialize_fop_worker_pool()`
2. **Shutdown** bei Projektwechsel oder Anwendungsende: `_shutdown_saxon_worker_pool()` / `_shutdown_fop_worker_pool()`
3. Globale Referenz über `set_saxon_worker_pool()` / `get_saxon_worker_pool()` in `src/transform.py`
Da DocuMentor permanent im Hintergrund läuft, ist sparsamer RAM-Umgang wichtig: Worker-Pools werden nach Verwendung heruntergefahren.
### Performance-Metriken
`WorkerPoolMetrics` (`src/worker_metrics.py`) sammelt:
- Kompilierungszeit
- Worker-Start-Zeiten
- RAM-Verbrauch pro Worker (vor/nach Transformation, via `psutil`)
- XSL-Kompilierungszeiten (nur Saxon)
Metriken werden im `WorkerPoolMetricsDialog` (`src/ui/WorkerPoolMetricsDialog.py`) angezeigt.
## XSL-Abhängigkeitsgraph
`XslDependencyGraph` (`src/xsl_dependencies.py`) ist für zwei Bereiche relevant:
1. **Entscheidungslogik**: `is_up_to_date()` nutzt `get_dependencies()`, um transitiv importierte XSL-Dateien auf Änderungen zu prüfen
2. **Visualisierung**: Der Dialog `XslDependencyDialog` (`src/ui/XslDependencyDialog.py`) stellt den Graph mit vis.js dar
Der Graph wird über Datei-mtime automatisch invalidiert bei Änderung einer XSL-Datei wird deren Eintrag neu geparst. Das Parsing verwendet lxml mit dem XSL-Namespace `http://www.w3.org/1999/XSL/Transform`.
## Transformations-Ausführung
Die `TransformationMixin` (`src/ui/mixins/transformation.py`) orchestriert die Ausführung:
1. Sammelt alle zu transformierenden Jobs aus dem Baum
2. Sammelt XSLT-Parameter (projektweit + knotenspezifisch, über `_collect_parent_params()`)
3. Startet `TransformationThread` (QThread) mit der Job-Liste
4. Zeigt Fortschrittsbalken in der Statusbar an
5. Aktualisiert Diff-Icons im Baum nach Abschluss
### "Accept Changes"
Nach Begutachtung der Diff-PDF kann der Benutzer Änderungen akzeptieren: Das `new/`-PDF wird zum `ref/`-PDF kopiert, und der Diff wird gelöscht.
## Datenbank-Integration
Über das `DatabaseMixin` (`src/ui/mixins/database.py`) können XML/XSL-Zuordnungen aus einer PostgreSQL-Datenbank geladen werden:
1. `DatabaseQueryThread` führt SQL asynchron via ConnectorX aus
2. Ergebnis wird als Polars DataFrame sortiert nach `reporttyp_bez`, `report_bez`, `repfile_bez`
3. DataFrame wird in `TreeNode`/`XslFile`-Struktur konvertiert und in den Baum geladen
4. `obsolete_detector` erkennt veraltete Einträge, die nicht mehr in der DB vorhanden sind
Siehe [Datenmodelle & Konfiguration](../data-models.md) für das `PostgreSqlDb`-Modell und SSL-Modi.
+685 -685
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
{"pyside6": "6.11.1", "pydantic": "2.13.4", "pydantic-settings": "2.14.1", "pydantic-yaml": "1.6.0", "polars": "1.41.0", "connectorx": "0.4.5", "pyarrow": "24.0.0", "psutil": "7.2.2", "lxml": "6.1.1", "ruff": "0.15.14", "pyinstaller": "6.20.0", "pillow": "12.2.0"}