Feat: Projektweite XSLT-Parameter mit Vererbungshierarchie (v1.4.0)

Ermöglicht die Definition von XSLT-Parametern auf Projektebene, die als
Basis für alle Transformationen dienen und von TreeNode- bzw. XslFile-
Parametern überschrieben werden können (Projekt < TreeNode < XslFile).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 10:57:05 +02:00
parent d1def05607
commit a0626a78a3
16 changed files with 579 additions and 195 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<!-- Paket-Definition (ersetzt Product in v4) -->
<Package
Name="DocuMentor"
Version="1.3.0"
Version="1.4.0"
Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
Language="1031"
+1 -1
View File
@@ -253,5 +253,5 @@ HINWEISE
================================================================================
Stand: April 2026
Erstellt für: DocuMentor v1.3.0
Erstellt für: DocuMentor v1.4.0
================================================================================
+1 -1
View File
@@ -10,7 +10,7 @@
; Build-Befehl: iscc installer.iss
#define MyAppName "DocuMentor"
#define MyAppVersion "1.3.0"
#define MyAppVersion "1.4.0"
#define MyAppPublisher "Ihr Name/Organisation"
#define MyAppURL "https://github.com/yourusername/xsl-validator"
#define MyAppExeName "DocuMentor.exe"
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "DocuMentor"
version = "1.3.0"
version = "1.4.0"
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
readme = "README.md"
license = {text = "MIT"}
+1
View File
@@ -162,6 +162,7 @@ class Project(BaseModel):
xsl_dir_id: int = Field(..., description="ID des XSL-Verzeichnisses", gt=0)
postgre_sql_db_id: int = Field(..., description="ID der PostgreSQL Datenbank", gt=0)
fop_config_dir: Path | None = Field(None, description="Optionaler Pfad zum Apache FOP Config-Verzeichnis")
xslt_params: dict[str, str] = Field(default_factory=dict, description="Projektweite XSLT-Parameter")
@staticmethod
def _lookup(collection, item_id: int, attr: str) -> str:
+3
View File
@@ -391,6 +391,7 @@ class AppSettingsDlg(QDialog):
xsl_dir_id=project_data["xsl_dir_id"] if project_data["xsl_dir_id"] != -1 else 1,
postgre_sql_db_id=project_data["postgre_sql_db_id"] if project_data["postgre_sql_db_id"] != -1 else 1,
fop_config_dir=Path(project_data["fop_config_dir"]) if project_data.get("fop_config_dir") else None,
xslt_params=project_data.get("xslt_params", {}),
)
self.temp_pdf_projects.append(new_project)
self._populate_pdf_project_table()
@@ -422,6 +423,7 @@ class AppSettingsDlg(QDialog):
"xsl_dir_id": pdf_project.xsl_dir_id,
"postgre_sql_db_id": pdf_project.postgre_sql_db_id,
"fop_config_dir": str(pdf_project.fop_config_dir) if pdf_project.fop_config_dir else None,
"xslt_params": dict(pdf_project.xslt_params),
}
dialog = PdfProjectDlg(self, project_data, edit_mode=True)
if dialog.exec() == PdfProjectDlg.DialogCode.Accepted:
@@ -450,6 +452,7 @@ class AppSettingsDlg(QDialog):
pdf_project.fop_config_dir = (
Path(new_data["fop_config_dir"]) if new_data.get("fop_config_dir") else None
)
pdf_project.xslt_params = new_data.get("xslt_params", {})
self._populate_pdf_project_table()
self.settings.pdf_projects = self.temp_pdf_projects.copy()
self.settings.save()
+1
View File
@@ -495,6 +495,7 @@ class MainWindow(
if project_data["postgre_sql_db_id"] != -1
else 1,
fop_config_dir=Path(project_data["fop_config_dir"]) if project_data.get("fop_config_dir") else None,
xslt_params=project_data.get("xslt_params", {}),
)
# Erstelle Projekt-Ordnerstruktur
+13 -1
View File
@@ -5,6 +5,7 @@ from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox
from conf import app_settings
from ui.PdfProject_ui import Ui_projectDlg
from ui.ProjectXsltParamsDialog import ProjectXsltParamsDialog
class PdfProjectDlg(QDialog):
@@ -26,6 +27,7 @@ class PdfProjectDlg(QDialog):
# Projektdaten speichern
self.project_data = project_data or {}
self.edit_mode = edit_mode
self.xslt_params: dict[str, str] = dict(self.project_data.get("xslt_params", {}))
# Dialog-Eigenschaften setzen
self.setModal(True)
@@ -53,6 +55,9 @@ class PdfProjectDlg(QDialog):
# Browse-Button für FOP-Config-Ordner
self.ui.btnBrowseFopConfig.clicked.connect(self.browse_fop_config_dir)
# XSLT-Parameter bearbeiten
self.ui.btnEditXsltParams.clicked.connect(self._edit_xslt_params)
# OK/Cancel Buttons sind bereits in der UI-Datei verbunden
# self.ui.buttonBox.accepted.connect(self.accept)
# self.ui.buttonBox.rejected.connect(self.reject)
@@ -190,6 +195,12 @@ class PdfProjectDlg(QDialog):
if selected_dir:
self.ui.lineFopConfigDir.setText(selected_dir)
def _edit_xslt_params(self):
"""Öffnet den Dialog zur Bearbeitung der projektweiten XSLT-Parameter."""
dialog = ProjectXsltParamsDialog(self, self.xslt_params)
if dialog.exec() == ProjectXsltParamsDialog.DialogCode.Accepted:
self.xslt_params = dialog.get_params()
def validate_and_accept(self):
"""Validiert die Eingaben und akzeptiert den Dialog."""
# Projekt-Name prüfen
@@ -269,7 +280,8 @@ class PdfProjectDlg(QDialog):
'apache_fop_id': self.ui.cB_ApacheFop.currentData(),
'diff_pdf_id': self.ui.cB_Diff_Pdf.currentData(),
'postgre_sql_db_id': self.ui.cB_Postgres.currentData(),
'fop_config_dir': fop_config_dir if fop_config_dir else None
'fop_config_dir': fop_config_dir if fop_config_dir else None,
'xslt_params': self.xslt_params,
}
def _configure_edit_mode(self):
+15 -1
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>608</width>
<height>331</height>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
@@ -135,6 +135,20 @@
<item row="8" column="1">
<widget class="QComboBox" name="cB_Postgres"/>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>XSLT-Parameter:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QPushButton" name="btnEditXsltParams">
<property name="text">
<string>Bearbeiten ...</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QFrame" name="frame_2">
<property name="frameShape">
+13 -1
View File
@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'PdfProject.ui'
##
## Created by: Qt User Interface Compiler version 6.9.2
## Created by: Qt User Interface Compiler version 6.10.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -131,6 +131,16 @@ class Ui_projectDlg(object):
self.formLayout.setWidget(8, QFormLayout.ItemRole.FieldRole, self.cB_Postgres)
self.label_10 = QLabel(self.widget)
self.label_10.setObjectName(u"label_10")
self.formLayout.setWidget(9, QFormLayout.ItemRole.LabelRole, self.label_10)
self.btnEditXsltParams = QPushButton(self.widget)
self.btnEditXsltParams.setObjectName(u"btnEditXsltParams")
self.formLayout.setWidget(9, QFormLayout.ItemRole.FieldRole, self.btnEditXsltParams)
self.frame_2 = QFrame(self.widget)
self.frame_2.setObjectName(u"frame_2")
self.frame_2.setFrameShape(QFrame.Shape.StyledPanel)
@@ -182,6 +192,8 @@ class Ui_projectDlg(object):
self.label_9.setText(QCoreApplication.translate("projectDlg", u"FOP-Config-Ordner:", None))
self.label_7.setText(QCoreApplication.translate("projectDlg", u"diff-pdf:", None))
self.label_8.setText(QCoreApplication.translate("projectDlg", u"Postgres:", None))
self.label_10.setText(QCoreApplication.translate("projectDlg", u"XSLT-Parameter:", None))
self.btnEditXsltParams.setText(QCoreApplication.translate("projectDlg", u"Bearbeiten ...", None))
self.btnBrowseFopConfig.setText(QCoreApplication.translate("projectDlg", u"Durchsuchen ...", None))
# retranslateUi
+64
View File
@@ -0,0 +1,64 @@
from PySide6.QtWidgets import QDialog, QTableWidgetItem
from ui.ProjectXsltParamsDialog_ui import Ui_ProjectXsltParamsDialog
class ProjectXsltParamsDialog(QDialog):
"""Dialog zur Bearbeitung projektweiter XSLT-Parameter."""
def __init__(self, parent=None, xslt_params: dict[str, str] | None = None):
super().__init__(parent)
self.ui = Ui_ProjectXsltParamsDialog()
self.ui.setupUi(self)
self.ui.addParamButton.clicked.connect(self._add_parameter)
self.ui.removeParamButton.clicked.connect(self._remove_parameter)
self._setup_table()
if xslt_params:
self._load_params(xslt_params)
def _setup_table(self):
"""Konfiguriert die Tabelle."""
self.ui.xsltParamsTable.setColumnWidth(0, 200)
self.ui.xsltParamsTable.setColumnWidth(1, 300)
self.ui.xsltParamsTable.horizontalHeader().setStretchLastSection(True)
def _load_params(self, params: dict[str, str]):
"""Lädt die XSLT-Parameter in die Tabelle."""
self.ui.xsltParamsTable.setRowCount(len(params))
for row, (key, value) in enumerate(params.items()):
self.ui.xsltParamsTable.setItem(row, 0, QTableWidgetItem(str(key)))
self.ui.xsltParamsTable.setItem(row, 1, QTableWidgetItem(str(value)))
def _add_parameter(self):
"""Fügt einen neuen Parameter hinzu."""
row_count = self.ui.xsltParamsTable.rowCount()
self.ui.xsltParamsTable.insertRow(row_count)
self.ui.xsltParamsTable.setItem(row_count, 0, QTableWidgetItem(""))
self.ui.xsltParamsTable.setItem(row_count, 1, QTableWidgetItem(""))
self.ui.xsltParamsTable.setCurrentCell(row_count, 0)
def _remove_parameter(self):
"""Entfernt den ausgewählten Parameter."""
current_row = self.ui.xsltParamsTable.currentRow()
if current_row >= 0:
self.ui.xsltParamsTable.removeRow(current_row)
def get_params(self) -> dict[str, str]:
"""
Gibt die bearbeiteten XSLT-Parameter zurück.
Returns:
dict[str, str]: Dictionary mit allen XSLT-Parametern
"""
params = {}
for row in range(self.ui.xsltParamsTable.rowCount()):
key_item = self.ui.xsltParamsTable.item(row, 0)
value_item = self.ui.xsltParamsTable.item(row, 1)
if key_item and value_item:
key = key_item.text().strip()
if key:
params[key] = value_item.text().strip()
return params
+164
View File
@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProjectXsltParamsDialog</class>
<widget class="QDialog" name="ProjectXsltParamsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>Projektweite XSLT-Parameter</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="xsltParamsGroupBox">
<property name="title">
<string>XSLT-Parameter</string>
</property>
<layout class="QVBoxLayout" name="xsltParamsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableWidget" name="xsltParamsTable">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Parameter</string>
</property>
</column>
<column>
<property name="text">
<string>Wert</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="xsltParamsButtonLayout">
<item>
<spacer name="horizontalSpacer_left">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="addParamButton">
<property name="text">
<string>Parameter hinzufügen</string>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ListAdd"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeParamButton">
<property name="text">
<string>Parameter entfernen</string>
</property>
<property name="icon">
<iconset theme="QIcon::ThemeIcon::ListRemove"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_right">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
<property name="centerButtons">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ProjectXsltParamsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ProjectXsltParamsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+106
View File
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'ProjectXsltParamsDialog.ui'
##
## Created by: Qt User Interface Compiler version 6.10.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
QFrame, QGroupBox, QHBoxLayout, QHeaderView,
QPushButton, QSizePolicy, QSpacerItem, QTableWidget,
QTableWidgetItem, QVBoxLayout, QWidget)
class Ui_ProjectXsltParamsDialog(object):
def setupUi(self, ProjectXsltParamsDialog):
if not ProjectXsltParamsDialog.objectName():
ProjectXsltParamsDialog.setObjectName(u"ProjectXsltParamsDialog")
ProjectXsltParamsDialog.resize(600, 400)
ProjectXsltParamsDialog.setModal(True)
self.verticalLayout = QVBoxLayout(ProjectXsltParamsDialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.xsltParamsGroupBox = QGroupBox(ProjectXsltParamsDialog)
self.xsltParamsGroupBox.setObjectName(u"xsltParamsGroupBox")
self.xsltParamsLayout = QVBoxLayout(self.xsltParamsGroupBox)
self.xsltParamsLayout.setObjectName(u"xsltParamsLayout")
self.xsltParamsLayout.setContentsMargins(0, 0, 0, 0)
self.xsltParamsTable = QTableWidget(self.xsltParamsGroupBox)
if (self.xsltParamsTable.columnCount() < 2):
self.xsltParamsTable.setColumnCount(2)
__qtablewidgetitem = QTableWidgetItem()
self.xsltParamsTable.setHorizontalHeaderItem(0, __qtablewidgetitem)
__qtablewidgetitem1 = QTableWidgetItem()
self.xsltParamsTable.setHorizontalHeaderItem(1, __qtablewidgetitem1)
self.xsltParamsTable.setObjectName(u"xsltParamsTable")
self.xsltParamsTable.setFrameShape(QFrame.Shape.NoFrame)
self.xsltParamsTable.setColumnCount(2)
self.xsltParamsTable.horizontalHeader().setVisible(True)
self.xsltParamsLayout.addWidget(self.xsltParamsTable)
self.xsltParamsButtonLayout = QHBoxLayout()
self.xsltParamsButtonLayout.setObjectName(u"xsltParamsButtonLayout")
self.horizontalSpacer_left = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.xsltParamsButtonLayout.addItem(self.horizontalSpacer_left)
self.addParamButton = QPushButton(self.xsltParamsGroupBox)
self.addParamButton.setObjectName(u"addParamButton")
icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ListAdd))
self.addParamButton.setIcon(icon)
self.xsltParamsButtonLayout.addWidget(self.addParamButton)
self.removeParamButton = QPushButton(self.xsltParamsGroupBox)
self.removeParamButton.setObjectName(u"removeParamButton")
icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ListRemove))
self.removeParamButton.setIcon(icon1)
self.xsltParamsButtonLayout.addWidget(self.removeParamButton)
self.horizontalSpacer_right = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.xsltParamsButtonLayout.addItem(self.horizontalSpacer_right)
self.xsltParamsLayout.addLayout(self.xsltParamsButtonLayout)
self.verticalLayout.addWidget(self.xsltParamsGroupBox)
self.buttonBox = QDialogButtonBox(ProjectXsltParamsDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
self.buttonBox.setCenterButtons(True)
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(ProjectXsltParamsDialog)
self.buttonBox.accepted.connect(ProjectXsltParamsDialog.accept)
self.buttonBox.rejected.connect(ProjectXsltParamsDialog.reject)
QMetaObject.connectSlotsByName(ProjectXsltParamsDialog)
# setupUi
def retranslateUi(self, ProjectXsltParamsDialog):
ProjectXsltParamsDialog.setWindowTitle(QCoreApplication.translate("ProjectXsltParamsDialog", u"Projektweite XSLT-Parameter", None))
self.xsltParamsGroupBox.setTitle(QCoreApplication.translate("ProjectXsltParamsDialog", u"XSLT-Parameter", None))
___qtablewidgetitem = self.xsltParamsTable.horizontalHeaderItem(0)
___qtablewidgetitem.setText(QCoreApplication.translate("ProjectXsltParamsDialog", u"Parameter", None));
___qtablewidgetitem1 = self.xsltParamsTable.horizontalHeaderItem(1)
___qtablewidgetitem1.setText(QCoreApplication.translate("ProjectXsltParamsDialog", u"Wert", None));
self.addParamButton.setText(QCoreApplication.translate("ProjectXsltParamsDialog", u"Parameter hinzuf\u00fcgen", None))
self.removeParamButton.setText(QCoreApplication.translate("ProjectXsltParamsDialog", u"Parameter entfernen", None))
# retranslateUi
+3
View File
@@ -486,6 +486,9 @@ class TransformationMixin:
xslt_params.update(parent_params)
logger.debug(f"Hierarchische Parameter gesammelt: {parent_params}")
else:
# Ohne TreeWidgetItem-Kontext: nur Projekt-Parameter als Basis
if hasattr(self, "project") and self.project and self.project.xslt_params:
xslt_params.update(self.project.xslt_params)
logger.warning(
"Kein TreeWidgetItem-Kontext verfügbar - "
"übergeordnete TreeNode-Parameter werden nicht berücksichtigt"
+5 -1
View File
@@ -1683,7 +1683,11 @@ class TreeManagerMixin:
parent_params = {}
try:
# Sammle alle Eltern-Items in einer Liste (von unten nach oben)
# 1. Projektweite Parameter als Basis (niedrigste Priorität)
if hasattr(self, "project") and self.project and self.project.xslt_params:
parent_params.update(self.project.xslt_params)
# 2. Sammle alle Eltern-Items in einer Liste (von unten nach oben)
parents = []
current_item = item.parent()
Generated
+1 -1
View File
@@ -34,7 +34,7 @@ wheels = [
[[package]]
name = "documentor"
version = "1.3.0"
version = "1.4.0"
source = { virtual = "." }
dependencies = [
{ name = "connectorx" },