Feat: Veraltete XSL-Einträge nach DB-Import erkennen und entfernen (v1.2.7)
Beim Import aus der PostgreSQL-Datenbank werden nun XSL-Einträge erkannt, die nicht mehr in der DB vorhanden sind. Ein Dialog zeigt diese gruppiert in einer Baumansicht an und bietet die Option, sie samt nicht mehr verwendeter XML-/PDF-Dateien aus dem Projekt zu entfernen. Leere TreeNodes werden automatisch bereinigt. Zusätzlich: SQL-Filter `r3.export = 0` in data.sql ergänzt. 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.6"
|
Version="1.2.7"
|
||||||
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.6
|
Erstellt für: DocuMentor v1.2.7
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|||||||
+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.6"
|
#define MyAppVersion "1.2.7"
|
||||||
#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.6"
|
version = "1.2.7"
|
||||||
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"}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
"""
|
||||||
|
Obsolete-Detector — Erkennung veralteter Projekteinträge nach DB-Import.
|
||||||
|
|
||||||
|
Reine Analyselogik ohne Qt-Abhängigkeit. Findet XslFile-Einträge im Projekt,
|
||||||
|
die nicht mehr in der Datenbank vorhanden sind.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from conf import TreeNode, XslFile
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ObsoleteXslEntry:
|
||||||
|
"""Ein XslFile-Eintrag, der nicht mehr in der Datenbank vorhanden ist."""
|
||||||
|
|
||||||
|
xsl_file: XslFile
|
||||||
|
parent_node: TreeNode
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ObsoleteGroup:
|
||||||
|
"""Gruppe veralteter XslFile-Einträge unter einem gemeinsamen Eltern-Pfad."""
|
||||||
|
|
||||||
|
node_path: list[str]
|
||||||
|
parent_node: TreeNode
|
||||||
|
xsl_entries: list[ObsoleteXslEntry] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_db_xsl_ids(new_nodes: list[TreeNode]) -> set[tuple]:
|
||||||
|
"""
|
||||||
|
Extrahiert alle XslFile-IDs aus den frisch aus der DB geladenen Nodes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_nodes: Nodes aus _process_sql_data()
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Set aller XslFile-IDs (Tupel)
|
||||||
|
"""
|
||||||
|
ids: set[tuple] = set()
|
||||||
|
_collect_ids_recursive(new_nodes, ids)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_ids_recursive(nodes: list, ids: set[tuple]) -> None:
|
||||||
|
for node in nodes:
|
||||||
|
if isinstance(node, XslFile):
|
||||||
|
ids.add(node.id)
|
||||||
|
elif isinstance(node, TreeNode) and node.children:
|
||||||
|
_collect_ids_recursive(node.children, ids)
|
||||||
|
|
||||||
|
|
||||||
|
def find_obsolete_xsl_entries(
|
||||||
|
project_nodes: list[TreeNode],
|
||||||
|
db_xsl_ids: set[tuple],
|
||||||
|
) -> list[ObsoleteGroup]:
|
||||||
|
"""
|
||||||
|
Findet alle XslFile-Einträge im Projekt, die nicht mehr in der DB vorhanden sind.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_nodes: Aktuelle Nodes aus pdf_project.nodes
|
||||||
|
db_xsl_ids: Set aller XslFile-IDs aus der DB (von extract_db_xsl_ids)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste von ObsoleteGroup, sortiert nach Hierarchiepfad
|
||||||
|
"""
|
||||||
|
groups: dict[int, ObsoleteGroup] = {}
|
||||||
|
|
||||||
|
_find_obsolete_recursive(project_nodes, db_xsl_ids, path=[], groups=groups)
|
||||||
|
|
||||||
|
# Sortieren nach Pfad für stabile Darstellung
|
||||||
|
return sorted(groups.values(), key=lambda g: g.node_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_obsolete_recursive(
|
||||||
|
nodes: list,
|
||||||
|
db_xsl_ids: set[tuple],
|
||||||
|
path: list[str],
|
||||||
|
groups: dict[int, ObsoleteGroup],
|
||||||
|
) -> None:
|
||||||
|
for node in nodes:
|
||||||
|
if isinstance(node, TreeNode):
|
||||||
|
_find_obsolete_in_tree_node(node, db_xsl_ids, path, groups)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_obsolete_in_tree_node(
|
||||||
|
node: TreeNode,
|
||||||
|
db_xsl_ids: set[tuple],
|
||||||
|
parent_path: list[str],
|
||||||
|
groups: dict[int, ObsoleteGroup],
|
||||||
|
) -> None:
|
||||||
|
current_path = parent_path + [node.bez]
|
||||||
|
|
||||||
|
for child in node.children:
|
||||||
|
if isinstance(child, XslFile):
|
||||||
|
if child.id not in db_xsl_ids:
|
||||||
|
node_id = id(node)
|
||||||
|
if node_id not in groups:
|
||||||
|
groups[node_id] = ObsoleteGroup(
|
||||||
|
node_path=current_path,
|
||||||
|
parent_node=node,
|
||||||
|
)
|
||||||
|
groups[node_id].xsl_entries.append(ObsoleteXslEntry(xsl_file=child, parent_node=node))
|
||||||
|
elif isinstance(child, TreeNode):
|
||||||
|
_find_obsolete_in_tree_node(child, db_xsl_ids, current_path, groups)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_empty_tree_nodes(nodes: list) -> list:
|
||||||
|
"""
|
||||||
|
Entfernt rekursiv alle TreeNodes, deren children-Liste nach der Bereinigung leer ist.
|
||||||
|
XslFile-Einträge werden immer behalten (sie wurden bereits entfernt oder sind noch gültig).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodes: Liste von TreeNode|XslFile
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Bereinigte Liste ohne leere TreeNodes
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for node in nodes:
|
||||||
|
if isinstance(node, TreeNode):
|
||||||
|
node.children = remove_empty_tree_nodes(node.children)
|
||||||
|
if node.children:
|
||||||
|
result.append(node)
|
||||||
|
# leere TreeNodes stillschweigend verwerfen
|
||||||
|
else:
|
||||||
|
result.append(node)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def collect_unused_xml_files(
|
||||||
|
obsolete_groups: list[ObsoleteGroup],
|
||||||
|
project_dir: Path,
|
||||||
|
is_xml_used_elsewhere_fn,
|
||||||
|
) -> list[tuple[Path, Path]]:
|
||||||
|
"""
|
||||||
|
Sammelt XML-Dateien der veralteten XslFiles, die nirgends anders mehr verwendet werden.
|
||||||
|
|
||||||
|
WICHTIG: Muss nach dem Entfernen der XslFiles aus dem Modell aufgerufen werden,
|
||||||
|
damit is_xml_used_elsewhere_fn korrekte Ergebnisse liefert.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obsolete_groups: Die veralteten Gruppen
|
||||||
|
project_dir: Absoluter Pfad zum Projektverzeichnis
|
||||||
|
is_xml_used_elsewhere_fn: Callable(xml_path, exclude_xsl_file) -> bool
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Liste von (xml_path_relativ, xml_path_absolut) für nicht mehr verwendete XML-Dateien
|
||||||
|
"""
|
||||||
|
unused: list[tuple[Path, Path]] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
|
||||||
|
for group in obsolete_groups:
|
||||||
|
for entry in group.xsl_entries:
|
||||||
|
for xml_file_obj in entry.xsl_file.xmls:
|
||||||
|
xml_path_str = str(xml_file_obj.xml)
|
||||||
|
if xml_path_str in seen:
|
||||||
|
continue
|
||||||
|
seen.add(xml_path_str)
|
||||||
|
xml_abs = project_dir / xml_file_obj.xml
|
||||||
|
if xml_abs.exists():
|
||||||
|
if not is_xml_used_elsewhere_fn(xml_file_obj.xml, entry.xsl_file):
|
||||||
|
unused.append((xml_file_obj.xml, xml_abs))
|
||||||
|
|
||||||
|
return unused
|
||||||
+1
-1
@@ -8,4 +8,4 @@ select
|
|||||||
r3.xsl_datei
|
r3.xsl_datei
|
||||||
from reporttyp r
|
from reporttyp r
|
||||||
inner join report r2 on r.reporttyp = r2.reporttyp and r2.aktiv = 1
|
inner join report r2 on r.reporttyp = r2.reporttyp and r2.aktiv = 1
|
||||||
inner join repfile r3 on r2.reporttyp = r3.reporttyp and r2.report = r3.report and r3.xsl_datei is not null and r3.aktiv = 1
|
inner join repfile r3 on r2.reporttyp = r3.reporttyp and r2.report = r3.report and r3.xsl_datei is not null and r3.aktiv = 1 and r3.export = 0
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
ObsoleteEntriesDialog — Dialog zur Bestätigung des Entfernens veralteter Projekteinträge.
|
||||||
|
|
||||||
|
Zeigt XslFile-Einträge an, die nicht mehr in der Datenbank vorhanden sind,
|
||||||
|
und lässt den Benutzer entscheiden ob sie entfernt werden sollen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QCheckBox,
|
||||||
|
QDialog,
|
||||||
|
QDialogButtonBox,
|
||||||
|
QLabel,
|
||||||
|
QSizePolicy,
|
||||||
|
QTreeWidget,
|
||||||
|
QTreeWidgetItem,
|
||||||
|
QVBoxLayout,
|
||||||
|
)
|
||||||
|
|
||||||
|
from obsolete_detector import ObsoleteGroup
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ObsoleteEntriesDialog(QDialog):
|
||||||
|
"""
|
||||||
|
Dialog zur Anzeige und Bestätigung veralteter Einträge nach einem DB-Import.
|
||||||
|
|
||||||
|
Zeigt die veralteten XslFile-Einträge gruppiert nach ihrer Baumhierarchie an.
|
||||||
|
Der Benutzer kann entscheiden ob die Einträge entfernt und ob nicht mehr
|
||||||
|
verwendete XML-Dateien physisch gelöscht werden sollen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent, obsolete_groups: list[ObsoleteGroup]):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
parent: Eltern-Widget
|
||||||
|
obsolete_groups: Veraltete Einträge gruppiert nach Hierarchiepfad
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self._obsolete_groups = obsolete_groups
|
||||||
|
self._setup_ui()
|
||||||
|
self._populate_tree()
|
||||||
|
|
||||||
|
def _setup_ui(self) -> None:
|
||||||
|
"""Erstellt die UI-Elemente des Dialogs."""
|
||||||
|
total_count = sum(len(g.xsl_entries) for g in self._obsolete_groups)
|
||||||
|
|
||||||
|
self.setWindowTitle("Veraltete Einträge gefunden")
|
||||||
|
self.resize(640, 420)
|
||||||
|
self.setSizeGripEnabled(True)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
# Erklärungstext
|
||||||
|
info_label = QLabel(
|
||||||
|
f"<b>{total_count} XSL-Datei(en)</b> sind nicht mehr in der Datenbank vorhanden "
|
||||||
|
f"und können aus dem Projekt entfernt werden."
|
||||||
|
)
|
||||||
|
info_label.setWordWrap(True)
|
||||||
|
layout.addWidget(info_label)
|
||||||
|
|
||||||
|
# Baumansicht der veralteten Einträge
|
||||||
|
self._tree = QTreeWidget()
|
||||||
|
self._tree.setColumnCount(3)
|
||||||
|
self._tree.setHeaderLabels(["Bezeichnung", "XSL-Datei", "XML-Dateien"])
|
||||||
|
self._tree.setColumnWidth(0, 280)
|
||||||
|
self._tree.setColumnWidth(1, 200)
|
||||||
|
self._tree.setColumnWidth(2, 80)
|
||||||
|
self._tree.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
|
self._tree.setAlternatingRowColors(True)
|
||||||
|
self._tree.setEditTriggers(QTreeWidget.EditTrigger.NoEditTriggers)
|
||||||
|
layout.addWidget(self._tree)
|
||||||
|
|
||||||
|
# Checkbox für physische XML-Löschung
|
||||||
|
self._delete_xml_checkbox = QCheckBox("Nicht mehr verwendete XML-Dateien physisch löschen")
|
||||||
|
self._delete_xml_checkbox.setChecked(False)
|
||||||
|
layout.addWidget(self._delete_xml_checkbox)
|
||||||
|
|
||||||
|
# Dialog-Buttons
|
||||||
|
self._button_box = QDialogButtonBox()
|
||||||
|
remove_button = self._button_box.addButton(
|
||||||
|
"Veraltete Einträge entfernen", QDialogButtonBox.ButtonRole.AcceptRole
|
||||||
|
)
|
||||||
|
remove_button.setToolTip("Entfernt alle aufgelisteten Einträge aus dem Projekt")
|
||||||
|
self._button_box.addButton(QDialogButtonBox.StandardButton.Cancel)
|
||||||
|
self._button_box.accepted.connect(self.accept)
|
||||||
|
self._button_box.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(self._button_box)
|
||||||
|
|
||||||
|
def _populate_tree(self) -> None:
|
||||||
|
"""Befüllt den QTreeWidget mit den veralteten Einträgen."""
|
||||||
|
self._tree.clear()
|
||||||
|
|
||||||
|
for group in self._obsolete_groups:
|
||||||
|
# Hierarchiepfad als verschachtelte Items aufbauen
|
||||||
|
parent_item = self._tree.invisibleRootItem()
|
||||||
|
for path_part in group.node_path:
|
||||||
|
# Prüfe ob dieser Pfadteil bereits als Kind vorhanden ist
|
||||||
|
existing = None
|
||||||
|
for i in range(parent_item.childCount()):
|
||||||
|
child = parent_item.child(i)
|
||||||
|
if child.text(0) == path_part and not child.data(0, Qt.ItemDataRole.UserRole):
|
||||||
|
existing = child
|
||||||
|
break
|
||||||
|
if existing:
|
||||||
|
parent_item = existing
|
||||||
|
else:
|
||||||
|
node_item = QTreeWidgetItem(parent_item, [path_part])
|
||||||
|
font = node_item.font(0)
|
||||||
|
font.setBold(True)
|
||||||
|
node_item.setFont(0, font)
|
||||||
|
parent_item = node_item
|
||||||
|
|
||||||
|
# XslFile-Einträge unter dem Hierarchiepfad
|
||||||
|
for entry in group.xsl_entries:
|
||||||
|
xsl = entry.xsl_file
|
||||||
|
xml_count = str(len(xsl.xmls)) if xsl.xmls else "0"
|
||||||
|
xsl_item = QTreeWidgetItem(
|
||||||
|
parent_item,
|
||||||
|
[xsl.bez, xsl.xsl_file.name, xml_count],
|
||||||
|
)
|
||||||
|
xsl_item.setData(0, Qt.ItemDataRole.UserRole, xsl)
|
||||||
|
xsl_item.setToolTip(1, str(xsl.xsl_file))
|
||||||
|
|
||||||
|
self._tree.expandAll()
|
||||||
|
|
||||||
|
def delete_xml_files(self) -> bool:
|
||||||
|
"""
|
||||||
|
Gibt zurück ob der Benutzer die physische Löschung der XML-Dateien gewünscht hat.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True wenn die Checkbox aktiviert ist
|
||||||
|
"""
|
||||||
|
return self._delete_xml_checkbox.isChecked()
|
||||||
@@ -14,6 +14,13 @@ from PySide6.QtCore import QThread, Signal, Qt
|
|||||||
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
||||||
|
|
||||||
from conf import app_settings, TreeNode, XslFile
|
from conf import app_settings, TreeNode, XslFile
|
||||||
|
from obsolete_detector import (
|
||||||
|
collect_unused_xml_files,
|
||||||
|
extract_db_xsl_ids,
|
||||||
|
find_obsolete_xsl_entries,
|
||||||
|
remove_empty_tree_nodes,
|
||||||
|
)
|
||||||
|
from ui.ObsoleteEntriesDialog import ObsoleteEntriesDialog
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -31,6 +38,7 @@ class DatabaseQueryThread(QThread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
try:
|
try:
|
||||||
df = pl.read_database_uri(self.sql_query, self.connection_string, engine="connectorx").sort(
|
df = pl.read_database_uri(self.sql_query, self.connection_string, engine="connectorx").sort(
|
||||||
["reporttyp_bez", "report_bez", "repfile_bez"]
|
["reporttyp_bez", "report_bez", "repfile_bez"]
|
||||||
@@ -107,6 +115,7 @@ class DatabaseMixin:
|
|||||||
try:
|
try:
|
||||||
new_nodes = self._process_sql_data(df)
|
new_nodes = self._process_sql_data(df)
|
||||||
self._merge_nodes_with_existing(new_nodes)
|
self._merge_nodes_with_existing(new_nodes)
|
||||||
|
self._check_and_remove_obsolete_entries(new_nodes)
|
||||||
self._save_project_settings()
|
self._save_project_settings()
|
||||||
self._load_nodes_to_tree()
|
self._load_nodes_to_tree()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -209,6 +218,7 @@ class DatabaseMixin:
|
|||||||
list[TreeNode]: Liste der erstellten Root-Nodes
|
list[TreeNode]: Liste der erstellten Root-Nodes
|
||||||
"""
|
"""
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
@@ -350,3 +360,82 @@ class DatabaseMixin:
|
|||||||
if new_child.id not in existing_child_ids:
|
if new_child.id not in existing_child_ids:
|
||||||
existing_node.children.append(new_child)
|
existing_node.children.append(new_child)
|
||||||
logger.info(f"Neues Kind hinzugefügt zu Node {existing_node.id}: {new_child.bez}")
|
logger.info(f"Neues Kind hinzugefügt zu Node {existing_node.id}: {new_child.bez}")
|
||||||
|
|
||||||
|
def _check_and_remove_obsolete_entries(self, new_nodes: list[TreeNode]) -> None:
|
||||||
|
"""
|
||||||
|
Prüft nach dem Merge ob XslFile-Einträge nicht mehr in der DB vorhanden sind.
|
||||||
|
Zeigt einen Bestätigungsdialog und entfernt veraltete Einträge inklusive
|
||||||
|
PDF- und optionaler XML-Bereinigung.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
new_nodes: Frisch aus der DB geladene Nodes (Ergebnis von _process_sql_data)
|
||||||
|
"""
|
||||||
|
if not self.pdf_project or not self.pdf_project.nodes:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
db_xsl_ids = extract_db_xsl_ids(new_nodes)
|
||||||
|
obsolete_groups = find_obsolete_xsl_entries(self.pdf_project.nodes, db_xsl_ids)
|
||||||
|
|
||||||
|
if not obsolete_groups:
|
||||||
|
logger.debug("Keine veralteten Einträge gefunden")
|
||||||
|
return
|
||||||
|
|
||||||
|
total_count = sum(len(g.xsl_entries) for g in obsolete_groups)
|
||||||
|
logger.info(f"{total_count} veraltete XSL-Einträge gefunden")
|
||||||
|
|
||||||
|
dialog = ObsoleteEntriesDialog(self, obsolete_groups)
|
||||||
|
if dialog.exec() != ObsoleteEntriesDialog.DialogCode.Accepted:
|
||||||
|
logger.info("Entfernung veralteter Einträge vom Benutzer abgebrochen")
|
||||||
|
return
|
||||||
|
|
||||||
|
delete_xml = dialog.delete_xml_files()
|
||||||
|
|
||||||
|
# Phase 1: XslFiles aus dem Datenmodell entfernen
|
||||||
|
# WICHTIG: Zuerst entfernen, damit _is_xml_xsl_combination_used_elsewhere
|
||||||
|
# und _is_xml_file_used_elsewhere die gelöschten Einträge nicht mehr sehen
|
||||||
|
# (gleiche Reihenfolge wie in _delete_tree_node)
|
||||||
|
for group in obsolete_groups:
|
||||||
|
parent = group.parent_node
|
||||||
|
obsolete_xsl_ids = {id(entry.xsl_file) for entry in group.xsl_entries}
|
||||||
|
parent.children = [c for c in parent.children if id(c) not in obsolete_xsl_ids]
|
||||||
|
logger.info(f"{len(group.xsl_entries)} XSL-Einträge aus '{' > '.join(group.node_path)}' entfernt")
|
||||||
|
|
||||||
|
# Phase 2: PDF-Bereinigung für alle entfernten XslFiles
|
||||||
|
total_deleted_pdfs = 0
|
||||||
|
for group in obsolete_groups:
|
||||||
|
for entry in group.xsl_entries:
|
||||||
|
xsl_id = entry.xsl_file.id
|
||||||
|
for xml_file_obj in entry.xsl_file.xmls:
|
||||||
|
is_used = self._is_xml_xsl_combination_used_elsewhere(xml_file_obj.xml, xsl_id, entry.xsl_file)
|
||||||
|
if not is_used:
|
||||||
|
total_deleted_pdfs += self._delete_pdf_files_for_xml_xsl_combination(
|
||||||
|
xml_file_obj.xml, xsl_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if total_deleted_pdfs > 0:
|
||||||
|
logger.info(f"{total_deleted_pdfs} PDF-Datei(en) bereinigt")
|
||||||
|
|
||||||
|
# Phase 3: Leere TreeNodes bereinigen (bottom-up durch rekursiven Aufruf)
|
||||||
|
self.pdf_project.nodes = remove_empty_tree_nodes(self.pdf_project.nodes)
|
||||||
|
|
||||||
|
# Phase 4: Optionale physische XML-Löschung
|
||||||
|
if delete_xml:
|
||||||
|
unused_xml = collect_unused_xml_files(
|
||||||
|
obsolete_groups,
|
||||||
|
Path(self.project.project_dir),
|
||||||
|
self._is_xml_file_used_elsewhere,
|
||||||
|
)
|
||||||
|
for _rel, xml_abs in unused_xml:
|
||||||
|
try:
|
||||||
|
xml_abs.unlink()
|
||||||
|
logger.info(f"Physische XML-Datei gelöscht: {xml_abs}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Konnte XML-Datei nicht löschen: {xml_abs} - {e}")
|
||||||
|
|
||||||
|
logger.info(f"Bereinigung abgeschlossen: {total_count} veraltete Einträge entfernt")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Fehler beim Entfernen veralteter Einträge: {str(e)}"
|
||||||
|
logger.exception(error_msg)
|
||||||
|
QMessageBox.critical(self, "Fehler", error_msg)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "documentor"
|
name = "documentor"
|
||||||
version = "1.2.5"
|
version = "1.2.7"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "connectorx" },
|
{ name = "connectorx" },
|
||||||
|
|||||||
Reference in New Issue
Block a user