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>
6.2 KiB
type, title, description, tags
| type | title | description | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Workflow | Transformations-Pipeline | Die dreistufige Pipeline XML→FO→PDF→Diff, Worker-Pool-Architektur, Entscheidungslogik für Neutransformation und XSL-Abhängigkeitsgraph. |
|
Transformations-Pipeline
Übersicht
Die Kern-Pipeline von DocuMentor besteht aus drei Stufen:
XML ──[Saxon XSLT]──→ FO ──[Apache FOP]──→ PDF ──[diff-pdf]──→ Diff-PDF
- XML → FO: XSLT-Transformation mit Saxon-HE (XSLT 1.0 via JAXP oder 2.0/3.0 via s9api)
- FO → PDF: PDF-Generierung aus XSL-FO mit Apache FOP
- 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:
- Das
new/-PDF existiert und - 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:
FopFactorywird 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:
- Initialisierung bei Projektwechsel:
_initialize_saxon_worker_pool()/_initialize_fop_worker_pool() - Shutdown bei Projektwechsel oder Anwendungsende:
_shutdown_saxon_worker_pool()/_shutdown_fop_worker_pool() - Globale Referenz über
set_saxon_worker_pool()/get_saxon_worker_pool()insrc/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:
- Entscheidungslogik:
is_up_to_date()nutztget_dependencies(), um transitiv importierte XSL-Dateien auf Änderungen zu prüfen - 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:
- Sammelt alle zu transformierenden Jobs aus dem Baum
- Sammelt XSLT-Parameter (projektweit + knotenspezifisch, über
_collect_parent_params()) - Startet
TransformationThread(QThread) mit der Job-Liste - Zeigt Fortschrittsbalken in der Statusbar an
- 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:
DatabaseQueryThreadführt SQL asynchron via ConnectorX aus- Ergebnis wird als Polars DataFrame sortiert nach
reporttyp_bez,report_bez,repfile_bez - DataFrame wird in
TreeNode/XslFile-Struktur konvertiert und in den Baum geladen obsolete_detectorerkennt veraltete Einträge, die nicht mehr in der DB vorhanden sind
Siehe Datenmodelle & Konfiguration für das PostgreSqlDb-Modell und SSL-Modi.