Feat: Sidebar mit Suchfilter und lxml-Parser im XslDependencyDialog

- Ein-/ausblendbare Sidebar mit tab-übergreifender Suche hinzugefügt
- Graph-Suchfilter blendet nicht-betroffene XSL-Dateien aus dem Netzwerkgraph aus
- Regex-basierte XSL-Abhängigkeitserkennung durch lxml-Parser ersetzt
- Suchfilter wird beim Tab-Wechsel erneut angewendet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 21:15:16 +01:00
parent 48ab596476
commit 3dcbf783b1
6 changed files with 639 additions and 183 deletions
+26 -9
View File
@@ -7,13 +7,28 @@ Der Graph wird im Speicher gehalten und bei Änderungen (mtime) automatisch inva
"""
import logging
import re
import time
from lxml import etree
from pathlib import Path
logger = logging.getLogger(__name__)
# Regex für xsl:import und xsl:include href-Attribute
_IMPORT_INCLUDE_PATTERN = re.compile(r'<xsl:(?:import|include)\s+href=["\']([^"\']+)["\']', re.IGNORECASE)
_XSL_NAMESPACE = "http://www.w3.org/1999/XSL/Transform"
def _parse_import_include_hrefs(content: str) -> list[str]:
"""Parst XSL-Dateiinhalt und gibt alle href-Werte von xsl:import/xsl:include zurück."""
try:
root = etree.fromstring(content.encode("utf-8"))
except etree.XMLSyntaxError:
return []
hrefs = []
for tag in ("import", "include"):
for elem in root.iter(f"{{{_XSL_NAMESPACE}}}{tag}"):
href = elem.get("href")
if href:
hrefs.append(href)
return hrefs
class XslDependencyGraph:
@@ -84,8 +99,7 @@ class XslDependencyGraph:
return result
# Finde alle import/include-Referenzen
for match in _IMPORT_INCLUDE_PATTERN.finditer(content):
href = match.group(1)
for href in _parse_import_include_hrefs(content):
referenced_path = (xsl_file.parent / href).resolve()
if referenced_path.exists() and referenced_path not in visited:
@@ -138,12 +152,14 @@ class XslDependencyGraph:
if not xsl_root_dir.exists():
return graph
start = time.perf_counter()
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
elapsed = time.perf_counter() - start
logger.info(f"Vollständiger XSL-Graph aufgebaut: {len(graph)} Dateien")
logger.info(f"Vollständiger XSL-Graph aufgebaut: {len(graph)} Dateien in {elapsed:.3f}s")
return graph
def _get_direct_dependencies(self, xsl_file: Path) -> set[Path]:
@@ -168,8 +184,7 @@ class XslDependencyGraph:
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)
for href in _parse_import_include_hrefs(content):
referenced_path = (xsl_file.parent / href).resolve()
if referenced_path.exists():
result.add(referenced_path)
@@ -191,11 +206,13 @@ class XslDependencyGraph:
if not xsl_root_dir.exists():
return graph
start = time.perf_counter()
for xsl_file in sorted(xsl_root_dir.rglob("*.xsl")):
xsl_file = xsl_file.resolve()
graph[xsl_file] = self._get_direct_dependencies(xsl_file)
elapsed = time.perf_counter() - start
logger.info(f"Direkter XSL-Graph aufgebaut: {len(graph)} Dateien")
logger.info(f"Direkter XSL-Graph aufgebaut: {len(graph)} Dateien in {elapsed:.3f}s")
return graph
def invalidate(self, xsl_file: Path | None = None):