Feat: XML-Knoten-Bearbeiten-Dialog implementiert (v1.6.0)
Neuer Dialog ermöglicht es, einem XML-Knoten XSL-Zuordnungen hinzuzufügen oder zu entfernen. XmlToXslAssignDialog wiederverwendet mit edit_mode, Vorauswahl per preselected_xsl_ids und get_selection_diff(). Beim Entfernen werden zugehörige PDF-Dateien gelöscht; bei verbleibend leerer Zuordnung wird das physische Löschen der XML-Datei angeboten. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ from PySide6.QtWidgets import (
|
||||
from conf import TreeNode, XslFile, XmlFile
|
||||
from ui.TreeNodeEditDialog import TreeNodeEditDialog
|
||||
from ui.XslFileEditDialog import XslFileEditDialog
|
||||
from ui.XmlToXslAssignDialog import XmlToXslAssignDialog
|
||||
|
||||
|
||||
class ItemType(Enum):
|
||||
@@ -1397,9 +1398,162 @@ class TreeManagerMixin:
|
||||
|
||||
# Kontextmenü-Aktionen für XmlFile
|
||||
def _edit_xml_file(self, item):
|
||||
"""Bearbeitet eine XML-Datei."""
|
||||
"""
|
||||
Bearbeitet die Zuordnungen einer XML-Datei zu XSL-Knoten.
|
||||
Öffnet den XmlToXslAssignDialog im Edit-Modus mit Vorauswahl aller bestehenden Zuordnungen.
|
||||
"""
|
||||
logger.debug(f"XmlFile bearbeiten: {item.text(0)}")
|
||||
# TODO: Dialog zum Bearbeiten der XML-Datei öffnen
|
||||
|
||||
try:
|
||||
if not hasattr(self, "project") or not self.project:
|
||||
QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.")
|
||||
return
|
||||
|
||||
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
||||
QMessageBox.warning(self, "Warnung", "Keine Projekt-Einstellungen geladen.")
|
||||
return
|
||||
|
||||
xml_file_obj = item.data(0, Qt.ItemDataRole.UserRole)
|
||||
if not xml_file_obj or not isinstance(xml_file_obj, XmlFile):
|
||||
QMessageBox.warning(self, "Warnung", "Keine gültige XML-Datei gefunden.")
|
||||
return
|
||||
|
||||
xml_path = xml_file_obj.xml
|
||||
# Alle aktuellen XSL-IDs, an denen diese XML hängt
|
||||
current_xsl_ids = self._find_xsl_ids_for_xml(xml_path)
|
||||
# Hash von einer vorhandenen Instanz übernehmen (falls vorhanden)
|
||||
existing_hash = self._find_existing_xml_hash(xml_path)
|
||||
|
||||
dialog = XmlToXslAssignDialog(
|
||||
parent=self,
|
||||
xml_file_path=xml_path,
|
||||
project_nodes=self.pdf_project.nodes,
|
||||
preselected_xsl_ids=current_xsl_ids,
|
||||
edit_mode=True,
|
||||
)
|
||||
|
||||
if dialog.exec() != XmlToXslAssignDialog.DialogCode.Accepted:
|
||||
logger.debug("Bearbeiten abgebrochen")
|
||||
return
|
||||
|
||||
added_nodes, removed_nodes = dialog.get_selection_diff()
|
||||
|
||||
if not added_nodes and not removed_nodes:
|
||||
logger.debug("Keine Änderungen an den XML-Zuordnungen")
|
||||
return
|
||||
|
||||
self._apply_xml_assignment_changes(xml_path, existing_hash, added_nodes, removed_nodes)
|
||||
|
||||
# Speichern und Baum neu laden
|
||||
self._save_project_settings()
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
# Wenn keine Zuordnungen mehr bestehen: Physische Datei löschen anbieten
|
||||
if not self._find_xsl_ids_for_xml(xml_path):
|
||||
xml_file_path = Path(self.project.project_dir) / xml_path
|
||||
if xml_file_path.exists():
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Physische Datei löschen",
|
||||
f"Die XML-Datei '{xml_path.name}' ist keinem XSL-Knoten mehr zugeordnet.\n\n"
|
||||
"Möchten Sie auch die physische Datei aus dem xml-Ordner löschen?",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.No,
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
try:
|
||||
xml_file_path.unlink()
|
||||
logger.info(f"Physische XML-Datei gelöscht: {xml_file_path}")
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Warnung", f"Fehler beim Löschen der physischen Datei:\n{str(e)}")
|
||||
|
||||
logger.info(
|
||||
f"XML-Zuordnungen aktualisiert: {len(added_nodes)} hinzugefügt, {len(removed_nodes)} entfernt"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Bearbeiten der XML-Zuordnungen: {str(e)}"
|
||||
logger.exception(error_msg)
|
||||
QMessageBox.critical(self, "Fehler", error_msg)
|
||||
|
||||
def _find_xsl_ids_for_xml(self, xml_path: Path) -> set:
|
||||
"""
|
||||
Findet alle XslFile-IDs (tuple), deren `xmls`-Liste den angegebenen Pfad enthält.
|
||||
|
||||
Args:
|
||||
xml_path: Relativer Pfad zur XML-Datei
|
||||
|
||||
Returns:
|
||||
set[tuple]: Set von XslFile-IDs
|
||||
"""
|
||||
found_ids: set = set()
|
||||
if not self.pdf_project or not self.pdf_project.nodes:
|
||||
return found_ids
|
||||
|
||||
def _walk(nodes):
|
||||
for node in nodes:
|
||||
if isinstance(node, XslFile):
|
||||
if any(xml.xml == xml_path for xml in node.xmls):
|
||||
found_ids.add(node.id)
|
||||
elif isinstance(node, TreeNode) and node.children:
|
||||
_walk(node.children)
|
||||
|
||||
_walk(self.pdf_project.nodes)
|
||||
return found_ids
|
||||
|
||||
def _find_existing_xml_hash(self, xml_path: Path) -> str | None:
|
||||
"""
|
||||
Sucht den blake2b-Hash einer bereits zugeordneten XmlFile-Instanz.
|
||||
Gibt den ersten gefundenen Hash zurück (oder None, wenn keiner vorhanden).
|
||||
"""
|
||||
if not self.pdf_project or not self.pdf_project.nodes:
|
||||
return None
|
||||
|
||||
def _walk(nodes):
|
||||
for node in nodes:
|
||||
if isinstance(node, XslFile):
|
||||
for xml_file in node.xmls:
|
||||
if xml_file.xml == xml_path and xml_file.hashsum:
|
||||
return xml_file.hashsum
|
||||
elif isinstance(node, TreeNode) and node.children:
|
||||
found = _walk(node.children)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
return _walk(self.pdf_project.nodes)
|
||||
|
||||
def _apply_xml_assignment_changes(
|
||||
self, xml_path: Path, hashsum: str | None, added_nodes: list, removed_nodes: list
|
||||
):
|
||||
"""
|
||||
Wendet Änderungen an den XML-Zuordnungen an:
|
||||
- Entfernt XmlFile-Instanzen aus `removed_nodes` und löscht zugehörige PDFs (falls Kombination nirgends mehr existiert)
|
||||
- Fügt XmlFile-Instanzen zu `added_nodes` hinzu (mit übernommenem Hash)
|
||||
|
||||
Args:
|
||||
xml_path: Relativer Pfad zur XML-Datei
|
||||
hashsum: blake2b-Hash, der für neue Instanzen übernommen wird
|
||||
added_nodes: Liste der XslFile-Objekte, denen die XML neu zugeordnet wird
|
||||
removed_nodes: Liste der XslFile-Objekte, aus denen die XML entfernt wird
|
||||
"""
|
||||
# 1. Entfernen
|
||||
for xsl_node in removed_nodes:
|
||||
xsl_node.xmls = [x for x in xsl_node.xmls if x.xml != xml_path]
|
||||
|
||||
# PDFs nur löschen, wenn die Kombination nirgends mehr existiert
|
||||
if not self._is_xml_xsl_combination_used_elsewhere(xml_path, xsl_node.id, xsl_node):
|
||||
deleted = self._delete_pdf_files_for_xml_xsl_combination(xml_path, xsl_node.id)
|
||||
if deleted:
|
||||
logger.info(f"{deleted} PDF-Datei(en) für '{xml_path.name}' (XSL: {xsl_node.bez}) gelöscht")
|
||||
|
||||
# 2. Hinzufügen (Hash übernehmen, Duplikate im gleichen XslFile vermeiden)
|
||||
for xsl_node in added_nodes:
|
||||
if any(x.xml == xml_path for x in xsl_node.xmls):
|
||||
logger.debug(f"XML '{xml_path.name}' bereits an XSL '{xsl_node.bez}' zugeordnet — übersprungen")
|
||||
continue
|
||||
xsl_node.xmls.append(XmlFile(xml=xml_path, hashsum=hashsum))
|
||||
logger.info(f"XML '{xml_path.name}' zu XSL '{xsl_node.bez}' hinzugefügt")
|
||||
|
||||
def _delete_xml_file(self, item):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user