Feat: Interaktiver XSL-Abhängigkeitsgraph mit vis.js und THIRD_PARTY_LICENSES aktualisiert
XslDependencyDialog mit zwei Tabs: Baumansicht (vorwärts/rückwärts-Abhängigkeiten) und interaktiver Netzwerkgraph (vis.js in QWebEngineView mit Physics-Simulation, Hover-Tooltips, Nachbar-Hervorhebung). Graceful Fallback wenn WebEngine fehlt. THIRD_PARTY_LICENSES um psutil, PyInstaller, Pillow, vis-network ergänzt und Versionen aktualisiert. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,109 @@ class XslDependencyGraph:
|
||||
|
||||
return result
|
||||
|
||||
def get_reverse_dependencies(self, xsl_file: Path, xsl_root_dir: Path) -> set[Path]:
|
||||
"""
|
||||
Gibt alle Dateien zurück, die die gegebene XSL-Datei direkt oder transitiv importieren/inkludieren.
|
||||
|
||||
Args:
|
||||
xsl_file: Absoluter Pfad zur XSL-Datei
|
||||
xsl_root_dir: Wurzelverzeichnis der XSL-Dateien (für den Scan)
|
||||
|
||||
Returns:
|
||||
set[Path]: Menge aller Dateien, die diese Datei importieren
|
||||
"""
|
||||
xsl_file = xsl_file.resolve()
|
||||
result: set[Path] = set()
|
||||
|
||||
# Scanne alle XSL-Dateien im Verzeichnis
|
||||
if not xsl_root_dir.exists():
|
||||
return result
|
||||
|
||||
for candidate in xsl_root_dir.rglob("*.xsl"):
|
||||
candidate = candidate.resolve()
|
||||
if candidate == xsl_file:
|
||||
continue
|
||||
deps = self.get_dependencies(candidate)
|
||||
if xsl_file in deps:
|
||||
result.add(candidate)
|
||||
|
||||
return result
|
||||
|
||||
def build_full_graph(self, xsl_root_dir: Path) -> dict[Path, set[Path]]:
|
||||
"""
|
||||
Baut den vollständigen Abhängigkeitsgraph für alle XSL-Dateien in einem Verzeichnis auf.
|
||||
|
||||
Args:
|
||||
xsl_root_dir: Wurzelverzeichnis der XSL-Dateien
|
||||
|
||||
Returns:
|
||||
dict[Path, set[Path]]: Mapping von XSL-Datei zu ihren Abhängigkeiten
|
||||
"""
|
||||
graph: dict[Path, set[Path]] = {}
|
||||
|
||||
if not xsl_root_dir.exists():
|
||||
return graph
|
||||
|
||||
for xsl_file in sorted(xsl_root_dir.rglob("*.xsl")):
|
||||
xsl_file = xsl_file.resolve()
|
||||
deps = self.get_dependencies(xsl_file)
|
||||
graph[xsl_file] = deps
|
||||
|
||||
logger.info(f"Vollständiger XSL-Graph aufgebaut: {len(graph)} Dateien")
|
||||
return graph
|
||||
|
||||
def _get_direct_dependencies(self, xsl_file: Path) -> set[Path]:
|
||||
"""
|
||||
Gibt nur die direkten (nicht-transitiven) import/include-Abhängigkeiten zurück.
|
||||
|
||||
Args:
|
||||
xsl_file: Absoluter Pfad zur XSL-Datei
|
||||
|
||||
Returns:
|
||||
set[Path]: Menge der direkt importierten/inkludierten XSL-Dateien
|
||||
"""
|
||||
xsl_file = xsl_file.resolve()
|
||||
result: set[Path] = set()
|
||||
|
||||
if not xsl_file.exists():
|
||||
return result
|
||||
|
||||
try:
|
||||
content = xsl_file.read_text(encoding="utf-8", errors="replace")
|
||||
except Exception as e:
|
||||
logger.warning(f"Konnte XSL-Datei nicht lesen: {xsl_file} ({e})")
|
||||
return result
|
||||
|
||||
for match in _IMPORT_INCLUDE_PATTERN.finditer(content):
|
||||
href = match.group(1)
|
||||
referenced_path = (xsl_file.parent / href).resolve()
|
||||
if referenced_path.exists():
|
||||
result.add(referenced_path)
|
||||
|
||||
return result
|
||||
|
||||
def build_direct_graph(self, xsl_root_dir: Path) -> dict[Path, set[Path]]:
|
||||
"""
|
||||
Baut einen Graph mit nur direkten (nicht-transitiven) Abhängigkeiten auf.
|
||||
|
||||
Args:
|
||||
xsl_root_dir: Wurzelverzeichnis der XSL-Dateien
|
||||
|
||||
Returns:
|
||||
dict[Path, set[Path]]: Mapping von XSL-Datei zu ihren direkten Abhängigkeiten
|
||||
"""
|
||||
graph: dict[Path, set[Path]] = {}
|
||||
|
||||
if not xsl_root_dir.exists():
|
||||
return graph
|
||||
|
||||
for xsl_file in sorted(xsl_root_dir.rglob("*.xsl")):
|
||||
xsl_file = xsl_file.resolve()
|
||||
graph[xsl_file] = self._get_direct_dependencies(xsl_file)
|
||||
|
||||
logger.info(f"Direkter XSL-Graph aufgebaut: {len(graph)} Dateien")
|
||||
return graph
|
||||
|
||||
def invalidate(self, xsl_file: Path | None = None):
|
||||
"""
|
||||
Invalidiert den Cache für eine bestimmte Datei oder den gesamten Cache.
|
||||
|
||||
Reference in New Issue
Block a user