Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 104698ab0f | |||
| 2e69c5b3d1 |
@@ -0,0 +1,14 @@
|
||||
# agents.md
|
||||
|
||||
Entwicklung mit Python und PySide6
|
||||
|
||||
## Richtlinien
|
||||
|
||||
### Allgemeines zum Projekt
|
||||
- In diesem Projekt wird der uv Packetmanager verwendet.
|
||||
|
||||
## PySide6-GUI
|
||||
- Beim Erstellen neuer Dialoge sollte immer eine passende UI-Datei erstellt werden.
|
||||
- Der Entwickler sollte später in der Lage sein, den neuen Dialog über die UI-Datei zu gestalten.
|
||||
- Aus der UI-Datei wird in Visual Studio Code über eine Erweiterung automatisch eine .py-Datei erzeugt.
|
||||
- Die automatisch generierte .py-Datei muss in den Code eingebunden und verwendet werden.
|
||||
@@ -1 +0,0 @@
|
||||
CLAUDE.md
|
||||
@@ -0,0 +1,14 @@
|
||||
# agents.md
|
||||
|
||||
Entwicklung mit Python und PySide6
|
||||
|
||||
## Richtlinien
|
||||
|
||||
### Allgemeines zum Projekt
|
||||
- In diesem Projekt wird der uv Packetmanager verwendet.
|
||||
|
||||
## PySide6-GUI
|
||||
- Beim Erstellen neuer Dialoge sollte immer eine passende UI-Datei erstellt werden.
|
||||
- Der Entwickler sollte später in der Lage sein, den neuen Dialog über die UI-Datei zu gestalten.
|
||||
- Aus der UI-Datei wird in Visual Studio Code über eine Erweiterung automatisch eine .py-Datei erzeugt.
|
||||
- Die automatisch generierte .py-Datei muss in den Code eingebunden und verwendet werden.
|
||||
@@ -1,169 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Spreche mit mir auf Deutsch! (Communicate with me in German!)
|
||||
|
||||
## 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.
|
||||
|
||||
## PySide6-GUI
|
||||
- Beim Erstellen neuer Dialoge sollte immer eine passende UI-Datei erstellt werden
|
||||
- Der Entwickler sollte später in der Lage sein, den neuen Dialog über die UI-Datei zu gestalten
|
||||
- Aus der UI-Datei wird in Visual Studio Code über eine Erweiterung automatisch eine .py-Datei erzeugt
|
||||
- Die automatisch generierte .py-Datei muss in den Code eingebunden und verwendet werden
|
||||
|
||||
## Entwicklungskommandos
|
||||
|
||||
### Paketverwaltung
|
||||
Dieses Projekt verwendet den `uv` Paketmanager (nicht pip oder poetry):
|
||||
```bash
|
||||
uv sync # Abhängigkeiten installieren
|
||||
uv run python src/main.py # Anwendung starten
|
||||
uv run python test_hash_implementation.py # Hash-Tests ausführen
|
||||
```
|
||||
|
||||
### Linting
|
||||
```bash
|
||||
uv run ruff check # Code-Style prüfen (Zeilenlänge: 120)
|
||||
uv run ruff format # Code formatieren
|
||||
```
|
||||
|
||||
## Architektur
|
||||
|
||||
### Konfigurationssystem (src/conf.py)
|
||||
|
||||
Die Anwendung verwendet ein zentralisiertes Konfigurationsmodell mit Pydantic:
|
||||
|
||||
- **AppSettings**: Globales Singleton (`app_settings`), das die gesamte Anwendungskonfiguration speichert
|
||||
- Wird an plattformspezifischen Orten gespeichert:
|
||||
- Linux: `~/.config/DocuMentor/config.json`
|
||||
- Windows: `%APPDATA%\DocuMentor\config.json`
|
||||
- macOS: `~/Library/Application Support/DocuMentor/config.json`
|
||||
- Enthält Listen von Tools: `java_vms`, `saxon_jars`, `apache_fops`, `diff_pdfs`, `xsl_dirs`, `postgresql_dbs`
|
||||
|
||||
- **ProjectData**: Projektspezifische Einstellungen, die in `project.yaml` im jeweiligen Projektverzeichnis gespeichert werden
|
||||
- Enthält hierarchische Baumstruktur von Transformationsknoten
|
||||
- Verwendet `TreeNode` und `XslFile` zur Organisation
|
||||
- Jede `XmlFile` hat eine optionale `hashsum` (blake2b) zur Änderungsverfolgung
|
||||
|
||||
### Wichtige Datenmodelle
|
||||
|
||||
1. **Tool-Konfigurationsmodelle** (JavaVm, SaxonJar, ApacheFop, DiffPdf, XslDir, PostgreSqlDb):
|
||||
- Jedes hat eine `id` und `version`
|
||||
- Speichert Pfade zu Binärdateien/Verzeichnissen
|
||||
|
||||
2. **Project-Modell**:
|
||||
- Referenziert Tool-Konfigurationen über ID
|
||||
- Verlinkt zu einem Projektverzeichnis mit `project.yaml`
|
||||
- Hat Hilfsmethoden wie `getXsl()`, `getJavaVm()` um IDs in Namen/Versionen aufzulösen
|
||||
|
||||
3. **Baumstruktur** (TreeNode → XslFile → XmlFile):
|
||||
- Hierarchische Organisation von Transformations-Workflows
|
||||
- `TreeNode`: Organisationseinheit mit `xslt_params` und Kindknoten/-dateien
|
||||
- `XslFile`: XSL-Stylesheet mit zugehörigen XML-Dateien und XSLT-Parametern
|
||||
- `XmlFile`: XML-Eingabedatei mit optionalem blake2b-Hash
|
||||
|
||||
### UI-Architektur (src/ui/)
|
||||
|
||||
Die Anwendung folgt einem spezifischen PySide6-Muster:
|
||||
|
||||
1. **UI-Definitionsdateien** (`*_ui.py`): Automatisch generiert aus UI-Designer-Dateien
|
||||
- Diese Dateien definieren die UI-Struktur als Klassen (z.B. `Ui_MainWindow`)
|
||||
- Sollten NICHT manuell bearbeitet werden
|
||||
|
||||
2. **Implementierungsdateien** (ohne `_ui` Suffix): Tatsächliche Dialog-/Fenster-Implementierungen
|
||||
- Importieren und verwenden die entsprechende `*_ui.py` Datei
|
||||
- Enthalten Business-Logik und Signal/Slot-Verbindungen
|
||||
- Beispiel: `MainWindow.py` verwendet `Ui_MainWindow` aus `MainWinddow_ui.py`
|
||||
|
||||
Beim Erstellen neuer Dialoge:
|
||||
- Immer zuerst eine entsprechende UI-Datei erstellen
|
||||
- Die UI-Datei wird automatisch als `.py`-Datei von einer VS Code Extension generiert
|
||||
- Die generierte UI-Klasse in der Implementierungsdatei importieren und verwenden
|
||||
|
||||
### Hauptfenster (src/ui/MainWindow.py)
|
||||
|
||||
Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten:
|
||||
|
||||
1. **Projektverwaltung**:
|
||||
- Öffnet und verwaltet PDF-Transformationsprojekte
|
||||
- Lädt/speichert `ProjectData` aus `project.yaml` Dateien
|
||||
|
||||
2. **Tree Widget**: Zeigt hierarchische Struktur von Transformationsknoten an
|
||||
- Kontextmenüs zum Hinzufügen/Bearbeiten/Löschen von Knoten, XSL-Dateien und XML-Dateien
|
||||
- Drag-and-Drop-Unterstützung für XML-Dateien
|
||||
|
||||
3. **PDF-Vergleichsansicht**:
|
||||
- Drei-Panel-Ansicht (Referenz, Diff, Neu)
|
||||
- Alpha-Blending für visuellen Vergleich
|
||||
- Zoom- und Pan-Funktionalität
|
||||
|
||||
4. **Asynchrone Operationen**:
|
||||
- `XmlHashCalculatorThread`: Hintergrund-blake2b-Hash-Berechnung für XML-Dateien
|
||||
- `DatabaseTestThread` (in PostgreSqlConfigDialog): Asynchrones Testen von Datenbankverbindungen
|
||||
|
||||
### Hash-Berechnungssystem
|
||||
|
||||
Die Anwendung verwendet blake2b-Hashing zur Verfolgung von XML-Dateiänderungen:
|
||||
|
||||
- **Automatisch**: Hashes werden berechnet, wenn Projekte geladen werden (nur für Dateien ohne existierenden Hash)
|
||||
- **Asynchron**: Hintergrund-Thread (`XmlHashCalculatorThread`) um die UI reaktionsfähig zu halten
|
||||
- **Format**: `blake2b:<64-Zeichen-Hexdigest>`
|
||||
- **Speicherung**: Persistiert in `project.yaml` innerhalb jedes `XmlFile`-Objekts
|
||||
- **Details**: Siehe `docs/blake2b_hash_implementation.md`
|
||||
|
||||
### Theme-System
|
||||
|
||||
Die Anwendung unterstützt mehrere Qt-Themes:
|
||||
- Theme-Auswahlmenü wird dynamisch aus `QStyleFactory.keys()` befüllt
|
||||
- Theme-Präferenz wird in `AppSettings.theme` gespeichert
|
||||
- Dark-Theme-Unterstützung via `qdarktheme` Paket (aktuell in main.py auskommentiert)
|
||||
|
||||
### Datenbankintegration
|
||||
|
||||
PostgreSQL-Integration mit Polars und ConnectorX:
|
||||
- Konfiguration wird im `PostgreSqlDb`-Modell mit SSL-Modus-Unterstützung gespeichert
|
||||
- SQL-Abfragen werden via `_execute_sql_query()` im MainWindow ausgeführt
|
||||
- Ergebnisse werden in Polars DataFrames geladen
|
||||
|
||||
## Wichtige Konventionen
|
||||
|
||||
### Deutsche Sprache
|
||||
Die Codebasis verwendet Deutsch für:
|
||||
- UI-Texte und Labels
|
||||
- Kommentare und Dokumentation
|
||||
- Variablennamen wo kontextuell passend
|
||||
- Log-Meldungen
|
||||
|
||||
### Pfadbehandlung
|
||||
- Immer `pathlib.Path`-Objekte verwenden, keine Strings
|
||||
- `expanduser()` und `expandvars()` für Benutzer-/Umgebungspfade verwenden
|
||||
- Projektrelative Pfade werden als relativ gespeichert, zur Laufzeit gegen `project_dir` aufgelöst
|
||||
|
||||
### ID-basierte Lookups
|
||||
Konfigurationsentitäten (Tools, Datenbanken) werden in Projekten über ID referenziert. Die Hilfsmethoden des `Project`-Modells (`getXsl()`, `getJavaVm()`, etc.) verwenden, um IDs in Anzeigewerte aufzulösen.
|
||||
|
||||
### Einstellungspersistenz
|
||||
- Globale Einstellungen: `app_settings.save()` nach Änderungen aufrufen
|
||||
- Projekteinstellungen: `project_data.writeSettings(project_dir)` nach Änderungen aufrufen
|
||||
|
||||
## Arbeiten mit der Codebasis
|
||||
|
||||
### Neue Tool-Konfigurationen hinzufügen
|
||||
1. Modell zu `conf.py` hinzufügen (ähnlich wie `JavaVm`, `SaxonJar`)
|
||||
2. Listenfeld zu `AppSettings` hinzufügen
|
||||
3. Konfigurationsdialog in `src/ui/` erstellen (UI-Datei + Implementierung)
|
||||
4. Zu `AppSettings.py` Tabs hinzufügen
|
||||
5. `Project`-Modell aktualisieren, falls das Tool projektspezifisch sein soll
|
||||
|
||||
### Neue Baumoperationen hinzufügen
|
||||
1. Aktion zum Kontextmenü in `_create_context_menu_for_type()` hinzufügen
|
||||
2. Handler-Methode implementieren nach Namensschema `_action_tree_node()`, `_action_xsl_file()`, etc.
|
||||
3. Baum nach Änderungen mit `_load_nodes_to_tree()` aktualisieren
|
||||
4. `self.project_data.writeSettings(self.project.project_dir)` aufrufen um Änderungen zu persistieren
|
||||
|
||||
### Projektstruktur modifizieren
|
||||
Das `ProjectData`-Modell ist die Quelle der Wahrheit. Alle Änderungen an der Baumstruktur müssen:
|
||||
1. Die `project_data.nodes` Liste modifizieren
|
||||
2. `project_data.writeSettings()` aufrufen um zu persistieren
|
||||
3. Baum mit `_load_nodes_to_tree()` neu laden um Änderungen in der UI zu reflektieren
|
||||
@@ -0,0 +1,8 @@
|
||||
# Projekt allgemeines
|
||||
- In diesem Projekt wirt uv Packetmanager verwendet.
|
||||
|
||||
## PySide6-GUI
|
||||
- Beim Erstellen neuer Dialoge sollte stets eine passende UI-Datei erstellt werden.
|
||||
- Der Entwickler soll den neuen Dialog später über die UI-Datei gestalten können.
|
||||
- Die UI-Datei wird in Visual Studio Code durch eine Erweiterung automatisch als .py-Datei generiert.
|
||||
- Die automatisch generierte .py-Datei muss in den Code eingebunden und genutzt werden.
|
||||
+1
-6
@@ -19,10 +19,5 @@ dependencies = [
|
||||
# ...but use a different line length.
|
||||
line-length = 120
|
||||
|
||||
# Ignoriere automatisch generierte UI-Dateien
|
||||
extend-exclude = ["*_ui.py"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff>=0.14.8",
|
||||
]
|
||||
dev = []
|
||||
|
||||
+8
-21
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import sys
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
from sys import platform
|
||||
from typing import Tuple, Type
|
||||
@@ -19,15 +18,15 @@ app_name = "DocuMentor"
|
||||
|
||||
|
||||
if platform == "win32":
|
||||
tmp_config_path = f"%APPDATA%\\{app_name}\\config.json"
|
||||
config_path = f"%APPDATA%\\{app_name}\\config.json"
|
||||
elif platform in ("linux", "linux2"):
|
||||
tmp_config_path = f"~/.config/{app_name}/config.json"
|
||||
config_path = f"~/.config/{app_name}/config.json"
|
||||
elif platform == "darwin":
|
||||
tmp_config_path = f"~/Library/Application Support/{app_name}/͏͏͏͏config.json"
|
||||
config_path = f"~/Library/Application Support/{app_name}/͏͏͏͏config.json"
|
||||
else:
|
||||
tmp_config_path = f"~/.config/{app_name}/config.json"
|
||||
config_path = f"~/.config/{app_name}/config.json"
|
||||
|
||||
config_path = Path(os.path.expandvars(tmp_config_path)).expanduser()
|
||||
config_path = Path(path.expandvars(config_path))
|
||||
|
||||
|
||||
class JavaVm(BaseModel):
|
||||
@@ -72,7 +71,6 @@ class SSLMode(str, Enum):
|
||||
VERIFY_CA = "verify-ca"
|
||||
VERIFY_FULL = "verify-full"
|
||||
|
||||
|
||||
class PostgreSqlDb(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
@@ -94,7 +92,6 @@ class Project(BaseModel):
|
||||
apache_fop_id: int = Field(..., description="ID der Apache FOP Konfiguration", gt=0)
|
||||
xsl_dir_id: int = Field(..., description="ID des XSL-Verzeichnisses", gt=0)
|
||||
postgre_sql_db_id: int = Field(..., description="ID der PostgreSQL Datenbank", gt=0)
|
||||
fop_config_dir: Path | None = Field(None, description="Optionaler Pfad zum Apache FOP Config-Verzeichnis")
|
||||
|
||||
def getXsl(self) -> str:
|
||||
global app_settings
|
||||
@@ -142,12 +139,6 @@ class AppSettings(BaseSettings):
|
||||
pdf_projects: list[Project] = []
|
||||
postgresql_dbs: list[PostgreSqlDb] = []
|
||||
theme: str | None = None
|
||||
max_workers: int = 8 # Anzahl paralleler Worker für Transformationen (Standard: 8)
|
||||
|
||||
# UI-Zustand
|
||||
window_geometry: tuple[int, int, int, int] | None = None # (x, y, width, height)
|
||||
splitter_sizes: list[int] | None = None # Splitter-Positionen
|
||||
tree_column_widths: list[int] | None = None # TreeWidget-Spaltenbreiten
|
||||
|
||||
model_config = SettingsConfigDict(json_file=config_path)
|
||||
|
||||
@@ -168,10 +159,6 @@ class AppSettings(BaseSettings):
|
||||
if not config_path.parent.exists():
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not config_path.parent.is_dir() or not os.access(config_path.parent, os.W_OK):
|
||||
logger.exception(f"{config_path.parent} ist kein Verzeichnis oder es gibt keine Schreibrechte")
|
||||
sys.exit(1)
|
||||
|
||||
# Konfiguration speichern
|
||||
with open(config_path, "wb") as c:
|
||||
c.write(app_settings.model_dump_json(indent=4).encode())
|
||||
@@ -212,8 +199,8 @@ class ProjectData(BaseModel):
|
||||
def readSettings(cls, project_dir: Path):
|
||||
# Explizit UTF-8 Encoding verwenden
|
||||
project_yaml_path = project_dir / "project.yaml"
|
||||
with open(project_yaml_path, "r", encoding="utf-8") as f:
|
||||
yaml = YAML(typ="safe")
|
||||
with open(project_yaml_path, 'r', encoding='utf-8') as f:
|
||||
yaml = YAML(typ='safe')
|
||||
yaml_data = yaml.load(f)
|
||||
return cls.model_validate(yaml_data)
|
||||
|
||||
|
||||
+5
-35
@@ -1,5 +1,4 @@
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
@@ -7,46 +6,17 @@ from ui.MainWindow import MainWindow
|
||||
from ui.AppSettings import AppSettingsDlg
|
||||
from conf import app_settings
|
||||
|
||||
# import qdarktheme
|
||||
|
||||
|
||||
def main():
|
||||
"""Haupteinstiegspunkt der Anwendung."""
|
||||
# Logging konfigurieren - sowohl Datei als auch Konsole
|
||||
from datetime import datetime
|
||||
|
||||
# Log-Verzeichnis erstellen (im selben Verzeichnis wie config.json)
|
||||
from conf import config_path
|
||||
|
||||
log_dir = config_path.parent / "logs"
|
||||
log_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Log-Dateiname mit Timestamp
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
log_file = log_dir / f"documentor_{timestamp}.log"
|
||||
|
||||
# Root-Logger konfigurieren
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Formatter für alle Handler
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S")
|
||||
|
||||
# Handler 1: Datei (alles ab DEBUG)
|
||||
file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Handler 2: Konsole (alles ab INFO)
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
logging.info(f"Logging initialisiert: {log_file}")
|
||||
|
||||
# QApplication-Instanz erstellen
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# Dark Theme aktivieren
|
||||
# qdarktheme.setup_theme("auto")
|
||||
|
||||
# Hauptfenster erstellen
|
||||
window = MainWindow()
|
||||
|
||||
|
||||
@@ -1,442 +0,0 @@
|
||||
"""
|
||||
Saxon Worker Pool - Persistente JVM-Prozesse für schnelle XSLT-Transformationen.
|
||||
|
||||
Eliminiert JVM-Startup-Overhead durch Vorinitialisierung von N Worker-Prozessen.
|
||||
Jeder Worker läuft als Daemon und verarbeitet mehrere Transformationen nacheinander.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
import tempfile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Java-Worker-Code (wird zur Laufzeit kompiliert)
|
||||
SAXON_WORKER_JAVA = """
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.stream.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class SaxonWorker {
|
||||
public static void main(String[] args) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
String line;
|
||||
|
||||
// Create TransformerFactory once and reuse
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
|
||||
System.err.println("SaxonWorker started and ready (using JAXP Transformer API)");
|
||||
System.err.flush();
|
||||
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
System.err.println("DEBUG: Received line: " + line.substring(0, Math.min(100, line.length())));
|
||||
System.err.flush();
|
||||
|
||||
if ("EXIT".equals(line.trim())) {
|
||||
System.err.println("SaxonWorker exiting");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse job
|
||||
System.err.println("DEBUG: Parsing job...");
|
||||
System.err.flush();
|
||||
|
||||
String[] parts = line.split("\\t");
|
||||
System.err.println("DEBUG: Parts count: " + parts.length);
|
||||
System.err.flush();
|
||||
|
||||
if (parts.length < 3) {
|
||||
System.out.println("ERROR: Invalid job format");
|
||||
System.out.flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
String sourceXml = parts[0];
|
||||
String xslStylesheet = parts[1];
|
||||
String outputFo = parts[2];
|
||||
|
||||
System.err.println("DEBUG: Creating transformer from stylesheet...");
|
||||
System.err.flush();
|
||||
|
||||
// Create Source and Result objects
|
||||
StreamSource xslSource = new StreamSource(new File(xslStylesheet));
|
||||
StreamSource xmlSource = new StreamSource(new File(sourceXml));
|
||||
StreamResult result = new StreamResult(new File(outputFo));
|
||||
|
||||
System.err.println("DEBUG: Compiling stylesheet...");
|
||||
System.err.flush();
|
||||
|
||||
// Create transformer from stylesheet
|
||||
Transformer transformer = factory.newTransformer(xslSource);
|
||||
|
||||
// Set parameters if present
|
||||
if (parts.length > 3 && !parts[3].isEmpty()) {
|
||||
String[] params = parts[3].split("\\\\|\\\\|\\\\|");
|
||||
for (String param : params) {
|
||||
if (!param.isEmpty() && param.contains("=")) {
|
||||
String[] kv = param.split("=", 2);
|
||||
transformer.setParameter(kv[0], kv[1]);
|
||||
System.err.println("DEBUG: Set parameter: " + kv[0] + " = " + kv[1]);
|
||||
}
|
||||
}
|
||||
System.err.flush();
|
||||
}
|
||||
|
||||
System.err.println("DEBUG: Running transformation...");
|
||||
System.err.flush();
|
||||
|
||||
// Capture errors via ErrorListener
|
||||
final StringBuilder errors = new StringBuilder();
|
||||
transformer.setErrorListener(new ErrorListener() {
|
||||
@Override
|
||||
public void warning(TransformerException e) {
|
||||
errors.append("WARNING: ").append(e.getMessage()).append("\\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(TransformerException e) {
|
||||
errors.append("ERROR: ").append(e.getMessage()).append("\\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(TransformerException e) throws TransformerException {
|
||||
errors.append("FATAL: ").append(e.getMessage()).append("\\n");
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
// Run transformation
|
||||
transformer.transform(xmlSource, result);
|
||||
|
||||
System.err.println("DEBUG: Transformation completed");
|
||||
System.err.flush();
|
||||
|
||||
// Check for errors
|
||||
if (errors.length() > 0) {
|
||||
System.out.println("ERROR: " + errors.toString().trim());
|
||||
} else {
|
||||
System.out.println("OK");
|
||||
}
|
||||
System.out.flush();
|
||||
|
||||
} catch (TransformerException e) {
|
||||
System.err.println("DEBUG: Transformer exception: " + e.getClass().getName());
|
||||
System.err.flush();
|
||||
e.printStackTrace(System.err);
|
||||
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg == null || errorMsg.isEmpty()) {
|
||||
errorMsg = e.getClass().getSimpleName();
|
||||
}
|
||||
System.out.println("ERROR: " + errorMsg);
|
||||
System.out.flush();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("DEBUG: Job processing exception: " + e.getClass().getName());
|
||||
System.err.flush();
|
||||
e.printStackTrace(System.err);
|
||||
System.out.println("ERROR: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getName()));
|
||||
System.out.flush();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("SaxonWorker I/O error: " + e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class SaxonWorkerPool:
|
||||
"""
|
||||
Pool von lang-laufenden JVM-Prozessen für Saxon-Transformationen.
|
||||
|
||||
Eliminiert JVM-Startup-Overhead durch Wiederverwendung von N Worker-Prozessen.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_workers: int,
|
||||
java_vm_path: Path,
|
||||
saxon_jar_path: Path,
|
||||
classpath_cache: dict[Path, str],
|
||||
log_dir: Optional[Path] = None,
|
||||
):
|
||||
"""
|
||||
Initialisiert den Saxon-Worker-Pool.
|
||||
|
||||
Args:
|
||||
num_workers: Anzahl der Worker-Prozesse
|
||||
java_vm_path: Pfad zur Java VM Binary
|
||||
saxon_jar_path: Pfad zur Saxon JAR-Datei
|
||||
classpath_cache: Cache für Saxon-Classpaths
|
||||
log_dir: Optionales Verzeichnis für Worker-Logs (Standard: temp_dir/temp)
|
||||
"""
|
||||
self.num_workers = num_workers
|
||||
self.java_vm_path = java_vm_path
|
||||
self.saxon_jar_path = saxon_jar_path
|
||||
self.classpath_cache = classpath_cache
|
||||
self.log_dir = log_dir
|
||||
|
||||
# Worker-Prozesse und Queues
|
||||
self.workers: list[subprocess.Popen] = []
|
||||
self.job_queue: Queue = Queue()
|
||||
self.result_queue: Queue = Queue()
|
||||
self.worker_locks: list[threading.Lock] = []
|
||||
|
||||
# Temporäres Verzeichnis für kompilierte Java-Klasse
|
||||
self.temp_dir: Optional[Path] = None
|
||||
self.worker_class_path: Optional[Path] = None
|
||||
self.worker_log_dir: Optional[Path] = None
|
||||
|
||||
# Initialisierung
|
||||
self._compile_worker_class()
|
||||
self._start_workers()
|
||||
|
||||
logger.info(f"SaxonWorkerPool initialisiert mit {num_workers} Workern")
|
||||
|
||||
def _compile_worker_class(self):
|
||||
"""Kompiliert die SaxonWorker-Java-Klasse."""
|
||||
try:
|
||||
# Erstelle temporäres Verzeichnis
|
||||
self.temp_dir = Path(tempfile.mkdtemp(prefix="saxon_worker_"))
|
||||
|
||||
# Schreibe Java-Quellcode
|
||||
java_file = self.temp_dir / "SaxonWorker.java"
|
||||
java_file.write_text(SAXON_WORKER_JAVA, encoding="utf-8")
|
||||
|
||||
# Hole Classpath
|
||||
saxon_dir = self.saxon_jar_path.parent
|
||||
if saxon_dir in self.classpath_cache:
|
||||
classpath = self.classpath_cache[saxon_dir]
|
||||
else:
|
||||
# Fallback: Baue Classpath neu
|
||||
import glob
|
||||
import sys
|
||||
|
||||
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||
lib_dir = saxon_dir / "lib"
|
||||
if lib_dir.exists():
|
||||
all_jars.extend(glob.glob(str(lib_dir / "*.jar")))
|
||||
|
||||
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||
classpath = classpath_separator.join(all_jars)
|
||||
|
||||
# Kompiliere Java-Klasse
|
||||
javac_cmd = [str(self.java_vm_path).replace("java", "javac"), "-cp", classpath, str(java_file)]
|
||||
|
||||
logger.debug(f"Kompiliere SaxonWorker: {' '.join(javac_cmd)}")
|
||||
|
||||
result = subprocess.run(javac_cmd, capture_output=True, text=True, timeout=30)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Java-Kompilierung fehlgeschlagen: {result.stderr}")
|
||||
|
||||
self.worker_class_path = self.temp_dir
|
||||
|
||||
logger.info(f"SaxonWorker erfolgreich kompiliert: {self.temp_dir}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Kompilieren von SaxonWorker: {e}")
|
||||
raise
|
||||
|
||||
def _start_workers(self):
|
||||
"""Startet N Worker-Prozesse."""
|
||||
# Hole Classpath
|
||||
saxon_dir = self.saxon_jar_path.parent
|
||||
classpath = self.classpath_cache.get(saxon_dir, "")
|
||||
|
||||
# Füge Worker-Classpath hinzu
|
||||
import sys
|
||||
|
||||
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||
full_classpath = str(self.worker_class_path) + classpath_separator + classpath
|
||||
|
||||
# Bestimme Log-Verzeichnis
|
||||
self.worker_log_dir = self.log_dir if self.log_dir else self.temp_dir
|
||||
if self.log_dir:
|
||||
self.worker_log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for i in range(self.num_workers):
|
||||
try:
|
||||
# Starte JVM-Prozess mit SaxonWorker
|
||||
cmd = [str(self.java_vm_path), "-cp", full_classpath, "SaxonWorker"]
|
||||
|
||||
# Öffne stderr-Log-Datei für diesen Worker
|
||||
stderr_log = self.worker_log_dir / f"worker_{i}_stderr.log"
|
||||
stderr_file = open(stderr_log, "w", encoding="utf-8")
|
||||
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=stderr_file, # Redirect stderr to file
|
||||
text=True,
|
||||
bufsize=1, # Line buffered
|
||||
)
|
||||
|
||||
self.workers.append(process)
|
||||
self.worker_locks.append(threading.Lock())
|
||||
|
||||
logger.debug(f"Worker {i} gestartet (PID: {process.pid}, stderr: {stderr_log})")
|
||||
|
||||
# Warte kurz damit Worker initialisieren kann
|
||||
import time
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
# Prüfe ob Worker noch läuft
|
||||
if process.poll() is not None:
|
||||
# Worker ist bereits beendet - Fehler!
|
||||
stderr_file.close()
|
||||
with open(stderr_log, "r") as f:
|
||||
stderr_content = f.read()
|
||||
raise RuntimeError(
|
||||
f"Worker {i} ist sofort beendet (Exit Code: {process.returncode})\nstderr:\n{stderr_content}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Starten von Worker {i}: {e}")
|
||||
raise
|
||||
|
||||
logger.info(f"{len(self.workers)} Saxon-Worker erfolgreich gestartet")
|
||||
|
||||
def transform(
|
||||
self, source_xml: Path, xsl_stylesheet: Path, output_fo: Path, xslt_params: dict[str, str]
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
Führt eine XSLT-Transformation mit einem Worker aus dem Pool aus.
|
||||
|
||||
Args:
|
||||
source_xml: Pfad zur XML-Eingabedatei
|
||||
xsl_stylesheet: Pfad zur XSL-Stylesheet-Datei
|
||||
output_fo: Pfad zur FO-Ausgabedatei
|
||||
xslt_params: Dictionary mit XSLT-Parametern
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: (Erfolg, Fehlermeldung/Info)
|
||||
"""
|
||||
# Finde freien Worker
|
||||
worker_idx = None
|
||||
for i, lock in enumerate(self.worker_locks):
|
||||
if lock.acquire(blocking=False):
|
||||
worker_idx = i
|
||||
break
|
||||
|
||||
if worker_idx is None:
|
||||
# Kein freier Worker, warte auf ersten verfügbaren
|
||||
for i, lock in enumerate(self.worker_locks):
|
||||
lock.acquire()
|
||||
worker_idx = i
|
||||
break
|
||||
|
||||
try:
|
||||
worker = self.workers[worker_idx]
|
||||
|
||||
# Prüfe ob Worker noch läuft
|
||||
if worker.poll() is not None:
|
||||
# Worker ist tot!
|
||||
stderr_log = self.worker_log_dir / f"worker_{worker_idx}_stderr.log"
|
||||
try:
|
||||
with open(stderr_log, "r") as f:
|
||||
stderr_content = f.read()
|
||||
error_msg = (
|
||||
f"Worker {worker_idx} ist beendet (Exit: {worker.returncode})\nstderr:\n{stderr_content}"
|
||||
)
|
||||
except Exception:
|
||||
error_msg = f"Worker {worker_idx} ist beendet (Exit: {worker.returncode})"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# Formatiere Parameter
|
||||
params_str = "|||".join([f"{key}={value}" for key, value in xslt_params.items()])
|
||||
|
||||
# Erstelle Job-String (Tab-separated)
|
||||
job = f"{source_xml}\t{xsl_stylesheet}\t{output_fo}\t{params_str}\n"
|
||||
|
||||
logger.debug(f"Sende Job an Worker {worker_idx}: {source_xml.name}")
|
||||
|
||||
# Sende Job an Worker
|
||||
worker.stdin.write(job)
|
||||
worker.stdin.flush()
|
||||
|
||||
# Warte auf Antwort
|
||||
response = worker.stdout.readline().strip()
|
||||
|
||||
logger.debug(f"Worker {worker_idx} Antwort: '{response}'")
|
||||
|
||||
if response == "OK":
|
||||
return True, "Erfolgreich"
|
||||
elif response.startswith("ERROR:"):
|
||||
error_msg = response[6:].strip()
|
||||
return False, f"Saxon-Fehler: {error_msg}"
|
||||
else:
|
||||
# Leere Antwort bedeutet Worker ist crashed
|
||||
if not response:
|
||||
stderr_log = self.worker_log_dir / f"worker_{worker_idx}_stderr.log"
|
||||
try:
|
||||
with open(stderr_log, "r") as f:
|
||||
stderr_content = f.read()[-500:] # Letzte 500 Zeichen
|
||||
return False, f"Worker {worker_idx} crashed (keine Antwort)\nstderr:\n{stderr_content}"
|
||||
except Exception:
|
||||
return False, f"Worker {worker_idx} crashed (keine Antwort)"
|
||||
return False, f"Unerwartete Antwort: {response}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei Worker {worker_idx}: {e}")
|
||||
return False, f"Worker-Fehler: {str(e)}"
|
||||
|
||||
finally:
|
||||
# Gebe Worker-Lock frei
|
||||
self.worker_locks[worker_idx].release()
|
||||
|
||||
def shutdown(self):
|
||||
"""Beendet alle Worker-Prozesse sauber."""
|
||||
logger.info("Beende Saxon-Worker-Pool...")
|
||||
|
||||
for i, worker in enumerate(self.workers):
|
||||
try:
|
||||
# Sende EXIT-Befehl
|
||||
if worker.stdin and not worker.stdin.closed:
|
||||
worker.stdin.write("EXIT\n")
|
||||
worker.stdin.flush()
|
||||
|
||||
# Warte auf Beendigung (max 2 Sekunden)
|
||||
worker.wait(timeout=2)
|
||||
logger.debug(f"Worker {i} beendet")
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
# Force kill falls nötig
|
||||
worker.kill()
|
||||
logger.warning(f"Worker {i} musste gekillt werden")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Beenden von Worker {i}: {e}")
|
||||
|
||||
# Lösche temporäres Verzeichnis
|
||||
if self.temp_dir and self.temp_dir.exists():
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(self.temp_dir)
|
||||
logger.debug(f"Temporäres Verzeichnis gelöscht: {self.temp_dir}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Konnte temporäres Verzeichnis nicht löschen: {e}")
|
||||
|
||||
logger.info("Saxon-Worker-Pool beendet")
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit."""
|
||||
self.shutdown()
|
||||
@@ -1,519 +0,0 @@
|
||||
"""
|
||||
Transformations-Engine für XSL-FO PDF-Generierung.
|
||||
|
||||
Dieses Modul implementiert die Transformations-Pipeline:
|
||||
1. XML → FO (Saxon XSLT Transformation)
|
||||
2. FO → PDF (Apache FOP)
|
||||
3. PDF-Vergleich (diff-pdf)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from saxon_pool import SaxonWorkerPool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Globaler Saxon-Worker-Pool (wird von MainWindow initialisiert)
|
||||
_saxon_worker_pool: Optional["SaxonWorkerPool"] = None
|
||||
|
||||
|
||||
def set_saxon_worker_pool(pool: Optional["SaxonWorkerPool"]):
|
||||
"""Setzt den globalen Saxon-Worker-Pool."""
|
||||
global _saxon_worker_pool
|
||||
_saxon_worker_pool = pool
|
||||
if pool:
|
||||
logger.info(f"Saxon-Worker-Pool aktiviert mit {pool.num_workers} Workern")
|
||||
else:
|
||||
logger.info("Saxon-Worker-Pool deaktiviert (Fallback auf subprocess)")
|
||||
|
||||
|
||||
class TransformationJob:
|
||||
"""
|
||||
Repräsentiert einen einzelnen Transformations-Job.
|
||||
|
||||
Ähnlich zur TestFall-Klasse in validate-xls.py, aber für DocuMentor angepasst.
|
||||
"""
|
||||
|
||||
# Klassenweiter Cache für Saxon-Classpaths (Performance-Optimierung)
|
||||
_classpath_cache: dict[Path, str] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_dir: Path,
|
||||
xml_file: Path,
|
||||
xsl_file: Path,
|
||||
xslt_params: dict[str, str],
|
||||
java_vm_path: Path,
|
||||
saxon_jar_path: Path,
|
||||
apache_fop_dir: Path,
|
||||
diff_pdf_path: Path,
|
||||
diff_pdf_params: list[str],
|
||||
xsl_id: tuple | None = None,
|
||||
fop_config_dir: Path | None = None,
|
||||
):
|
||||
"""
|
||||
Initialisiert einen Transformations-Job.
|
||||
|
||||
Args:
|
||||
project_dir: Pfad zum Projekt-Verzeichnis
|
||||
xml_file: Relative Pfad zur XML-Eingabedatei (relativ zu project_dir)
|
||||
xsl_file: Absolute Pfad zur XSL-Stylesheet-Datei
|
||||
xslt_params: Dictionary mit XSLT-Parametern
|
||||
java_vm_path: Pfad zur Java VM Binary
|
||||
saxon_jar_path: Pfad zur Saxon JAR-Datei
|
||||
apache_fop_dir: Pfad zum Apache FOP-Verzeichnis
|
||||
diff_pdf_path: Pfad zur diff-pdf Binary
|
||||
diff_pdf_params: Standard-Parameter für diff-pdf
|
||||
xsl_id: ID der XSL-Datei (als Tuple)
|
||||
fop_config_dir: Optionaler Pfad zum FOP-Config-Verzeichnis (überschreibt Standardpfad)
|
||||
"""
|
||||
self.project_dir = project_dir
|
||||
self.xml_file = xml_file # Relativ
|
||||
self.xsl_file = xsl_file # Absolut
|
||||
self.xslt_params = xslt_params
|
||||
self.xsl_id = xsl_id
|
||||
|
||||
# Tool-Pfade
|
||||
self.java_vm_path = java_vm_path
|
||||
self.saxon_jar_path = saxon_jar_path
|
||||
self.apache_fop_dir = apache_fop_dir
|
||||
self.fop_config_dir = fop_config_dir
|
||||
self.diff_pdf_path = diff_pdf_path
|
||||
self.diff_pdf_params = diff_pdf_params
|
||||
|
||||
# Ausgabe-Verzeichnisse im Projektordner
|
||||
self.new_dir = project_dir / "new"
|
||||
self.ref_dir = project_dir / "ref"
|
||||
self.diff_dir = project_dir / "diff"
|
||||
|
||||
# Stelle sicher, dass Ausgabe-Verzeichnisse existieren
|
||||
self.new_dir.mkdir(exist_ok=True)
|
||||
self.ref_dir.mkdir(exist_ok=True)
|
||||
self.diff_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Dateinamen basierend auf XML-Datei + XSL-ID
|
||||
base_name = self.xml_file.stem
|
||||
|
||||
# Füge XSL-ID zum Dateinamen hinzu, falls vorhanden
|
||||
if xsl_id:
|
||||
# Konvertiere Tuple (1, 2, 3) zu String "1_2_3"
|
||||
xsl_id_str = "_".join(str(x) for x in xsl_id)
|
||||
file_name_base = f"{base_name}_xsl_{xsl_id_str}"
|
||||
else:
|
||||
file_name_base = base_name
|
||||
|
||||
self.temp_fo = self.new_dir / f"{file_name_base}.fo"
|
||||
self.new_pdf = self.new_dir / f"{file_name_base}.pdf"
|
||||
self.ref_pdf = self.ref_dir / f"{file_name_base}.pdf"
|
||||
self.diff_pdf = self.diff_dir / f"{file_name_base}.pdf"
|
||||
|
||||
# Apache FOP Binaries (plattformabhängig)
|
||||
import sys
|
||||
|
||||
if sys.platform == "win32":
|
||||
self.fop_cmd = self.apache_fop_dir / "fop.cmd"
|
||||
else:
|
||||
self.fop_cmd = self.apache_fop_dir / "fop"
|
||||
|
||||
# FOP-Konfigurationsdatei: Verwende fop_config_dir falls angegeben, sonst Standardpfad
|
||||
if self.fop_config_dir:
|
||||
self.fop_conf = self.fop_config_dir / "fop.xconf"
|
||||
else:
|
||||
self.fop_conf = self.apache_fop_dir / "conf" / "fop.xconf"
|
||||
|
||||
def is_up_to_date(self) -> bool:
|
||||
"""
|
||||
Prüft, ob die Transformation aktuell ist.
|
||||
|
||||
Returns:
|
||||
bool: True wenn New-PDF existiert und aktueller ist als alle Inputs
|
||||
"""
|
||||
if not self.new_pdf.exists():
|
||||
logger.debug(f"New-PDF existiert nicht: {self.new_pdf}")
|
||||
return False
|
||||
|
||||
output_mtime = self.new_pdf.stat().st_mtime
|
||||
|
||||
# Prüfe XML-Datei
|
||||
xml_abs = self.project_dir / self.xml_file
|
||||
if xml_abs.exists() and xml_abs.stat().st_mtime > output_mtime:
|
||||
logger.debug(f"XML-Datei ist neuer: {xml_abs}")
|
||||
return False
|
||||
|
||||
# Prüfe XSL-Datei
|
||||
if self.xsl_file.exists() and self.xsl_file.stat().st_mtime > output_mtime:
|
||||
logger.debug(f"XSL-Datei ist neuer: {self.xsl_file}")
|
||||
return False
|
||||
|
||||
logger.debug(f"Transformation ist aktuell: {self.new_pdf}")
|
||||
return True
|
||||
|
||||
def transform_saxon(self, force: bool = False) -> tuple[bool, str]:
|
||||
"""
|
||||
Führt XSLT-Transformation mit Saxon aus: XML → FO.
|
||||
|
||||
Args:
|
||||
force: Wenn True, wird Transformation auch bei aktuellem Output durchgeführt
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: (Erfolg, Fehlermeldung/Info)
|
||||
"""
|
||||
if not force and self.is_up_to_date():
|
||||
logger.info(f"Transformation übersprungen (aktuell): {self.xml_file.name}")
|
||||
return True, "Übersprungen (aktuell)"
|
||||
|
||||
xml_abs = self.project_dir / self.xml_file
|
||||
|
||||
# Prüfe ob Eingabedateien existieren
|
||||
if not xml_abs.exists():
|
||||
error_msg = f"XML-Datei nicht gefunden: {xml_abs}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
if not self.xsl_file.exists():
|
||||
error_msg = f"XSL-Datei nicht gefunden: {self.xsl_file}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
logger.info(f"Starte Saxon-Transformation: {self.xml_file.name}")
|
||||
|
||||
# Versuche zuerst den Worker-Pool zu nutzen (schneller!)
|
||||
global _saxon_worker_pool
|
||||
if _saxon_worker_pool:
|
||||
try:
|
||||
success, message = _saxon_worker_pool.transform(
|
||||
source_xml=xml_abs,
|
||||
xsl_stylesheet=self.xsl_file,
|
||||
output_fo=self.temp_fo,
|
||||
xslt_params=self.xslt_params,
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info(f"Saxon-Transformation erfolgreich (Worker-Pool): {self.xml_file.name}")
|
||||
else:
|
||||
logger.error(f"Saxon-Transformation fehlgeschlagen (Worker-Pool): {message}")
|
||||
|
||||
return success, message
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Worker-Pool-Fehler, Fallback auf subprocess: {e}")
|
||||
# Fallback auf subprocess unten
|
||||
|
||||
# Fallback: Traditionelle subprocess-Methode (langsamer, aber robuster)
|
||||
# XSLT-Parameter formatieren
|
||||
params = [f"{key}={value}" for key, value in self.xslt_params.items()]
|
||||
|
||||
# Hole Classpath aus Cache oder erstelle ihn
|
||||
saxon_dir = self.saxon_jar_path.parent
|
||||
if saxon_dir not in TransformationJob._classpath_cache:
|
||||
# Sammle alle JAR-Dateien im Saxon-Verzeichnis für den Classpath
|
||||
import glob
|
||||
|
||||
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||
|
||||
# Sammle auch alle JARs aus dem lib-Unterordner (z.B. xmlresolver)
|
||||
lib_dir = saxon_dir / "lib"
|
||||
if lib_dir.exists() and lib_dir.is_dir():
|
||||
lib_jars = glob.glob(str(lib_dir / "*.jar"))
|
||||
all_jars.extend(lib_jars)
|
||||
logger.debug(f"Zusätzliche JARs aus lib-Verzeichnis gefunden: {len(lib_jars)}")
|
||||
|
||||
# Verwende alle JARs im Classpath (getrennt durch : auf Linux/Mac, ; auf Windows)
|
||||
import sys
|
||||
|
||||
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||
classpath = classpath_separator.join(all_jars)
|
||||
|
||||
# Cache den Classpath für zukünftige Jobs
|
||||
TransformationJob._classpath_cache[saxon_dir] = classpath
|
||||
logger.debug(f"Classpath für {saxon_dir} gecacht")
|
||||
else:
|
||||
classpath = TransformationJob._classpath_cache[saxon_dir]
|
||||
logger.debug("Classpath aus Cache verwendet")
|
||||
|
||||
# Saxon-Kommandozeile
|
||||
cmd_line = [
|
||||
str(self.java_vm_path),
|
||||
"-cp",
|
||||
classpath,
|
||||
"net.sf.saxon.Transform",
|
||||
f"-s:{xml_abs}",
|
||||
f"-xsl:{self.xsl_file}",
|
||||
f"-o:{self.temp_fo}",
|
||||
*params,
|
||||
]
|
||||
|
||||
logger.debug(f"Kommandozeile (subprocess fallback): {' '.join(cmd_line)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd_line,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120, # 2 Minuten Timeout
|
||||
)
|
||||
|
||||
# Saxon Ausgaben loggen
|
||||
if result.stdout:
|
||||
logger.debug(f"Saxon StdOut:\n{result.stdout}")
|
||||
if result.stderr:
|
||||
logger.debug(f"Saxon StdErr:\n{result.stderr}")
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"Saxon-Transformation erfolgreich (subprocess): {self.xml_file.name}")
|
||||
return True, "Erfolgreich"
|
||||
else:
|
||||
error_msg = (
|
||||
f"Saxon-Fehler (Exit {result.returncode}):\nStdOut: {result.stdout}\nStdErr: {result.stderr}"
|
||||
)
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
error_msg = "Saxon-Transformation Timeout (>120s)"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"Unerwarteter Fehler bei Saxon-Transformation: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
def build_pdf(self, force: bool = False) -> tuple[bool, str]:
|
||||
"""
|
||||
Generiert PDF aus FO-Datei mit Apache FOP: FO → PDF.
|
||||
|
||||
Args:
|
||||
force: Wenn True, wird Build auch bei aktuellem Output durchgeführt
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: (Erfolg, Fehlermeldung/Info)
|
||||
"""
|
||||
if not force and self.is_up_to_date():
|
||||
logger.info(f"PDF-Build übersprungen (aktuell): {self.xml_file.name}")
|
||||
return True, "Übersprungen (aktuell)"
|
||||
|
||||
# Prüfe ob FO-Datei existiert
|
||||
if not self.temp_fo.exists():
|
||||
error_msg = f"FO-Datei nicht gefunden: {self.temp_fo}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
# Apache FOP Kommandozeile
|
||||
cmd_line = [
|
||||
str(self.fop_cmd),
|
||||
"-c",
|
||||
str(self.fop_conf) if self.fop_conf.exists() else "",
|
||||
"-r",
|
||||
"-fo",
|
||||
str(self.temp_fo),
|
||||
"-pdf",
|
||||
str(self.new_pdf),
|
||||
]
|
||||
|
||||
# Entferne leere Config-Parameter wenn fop.xconf nicht existiert
|
||||
if not self.fop_conf.exists():
|
||||
cmd_line = [c for c in cmd_line if c not in ["-c", ""]]
|
||||
|
||||
logger.info(f"Starte Apache FOP PDF-Generierung: {self.xml_file.name}")
|
||||
logger.debug(f"Kommandozeile: {' '.join(cmd_line)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd_line,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180, # 3 Minuten Timeout
|
||||
)
|
||||
|
||||
# Apache FOP Ausgaben loggen
|
||||
if result.stdout:
|
||||
logger.debug(f"FOP StdOut:\n{result.stdout}")
|
||||
if result.stderr:
|
||||
logger.debug(f"FOP StdErr:\n{result.stderr}")
|
||||
|
||||
# Temporäre FO-Datei löschen
|
||||
if self.temp_fo.exists():
|
||||
try:
|
||||
self.temp_fo.unlink()
|
||||
logger.debug(f"Temporäre FO-Datei gelöscht: {self.temp_fo}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Konnte FO-Datei nicht löschen: {e}")
|
||||
|
||||
if result.returncode == 0:
|
||||
# Wenn kein Ref-PDF existiert, erstelle es
|
||||
if not self.ref_pdf.exists():
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.copy2(self.new_pdf, self.ref_pdf)
|
||||
logger.info(f"Ref-PDF erstellt: {self.ref_pdf}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Konnte Ref-PDF nicht erstellen: {e}")
|
||||
|
||||
logger.info(f"PDF-Generierung erfolgreich: {self.new_pdf}")
|
||||
return True, "Erfolgreich"
|
||||
else:
|
||||
error_msg = f"FOP-Fehler (Exit {result.returncode}):\nStdOut: {result.stdout}\nStdErr: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
error_msg = "FOP PDF-Generierung Timeout (>180s)"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"Unerwarteter Fehler bei PDF-Generierung: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
def compare_pdf(self) -> tuple[bool, str]:
|
||||
"""
|
||||
Vergleicht New-PDF mit Ref-PDF und erstellt ggf. Diff-PDF.
|
||||
|
||||
Returns:
|
||||
tuple[bool, str]: (PDFs sind identisch, Fehlermeldung/Info)
|
||||
"""
|
||||
# Prüfe ob beide PDFs existieren
|
||||
if not self.ref_pdf.exists():
|
||||
info_msg = "Kein Ref-PDF vorhanden (wird beim nächsten Build erstellt)"
|
||||
logger.info(info_msg)
|
||||
return True, info_msg
|
||||
|
||||
if not self.new_pdf.exists():
|
||||
error_msg = f"New-PDF nicht gefunden: {self.new_pdf}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
logger.info(f"Vergleiche PDFs: {self.xml_file.name}")
|
||||
|
||||
# Erster Vergleich (ohne Diff-Generierung)
|
||||
cmd_compare = [
|
||||
str(self.diff_pdf_path),
|
||||
*self.diff_pdf_params,
|
||||
str(self.ref_pdf),
|
||||
str(self.new_pdf),
|
||||
]
|
||||
|
||||
logger.debug(f"Kommandozeile Vergleich: {' '.join(cmd_compare)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd_compare,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60, # 1 Minute Timeout
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# PDFs sind identisch
|
||||
logger.info(f"PDFs sind identisch: {self.xml_file.name}")
|
||||
|
||||
# Lösche altes Diff-PDF falls vorhanden
|
||||
if self.diff_pdf.exists():
|
||||
try:
|
||||
self.diff_pdf.unlink()
|
||||
logger.debug(f"Diff-PDF gelöscht (nicht mehr nötig): {self.diff_pdf}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Konnte Diff-PDF nicht löschen: {e}")
|
||||
|
||||
return True, "PDFs sind identisch"
|
||||
else:
|
||||
# PDFs unterscheiden sich - erstelle Diff-PDF
|
||||
logger.info(f"PDFs unterscheiden sich, erstelle Diff-PDF: {self.xml_file.name}")
|
||||
|
||||
cmd_diff = [
|
||||
str(self.diff_pdf_path),
|
||||
f"--output-diff={self.diff_pdf}",
|
||||
*self.diff_pdf_params,
|
||||
"--mark-differences",
|
||||
str(self.ref_pdf),
|
||||
str(self.new_pdf),
|
||||
]
|
||||
|
||||
logger.debug(f"Kommandozeile Diff: {' '.join(cmd_diff)}")
|
||||
|
||||
result_diff = subprocess.run(
|
||||
cmd_diff,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=90, # 1.5 Minuten Timeout
|
||||
)
|
||||
|
||||
if result_diff.returncode == 0 or self.diff_pdf.exists():
|
||||
logger.info(f"Diff-PDF erstellt: {self.diff_pdf}")
|
||||
return False, f"Unterschiede gefunden - Diff-PDF: {self.diff_pdf.name}"
|
||||
else:
|
||||
error_msg = f"Diff-PDF-Erstellung fehlgeschlagen: {result_diff.stderr}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
error_msg = "PDF-Vergleich Timeout"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"Unerwarteter Fehler beim PDF-Vergleich: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
def run_full_pipeline(self, force: bool = False) -> dict[str, Any]:
|
||||
"""
|
||||
Führt die komplette Transformations-Pipeline aus:
|
||||
1. Saxon-Transformation (XML → FO)
|
||||
2. PDF-Generierung (FO → PDF)
|
||||
3. PDF-Vergleich
|
||||
|
||||
Args:
|
||||
force: Wenn True, werden alle Schritte ausgeführt (ignoriert Up-to-Date)
|
||||
|
||||
Returns:
|
||||
dict: Ergebnis-Dictionary mit Status und Meldungen
|
||||
"""
|
||||
start_time = datetime.now()
|
||||
result = {
|
||||
"success": False,
|
||||
"xml_file": str(self.xml_file),
|
||||
"xsl_id": self.xsl_id,
|
||||
"steps": {},
|
||||
"duration": None,
|
||||
"new_pdf": str(self.new_pdf) if self.new_pdf.exists() else None,
|
||||
"diff_pdf": str(self.diff_pdf) if self.diff_pdf.exists() else None,
|
||||
}
|
||||
|
||||
logger.info(f"Starte Transformations-Pipeline: {self.xml_file.name}")
|
||||
|
||||
# Schritt 1: Saxon-Transformation
|
||||
success_saxon, msg_saxon = self.transform_saxon(force=force)
|
||||
result["steps"]["saxon"] = {"success": success_saxon, "message": msg_saxon}
|
||||
|
||||
if not success_saxon:
|
||||
result["success"] = False
|
||||
result["duration"] = (datetime.now() - start_time).total_seconds()
|
||||
return result
|
||||
|
||||
# Schritt 2: PDF-Generierung
|
||||
success_build, msg_build = self.build_pdf(force=force)
|
||||
result["steps"]["build"] = {"success": success_build, "message": msg_build}
|
||||
|
||||
if not success_build:
|
||||
result["success"] = False
|
||||
result["duration"] = (datetime.now() - start_time).total_seconds()
|
||||
return result
|
||||
|
||||
# Schritt 3: PDF-Vergleich
|
||||
pdfs_identical, msg_compare = self.compare_pdf()
|
||||
result["steps"]["compare"] = {"identical": pdfs_identical, "message": msg_compare}
|
||||
result["pdfs_identical"] = pdfs_identical
|
||||
|
||||
# Pipeline erfolgreich abgeschlossen
|
||||
result["success"] = True
|
||||
result["duration"] = (datetime.now() - start_time).total_seconds()
|
||||
|
||||
logger.info(f"Pipeline abgeschlossen: {self.xml_file.name} ({result['duration']:.2f}s)")
|
||||
|
||||
return result
|
||||
+1
-10
@@ -466,7 +466,6 @@ class AppSettingsDlg(QDialog):
|
||||
apache_fop_id=project_data['apache_fop_id'] if project_data['apache_fop_id'] != -1 else 1,
|
||||
xsl_dir_id=project_data['xsl_dir_id'] if project_data['xsl_dir_id'] != -1 else 1,
|
||||
postgre_sql_db_id=project_data['postgre_sql_db_id'] if project_data['postgre_sql_db_id'] != -1 else 1,
|
||||
fop_config_dir=Path(project_data['fop_config_dir']) if project_data.get('fop_config_dir') else None,
|
||||
)
|
||||
|
||||
self.temp_pdf_projects.append(new_project)
|
||||
@@ -618,9 +617,7 @@ class AppSettingsDlg(QDialog):
|
||||
'diff_pdf_id': pdf_project.diff_pdf_id,
|
||||
'saxon_jar_id': pdf_project.saxon_jar_id,
|
||||
'apache_fop_id': pdf_project.apache_fop_id,
|
||||
'xsl_dir_id': pdf_project.xsl_dir_id,
|
||||
'postgre_sql_db_id': pdf_project.postgre_sql_db_id,
|
||||
'fop_config_dir': str(pdf_project.fop_config_dir) if pdf_project.fop_config_dir else None
|
||||
'xsl_dir_id': pdf_project.xsl_dir_id
|
||||
}
|
||||
|
||||
# Dialog im Edit-Modus öffnen (Projekt-Name und -Ordner deaktiviert)
|
||||
@@ -635,15 +632,9 @@ class AppSettingsDlg(QDialog):
|
||||
pdf_project.saxon_jar_id = new_data['saxon_jar_id'] if new_data['saxon_jar_id'] != -1 else pdf_project.saxon_jar_id
|
||||
pdf_project.apache_fop_id = new_data['apache_fop_id'] if new_data['apache_fop_id'] != -1 else pdf_project.apache_fop_id
|
||||
pdf_project.xsl_dir_id = new_data['xsl_dir_id'] if new_data['xsl_dir_id'] != -1 else pdf_project.xsl_dir_id
|
||||
pdf_project.postgre_sql_db_id = new_data['postgre_sql_db_id'] if new_data['postgre_sql_db_id'] != -1 else pdf_project.postgre_sql_db_id
|
||||
pdf_project.fop_config_dir = Path(new_data['fop_config_dir']) if new_data.get('fop_config_dir') else None
|
||||
|
||||
self._populate_pdf_project_table()
|
||||
|
||||
# Einstellungen speichern
|
||||
self.settings.pdf_projects = self.temp_pdf_projects.copy()
|
||||
self.settings.save()
|
||||
|
||||
# PostgreSQL Methoden
|
||||
def _add_postgresql_db(self):
|
||||
"""Fügt eine neue PostgreSQL-Datenbank hinzu."""
|
||||
|
||||
+34
-54
@@ -32,12 +32,6 @@
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="opaqueResize">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
@@ -70,7 +64,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>3</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="headerHighlightSections">
|
||||
<bool>true</bool>
|
||||
@@ -88,11 +82,6 @@
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">3</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -174,12 +163,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<property name="midLineWidth">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -188,8 +171,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>68</width>
|
||||
<height>728</height>
|
||||
<width>54</width>
|
||||
<height>718</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
@@ -225,7 +208,7 @@
|
||||
</widget>
|
||||
<widget class="QFrame" name="frame_3">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
@@ -246,7 +229,7 @@
|
||||
<item>
|
||||
<widget class="QFrame" name="frame_4">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
@@ -353,40 +336,11 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="accept_changes">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>✅ Änderungen übernehmen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea_2">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -395,8 +349,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>726</width>
|
||||
<height>697</height>
|
||||
<width>649</width>
|
||||
<height>690</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
@@ -412,6 +366,20 @@
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
@@ -428,7 +396,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1263</width>
|
||||
<height>22</height>
|
||||
<height>33</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuProjekt">
|
||||
@@ -436,6 +404,7 @@
|
||||
<string>Projekt</string>
|
||||
</property>
|
||||
<addaction name="actionNeu"/>
|
||||
<addaction name="action_ffnen"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionVorhandene_Projekte"/>
|
||||
<addaction name="separator"/>
|
||||
@@ -463,6 +432,17 @@
|
||||
<string>Ctrl+N</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_ffnen">
|
||||
<property name="icon">
|
||||
<iconset theme="folder-open"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Öffnen ...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+O</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionBeenden">
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::ApplicationExit"/>
|
||||
|
||||
+38
-35
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'MainWinddow.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.9.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -31,14 +31,18 @@ class Ui_MainWindow(object):
|
||||
self.actionNeu.setObjectName(u"actionNeu")
|
||||
icon = QIcon(QIcon.fromTheme(u"folder-new"))
|
||||
self.actionNeu.setIcon(icon)
|
||||
self.action_ffnen = QAction(MainWindow)
|
||||
self.action_ffnen.setObjectName(u"action_ffnen")
|
||||
icon1 = QIcon(QIcon.fromTheme(u"folder-open"))
|
||||
self.action_ffnen.setIcon(icon1)
|
||||
self.actionBeenden = QAction(MainWindow)
|
||||
self.actionBeenden.setObjectName(u"actionBeenden")
|
||||
icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit))
|
||||
self.actionBeenden.setIcon(icon1)
|
||||
icon2 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit))
|
||||
self.actionBeenden.setIcon(icon2)
|
||||
self.actionEinstellungen = QAction(MainWindow)
|
||||
self.actionEinstellungen.setObjectName(u"actionEinstellungen")
|
||||
icon2 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties))
|
||||
self.actionEinstellungen.setIcon(icon2)
|
||||
icon3 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties))
|
||||
self.actionEinstellungen.setIcon(icon3)
|
||||
self.actionVorhandene_Projekte = QAction(MainWindow)
|
||||
self.actionVorhandene_Projekte.setObjectName(u"actionVorhandene_Projekte")
|
||||
self.actionVorhandene_Projekte.setEnabled(False)
|
||||
@@ -50,8 +54,6 @@ class Ui_MainWindow(object):
|
||||
self.splitter = QSplitter(self.centralwidget)
|
||||
self.splitter.setObjectName(u"splitter")
|
||||
self.splitter.setOrientation(Qt.Orientation.Horizontal)
|
||||
self.splitter.setOpaqueResize(False)
|
||||
self.splitter.setChildrenCollapsible(False)
|
||||
self.frame = QFrame(self.splitter)
|
||||
self.frame.setObjectName(u"frame")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
|
||||
@@ -67,7 +69,6 @@ class Ui_MainWindow(object):
|
||||
self.verticalLayout.setContentsMargins(-1, -1, -1, 0)
|
||||
self.treeWidget = QTreeWidget(self.frame)
|
||||
__qtreewidgetitem = QTreeWidgetItem()
|
||||
__qtreewidgetitem.setText(2, u"3");
|
||||
__qtreewidgetitem.setText(1, u"2");
|
||||
__qtreewidgetitem.setText(0, u"1");
|
||||
self.treeWidget.setHeaderItem(__qtreewidgetitem)
|
||||
@@ -77,7 +78,7 @@ class Ui_MainWindow(object):
|
||||
sizePolicy1.setVerticalStretch(0)
|
||||
sizePolicy1.setHeightForWidth(self.treeWidget.sizePolicy().hasHeightForWidth())
|
||||
self.treeWidget.setSizePolicy(sizePolicy1)
|
||||
self.treeWidget.setColumnCount(3)
|
||||
self.treeWidget.setColumnCount(2)
|
||||
self.treeWidget.header().setHighlightSections(True)
|
||||
self.treeWidget.header().setStretchLastSection(True)
|
||||
|
||||
@@ -92,16 +93,16 @@ class Ui_MainWindow(object):
|
||||
self.pushButton = QPushButton(self.frame_2)
|
||||
self.pushButton.setObjectName(u"pushButton")
|
||||
self.pushButton.setLayoutDirection(Qt.LayoutDirection.LeftToRight)
|
||||
icon3 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart))
|
||||
self.pushButton.setIcon(icon3)
|
||||
icon4 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart))
|
||||
self.pushButton.setIcon(icon4)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.pushButton)
|
||||
|
||||
self.pushButton_2 = QPushButton(self.frame_2)
|
||||
self.pushButton_2.setObjectName(u"pushButton_2")
|
||||
self.pushButton_2.setAutoFillBackground(False)
|
||||
icon4 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.MediaSeekForward))
|
||||
self.pushButton_2.setIcon(icon4)
|
||||
icon5 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.MediaSeekForward))
|
||||
self.pushButton_2.setIcon(icon5)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.pushButton_2)
|
||||
|
||||
@@ -111,8 +112,8 @@ class Ui_MainWindow(object):
|
||||
|
||||
self.pB_lade_aus_fn2 = QPushButton(self.frame_2)
|
||||
self.pB_lade_aus_fn2.setObjectName(u"pB_lade_aus_fn2")
|
||||
icon5 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.GoDown))
|
||||
self.pB_lade_aus_fn2.setIcon(icon5)
|
||||
icon6 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.GoDown))
|
||||
self.pB_lade_aus_fn2.setIcon(icon6)
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.pB_lade_aus_fn2)
|
||||
|
||||
@@ -127,12 +128,10 @@ class Ui_MainWindow(object):
|
||||
sizePolicy2.setVerticalStretch(0)
|
||||
sizePolicy2.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth())
|
||||
self.scrollArea.setSizePolicy(sizePolicy2)
|
||||
self.scrollArea.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.scrollArea.setMidLineWidth(1)
|
||||
self.scrollArea.setWidgetResizable(True)
|
||||
self.scrollAreaWidgetContents = QWidget()
|
||||
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
|
||||
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 68, 728))
|
||||
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 54, 718))
|
||||
self.verticalLayout_2 = QVBoxLayout(self.scrollAreaWidgetContents)
|
||||
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
||||
self.label = QLabel(self.scrollAreaWidgetContents)
|
||||
@@ -153,14 +152,14 @@ class Ui_MainWindow(object):
|
||||
self.splitter.addWidget(self.scrollArea)
|
||||
self.frame_3 = QFrame(self.splitter)
|
||||
self.frame_3.setObjectName(u"frame_3")
|
||||
self.frame_3.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.frame_3.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.frame_3.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.verticalLayout_4 = QVBoxLayout(self.frame_3)
|
||||
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
|
||||
self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||
self.frame_4 = QFrame(self.frame_3)
|
||||
self.frame_4.setObjectName(u"frame_4")
|
||||
self.frame_4.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.frame_4.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.frame_4.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.horizontalLayout_3 = QHBoxLayout(self.frame_4)
|
||||
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
||||
@@ -209,30 +208,28 @@ class Ui_MainWindow(object):
|
||||
|
||||
self.horizontalLayout_3.addItem(self.horizontalSpacer_5)
|
||||
|
||||
self.accept_changes = QPushButton(self.frame_4)
|
||||
self.accept_changes.setObjectName(u"accept_changes")
|
||||
self.accept_changes.setEnabled(False)
|
||||
|
||||
self.horizontalLayout_3.addWidget(self.accept_changes)
|
||||
|
||||
self.horizontalSpacer_3 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
|
||||
self.horizontalLayout_3.addItem(self.horizontalSpacer_3)
|
||||
|
||||
|
||||
self.verticalLayout_4.addWidget(self.frame_4)
|
||||
|
||||
self.scrollArea_2 = QScrollArea(self.frame_3)
|
||||
self.scrollArea_2.setObjectName(u"scrollArea_2")
|
||||
self.scrollArea_2.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scrollArea_2.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.scrollArea_2.setWidgetResizable(True)
|
||||
self.scrollAreaWidgetContents_2 = QWidget()
|
||||
self.scrollAreaWidgetContents_2.setObjectName(u"scrollAreaWidgetContents_2")
|
||||
self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 726, 697))
|
||||
self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 649, 690))
|
||||
self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents_2)
|
||||
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
|
||||
self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||
self.label_3 = QLabel(self.scrollAreaWidgetContents_2)
|
||||
self.label_3.setObjectName(u"label_3")
|
||||
|
||||
self.verticalLayout_3.addWidget(self.label_3)
|
||||
|
||||
self.label_4 = QLabel(self.scrollAreaWidgetContents_2)
|
||||
self.label_4.setObjectName(u"label_4")
|
||||
|
||||
self.verticalLayout_3.addWidget(self.label_4)
|
||||
|
||||
self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2)
|
||||
|
||||
self.verticalLayout_4.addWidget(self.scrollArea_2)
|
||||
@@ -244,7 +241,7 @@ class Ui_MainWindow(object):
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QMenuBar(MainWindow)
|
||||
self.menubar.setObjectName(u"menubar")
|
||||
self.menubar.setGeometry(QRect(0, 0, 1263, 22))
|
||||
self.menubar.setGeometry(QRect(0, 0, 1263, 33))
|
||||
self.menuProjekt = QMenu(self.menubar)
|
||||
self.menuProjekt.setObjectName(u"menuProjekt")
|
||||
self.menuThema = QMenu(self.menubar)
|
||||
@@ -257,6 +254,7 @@ class Ui_MainWindow(object):
|
||||
self.menubar.addAction(self.menuProjekt.menuAction())
|
||||
self.menubar.addAction(self.menuThema.menuAction())
|
||||
self.menuProjekt.addAction(self.actionNeu)
|
||||
self.menuProjekt.addAction(self.action_ffnen)
|
||||
self.menuProjekt.addSeparator()
|
||||
self.menuProjekt.addAction(self.actionVorhandene_Projekte)
|
||||
self.menuProjekt.addSeparator()
|
||||
@@ -275,6 +273,10 @@ class Ui_MainWindow(object):
|
||||
self.actionNeu.setText(QCoreApplication.translate("MainWindow", u"Neu ...", None))
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.actionNeu.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+N", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.action_ffnen.setText(QCoreApplication.translate("MainWindow", u"\u00d6ffnen ...", None))
|
||||
#if QT_CONFIG(shortcut)
|
||||
self.action_ffnen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
|
||||
#endif // QT_CONFIG(shortcut)
|
||||
self.actionBeenden.setText(QCoreApplication.translate("MainWindow", u"Beenden", None))
|
||||
self.actionEinstellungen.setText(QCoreApplication.translate("MainWindow", u"Einstellungen ...", None))
|
||||
@@ -290,7 +292,8 @@ class Ui_MainWindow(object):
|
||||
self.label_6.setText(QCoreApplication.translate("MainWindow", u"Vorher (Referenz)", None))
|
||||
self.label_7.setText(QCoreApplication.translate("MainWindow", u"Nachher (Neu)", None))
|
||||
self.label_5.setText(QCoreApplication.translate("MainWindow", u"Zoom", None))
|
||||
self.accept_changes.setText(QCoreApplication.translate("MainWindow", u"\u2705 \u00c4nderungen \u00fcbernehmen", None))
|
||||
self.label_3.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
|
||||
self.label_4.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
|
||||
self.menuProjekt.setTitle(QCoreApplication.translate("MainWindow", u"Projekt", None))
|
||||
self.menuThema.setTitle(QCoreApplication.translate("MainWindow", u"Thema", None))
|
||||
# retranslateUi
|
||||
|
||||
+418
-2531
File diff suppressed because it is too large
Load Diff
+1
-26
@@ -50,9 +50,6 @@ class PdfProjectDlg(QDialog):
|
||||
# Browse-Button für Projekt-Ordner
|
||||
self.ui.pushButton.clicked.connect(self.browse_project_dir)
|
||||
|
||||
# Browse-Button für FOP-Config-Ordner
|
||||
self.ui.btnBrowseFopConfig.clicked.connect(self.browse_fop_config_dir)
|
||||
|
||||
# OK/Cancel Buttons sind bereits in der UI-Datei verbunden
|
||||
# self.ui.buttonBox.accepted.connect(self.accept)
|
||||
# self.ui.buttonBox.rejected.connect(self.reject)
|
||||
@@ -136,10 +133,6 @@ class PdfProjectDlg(QDialog):
|
||||
if 'postgre_sql_db_id' in self.project_data:
|
||||
self._select_combo_by_data(self.ui.cB_Postgres, self.project_data['postgre_sql_db_id'])
|
||||
|
||||
# FOP-Config-Ordner
|
||||
if 'fop_config_dir' in self.project_data and self.project_data['fop_config_dir']:
|
||||
self.ui.lineFopConfigDir.setText(str(self.project_data['fop_config_dir']))
|
||||
|
||||
def _select_combo_by_data(self, combo_box, data_value):
|
||||
"""
|
||||
Wählt einen ComboBox-Eintrag basierend auf dem data-Wert aus.
|
||||
@@ -174,22 +167,6 @@ class PdfProjectDlg(QDialog):
|
||||
project_name = os.path.basename(selected_dir)
|
||||
self.ui.lineProjectName.setText(project_name)
|
||||
|
||||
def browse_fop_config_dir(self):
|
||||
"""Öffnet einen Dialog zum Auswählen des FOP-Config-Ordners."""
|
||||
current_dir = self.ui.lineFopConfigDir.text()
|
||||
if not current_dir or not os.path.exists(current_dir):
|
||||
current_dir = os.path.expanduser("~")
|
||||
|
||||
selected_dir = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"FOP-Config-Ordner auswählen",
|
||||
current_dir,
|
||||
QFileDialog.Option.ShowDirsOnly | QFileDialog.Option.DontResolveSymlinks
|
||||
)
|
||||
|
||||
if selected_dir:
|
||||
self.ui.lineFopConfigDir.setText(selected_dir)
|
||||
|
||||
def validate_and_accept(self):
|
||||
"""Validiert die Eingaben und akzeptiert den Dialog."""
|
||||
# Projekt-Name prüfen
|
||||
@@ -259,7 +236,6 @@ class PdfProjectDlg(QDialog):
|
||||
Returns:
|
||||
dict: Dictionary mit allen Projektdaten
|
||||
"""
|
||||
fop_config_dir = self.ui.lineFopConfigDir.text().strip()
|
||||
return {
|
||||
'name': self.ui.lineProjectName.text().strip(),
|
||||
'project_dir': self.ui.lineProjectDir.text().strip(),
|
||||
@@ -268,8 +244,7 @@ class PdfProjectDlg(QDialog):
|
||||
'saxon_jar_id': self.ui.cB_SaxonJar.currentData(),
|
||||
'apache_fop_id': self.ui.cB_ApacheFop.currentData(),
|
||||
'diff_pdf_id': self.ui.cB_Diff_Pdf.currentData(),
|
||||
'postgre_sql_db_id': self.ui.cB_Postgres.currentData(),
|
||||
'fop_config_dir': fop_config_dir if fop_config_dir else None
|
||||
'postgre_sql_db_id': self.ui.cB_Postgres.currentData()
|
||||
}
|
||||
|
||||
def _configure_edit_mode(self):
|
||||
|
||||
+4
-45
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>608</width>
|
||||
<height>331</height>
|
||||
<height>299</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -109,66 +109,25 @@
|
||||
<widget class="QComboBox" name="cB_ApacheFop"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>FOP-Config-Ordner:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>diff-pdf:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="cB_Diff_Pdf"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Postgres:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="cB_Postgres"/>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QFrame" name="frame_2">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineFopConfigDir"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnBrowseFopConfig">
|
||||
<property name="text">
|
||||
<string>Durchsuchen ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
+6
-33
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'PdfProject.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.9.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -24,7 +24,7 @@ class Ui_projectDlg(object):
|
||||
def setupUi(self, projectDlg):
|
||||
if not projectDlg.objectName():
|
||||
projectDlg.setObjectName(u"projectDlg")
|
||||
projectDlg.resize(608, 331)
|
||||
projectDlg.resize(608, 299)
|
||||
self.verticalLayout = QVBoxLayout(projectDlg)
|
||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||
self.widget = QWidget(projectDlg)
|
||||
@@ -106,50 +106,25 @@ class Ui_projectDlg(object):
|
||||
|
||||
self.formLayout.setWidget(5, QFormLayout.ItemRole.FieldRole, self.cB_ApacheFop)
|
||||
|
||||
self.label_9 = QLabel(self.widget)
|
||||
self.label_9.setObjectName(u"label_9")
|
||||
|
||||
self.formLayout.setWidget(6, QFormLayout.ItemRole.LabelRole, self.label_9)
|
||||
|
||||
self.label_7 = QLabel(self.widget)
|
||||
self.label_7.setObjectName(u"label_7")
|
||||
|
||||
self.formLayout.setWidget(7, QFormLayout.ItemRole.LabelRole, self.label_7)
|
||||
self.formLayout.setWidget(6, QFormLayout.ItemRole.LabelRole, self.label_7)
|
||||
|
||||
self.cB_Diff_Pdf = QComboBox(self.widget)
|
||||
self.cB_Diff_Pdf.setObjectName(u"cB_Diff_Pdf")
|
||||
|
||||
self.formLayout.setWidget(7, QFormLayout.ItemRole.FieldRole, self.cB_Diff_Pdf)
|
||||
self.formLayout.setWidget(6, QFormLayout.ItemRole.FieldRole, self.cB_Diff_Pdf)
|
||||
|
||||
self.label_8 = QLabel(self.widget)
|
||||
self.label_8.setObjectName(u"label_8")
|
||||
|
||||
self.formLayout.setWidget(8, QFormLayout.ItemRole.LabelRole, self.label_8)
|
||||
self.formLayout.setWidget(7, QFormLayout.ItemRole.LabelRole, self.label_8)
|
||||
|
||||
self.cB_Postgres = QComboBox(self.widget)
|
||||
self.cB_Postgres.setObjectName(u"cB_Postgres")
|
||||
|
||||
self.formLayout.setWidget(8, QFormLayout.ItemRole.FieldRole, self.cB_Postgres)
|
||||
|
||||
self.frame_2 = QFrame(self.widget)
|
||||
self.frame_2.setObjectName(u"frame_2")
|
||||
self.frame_2.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.frame_2.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.horizontalLayout_2 = QHBoxLayout(self.frame_2)
|
||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
||||
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||
self.lineFopConfigDir = QLineEdit(self.frame_2)
|
||||
self.lineFopConfigDir.setObjectName(u"lineFopConfigDir")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.lineFopConfigDir)
|
||||
|
||||
self.btnBrowseFopConfig = QPushButton(self.frame_2)
|
||||
self.btnBrowseFopConfig.setObjectName(u"btnBrowseFopConfig")
|
||||
|
||||
self.horizontalLayout_2.addWidget(self.btnBrowseFopConfig)
|
||||
|
||||
|
||||
self.formLayout.setWidget(6, QFormLayout.ItemRole.FieldRole, self.frame_2)
|
||||
self.formLayout.setWidget(7, QFormLayout.ItemRole.FieldRole, self.cB_Postgres)
|
||||
|
||||
|
||||
self.verticalLayout.addWidget(self.widget)
|
||||
@@ -179,9 +154,7 @@ class Ui_projectDlg(object):
|
||||
self.label_4.setText(QCoreApplication.translate("projectDlg", u"Java VM:", None))
|
||||
self.label_5.setText(QCoreApplication.translate("projectDlg", u"Saxon Jar:", None))
|
||||
self.label_6.setText(QCoreApplication.translate("projectDlg", u"Apache FOP:", None))
|
||||
self.label_9.setText(QCoreApplication.translate("projectDlg", u"FOP-Config-Ordner:", None))
|
||||
self.label_7.setText(QCoreApplication.translate("projectDlg", u"diff-pdf:", None))
|
||||
self.label_8.setText(QCoreApplication.translate("projectDlg", u"Postgres:", None))
|
||||
self.btnBrowseFopConfig.setText(QCoreApplication.translate("projectDlg", u"Durchsuchen ...", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from PySide6.QtWidgets import QDialog, QTableWidgetItem, QMessageBox
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from ui.TreeNodeEditDialog_ui import Ui_TreeNodeEditDialog
|
||||
from conf import TreeNode
|
||||
|
||||
|
||||
class TreeNodeEditDialog(QDialog):
|
||||
@@ -145,13 +146,9 @@ class TreeNodeEditDialog(QDialog):
|
||||
if key: # Nur Parameter mit nicht-leerem Schlüssel hinzufügen
|
||||
xslt_params[key] = value
|
||||
|
||||
# CheckBox für Force-Transformation prüfen
|
||||
force_transform = self.ui.alle_xml_transformieren.isChecked()
|
||||
|
||||
return {
|
||||
"bez": bez,
|
||||
"xslt_params": xslt_params,
|
||||
"force_transform": force_transform
|
||||
"xslt_params": xslt_params
|
||||
}
|
||||
|
||||
def accept(self):
|
||||
|
||||
+76
-156
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>870</width>
|
||||
<width>600</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -35,175 +35,95 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
<widget class="QGroupBox" name="xsltParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>XSLT-Parameter</string>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="xsltParamsLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="xsltParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>XSLT-Parameter</string>
|
||||
<widget class="QTableWidget" name="xsltParamsTable">
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="xsltParamsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="xsltParamsTable">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="xsltParamsButtonLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter hinzufügen</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::ListAdd"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter entfernen</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::ListRemove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="parentParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>Geerbte XSLT-Parameter (nur anzeigen)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="parentParamsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="parentParamsTable">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="xsltParamsButtonLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter hinzufügen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter entfernen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alle_xml_transformieren">
|
||||
<property name="text">
|
||||
<string>Alle XML-Dateien neu transformieren (force)</string>
|
||||
<widget class="QGroupBox" name="parentParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>Geerbte XSLT-Parameter (nur anzeigen)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="parentParamsLayout">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="parentParamsTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'TreeNodeEditDialog.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.9.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -15,18 +15,17 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QAbstractButton, QAbstractItemView, QApplication, QCheckBox,
|
||||
QDialog, QDialogButtonBox, QFormLayout, QFrame,
|
||||
QGroupBox, QHBoxLayout, QHeaderView, QLabel,
|
||||
QLayout, QLineEdit, QPushButton, QSizePolicy,
|
||||
QSpacerItem, QTableWidget, QTableWidgetItem, QVBoxLayout,
|
||||
QWidget)
|
||||
from PySide6.QtWidgets import (QAbstractButton, QAbstractItemView, QApplication, QDialog,
|
||||
QDialogButtonBox, QFormLayout, QGroupBox, QHBoxLayout,
|
||||
QHeaderView, QLabel, QLayout, QLineEdit,
|
||||
QPushButton, QSizePolicy, QSpacerItem, QTableWidget,
|
||||
QTableWidgetItem, QVBoxLayout, QWidget)
|
||||
|
||||
class Ui_TreeNodeEditDialog(object):
|
||||
def setupUi(self, TreeNodeEditDialog):
|
||||
if not TreeNodeEditDialog.objectName():
|
||||
TreeNodeEditDialog.setObjectName(u"TreeNodeEditDialog")
|
||||
TreeNodeEditDialog.resize(870, 400)
|
||||
TreeNodeEditDialog.resize(600, 400)
|
||||
TreeNodeEditDialog.setModal(True)
|
||||
self.verticalLayout = QVBoxLayout(TreeNodeEditDialog)
|
||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||
@@ -46,18 +45,10 @@ class Ui_TreeNodeEditDialog(object):
|
||||
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
|
||||
self.frame = QFrame(TreeNodeEditDialog)
|
||||
self.frame.setObjectName(u"frame")
|
||||
self.frame.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.frame.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.horizontalLayout = QHBoxLayout(self.frame)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.xsltParamsGroupBox = QGroupBox(self.frame)
|
||||
self.xsltParamsGroupBox = QGroupBox(TreeNodeEditDialog)
|
||||
self.xsltParamsGroupBox.setObjectName(u"xsltParamsGroupBox")
|
||||
self.xsltParamsLayout = QVBoxLayout(self.xsltParamsGroupBox)
|
||||
self.xsltParamsLayout.setObjectName(u"xsltParamsLayout")
|
||||
self.xsltParamsLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.xsltParamsTable = QTableWidget(self.xsltParamsGroupBox)
|
||||
if (self.xsltParamsTable.columnCount() < 2):
|
||||
self.xsltParamsTable.setColumnCount(2)
|
||||
@@ -66,7 +57,6 @@ class Ui_TreeNodeEditDialog(object):
|
||||
__qtablewidgetitem1 = QTableWidgetItem()
|
||||
self.xsltParamsTable.setHorizontalHeaderItem(1, __qtablewidgetitem1)
|
||||
self.xsltParamsTable.setObjectName(u"xsltParamsTable")
|
||||
self.xsltParamsTable.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.xsltParamsTable.setColumnCount(2)
|
||||
self.xsltParamsTable.horizontalHeader().setVisible(True)
|
||||
|
||||
@@ -74,21 +64,13 @@ class Ui_TreeNodeEditDialog(object):
|
||||
|
||||
self.xsltParamsButtonLayout = QHBoxLayout()
|
||||
self.xsltParamsButtonLayout.setObjectName(u"xsltParamsButtonLayout")
|
||||
self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
|
||||
self.xsltParamsButtonLayout.addItem(self.horizontalSpacer_2)
|
||||
|
||||
self.addParamButton = QPushButton(self.xsltParamsGroupBox)
|
||||
self.addParamButton.setObjectName(u"addParamButton")
|
||||
icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ListAdd))
|
||||
self.addParamButton.setIcon(icon)
|
||||
|
||||
self.xsltParamsButtonLayout.addWidget(self.addParamButton)
|
||||
|
||||
self.removeParamButton = QPushButton(self.xsltParamsGroupBox)
|
||||
self.removeParamButton.setObjectName(u"removeParamButton")
|
||||
icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ListRemove))
|
||||
self.removeParamButton.setIcon(icon1)
|
||||
|
||||
self.xsltParamsButtonLayout.addWidget(self.removeParamButton)
|
||||
|
||||
@@ -100,13 +82,12 @@ class Ui_TreeNodeEditDialog(object):
|
||||
self.xsltParamsLayout.addLayout(self.xsltParamsButtonLayout)
|
||||
|
||||
|
||||
self.horizontalLayout.addWidget(self.xsltParamsGroupBox)
|
||||
self.verticalLayout.addWidget(self.xsltParamsGroupBox)
|
||||
|
||||
self.parentParamsGroupBox = QGroupBox(self.frame)
|
||||
self.parentParamsGroupBox = QGroupBox(TreeNodeEditDialog)
|
||||
self.parentParamsGroupBox.setObjectName(u"parentParamsGroupBox")
|
||||
self.parentParamsLayout = QVBoxLayout(self.parentParamsGroupBox)
|
||||
self.parentParamsLayout.setObjectName(u"parentParamsLayout")
|
||||
self.parentParamsLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.parentParamsTable = QTableWidget(self.parentParamsGroupBox)
|
||||
if (self.parentParamsTable.columnCount() < 2):
|
||||
self.parentParamsTable.setColumnCount(2)
|
||||
@@ -115,7 +96,6 @@ class Ui_TreeNodeEditDialog(object):
|
||||
__qtablewidgetitem3 = QTableWidgetItem()
|
||||
self.parentParamsTable.setHorizontalHeaderItem(1, __qtablewidgetitem3)
|
||||
self.parentParamsTable.setObjectName(u"parentParamsTable")
|
||||
self.parentParamsTable.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.parentParamsTable.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.parentParamsTable.setColumnCount(2)
|
||||
self.parentParamsTable.horizontalHeader().setVisible(True)
|
||||
@@ -123,15 +103,7 @@ class Ui_TreeNodeEditDialog(object):
|
||||
self.parentParamsLayout.addWidget(self.parentParamsTable)
|
||||
|
||||
|
||||
self.horizontalLayout.addWidget(self.parentParamsGroupBox)
|
||||
|
||||
|
||||
self.verticalLayout.addWidget(self.frame)
|
||||
|
||||
self.alle_xml_transformieren = QCheckBox(TreeNodeEditDialog)
|
||||
self.alle_xml_transformieren.setObjectName(u"alle_xml_transformieren")
|
||||
|
||||
self.verticalLayout.addWidget(self.alle_xml_transformieren)
|
||||
self.verticalLayout.addWidget(self.parentParamsGroupBox)
|
||||
|
||||
self.buttonBox = QDialogButtonBox(TreeNodeEditDialog)
|
||||
self.buttonBox.setObjectName(u"buttonBox")
|
||||
@@ -164,6 +136,5 @@ class Ui_TreeNodeEditDialog(object):
|
||||
___qtablewidgetitem2.setText(QCoreApplication.translate("TreeNodeEditDialog", u"Parameter", None));
|
||||
___qtablewidgetitem3 = self.parentParamsTable.horizontalHeaderItem(1)
|
||||
___qtablewidgetitem3.setText(QCoreApplication.translate("TreeNodeEditDialog", u"Wert", None));
|
||||
self.alle_xml_transformieren.setText(QCoreApplication.translate("TreeNodeEditDialog", u"Alle XML-Dateien neu transformieren (force)", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import logging
|
||||
from PySide6.QtWidgets import QDialog, QTreeWidgetItem, QCheckBox, QMessageBox, QWidget, QHBoxLayout
|
||||
from PySide6.QtCore import Qt
|
||||
from pathlib import Path
|
||||
@@ -7,9 +6,6 @@ from ui.XmlToXslAssignDialog_ui import Ui_XmlToXslAssignDialog
|
||||
from conf import TreeNode, XslFile
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XmlToXslAssignDialog(QDialog):
|
||||
"""Dialog zur Zuordnung einer XML-Datei zu XSL-Knoten."""
|
||||
|
||||
@@ -89,10 +85,10 @@ class XmlToXslAssignDialog(QDialog):
|
||||
root = self.ui.xslNodesTree.invisibleRootItem()
|
||||
self._add_checkboxes_recursive(root)
|
||||
|
||||
logger.debug(f"Checkboxen zu {len(self.xsl_checkboxes)} XSL-Knoten hinzugefügt")
|
||||
print(f"Checkboxen zu {len(self.xsl_checkboxes)} XSL-Knoten hinzugefügt")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Hinzufügen der Checkboxen: {e}")
|
||||
print(f"Fehler beim Hinzufügen der Checkboxen: {e}")
|
||||
|
||||
def _add_checkboxes_recursive(self, parent_item):
|
||||
"""
|
||||
@@ -117,7 +113,7 @@ class XmlToXslAssignDialog(QDialog):
|
||||
# Speichere Checkbox-Referenz
|
||||
self.xsl_checkboxes[id(node)] = checkbox
|
||||
|
||||
logger.debug(f"Checkbox für XSL-Knoten '{node.bez}' hinzugefügt")
|
||||
print(f"Checkbox für XSL-Knoten '{node.bez}' hinzugefügt")
|
||||
|
||||
# Rekursiv für Kinder
|
||||
if item.childCount() > 0:
|
||||
@@ -205,7 +201,7 @@ class XmlToXslAssignDialog(QDialog):
|
||||
return item
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Erstellen des Tree-Items: {e}")
|
||||
print(f"Fehler beim Erstellen des Tree-Items: {e}")
|
||||
return None
|
||||
|
||||
def select_all(self):
|
||||
@@ -239,7 +235,7 @@ class XmlToXslAssignDialog(QDialog):
|
||||
return selected_nodes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Sammeln der ausgewählten XSL-Knoten: {e}")
|
||||
print(f"Fehler beim Sammeln der ausgewählten XSL-Knoten: {e}")
|
||||
return []
|
||||
|
||||
def _find_xsl_node_by_id(self, node_id):
|
||||
@@ -286,15 +282,6 @@ class XmlToXslAssignDialog(QDialog):
|
||||
"""
|
||||
return self.xml_file_path
|
||||
|
||||
def is_apply_to_all(self):
|
||||
"""
|
||||
Prüft, ob die Checkbox 'Alle XML-Dateien' aktiviert ist.
|
||||
|
||||
Returns:
|
||||
bool: True wenn die Checkbox aktiviert ist, sonst False
|
||||
"""
|
||||
return self.ui.alle_xml.isChecked()
|
||||
|
||||
def accept(self):
|
||||
"""Überschreibt accept() um Validierung durchzuführen."""
|
||||
selected_nodes = self.get_selected_xsl_nodes()
|
||||
|
||||
@@ -97,13 +97,6 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alle_xml">
|
||||
<property name="text">
|
||||
<string>Alle XML-Dateien den ausgewählten XSL-Dateien zuordnen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'XmlToXslAssignDialog.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.9.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -15,10 +15,10 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
|
||||
QDialogButtonBox, QHBoxLayout, QHeaderView, QLabel,
|
||||
QPushButton, QSizePolicy, QSpacerItem, QTreeWidget,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget)
|
||||
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
|
||||
QHBoxLayout, QHeaderView, QLabel, QPushButton,
|
||||
QSizePolicy, QSpacerItem, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget)
|
||||
|
||||
class Ui_XmlToXslAssignDialog(object):
|
||||
def setupUi(self, XmlToXslAssignDialog):
|
||||
@@ -64,11 +64,6 @@ class Ui_XmlToXslAssignDialog(object):
|
||||
|
||||
self.buttonLayout.addItem(self.horizontalSpacer)
|
||||
|
||||
self.alle_xml = QCheckBox(XmlToXslAssignDialog)
|
||||
self.alle_xml.setObjectName(u"alle_xml")
|
||||
|
||||
self.buttonLayout.addWidget(self.alle_xml)
|
||||
|
||||
|
||||
self.verticalLayout.addLayout(self.buttonLayout)
|
||||
|
||||
@@ -99,6 +94,5 @@ class Ui_XmlToXslAssignDialog(object):
|
||||
___qtreewidgetitem.setText(0, QCoreApplication.translate("XmlToXslAssignDialog", u"XSL-Knoten", None));
|
||||
self.selectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle ausw\u00e4hlen", None))
|
||||
self.deselectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle abw\u00e4hlen", None))
|
||||
self.alle_xml.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle XML-Dateien den ausgew\u00e4hlten XSL-Dateien zuordnen", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from PySide6.QtWidgets import QDialog, QTableWidgetItem, QMessageBox
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from ui.XslFileEditDialog_ui import Ui_XslFileEditDialog
|
||||
from conf import XslFile
|
||||
|
||||
|
||||
class XslFileEditDialog(QDialog):
|
||||
@@ -145,13 +146,9 @@ class XslFileEditDialog(QDialog):
|
||||
if key: # Nur Parameter mit nicht-leerem Schlüssel hinzufügen
|
||||
xslt_params[key] = value
|
||||
|
||||
# CheckBox für Force-Transformation prüfen
|
||||
force_transform = self.ui.alle_xml_transformieren.isChecked()
|
||||
|
||||
return {
|
||||
"bez": bez,
|
||||
"xslt_params": xslt_params,
|
||||
"force_transform": force_transform
|
||||
"xslt_params": xslt_params
|
||||
}
|
||||
|
||||
def accept(self):
|
||||
|
||||
+76
-156
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>865</width>
|
||||
<width>600</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -35,175 +35,95 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
<widget class="QGroupBox" name="xsltParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>XSLT-Parameter</string>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="xsltParamsLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="xsltParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>XSLT-Parameter</string>
|
||||
<widget class="QTableWidget" name="xsltParamsTable">
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="xsltParamsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="xsltParamsTable">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="xsltParamsButtonLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter hinzufügen</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::ListAdd"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter entfernen</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::ListRemove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="parentParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>Geerbte XSLT-Parameter (nur anzeigen)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="parentParamsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="parentParamsTable">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="xsltParamsButtonLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter hinzufügen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeParamButton">
|
||||
<property name="text">
|
||||
<string>Parameter entfernen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alle_xml_transformieren">
|
||||
<property name="text">
|
||||
<string>Alle XML-Dateien neu transformieren (force)</string>
|
||||
<widget class="QGroupBox" name="parentParamsGroupBox">
|
||||
<property name="title">
|
||||
<string>Geerbte XSLT-Parameter (nur anzeigen)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="parentParamsLayout">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="parentParamsTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Wert</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
################################################################################
|
||||
## Form generated from reading UI file 'XslFileEditDialog.ui'
|
||||
##
|
||||
## Created by: Qt User Interface Compiler version 6.9.2
|
||||
## Created by: Qt User Interface Compiler version 6.9.1
|
||||
##
|
||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||
################################################################################
|
||||
@@ -15,18 +15,17 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
QFont, QFontDatabase, QGradient, QIcon,
|
||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||
from PySide6.QtWidgets import (QAbstractButton, QAbstractItemView, QApplication, QCheckBox,
|
||||
QDialog, QDialogButtonBox, QFormLayout, QFrame,
|
||||
QGroupBox, QHBoxLayout, QHeaderView, QLabel,
|
||||
QLayout, QLineEdit, QPushButton, QSizePolicy,
|
||||
QSpacerItem, QTableWidget, QTableWidgetItem, QVBoxLayout,
|
||||
QWidget)
|
||||
from PySide6.QtWidgets import (QAbstractButton, QAbstractItemView, QApplication, QDialog,
|
||||
QDialogButtonBox, QFormLayout, QGroupBox, QHBoxLayout,
|
||||
QHeaderView, QLabel, QLayout, QLineEdit,
|
||||
QPushButton, QSizePolicy, QSpacerItem, QTableWidget,
|
||||
QTableWidgetItem, QVBoxLayout, QWidget)
|
||||
|
||||
class Ui_XslFileEditDialog(object):
|
||||
def setupUi(self, XslFileEditDialog):
|
||||
if not XslFileEditDialog.objectName():
|
||||
XslFileEditDialog.setObjectName(u"XslFileEditDialog")
|
||||
XslFileEditDialog.resize(865, 400)
|
||||
XslFileEditDialog.resize(600, 400)
|
||||
XslFileEditDialog.setModal(True)
|
||||
self.verticalLayout = QVBoxLayout(XslFileEditDialog)
|
||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||
@@ -46,18 +45,10 @@ class Ui_XslFileEditDialog(object):
|
||||
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
|
||||
self.frame = QFrame(XslFileEditDialog)
|
||||
self.frame.setObjectName(u"frame")
|
||||
self.frame.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.frame.setFrameShadow(QFrame.Shadow.Raised)
|
||||
self.horizontalLayout = QHBoxLayout(self.frame)
|
||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
||||
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.xsltParamsGroupBox = QGroupBox(self.frame)
|
||||
self.xsltParamsGroupBox = QGroupBox(XslFileEditDialog)
|
||||
self.xsltParamsGroupBox.setObjectName(u"xsltParamsGroupBox")
|
||||
self.xsltParamsLayout = QVBoxLayout(self.xsltParamsGroupBox)
|
||||
self.xsltParamsLayout.setObjectName(u"xsltParamsLayout")
|
||||
self.xsltParamsLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.xsltParamsTable = QTableWidget(self.xsltParamsGroupBox)
|
||||
if (self.xsltParamsTable.columnCount() < 2):
|
||||
self.xsltParamsTable.setColumnCount(2)
|
||||
@@ -66,7 +57,6 @@ class Ui_XslFileEditDialog(object):
|
||||
__qtablewidgetitem1 = QTableWidgetItem()
|
||||
self.xsltParamsTable.setHorizontalHeaderItem(1, __qtablewidgetitem1)
|
||||
self.xsltParamsTable.setObjectName(u"xsltParamsTable")
|
||||
self.xsltParamsTable.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.xsltParamsTable.setColumnCount(2)
|
||||
self.xsltParamsTable.horizontalHeader().setVisible(True)
|
||||
|
||||
@@ -74,21 +64,13 @@ class Ui_XslFileEditDialog(object):
|
||||
|
||||
self.xsltParamsButtonLayout = QHBoxLayout()
|
||||
self.xsltParamsButtonLayout.setObjectName(u"xsltParamsButtonLayout")
|
||||
self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
|
||||
self.xsltParamsButtonLayout.addItem(self.horizontalSpacer_2)
|
||||
|
||||
self.addParamButton = QPushButton(self.xsltParamsGroupBox)
|
||||
self.addParamButton.setObjectName(u"addParamButton")
|
||||
icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ListAdd))
|
||||
self.addParamButton.setIcon(icon)
|
||||
|
||||
self.xsltParamsButtonLayout.addWidget(self.addParamButton)
|
||||
|
||||
self.removeParamButton = QPushButton(self.xsltParamsGroupBox)
|
||||
self.removeParamButton.setObjectName(u"removeParamButton")
|
||||
icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.ListRemove))
|
||||
self.removeParamButton.setIcon(icon1)
|
||||
|
||||
self.xsltParamsButtonLayout.addWidget(self.removeParamButton)
|
||||
|
||||
@@ -100,13 +82,12 @@ class Ui_XslFileEditDialog(object):
|
||||
self.xsltParamsLayout.addLayout(self.xsltParamsButtonLayout)
|
||||
|
||||
|
||||
self.horizontalLayout.addWidget(self.xsltParamsGroupBox)
|
||||
self.verticalLayout.addWidget(self.xsltParamsGroupBox)
|
||||
|
||||
self.parentParamsGroupBox = QGroupBox(self.frame)
|
||||
self.parentParamsGroupBox = QGroupBox(XslFileEditDialog)
|
||||
self.parentParamsGroupBox.setObjectName(u"parentParamsGroupBox")
|
||||
self.parentParamsLayout = QVBoxLayout(self.parentParamsGroupBox)
|
||||
self.parentParamsLayout.setObjectName(u"parentParamsLayout")
|
||||
self.parentParamsLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.parentParamsTable = QTableWidget(self.parentParamsGroupBox)
|
||||
if (self.parentParamsTable.columnCount() < 2):
|
||||
self.parentParamsTable.setColumnCount(2)
|
||||
@@ -115,7 +96,6 @@ class Ui_XslFileEditDialog(object):
|
||||
__qtablewidgetitem3 = QTableWidgetItem()
|
||||
self.parentParamsTable.setHorizontalHeaderItem(1, __qtablewidgetitem3)
|
||||
self.parentParamsTable.setObjectName(u"parentParamsTable")
|
||||
self.parentParamsTable.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.parentParamsTable.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.parentParamsTable.setColumnCount(2)
|
||||
self.parentParamsTable.horizontalHeader().setVisible(True)
|
||||
@@ -123,15 +103,7 @@ class Ui_XslFileEditDialog(object):
|
||||
self.parentParamsLayout.addWidget(self.parentParamsTable)
|
||||
|
||||
|
||||
self.horizontalLayout.addWidget(self.parentParamsGroupBox)
|
||||
|
||||
|
||||
self.verticalLayout.addWidget(self.frame)
|
||||
|
||||
self.alle_xml_transformieren = QCheckBox(XslFileEditDialog)
|
||||
self.alle_xml_transformieren.setObjectName(u"alle_xml_transformieren")
|
||||
|
||||
self.verticalLayout.addWidget(self.alle_xml_transformieren)
|
||||
self.verticalLayout.addWidget(self.parentParamsGroupBox)
|
||||
|
||||
self.buttonBox = QDialogButtonBox(XslFileEditDialog)
|
||||
self.buttonBox.setObjectName(u"buttonBox")
|
||||
@@ -164,6 +136,5 @@ class Ui_XslFileEditDialog(object):
|
||||
___qtablewidgetitem2.setText(QCoreApplication.translate("XslFileEditDialog", u"Parameter", None));
|
||||
___qtablewidgetitem3 = self.parentParamsTable.horizontalHeaderItem(1)
|
||||
___qtablewidgetitem3.setText(QCoreApplication.translate("XslFileEditDialog", u"Wert", None));
|
||||
self.alle_xml_transformieren.setText(QCoreApplication.translate("XslFileEditDialog", u"Alle XML-Dateien neu transformieren (force)", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -225,11 +225,11 @@ def test_integration_workflow():
|
||||
assert duplicate_found is not None, "Duplikat sollte gefunden werden"
|
||||
assert duplicate_found.xml == Path("xml/existing3.xml"), "Falsches Duplikat gefunden"
|
||||
|
||||
print("Workflow-Test erfolgreich:")
|
||||
print(f"Workflow-Test erfolgreich:")
|
||||
print(f" - Neue Datei: {new_xml_file.name}")
|
||||
print(f" - Berechneter Hash: {new_hash}")
|
||||
print(f" - Duplikat gefunden: {duplicate_found.xml}")
|
||||
print(" - Automatische Zuordnung würde erfolgen")
|
||||
print(f" - Automatische Zuordnung würde erfolgen")
|
||||
|
||||
print("[OK] Integration Workflow funktioniert korrekt")
|
||||
|
||||
|
||||
@@ -44,11 +44,6 @@ dependencies = [
|
||||
{ name = "pyside6" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "polars", extras = ["connectorx", "pyarrow"], specifier = ">=1.31.0" },
|
||||
@@ -59,7 +54,7 @@ requires-dist = [
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ruff", specifier = ">=0.14.8" }]
|
||||
dev = []
|
||||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
@@ -274,32 +269,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shiboken6"
|
||||
version = "6.9.2"
|
||||
|
||||
Reference in New Issue
Block a user