3 Commits

Author SHA1 Message Date
info bf2db92040 Version 1.6.6: Versionsanzeige im Info-Dialog bei PyInstaller-Bundle gefixt
Im PyInstaller-Bundle fehlten .dist-info-Metadaten, wodurch importlib.metadata
keine Paketversionen liefern konnte. Lösung: DocuMentor.spec erzeugt beim Build
einen Versions-Snapshot (versions.json) der ins Bundle eingebettet wird.
license_parser.py liest diesen Snapshot im Bundle-Modus statt importlib.metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:54:27 +02:00
info 19f7deec20 build_msi.py: Klare Fehlermeldung bei fehlendem wix-Befehl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:34:30 +02:00
info 5f10b79906 Version 1.6.5: 'Bearbeiten' als Standard-Aktion im Tree-Kontextmenü
Der Menüpunkt 'Bearbeiten' steht nun ganz oben im Kontextmenü aller
Knotentypen (TreeNode, XslFile, XmlFile), wird fett dargestellt und
öffnet sich per Doppelklick auf den jeweiligen Knoten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:17:01 +02:00
9 changed files with 114 additions and 25 deletions
+20
View File
@@ -4,7 +4,10 @@ PyInstaller Konfiguration für DocuMentor
Erstellt eine eigenständige Windows-Executable ohne Python-Installation Erstellt eine eigenständige Windows-Executable ohne Python-Installation
""" """
import json
import sys import sys
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as pkg_version
from pathlib import Path from pathlib import Path
from PyInstaller.utils.hooks import collect_all from PyInstaller.utils.hooks import collect_all
@@ -14,6 +17,22 @@ block_cipher = None
project_root = Path(SPECPATH) project_root = Path(SPECPATH)
src_path = project_root / 'src' src_path = project_root / 'src'
# Versions-Snapshot erzeugen und ins Bundle einbetten
_packages_to_snapshot = [
"PySide6", "pydantic", "pydantic-settings", "pydantic-yaml",
"polars", "connectorx", "pyarrow", "psutil", "lxml",
"ruff", "pyinstaller", "pillow",
]
_versions_snapshot: dict[str, str] = {}
for _pkg in _packages_to_snapshot:
try:
_versions_snapshot[_pkg.lower()] = pkg_version(_pkg)
except PackageNotFoundError:
_versions_snapshot[_pkg.lower()] = ""
_versions_file = project_root / "versions.json"
_versions_file.write_text(json.dumps(_versions_snapshot), encoding="utf-8")
# connectorx komplett sammeln (Python-Code, native .pyd und Metadaten) # connectorx komplett sammeln (Python-Code, native .pyd und Metadaten)
# PyInstaller erkennt connectorx nicht automatisch, da es zur Laufzeit # PyInstaller erkennt connectorx nicht automatisch, da es zur Laufzeit
# von polars per importlib.import_module() geladen wird # von polars per importlib.import_module() geladen wird
@@ -36,6 +55,7 @@ a = Analysis(
datas=ui_files + res_files + cx_datas + [ datas=ui_files + res_files + cx_datas + [
(str(project_root / 'pyproject.toml'), '.'), (str(project_root / 'pyproject.toml'), '.'),
(str(project_root / 'THIRD_PARTY_LICENSES.txt'), '.'), (str(project_root / 'THIRD_PARTY_LICENSES.txt'), '.'),
(str(_versions_file), '.'),
(str(project_root / 'resources' / 'icon.ico'), 'resources'), (str(project_root / 'resources' / 'icon.ico'), 'resources'),
], ],
hiddenimports=[ hiddenimports=[
+1 -1
View File
@@ -4,7 +4,7 @@
<!-- Paket-Definition (ersetzt Product in v4) --> <!-- Paket-Definition (ersetzt Product in v4) -->
<Package <Package
Name="DocuMentor" Name="DocuMentor"
Version="1.6.4" Version="1.6.6"
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"
+1 -1
View File
@@ -253,5 +253,5 @@ HINWEISE
================================================================================ ================================================================================
Stand: April 2026 Stand: April 2026
Erstellt für: DocuMentor v1.6.4 Erstellt für: DocuMentor v1.6.6
================================================================================ ================================================================================
+9
View File
@@ -48,10 +48,19 @@ def build_msi():
# Schritt 2: MSI kompilieren mit WiX v6 # Schritt 2: MSI kompilieren mit WiX v6
print("Schritt 2/2: Kompiliere MSI-Installer...") print("Schritt 2/2: Kompiliere MSI-Installer...")
try:
result = subprocess.run( result = subprocess.run(
["wix", "build", "DocuMentor.wxs", "ProductFiles.wxs", "-o", str(msi_output)], ["wix", "build", "DocuMentor.wxs", "ProductFiles.wxs", "-o", str(msi_output)],
check=False, check=False,
) )
except FileNotFoundError:
print("\nFEHLER: 'wix' wurde nicht gefunden!")
print("WiX v6 muss installiert sein. Installationsschritte:")
print(" 1. .NET SDK installieren: https://dot.net")
print(" 2. WiX als dotnet tool installieren:")
print(" dotnet tool install --global wix --version 6.*")
print(" 3. Neues Terminal öffnen (PATH aktualisieren)")
sys.exit(1)
if result.returncode != 0: if result.returncode != 0:
print("\nFEHLER: MSI-Kompilierung fehlgeschlagen!") print("\nFEHLER: MSI-Kompilierung fehlgeschlagen!")
+1 -1
View File
@@ -10,7 +10,7 @@
; Build-Befehl: iscc installer.iss ; Build-Befehl: iscc installer.iss
#define MyAppName "DocuMentor" #define MyAppName "DocuMentor"
#define MyAppVersion "1.6.4" #define MyAppVersion "1.6.6"
#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
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "DocuMentor" name = "DocuMentor"
version = "1.6.4" version = "1.6.6"
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"}
+24 -1
View File
@@ -2,8 +2,10 @@
Parser für THIRD_PARTY_LICENSES.txt. Parser für THIRD_PARTY_LICENSES.txt.
Extrahiert strukturierte Lizenzinformationen und ergänzt sie Extrahiert strukturierte Lizenzinformationen und ergänzt sie
mit den tatsächlich installierten Paketversionen via importlib.metadata. mit den tatsächlich installierten Paketversionen via importlib.metadata
oder (im PyInstaller-Bundle) aus der mitgebündelten versions.json.
""" """
import json
import logging import logging
import re import re
import sys import sys
@@ -71,8 +73,29 @@ def _normalize_package_name(display_name: str) -> str:
return display_name.lower().split("(")[0].strip() return display_name.lower().split("(")[0].strip()
_bundled_versions: dict[str, str] | None = None
def _load_bundled_versions() -> dict[str, str]:
"""Lädt den Versions-Snapshot aus der mitgebündelten versions.json (nur im PyInstaller-Bundle)."""
global _bundled_versions
if _bundled_versions is None:
if hasattr(sys, "_MEIPASS"):
versions_file = Path(sys._MEIPASS) / "versions.json" # type: ignore[attr-defined]
if versions_file.exists():
_bundled_versions = json.loads(versions_file.read_text(encoding="utf-8"))
else:
_bundled_versions = {}
else:
_bundled_versions = {}
return _bundled_versions
def _get_installed_version(package_name: str) -> str: def _get_installed_version(package_name: str) -> str:
"""Ermittelt die installierte Version eines Pakets.""" """Ermittelt die installierte Version eines Pakets."""
bundled = _load_bundled_versions()
if bundled:
return bundled.get(package_name.lower(), "")
try: try:
return version(package_name) return version(package_name)
except PackageNotFoundError: except PackageNotFoundError:
+52 -15
View File
@@ -59,12 +59,31 @@ class TreeManagerMixin:
# Aktiviere Kontextmenü für das TreeWidget # Aktiviere Kontextmenü für das TreeWidget
self.ui.treeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.ui.treeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.ui.treeWidget.customContextMenuRequested.connect(self._show_tree_context_menu) self.ui.treeWidget.customContextMenuRequested.connect(self._show_tree_context_menu)
self.ui.treeWidget.itemDoubleClicked.connect(self._on_tree_item_double_clicked)
# Verbinde Selection-Changed-Signal für automatisches Laden von Diff-PDFs # Verbinde Selection-Changed-Signal für automatisches Laden von Diff-PDFs
self.ui.treeWidget.itemSelectionChanged.connect(self._on_tree_selection_changed) self.ui.treeWidget.itemSelectionChanged.connect(self._on_tree_selection_changed)
logger.debug("Kontextmenü und Selection-Handler für TreeWidget eingerichtet") logger.debug("Kontextmenü und Selection-Handler für TreeWidget eingerichtet")
def _on_tree_item_double_clicked(self, item, column):
"""
Behandelt Doppelklick auf ein TreeWidget-Item, indem die Bearbeiten-Aktion ausgeführt wird.
Args:
item: Das angeklickte TreeWidgetItem
column: Die Spalte des Doppelklicks
"""
if item.isDisabled():
return
node_type = self._get_node_type_from_item(item)
if node_type == ItemType.TREE_NODE:
self._edit_tree_node(item)
elif node_type == ItemType.XSL_FILE:
self._edit_xsl_file(item)
elif node_type == ItemType.XML_FILE:
self._edit_xml_file(item)
def _show_tree_context_menu(self, position): def _show_tree_context_menu(self, position):
""" """
Zeigt das Kontextmenü für das TreeWidget an. Zeigt das Kontextmenü für das TreeWidget an.
@@ -299,6 +318,17 @@ class TreeManagerMixin:
if node_type == ItemType.TREE_NODE: if node_type == ItemType.TREE_NODE:
# Kontextmenü für TreeNode # Kontextmenü für TreeNode
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_tree_node(item))
font = action_edit.font()
font.setBold(True)
action_edit.setFont(font)
menu.addAction(action_edit)
menu.setDefaultAction(action_edit)
menu.addSeparator()
action_add_child = QAction("Unterknoten hinzufügen", self) action_add_child = QAction("Unterknoten hinzufügen", self)
action_add_child.setIcon(QIcon(QIcon.fromTheme("folder-new"))) action_add_child.setIcon(QIcon(QIcon.fromTheme("folder-new")))
action_add_child.triggered.connect(lambda: self._add_tree_node_child(item)) action_add_child.triggered.connect(lambda: self._add_tree_node_child(item))
@@ -341,11 +371,6 @@ class TreeManagerMixin:
menu.addSeparator() menu.addSeparator()
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_tree_node(item))
menu.addAction(action_edit)
action_delete = QAction("Löschen", self) action_delete = QAction("Löschen", self)
action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete))) action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete)))
action_delete.triggered.connect(lambda: self._delete_tree_node(item)) action_delete.triggered.connect(lambda: self._delete_tree_node(item))
@@ -353,6 +378,17 @@ class TreeManagerMixin:
elif node_type == ItemType.XSL_FILE: elif node_type == ItemType.XSL_FILE:
# Kontextmenü für XslFile # Kontextmenü für XslFile
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_xsl_file(item))
font = action_edit.font()
font.setBold(True)
action_edit.setFont(font)
menu.addAction(action_edit)
menu.setDefaultAction(action_edit)
menu.addSeparator()
action_add_xml = QAction("XML-Datei hinzufügen", self) action_add_xml = QAction("XML-Datei hinzufügen", self)
action_add_xml.setIcon(QIcon(QIcon.fromTheme("document-new"))) action_add_xml.setIcon(QIcon(QIcon.fromTheme("document-new")))
action_add_xml.triggered.connect(lambda: self._add_xml_file_to_xsl(item)) action_add_xml.triggered.connect(lambda: self._add_xml_file_to_xsl(item))
@@ -397,11 +433,6 @@ class TreeManagerMixin:
menu.addSeparator() menu.addSeparator()
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_xsl_file(item))
menu.addAction(action_edit)
action_delete = QAction("Löschen", self) action_delete = QAction("Löschen", self)
action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete))) action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete)))
action_delete.triggered.connect(lambda: self._delete_xsl_file(item)) action_delete.triggered.connect(lambda: self._delete_xsl_file(item))
@@ -409,6 +440,17 @@ class TreeManagerMixin:
elif node_type == ItemType.XML_FILE: elif node_type == ItemType.XML_FILE:
# Kontextmenü für XmlFile # Kontextmenü für XmlFile
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_xml_file(item))
font = action_edit.font()
font.setBold(True)
action_edit.setFont(font)
menu.addAction(action_edit)
menu.setDefaultAction(action_edit)
menu.addSeparator()
# Transformations-Aktionen # Transformations-Aktionen
action_transform = QAction("Transformieren", self) action_transform = QAction("Transformieren", self)
action_transform.setIcon(QIcon(QIcon.fromTheme("system-run"))) action_transform.setIcon(QIcon(QIcon.fromTheme("system-run")))
@@ -422,11 +464,6 @@ class TreeManagerMixin:
menu.addSeparator() menu.addSeparator()
action_edit = QAction("Bearbeiten", self)
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
action_edit.triggered.connect(lambda: self._edit_xml_file(item))
menu.addAction(action_edit)
action_delete = QAction("Löschen", self) action_delete = QAction("Löschen", self)
action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete))) action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete)))
action_delete.triggered.connect(lambda: self._delete_xml_file(item)) action_delete.triggered.connect(lambda: self._delete_xml_file(item))
Generated
+1 -1
View File
@@ -39,7 +39,7 @@ wheels = [
[[package]] [[package]]
name = "documentor" name = "documentor"
version = "1.6.4" version = "1.6.6"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "connectorx" }, { name = "connectorx" },