Files
xsl-validator/openwiki/workflows/transformation-pipeline.md
T
info 4733ea6b82 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>
2026-07-23 14:22:57 +02:00

135 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.