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:
@@ -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.
|
||||||
@@ -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 -->
|
||||||
@@ -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.
|
**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 -->
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"lastUpdate": "2025-07-25T12:00:00Z", "mode": "code", "version": "1.0.0"}
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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 |
|
||||||
@@ -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
|
||||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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"}
|
||||||
Reference in New Issue
Block a user