Files
xsl-validator/src/conf.py
T

283 lines
8.5 KiB
Python
Raw Normal View History

import os
import sys
from pathlib import Path
2025-06-12 19:21:58 +02:00
from sys import platform
2025-06-13 20:23:19 +02:00
from typing import Tuple, Type
2025-07-17 19:12:41 +02:00
from pydantic import Field
2025-07-27 19:41:04 +02:00
from pydantic_yaml import to_yaml_str
from ruamel.yaml import YAML
2025-07-17 19:12:41 +02:00
from enum import Enum
import logging
from pydantic import BaseModel
2025-06-13 20:23:19 +02:00
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, JsonConfigSettingsSource
2025-06-12 19:21:58 +02:00
2025-07-17 19:12:41 +02:00
logger = logging.getLogger(__name__)
2025-06-12 19:21:58 +02:00
app_name = "DocuMentor"
if platform == "win32":
tmp_config_path = f"%APPDATA%\\{app_name}\\config.json"
elif platform in ("linux", "linux2"):
tmp_config_path = f"~/.config/{app_name}/config.json"
2025-06-12 19:21:58 +02:00
elif platform == "darwin":
tmp_config_path = f"~/Library/Application Support/{app_name}/͏͏͏͏config.json"
2025-06-12 19:21:58 +02:00
else:
tmp_config_path = f"~/.config/{app_name}/config.json"
2025-06-12 19:21:58 +02:00
config_path = Path(os.path.expandvars(tmp_config_path)).expanduser()
class JavaVm(BaseModel):
2025-06-09 19:50:17 +02:00
id: int
2025-06-06 20:18:29 +02:00
version: str
path_to_binary_file: Path
class DiffPdf(BaseModel):
2025-06-09 19:50:17 +02:00
id: int
2025-06-06 20:18:29 +02:00
version: str
path_to_binary_file: Path
default_params: list[str]
2025-06-06 20:18:29 +02:00
output_file_extension: str = "pdf"
class SaxonJar(BaseModel):
2025-06-09 19:50:17 +02:00
id: int
2025-06-06 20:18:29 +02:00
version: str
path_to_jar_file: Path
2025-06-06 20:18:29 +02:00
output_file_extension: str = "fo"
class ApacheFop(BaseModel):
2025-06-09 19:50:17 +02:00
id: int
2025-06-06 20:18:29 +02:00
version: str
path_to_dir: Path
2025-06-06 20:18:29 +02:00
output_file_extension: str = "pdf"
class XslDir(BaseModel):
2025-06-09 19:50:17 +02:00
id: int
2025-06-06 20:18:29 +02:00
name: str
path_to_root_dir: Path
2025-07-17 19:12:41 +02:00
class SSLMode(str, Enum):
DISABLE = "disable"
ALLOW = "allow"
PREFER = "prefer"
REQUIRE = "require"
VERIFY_CA = "verify-ca"
VERIFY_FULL = "verify-full"
class XsltVersion(str, Enum):
"""XSLT-Version für Saxon-Transformationen."""
XSLT_1_0 = "1.0" # JAXP API (nur XSLT 1.0)
XSLT_2_0_3_0 = "2.0/3.0" # s9api (XSLT 2.0 und 3.0)
class GraphLayout(str, Enum):
"""vis.js Physics-Solver / Layout-Modus."""
BARNES_HUT = "barnesHut"
FORCE_ATLAS2 = "forceAtlas2Based"
REPULSION = "repulsion"
HIERARCHICAL = "hierarchical"
class HierarchicalDirection(str, Enum):
"""Richtung für hierarchisches Layout."""
UD = "UD"
DU = "DU"
LR = "LR"
RL = "RL"
class HierarchicalSortMethod(str, Enum):
"""Sortiermethode für hierarchisches Layout."""
HUBSIZE = "hubsize"
DIRECTED = "directed"
class GraphLayoutSettings(BaseModel):
"""Persistierte vis.js Layout-Einstellungen für den XSL-Abhängigkeitsgraph."""
layout: GraphLayout = GraphLayout.BARNES_HUT
# barnesHut
bh_gravitational_constant: int = -3000
bh_central_gravity: float = 0.3
bh_spring_length: int = 150
bh_spring_constant: float = 0.04
bh_damping: float = 0.09
# forceAtlas2Based
fa_gravitational_constant: int = -50
fa_central_gravity: float = 0.01
fa_spring_length: int = 100
fa_spring_constant: float = 0.08
fa_damping: float = 0.4
# repulsion
re_node_distance: int = 120
re_central_gravity: float = 0.0
re_spring_length: int = 200
re_spring_constant: float = 0.05
re_damping: float = 0.09
# hierarchical
hi_direction: HierarchicalDirection = HierarchicalDirection.UD
hi_sort_method: HierarchicalSortMethod = HierarchicalSortMethod.HUBSIZE
hi_level_separation: int = 150
hi_node_spacing: int = 100
hi_tree_spacing: int = 200
2025-06-22 18:12:27 +02:00
class PostgreSqlDb(BaseModel):
id: int
name: str
host: str
port: int = 5432
database: str
username: str
password: str
2025-07-17 19:12:41 +02:00
ssl_mode: SSLMode = SSLMode.PREFER
timeout: int = 10
2025-06-22 18:12:27 +02:00
2025-08-10 17:32:22 +02:00
class Project(BaseModel):
2025-07-17 19:12:41 +02:00
id: int = Field(..., description="Eindeutige Projekt-ID", gt=0)
name: str = Field(..., description="Projekt-Name", min_length=1, max_length=255)
project_dir: Path = Field(..., description="Pfad zum Projekt-Verzeichnis")
java_vm_id: int = Field(..., description="ID der Java VM", gt=0)
diff_pdf_id: int = Field(..., description="ID der diff-pdf Konfiguration", gt=0)
saxon_jar_id: int = Field(..., description="ID der Saxon JAR Konfiguration", gt=0)
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")
xslt_params: dict[str, str] = Field(default_factory=dict, description="Projektweite XSLT-Parameter")
@staticmethod
def _lookup(collection, item_id: int, attr: str) -> str:
"""Sucht einen Wert in einer Konfigurationsliste anhand der ID."""
value = [getattr(x, attr) for x in collection if x.id == item_id]
return value[0] if value else ""
def getXsl(self) -> str:
return self._lookup(app_settings.xsl_dirs, self.xsl_dir_id, "name")
def getJavaVm(self) -> str:
return self._lookup(app_settings.java_vms, self.java_vm_id, "version")
def getSaxon(self) -> str:
return self._lookup(app_settings.saxon_jars, self.saxon_jar_id, "version")
def getApacheFop(self) -> str:
return self._lookup(app_settings.apache_fops, self.apache_fop_id, "version")
def getDiffPdf(self) -> str:
return self._lookup(app_settings.diff_pdfs, self.diff_pdf_id, "version")
2025-07-14 21:00:06 +02:00
def getPostgreSqlDb(self) -> str:
return self._lookup(app_settings.postgresql_dbs, self.postgre_sql_db_id, "name")
2025-07-14 21:00:06 +02:00
class AppSettings(BaseSettings):
2025-06-12 19:21:58 +02:00
java_vms: list[JavaVm] = []
diff_pdfs: list[DiffPdf] = []
saxon_jars: list[SaxonJar] = []
apache_fops: list[ApacheFop] = []
xsl_dirs: list[XslDir] = []
2025-08-10 17:32:22 +02:00
pdf_projects: list[Project] = []
2025-06-22 18:12:27 +02:00
postgresql_dbs: list[PostgreSqlDb] = []
theme: str | None = None
max_workers: int = 8 # Anzahl paralleler Worker für Transformationen (Standard: 8)
use_saxon_worker_pool: bool = True # SaxonWorkerPool aktivieren (schneller, benötigt JDK)
saxon_xslt_version: XsltVersion = XsltVersion.XSLT_2_0_3_0 # XSLT-Version für Saxon (Standard: 2.0/3.0 mit s9api)
use_fop_worker_pool: bool = True # FopWorkerPool aktivieren (schneller, benötigt JDK)
2025-06-06 20:32:23 +02:00
# 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
graph_layout_settings: GraphLayoutSettings = Field(default_factory=GraphLayoutSettings)
2025-06-12 19:21:58 +02:00
model_config = SettingsConfigDict(json_file=config_path)
2025-06-13 20:23:19 +02:00
@classmethod
def settings_customise_sources(
cls,
settings_cls: Type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> Tuple[PydanticBaseSettingsSource, ...]:
return (JsonConfigSettingsSource(settings_cls),)
def save(self):
# Ordner existert nicht
2025-06-13 20:23:19 +02:00
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)
2025-06-12 19:21:58 +02:00
2025-06-13 20:23:19 +02:00
# Konfiguration speichern
with open(config_path, "wb") as c:
c.write(app_settings.model_dump_json(indent=4).encode())
2025-06-12 19:21:58 +02:00
2025-06-06 20:18:29 +02:00
2025-06-13 20:23:19 +02:00
app_settings = AppSettings()
2025-06-13 20:23:19 +02:00
2025-07-27 19:41:04 +02:00
class XmlFile(BaseModel):
xml: Path
# blake2b hashsum
hashsum: str | None = None
2025-06-20 21:42:30 +02:00
class XslFile(BaseModel):
id: tuple
bez: str
xsl_file: Path
2025-07-14 21:00:06 +02:00
xslt_params: dict[str, str] = {}
2025-07-27 19:41:04 +02:00
xmls: list[XmlFile] = []
2025-06-20 21:42:30 +02:00
class TreeNode(BaseModel):
id: tuple
bez: str
2025-07-14 21:00:06 +02:00
xslt_params: dict[str, str] = {}
children: list["TreeNode|XslFile"]
2025-06-20 21:42:30 +02:00
2025-08-10 17:32:22 +02:00
class ProjectData(BaseModel):
2025-06-06 20:18:29 +02:00
"""
2025-07-14 21:00:06 +02:00
Speichert die Projekteinstellungen direkt im Projektordner in einer .yaml-Datei.
2025-06-06 20:18:29 +02:00
"""
2025-06-06 20:32:23 +02:00
nodes: list[TreeNode] = []
expanded_nodes: list[tuple] | None = None # Optional: IDs der aufgeklappten Knoten (TreeNode und XslFile)
@classmethod
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")
yaml_data = yaml.load(f)
return cls.model_validate(yaml_data)
def writeSettings(self, project_dir: Path):
with open(project_dir / "project.yaml", "w", encoding="utf8") as f:
f.write(to_yaml_str(self))