3 Commits

Author SHA1 Message Date
info 789fb5d77f docs: Projektnamen bereinigen, README und Web aktualisieren
Entfernt den Zusatz "ehemals xsl-validator" aus CLAUDE.md und README.
README mit korrekten Infos zu Theme, Worker-Pool und externen Tools ergänzt.
Download-Links auf Web-Seite auf Version 1.7.3 aktualisiert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 19:37:17 +02:00
info d19c36191c Version 1.7.3: Fehlende Menü-Icons ergänzt
- MainWindow: Icons für drei bislang icon-lose Menü-Aktionen gesetzt
  - "Alle XML-Dateien transformieren" → play-circle
  - "Aus Datenbank laden" → database (neu)
  - "Worker-Pool-Metriken" → activity (neu)
- Zwei neue Feather-Icons (database, activity) ergänzt: Download-Skript,
  resources.qrc und resources_rc.py (via pyside6-rcc) aktualisiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 12:45:25 +02:00
info 3e20b186c7 Version 1.7.2: Icon-System – Theme-Reaktivität generalisiert, Render vereinfacht
- icons.py: IconRefreshMixin ergänzt – reagiert auf PaletteChange/StyleChange
  und färbt Icons nur bei tatsächlichem Textfarb-Wechsel neu ein (farb-gegated)
- icons.py: Render auf einen einzelnen 64px-Pixmap vereinfacht (Qt skaliert),
  statt drei fixe Größen (16/24/32)
- MainWindow: nutzt IconRefreshMixin; hasattr-Guards in _setup_icons entfernt
  (läuft jetzt nur im fertig initialisierten Zustand); change_theme entschlackt –
  Icon-/Baum-Aktualisierung erfolgt über changeEvent statt unbedingtem Aufruf
- XslDependencyDialog: nutzt IconRefreshMixin, färbt Button- und Baum-Icons bei
  Theme-Wechsel neu ein (bislang blieben offene non-modale Dialoge stale)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 12:36:58 +02:00
16 changed files with 808 additions and 671 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ Spreche mit mir auf Deutsch! (Communicate with me in German!)
## Projektübersicht ## Projektübersicht
DocuMentor (ehemals xsl-validator) ist eine PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit XML-Dateien. Sie bietet eine GUI zur Konfiguration von Transformations-Toolchains (Saxon, Apache FOP, diff-pdf) und zur Verwaltung von PDF-Generierungsprojekten mit PostgreSQL-Datenbankintegration. DocuMentor ist eine PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit XML-Dateien. Sie bietet eine GUI zur Konfiguration von Transformations-Toolchains (Saxon, Apache FOP, diff-pdf) und zur Verwaltung von PDF-Generierungsprojekten mit PostgreSQL-Datenbankintegration.
## Anvisiertes Nutzungsszenario ## Anvisiertes Nutzungsszenario
Der primäre Einsatz ist die kontinuierliche Weiterentwicklung von PDF-Dokumenten in Flexnow (Software zur Prüfungsverwaltung). Dabei handelt es sich beispielsweise um amtliche Urkunden, Zeugnisse und Bescheide. Der primäre Einsatz ist die kontinuierliche Weiterentwicklung von PDF-Dokumenten in Flexnow (Software zur Prüfungsverwaltung). Dabei handelt es sich beispielsweise um amtliche Urkunden, Zeugnisse und Bescheide.
+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.7.1" Version="1.7.3"
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"
+10 -7
View File
@@ -2,7 +2,7 @@
**Professionelle XSL-Transformations-Verwaltung und PDF-Generierung** **Professionelle XSL-Transformations-Verwaltung und PDF-Generierung**
DocuMentor (ehemals xsl-validator) ist eine leistungsstarke PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit automatischer PDF-Generierung. Die Anwendung bietet eine intuitive GUI zur Konfiguration von Transformations-Toolchains (Saxon, Apache FOP, diff-pdf) und zur Verwaltung komplexer PDF-Generierungsprojekte mit PostgreSQL-Datenbankintegration. DocuMentor ist eine leistungsstarke PySide6-basierte Desktop-Anwendung zur Verwaltung und Validierung von XSL-Transformationen mit automatischer PDF-Generierung. Die Anwendung bietet eine intuitive GUI zur Konfiguration von Transformations-Toolchains (Saxon, Apache FOP, diff-pdf) und zur Verwaltung komplexer PDF-Generierungsprojekte mit PostgreSQL-Datenbankintegration.
## Features ## Features
@@ -14,7 +14,7 @@ DocuMentor (ehemals xsl-validator) ist eine leistungsstarke PySide6-basierte Des
### ⚡ Asynchrone Batch-Verarbeitung ### ⚡ Asynchrone Batch-Verarbeitung
- Verarbeiten Sie große Mengen von XML-Dateien im Hintergrund - Verarbeiten Sie große Mengen von XML-Dateien im Hintergrund
- Fortschrittsanzeige für lange Transformationen - Fortschrittsanzeige für lange Transformationen
- 4x schnellere XSLT-Transformationen durch Worker-Pool-Architektur - Parallelisierte XSLT-Transformationen durch eine Worker-Pool-Architektur
### 🔍 Intelligente Duplikatserkennung ### 🔍 Intelligente Duplikatserkennung
- Automatische Hash-basierte Erkennung von identischen XML-Dateien (Blake2b) - Automatische Hash-basierte Erkennung von identischen XML-Dateien (Blake2b)
@@ -37,7 +37,7 @@ DocuMentor (ehemals xsl-validator) ist eine leistungsstarke PySide6-basierte Des
- Plattformübergreifende Unterstützung (Linux, Windows, macOS) - Plattformübergreifende Unterstützung (Linux, Windows, macOS)
### 🎨 Modernes UI ### 🎨 Modernes UI
- Dark-Theme-Unterstützung via `qdarktheme` - Dark/Light-Theme-Unterstützung
- Drag-and-Drop für XML-Dateien - Drag-and-Drop für XML-Dateien
- Responsive und intuitive Benutzeroberfläche - Responsive und intuitive Benutzeroberfläche
@@ -58,13 +58,14 @@ uv sync
pip install -e . pip install -e .
``` ```
### Externe Tools (optional) ### Externe Tools
Für die volle Funktionalität benötigen Sie: Für die volle Funktionalität benötigen Sie:
- **Saxon-HE**: XSLT 3.0 Prozessor ([Download](https://www.saxonica.com/download/)) - **Saxon-HE**: XSLT 3.0 Prozessor ([Download](https://www.saxonica.com/download/))
- **Apache FOP**: PDF-Generierung aus XSL-FO ([Download](https://xmlgraphics.apache.org/fop/download.html)) - **Apache FOP**: PDF-Generierung aus XSL-FO ([Download](https://xmlgraphics.apache.org/fop/download.html))
- **diff-pdf**: PDF-Vergleich ([GitHub](https://github.com/vslavik/diff-pdf)) - **diff-pdf**: PDF-Vergleich ([GitHub](https://github.com/vslavik/diff-pdf))
- **OpenJDK/JRE**: für Saxon und Apache FOP. JDK empfohlen für Worker-Pools ([Eclipse Temurin](https://adoptium.net))
## Verwendung ## Verwendung
@@ -74,12 +75,14 @@ Für die volle Funktionalität benötigen Sie:
uv run python src/main.py uv run python src/main.py
``` ```
### Erste Start
Konfigurieren Sie Ihre Tools (Saxon, Apache FOP, diff-pdf) in den Einstellungen
### Projekt erstellen ### Projekt erstellen
1. Legen Sie ein neues Projekt an 1. Legen Sie ein neues Projekt an
2. Konfigurieren Sie Ihre Tools (Saxon, Apache FOP) in den Einstellungen 2. Organisieren Sie XSL-Stylesheets und XML-Dateien in der Baumstruktur
3. Organisieren Sie XSL-Stylesheets und XML-Dateien in der Baumstruktur 3. Führen Sie Transformationen aus
4. Führen Sie Transformationen aus
### Konfiguration ### Konfiguration
+1 -1
View File
@@ -263,5 +263,5 @@ HINWEISE
================================================================================ ================================================================================
Stand: Mai 2026 Stand: Mai 2026
Erstellt für: DocuMentor v1.7.1 Erstellt für: DocuMentor v1.7.3
================================================================================ ================================================================================
+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.7.1" #define MyAppVersion "1.7.3"
#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.7.1" version = "1.7.3"
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"}
+2
View File
@@ -29,6 +29,8 @@ ICONS = [
"file-plus", "file-plus",
"columns", "columns",
"sliders", "sliders",
"database",
"activity",
] ]
BASE_URL = "https://raw.githubusercontent.com/feathericons/feather/master/icons/{name}.svg" BASE_URL = "https://raw.githubusercontent.com/feathericons/feather/master/icons/{name}.svg"
+57 -9
View File
@@ -1,5 +1,5 @@
import logging import logging
from PySide6.QtCore import QByteArray, QFile, Qt from PySide6.QtCore import QByteArray, QEvent, QFile, Qt
from PySide6.QtGui import QIcon, QPainter, QPixmap, QPalette from PySide6.QtGui import QIcon, QPainter, QPixmap, QPalette
from PySide6.QtSvg import QSvgRenderer from PySide6.QtSvg import QSvgRenderer
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
@@ -7,6 +7,10 @@ import res.resources_rc # noqa: F401 # registriert die Qt-Ressourcen (Icons) b
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Basisgröße für das einmalige Rendern; Qt skaliert daraus die benötigten Icon-Größen
# (16/24/32 px in Menüs, Bäumen, Buttons). 64 px bietet genug Reserve auch für HiDPI.
_RENDER_SIZE = 64
# Cache: (Icon-Name, Theme-Textfarbe) → fertig gerendertes QIcon. # Cache: (Icon-Name, Theme-Textfarbe) → fertig gerendertes QIcon.
# Der Farb-Anteil im Schlüssel sorgt dafür, dass ein Theme-Wechsel automatisch # Der Farb-Anteil im Schlüssel sorgt dafür, dass ein Theme-Wechsel automatisch
# neue Einträge erzeugt, ohne dass der Cache explizit geleert werden muss. # neue Einträge erzeugt, ohne dass der Cache explizit geleert werden muss.
@@ -44,14 +48,58 @@ def icon(name: str) -> QIcon:
f.close() f.close()
renderer = QSvgRenderer(svg_data) renderer = QSvgRenderer(svg_data)
result = QIcon() pixmap = QPixmap(_RENDER_SIZE, _RENDER_SIZE)
for size in (16, 24, 32): pixmap.fill(Qt.GlobalColor.transparent)
pixmap = QPixmap(size, size) painter = QPainter(pixmap)
pixmap.fill(Qt.GlobalColor.transparent) renderer.render(painter)
painter = QPainter(pixmap) painter.end()
renderer.render(painter)
painter.end()
result.addPixmap(pixmap)
result = QIcon(pixmap)
_ICON_CACHE[cache_key] = result _ICON_CACHE[cache_key] = result
return result return result
class IconRefreshMixin:
"""
Mischklasse für Top-Level-Widgets, deren Icons sich beim Theme-Wechsel automatisch neu einfärben.
Voraussetzungen für die nutzende Klasse:
- Sie stellt eine Methode ``_setup_icons()`` bereit, die alle ihre Icons neu setzt.
- Nach abgeschlossener Initialisierung setzt sie ``self._icons_ready = True``
(idealerweise zusammen mit ``self._last_icon_color``, siehe ``mark_icons_ready()``).
Bei einem Style-/Palette-Wechsel wird nur wenn sich die Theme-Textfarbe tatsächlich
geändert hat ``_on_icon_theme_changed()`` aufgerufen (Standard: ``_setup_icons()``).
Unterklassen können ``_on_icon_theme_changed()`` überschreiben, um zusätzlich z.B.
Baum-Icons neu zu zeichnen.
Hinweis: ``IconRefreshMixin`` muss in der Klassenbasis VOR der Qt-Basisklasse
(``QMainWindow``/``QDialog``) stehen, damit das überschriebene ``changeEvent`` greift.
"""
_icons_ready: bool = False
_last_icon_color: str | None = None
@staticmethod
def _current_text_color() -> str | None:
app = QApplication.instance()
if app is None:
return None
return app.palette().color(QPalette.ColorRole.WindowText).name()
def mark_icons_ready(self):
"""Schaltet die Theme-Reaktivität scharf (nach dem ersten ``_setup_icons()`` aufrufen)."""
self._last_icon_color = self._current_text_color()
self._icons_ready = True
def changeEvent(self, event):
if event.type() in (QEvent.Type.PaletteChange, QEvent.Type.StyleChange) and self._icons_ready:
color = self._current_text_color()
if color is not None and color != self._last_icon_color:
self._last_icon_color = color
self._on_icon_theme_changed()
super().changeEvent(event)
def _on_icon_theme_changed(self):
"""Reaktion auf eine geänderte Theme-Textfarbe. Standard: alle Icons neu setzen."""
self._setup_icons()
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>

After

Width:  |  Height:  |  Size: 239 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>

After

Width:  |  Height:  |  Size: 318 B

+2
View File
@@ -21,5 +21,7 @@
<file>icons/file-plus.svg</file> <file>icons/file-plus.svg</file>
<file>icons/columns.svg</file> <file>icons/columns.svg</file>
<file>icons/sliders.svg</file> <file>icons/sliders.svg</file>
<file>icons/database.svg</file>
<file>icons/activity.svg</file>
</qresource> </qresource>
</RCC> </RCC>
+685 -634
View File
File diff suppressed because it is too large Load Diff
+19 -9
View File
@@ -25,7 +25,7 @@ from ui.mixins import (
TransformationMixin, TransformationMixin,
) )
from conf import app_settings, Project, ProjectData, TreeNode, XslFile from conf import app_settings, Project, ProjectData, TreeNode, XslFile
from icons import icon from icons import icon, IconRefreshMixin
from pathlib import Path from pathlib import Path
@@ -33,6 +33,7 @@ logger = logging.getLogger(__name__)
class MainWindow( class MainWindow(
IconRefreshMixin,
QMainWindow, QMainWindow,
TreeManagerMixin, TreeManagerMixin,
PdfViewerMixin, PdfViewerMixin,
@@ -141,8 +142,11 @@ class MainWindow(
# Zoom per Mausrad (STRG+Mausrad) für PDF-Viewer aktivieren # Zoom per Mausrad (STRG+Mausrad) für PDF-Viewer aktivieren
self._setup_scroll_area_zoom() self._setup_scroll_area_zoom()
# Icons setzen (überschreibt fromTheme()-Icons aus _ui.py) # Icons setzen (überschreibt fromTheme()-Icons aus _ui.py). Läuft nach
# _connect_signals(), daher existieren auch die programmatisch erzeugten Aktionen.
self._setup_icons() self._setup_icons()
# Ab hier reagiert das Fenster auf Theme-Wechsel (siehe IconRefreshMixin)
self.mark_icons_ready()
# Gespeicherte UI-Zustände wiederherstellen # Gespeicherte UI-Zustände wiederherstellen
self._restore_ui_state() self._restore_ui_state()
@@ -153,14 +157,21 @@ class MainWindow(
self.ui.actionBeenden.setIcon(icon("log-out")) self.ui.actionBeenden.setIcon(icon("log-out"))
self.ui.actionEinstellungen.setIcon(icon("settings")) self.ui.actionEinstellungen.setIcon(icon("settings"))
self.ui.actionVorhandene_Projekte.setIcon(icon("folder")) self.ui.actionVorhandene_Projekte.setIcon(icon("folder"))
self.ui.actionAlle_XML_Dateien_transformieren.setIcon(icon("play-circle"))
self.ui.actionAlle_XML_Dateien_neu_transformieren_force.setIcon(icon("refresh-cw")) self.ui.actionAlle_XML_Dateien_neu_transformieren_force.setIcon(icon("refresh-cw"))
self.ui.actionAus_Datenbank_laden.setIcon(icon("database"))
self.ui.view_ref_pdf.setIcon(icon("file")) self.ui.view_ref_pdf.setIcon(icon("file"))
self.ui.view_new_pdf.setIcon(icon("file")) self.ui.view_new_pdf.setIcon(icon("file"))
self.ui.accept_changes.setIcon(icon("check-circle")) self.ui.accept_changes.setIcon(icon("check-circle"))
if hasattr(self, "action_xsl_dependencies"): self.action_worker_metrics.setIcon(icon("activity"))
self.action_xsl_dependencies.setIcon(icon("git-branch")) self.action_xsl_dependencies.setIcon(icon("git-branch"))
if hasattr(self, "action_info"): self.action_info.setIcon(icon("info"))
self.action_info.setIcon(icon("info"))
def _on_icon_theme_changed(self):
"""Theme-Wechsel: Icons neu einfärben und falls ein Projekt offen ist die Baum-Icons."""
self._setup_icons()
if getattr(self, "pdf_project", None):
self._load_nodes_to_tree()
def _restore_ui_state(self): def _restore_ui_state(self):
"""Stellt die gespeicherten UI-Zustände wieder her (Fenstergeometrie, Splitter, TreeWidget-Spalten).""" """Stellt die gespeicherten UI-Zustände wieder her (Fenstergeometrie, Splitter, TreeWidget-Spalten)."""
@@ -358,9 +369,8 @@ class MainWindow(
logger.info(f"Theme erfolgreich gewechselt zu: {theme_name}") logger.info(f"Theme erfolgreich gewechselt zu: {theme_name}")
app_settings.theme = theme_name app_settings.theme = theme_name
app_settings.save() app_settings.save()
self._setup_icons() # Icons/Baum werden über changeEvent (PaletteChange/StyleChange) neu eingefärbt,
if hasattr(self, "pdf_project") and self.pdf_project: # siehe IconRefreshMixin._on_icon_theme_changed()
self._load_nodes_to_tree()
else: else:
logger.error(f"Fehler: Theme '{theme_name}' konnte nicht erstellt werden") logger.error(f"Fehler: Theme '{theme_name}' konnte nicht erstellt werden")
+21 -2
View File
@@ -12,7 +12,7 @@ import tempfile
from pathlib import Path from pathlib import Path
from PySide6.QtCore import QUrl, Qt from PySide6.QtCore import QUrl, Qt
from icons import icon from icons import icon, IconRefreshMixin
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QComboBox, QComboBox,
QDialog, QDialog,
@@ -120,7 +120,7 @@ except ImportError:
logger.warning("PySide6-WebEngine nicht verfügbar — Netzwerkgraph-Tab deaktiviert") logger.warning("PySide6-WebEngine nicht verfügbar — Netzwerkgraph-Tab deaktiviert")
class XslDependencyDialog(QDialog): class XslDependencyDialog(IconRefreshMixin, QDialog):
"""Dialog zur Anzeige des vollständigen XSL-Abhängigkeitsgraphen.""" """Dialog zur Anzeige des vollständigen XSL-Abhängigkeitsgraphen."""
def __init__( def __init__(
@@ -185,6 +185,25 @@ class XslDependencyDialog(QDialog):
self._build_layout_controls() self._build_layout_controls()
self._restore_graph_layout_settings() self._restore_graph_layout_settings()
# Ab hier auf Theme-Wechsel reagieren (siehe IconRefreshMixin)
self.mark_icons_ready()
def _setup_icons(self):
"""Färbt alle Icons gemäß aktuellem Theme neu ein (Button und Baum-Icons)."""
self.ui.settingsButton.setIcon(icon("sliders"))
# Datei-Baum neu einfärben, dabei die aktuelle Auswahl erhalten
current = self.ui.fileTree.currentItem()
current_path = current.data(0, Qt.ItemDataRole.UserRole) if current else None
self._populate_file_tree()
if current_path is not None:
for i in range(self.ui.fileTree.topLevelItemCount()):
item = self.ui.fileTree.topLevelItem(i)
if item.data(0, Qt.ItemDataRole.UserRole) == current_path:
# Setzt die Auswahl → _on_file_selected zeichnet den Abhängigkeitsbaum neu ein
self.ui.fileTree.setCurrentItem(item)
break
def reject(self): def reject(self):
"""Speichert Layout-Einstellungen und räumt temporäre Dateien und QWebEngineView auf.""" """Speichert Layout-Einstellungen und räumt temporäre Dateien und QWebEngineView auf."""
self._save_graph_layout_settings() self._save_graph_layout_settings()
Generated
+1 -1
View File
@@ -39,7 +39,7 @@ wheels = [
[[package]] [[package]]
name = "documentor" name = "documentor"
version = "1.7.1" version = "1.7.3"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "connectorx" }, { name = "connectorx" },
+4 -4
View File
@@ -1873,8 +1873,8 @@
<span class="download-card-badge">EMPFOHLEN</span> <span class="download-card-badge">EMPFOHLEN</span>
</div> </div>
<p class="download-card-desc">Windows-Installer mit automatischer Einrichtung. Erstellt Startmenü-Einträge und ermöglicht saubere Deinstallation über die Systemsteuerung.</p> <p class="download-card-desc">Windows-Installer mit automatischer Einrichtung. Erstellt Startmenü-Einträge und ermöglicht saubere Deinstallation über die Systemsteuerung.</p>
<span class="download-card-meta">DocuMentor-1.7.0.msi &mdash; ca. 255 MB</span> <span class="download-card-meta">DocuMentor-1.7.3.msi &mdash; ca. 255 MB</span>
<a href="https://code.vitaligraf.de/info/xsl-validator/releases/download/v1.7.0/DocuMentor-1.7.0.msi" class="btn-download">&#9660; MSI herunterladen</a> <a href="https://code.vitaligraf.de/info/xsl-validator/releases/download/v1.7.3/DocuMentor-1.7.3.msi" class="btn-download">&#9660; MSI herunterladen</a>
</div> </div>
<div class="download-card corner-brackets"> <div class="download-card corner-brackets">
<div class="download-card-header"> <div class="download-card-header">
@@ -1882,8 +1882,8 @@
<span class="download-card-badge">PORTABEL</span> <span class="download-card-badge">PORTABEL</span>
</div> </div>
<p class="download-card-desc">Portable Version ohne Installation. Entpacken und direkt starten &mdash; ideal für eingeschränkte Umgebungen ohne Administratorrechte.</p> <p class="download-card-desc">Portable Version ohne Installation. Entpacken und direkt starten &mdash; ideal für eingeschränkte Umgebungen ohne Administratorrechte.</p>
<span class="download-card-meta">DocuMentor-1.7.0.zip &mdash; ca. 315 MB</span> <span class="download-card-meta">DocuMentor-1.7.3.zip &mdash; ca. 315 MB</span>
<a href="https://code.vitaligraf.de/info/xsl-validator/releases/download/v1.7.0/DocuMentor-1.7.0.zip" class="btn-download">&#9660; ZIP herunterladen</a> <a href="https://code.vitaligraf.de/info/xsl-validator/releases/download/v1.7.3/DocuMentor-1.7.3.zip" class="btn-download">&#9660; ZIP herunterladen</a>
</div> </div>
</div> </div>
</div> </div>