Files
xsl-validator/openwiki/workflows/transformation-pipeline.md
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

6.2 KiB
Raw Permalink Blame History

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.
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 für das PostgreSqlDb-Modell und SSL-Modi.