Feat: Individuelle Knoten-Styles im XSL-Abhängigkeitsgraph nach Dateistatus (v1.2.4)
Knoten im vis.js Netzwerkgraph werden nun farblich nach drei Kategorien unterschieden: blau (nur im Verzeichnis), grün (im Projekt referenziert), rot/gestrichelt (im Projekt, aber Datei fehlt). Inkl. Legende und erweitertem Tooltip mit Projekt-Zugehörigkeit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<!-- Paket-Definition (ersetzt Product in v4) -->
|
<!-- Paket-Definition (ersetzt Product in v4) -->
|
||||||
<Package
|
<Package
|
||||||
Name="DocuMentor"
|
Name="DocuMentor"
|
||||||
Version="1.2.3"
|
Version="1.2.4"
|
||||||
Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
|
Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
|
||||||
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
|
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
|
||||||
Language="1031"
|
Language="1031"
|
||||||
|
|||||||
@@ -253,5 +253,5 @@ HINWEISE
|
|||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
Stand: März 2026
|
Stand: März 2026
|
||||||
Erstellt für: DocuMentor v1.2.3
|
Erstellt für: DocuMentor v1.2.4
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
; Build-Befehl: iscc installer.iss
|
; Build-Befehl: iscc installer.iss
|
||||||
|
|
||||||
#define MyAppName "DocuMentor"
|
#define MyAppName "DocuMentor"
|
||||||
#define MyAppVersion "1.2.3"
|
#define MyAppVersion "1.2.4"
|
||||||
#define MyAppPublisher "Ihr Name/Organisation"
|
#define MyAppPublisher "Ihr Name/Organisation"
|
||||||
#define MyAppURL "https://github.com/yourusername/xsl-validator"
|
#define MyAppURL "https://github.com/yourusername/xsl-validator"
|
||||||
#define MyAppExeName "DocuMentor.exe"
|
#define MyAppExeName "DocuMentor.exe"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "DocuMentor"
|
name = "DocuMentor"
|
||||||
version = "1.2.3"
|
version = "1.2.4"
|
||||||
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
|
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|||||||
+10
-1
@@ -448,10 +448,19 @@ class MainWindow(
|
|||||||
|
|
||||||
self.xsl_dependency_graph = XslDependencyGraph()
|
self.xsl_dependency_graph = XslDependencyGraph()
|
||||||
|
|
||||||
|
# Projekt-XSL-Pfade sammeln (absolute Pfade aller im Projekt referenzierten XSL-Dateien)
|
||||||
|
project_xsl_paths: set[Path] = set()
|
||||||
|
if hasattr(self, "pdf_project") and self.pdf_project is not None:
|
||||||
|
for top_node in self.pdf_project.nodes:
|
||||||
|
for xsl_file in self._collect_xsl_files_recursive(top_node):
|
||||||
|
project_xsl_paths.add((xsl_dir.path_to_root_dir / xsl_file.xsl_file).resolve())
|
||||||
|
|
||||||
from ui.XslDependencyDialog import XslDependencyDialog
|
from ui.XslDependencyDialog import XslDependencyDialog
|
||||||
|
|
||||||
# open() statt exec() verwenden — QWebEngineView verträgt keinen verschachtelten Event-Loop
|
# open() statt exec() verwenden — QWebEngineView verträgt keinen verschachtelten Event-Loop
|
||||||
self._xsl_dep_dialog = XslDependencyDialog(self, xsl_dir.path_to_root_dir, self.xsl_dependency_graph)
|
self._xsl_dep_dialog = XslDependencyDialog(
|
||||||
|
self, xsl_dir.path_to_root_dir, self.xsl_dependency_graph, project_xsl_paths
|
||||||
|
)
|
||||||
self._xsl_dep_dialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
self._xsl_dep_dialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||||
self._xsl_dep_dialog.open()
|
self._xsl_dep_dialog.open()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -123,16 +123,24 @@ except ImportError:
|
|||||||
class XslDependencyDialog(QDialog):
|
class XslDependencyDialog(QDialog):
|
||||||
"""Dialog zur Anzeige des vollständigen XSL-Abhängigkeitsgraphen."""
|
"""Dialog zur Anzeige des vollständigen XSL-Abhängigkeitsgraphen."""
|
||||||
|
|
||||||
def __init__(self, parent, xsl_root_dir: Path, dependency_graph: XslDependencyGraph):
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent,
|
||||||
|
xsl_root_dir: Path,
|
||||||
|
dependency_graph: XslDependencyGraph,
|
||||||
|
project_xsl_paths: set[Path] | None = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
parent: Eltern-Widget
|
parent: Eltern-Widget
|
||||||
xsl_root_dir: Wurzelverzeichnis der XSL-Dateien
|
xsl_root_dir: Wurzelverzeichnis der XSL-Dateien
|
||||||
dependency_graph: XSL-Abhängigkeitsgraph-Instanz
|
dependency_graph: XSL-Abhängigkeitsgraph-Instanz
|
||||||
|
project_xsl_paths: Absolute Pfade aller im Projekt referenzierten XSL-Dateien (None = kein Projekt)
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.xsl_root_dir = xsl_root_dir
|
self.xsl_root_dir = xsl_root_dir
|
||||||
self.dependency_graph = dependency_graph
|
self.dependency_graph = dependency_graph
|
||||||
|
self._project_xsl_paths: set[Path] = project_xsl_paths if project_xsl_paths is not None else set()
|
||||||
self._web_view: "QWebEngineView | None" = None
|
self._web_view: "QWebEngineView | None" = None
|
||||||
self._graph_loaded = False
|
self._graph_loaded = False
|
||||||
self._temp_html_file: tempfile.NamedTemporaryFile | None = None
|
self._temp_html_file: tempfile.NamedTemporaryFile | None = None
|
||||||
@@ -660,37 +668,81 @@ class XslDependencyDialog(QDialog):
|
|||||||
"""
|
"""
|
||||||
Konvertiert den Abhängigkeitsgraph in vis.js-kompatible JSON-Strukturen.
|
Konvertiert den Abhängigkeitsgraph in vis.js-kompatible JSON-Strukturen.
|
||||||
|
|
||||||
|
Knoten werden in drei Kategorien eingeteilt:
|
||||||
|
- Kategorie 1 (blau): Nur im Verzeichnis, nicht im Projekt
|
||||||
|
- Kategorie 2 (grün): Im Projekt und im Verzeichnis
|
||||||
|
- Kategorie 3 (rot): Im Projekt referenziert, aber Datei fehlt
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple[str, str]: (nodes_json, edges_json)
|
tuple[str, str]: (nodes_json, edges_json)
|
||||||
"""
|
"""
|
||||||
# Direkten Graph verwenden (nicht transitiv)
|
# Direkten Graph verwenden (nicht transitiv)
|
||||||
direct_graph = self.dependency_graph.build_direct_graph(self.xsl_root_dir)
|
direct_graph = self.dependency_graph.build_direct_graph(self.xsl_root_dir)
|
||||||
|
|
||||||
# Pfad → ID Mapping
|
# Dateisystem-Pfade (Kategorie 1 und 2)
|
||||||
all_paths = sorted(direct_graph.keys(), key=lambda p: self._rel_path(p).lower())
|
fs_paths = sorted(direct_graph.keys(), key=lambda p: self._rel_path(p).lower())
|
||||||
path_to_id: dict[Path, int] = {path: idx for idx, path in enumerate(all_paths)}
|
path_to_id: dict[Path, int] = {path: idx for idx, path in enumerate(fs_paths)}
|
||||||
|
|
||||||
|
# Kategorie 3: Im Projekt referenziert, aber nicht im Dateisystem
|
||||||
|
ghost_paths = sorted(
|
||||||
|
self._project_xsl_paths - set(direct_graph.keys()),
|
||||||
|
key=lambda p: p.name.lower(),
|
||||||
|
)
|
||||||
|
ghost_offset = len(fs_paths)
|
||||||
|
for idx, path in enumerate(ghost_paths):
|
||||||
|
path_to_id[path] = ghost_offset + idx
|
||||||
|
|
||||||
|
# Farb-Definitionen pro Kategorie
|
||||||
|
color_fs_only = {"background": "#4a90d9", "border": "#2c5f9e"} # Kat. 1: nur Verzeichnis
|
||||||
|
color_in_project = {"background": "#4caf50", "border": "#2e7d32"} # Kat. 2: Projekt + Verzeichnis
|
||||||
|
color_ghost = {"background": "#e74c3c", "border": "#c0392b"} # Kat. 3: Projekt, Datei fehlt
|
||||||
|
|
||||||
# Nodes
|
|
||||||
nodes = []
|
nodes = []
|
||||||
for path, node_id in path_to_id.items():
|
|
||||||
|
# Kategorie 1 und 2: Dateien die im Verzeichnis existieren
|
||||||
|
for path in fs_paths:
|
||||||
|
node_id = path_to_id[path]
|
||||||
rel = self._rel_path(path)
|
rel = self._rel_path(path)
|
||||||
label = path.name
|
label = path.name
|
||||||
direct_deps = len(direct_graph.get(path, set()))
|
direct_deps = len(direct_graph.get(path, set()))
|
||||||
reverse_count = len(self._reverse_map.get(path, set()))
|
reverse_count = len(self._reverse_map.get(path, set()))
|
||||||
title = f"<b>{rel}</b><br>Importiert: {direct_deps}<br>Importiert von: {reverse_count}"
|
value = max(direct_deps + reverse_count, 1)
|
||||||
# Knotengröße basierend auf Verbindungsanzahl
|
in_project = path in self._project_xsl_paths
|
||||||
value = direct_deps + reverse_count
|
|
||||||
|
|
||||||
nodes.append(
|
title = (
|
||||||
{
|
f"<b>{rel}</b><br>"
|
||||||
|
f"Importiert: {direct_deps}<br>"
|
||||||
|
f"Importiert von: {reverse_count}<br>"
|
||||||
|
f"Im Projekt: {'Ja' if in_project else 'Nein'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
node: dict = {
|
||||||
"id": node_id,
|
"id": node_id,
|
||||||
"label": label,
|
"label": label,
|
||||||
"title": title,
|
"title": title,
|
||||||
"value": max(value, 1),
|
"value": value,
|
||||||
|
"color": color_in_project if in_project else color_fs_only,
|
||||||
|
"borderWidth": 3 if in_project else 1,
|
||||||
|
}
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
# Kategorie 3: Ghost-Knoten (im Projekt, aber Datei fehlt im Verzeichnis)
|
||||||
|
for path in ghost_paths:
|
||||||
|
rel = self._rel_path(path)
|
||||||
|
title = f"<b>{rel}</b><br><i>Datei nicht gefunden</i><br>Im Projekt: Ja"
|
||||||
|
nodes.append(
|
||||||
|
{
|
||||||
|
"id": path_to_id[path],
|
||||||
|
"label": path.name,
|
||||||
|
"title": title,
|
||||||
|
"value": 1,
|
||||||
|
"color": color_ghost,
|
||||||
|
"borderWidth": 2,
|
||||||
|
"shapeProperties": {"borderDashes": [5, 5]},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Edges (nur direkte Abhängigkeiten)
|
# Edges (nur zwischen Dateisystem-Knoten — Ghost-Knoten haben keine bekannten Abhängigkeiten)
|
||||||
edges = []
|
edges = []
|
||||||
for path, deps in direct_graph.items():
|
for path, deps in direct_graph.items():
|
||||||
from_id = path_to_id.get(path)
|
from_id = path_to_id.get(path)
|
||||||
@@ -757,6 +809,28 @@ class XslDependencyDialog(QDialog):
|
|||||||
font-size: 13px !important;
|
font-size: 13px !important;
|
||||||
box-shadow: 2px 2px 6px rgba(0,0,0,0.3) !important;
|
box-shadow: 2px 2px 6px rgba(0,0,0,0.3) !important;
|
||||||
}}
|
}}
|
||||||
|
#graph-legend {{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
left: 16px;
|
||||||
|
background: {bg_hex}cc;
|
||||||
|
border: 1px solid #888;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: {text_hex};
|
||||||
|
z-index: 10;
|
||||||
|
line-height: 1.8;
|
||||||
|
pointer-events: none;
|
||||||
|
}}
|
||||||
|
.legend-dot {{
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{vis_js_content}
|
{vis_js_content}
|
||||||
@@ -764,6 +838,11 @@ class XslDependencyDialog(QDialog):
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="graph-container"></div>
|
<div id="graph-container"></div>
|
||||||
|
<div id="graph-legend">
|
||||||
|
<span class="legend-dot" style="background:#4a90d9;border:2px solid #2c5f9e;"></span>Nur im Verzeichnis<br>
|
||||||
|
<span class="legend-dot" style="background:#4caf50;border:3px solid #2e7d32;"></span>Im Projekt & Verzeichnis<br>
|
||||||
|
<span class="legend-dot" style="background:#e74c3c;border:2px dashed #c0392b;"></span>Im Projekt, Datei fehlt
|
||||||
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var nodesData = {nodes_json};
|
var nodesData = {nodes_json};
|
||||||
var edgesData = {edges_json};
|
var edgesData = {edges_json};
|
||||||
|
|||||||
Reference in New Issue
Block a user