Feat: XSL-Abhängigkeitsgraph für import/include-Erkennung in Transformations-Pipeline

is_up_to_date() prüft nun auch transitiv importierte/inkludierte XSL-Dateien.
Abhängigkeiten werden per Tooltip und Kontextmenü-Aktion im TreeWidget angezeigt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 19:37:05 +01:00
parent 140905af77
commit 71fa48a514
4 changed files with 248 additions and 0 deletions
+109
View File
@@ -389,6 +389,13 @@ class TreeManagerMixin:
menu.addSeparator()
action_deps = QAction("Abhängigkeiten anzeigen", self)
action_deps.setIcon(QIcon(QIcon.fromTheme("view-list-tree")))
action_deps.triggered.connect(lambda: self._show_xsl_dependencies(item))
menu.addAction(action_deps)
menu.addSeparator()
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_xsl_file(item))
@@ -567,6 +574,9 @@ class TreeManagerMixin:
item.setDisabled(True)
item.setToolTip(0, f"XSL-Datei nicht gefunden: {xsl_file_abs}")
logger.warning(f"XSL-Datei nicht vorhanden: {xsl_file_abs}")
else:
# Tooltip mit Abhängigkeiten (import/include) setzen
self._set_xsl_dependency_tooltip(item, xsl_file_abs)
# Lade XML-Dateien als Knoten
if node.xmls:
@@ -1189,6 +1199,105 @@ class TreeManagerMixin:
logger.error(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)
def _get_xsl_abs_path(self, xsl_file_obj: XslFile) -> Path | None:
"""
Ermittelt den absoluten Pfad einer XSL-Datei anhand der Projekt-Konfiguration.
Args:
xsl_file_obj: Das XslFile-Objekt
Returns:
Absoluter Pfad oder None wenn nicht ermittelbar
"""
if not hasattr(self, "project") or not self.project:
return None
from conf import app_settings
xsl_dir = next((xd for xd in app_settings.xsl_dirs if xd.id == self.project.xsl_dir_id), None)
if not xsl_dir:
return None
return xsl_dir.path_to_root_dir / xsl_file_obj.xsl_file
def _ensure_xsl_dependency_graph(self):
"""Stellt sicher, dass der XSL-Abhängigkeitsgraph initialisiert ist."""
if not hasattr(self, "xsl_dependency_graph") or self.xsl_dependency_graph is None:
from xsl_dependencies import XslDependencyGraph
self.xsl_dependency_graph = XslDependencyGraph()
def _set_xsl_dependency_tooltip(self, item: QTreeWidgetItem, xsl_file_abs: Path):
"""
Setzt einen Tooltip mit den Abhängigkeiten (import/include) einer XSL-Datei.
Args:
item: Das TreeWidgetItem der XSL-Datei
xsl_file_abs: Absoluter Pfad zur XSL-Datei
"""
self._ensure_xsl_dependency_graph()
deps = self.xsl_dependency_graph.get_dependencies(xsl_file_abs)
if not deps:
item.setToolTip(0, f"{xsl_file_abs.name}\nKeine Abhängigkeiten (import/include)")
return
# Sortierte Liste der Abhängigkeiten (nur Dateinamen)
dep_names = sorted(dep.name for dep in deps)
dep_list = "\n".join(f" - {name}" for name in dep_names)
tooltip = f"{xsl_file_abs.name}\n{len(deps)} Abhängigkeit(en):\n{dep_list}"
item.setToolTip(0, tooltip)
def _show_xsl_dependencies(self, item: QTreeWidgetItem):
"""
Zeigt einen Dialog mit den Abhängigkeiten einer XSL-Datei.
Args:
item: Das TreeWidgetItem der XSL-Datei
"""
xsl_file_obj = item.data(0, Qt.ItemDataRole.UserRole)
if not isinstance(xsl_file_obj, XslFile):
return
xsl_file_abs = self._get_xsl_abs_path(xsl_file_obj)
if not xsl_file_abs or not xsl_file_abs.exists():
QMessageBox.warning(self, "Fehler", f"XSL-Datei nicht gefunden: {xsl_file_abs}")
return
self._ensure_xsl_dependency_graph()
deps = self.xsl_dependency_graph.get_dependencies(xsl_file_abs)
if not deps:
QMessageBox.information(
self,
f"Abhängigkeiten: {xsl_file_obj.bez}",
f"Die XSL-Datei '{xsl_file_abs.name}' hat keine Abhängigkeiten (import/include).",
)
return
# Sortierte Liste mit relativen Pfaden (relativ zum XSL-Verzeichnis)
xsl_root = xsl_file_abs.parent
from conf import app_settings
xsl_dir = next((xd for xd in app_settings.xsl_dirs if xd.id == self.project.xsl_dir_id), None)
if xsl_dir:
xsl_root = xsl_dir.path_to_root_dir
dep_lines = []
for dep in sorted(deps, key=lambda p: p.name):
try:
rel_path = dep.relative_to(xsl_root)
except ValueError:
rel_path = dep.name
dep_lines.append(f" - {rel_path}")
dep_text = "\n".join(dep_lines)
QMessageBox.information(
self,
f"Abhängigkeiten: {xsl_file_obj.bez}",
f"Die XSL-Datei '{xsl_file_abs.name}' importiert/inkludiert {len(deps)} Datei(en):\n\n{dep_text}",
)
# Kontextmenü-Aktionen für XmlFile
def _edit_xml_file(self, item):
"""Bearbeitet eine XML-Datei."""