#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ sivas_mapping.py - Liest die Attribut->Sivas-Merkmal-Zuordnung aus cfg/sivas.cfg und wendet sie auf ein Block-Attribut-Dict an. Einzige Quelle fuer die "einfachen" build_*_details-Zuordnungen in export_sivas.py (siehe cfg/sivas.cfg fuer das Zeilenformat und die Begruendung, warum nur die einfachen Faelle hier landen). """ import configparser import functools import os def cfg_path_from_env(default_dir=None): """Pfad zu sivas.cfg: DXFM_CFG env var, sonst /cfg.""" dxfm_cfg = os.environ.get("DXFM_CFG") if dxfm_cfg: return os.path.join(dxfm_cfg, "sivas.cfg") base = default_dir or os.path.dirname(os.path.dirname(os.path.abspath(__file__))) return os.path.join(base, "cfg", "sivas.cfg") def _parse_wert(roh): """Zerlegt eine Config-Wertzeile "attribut|alt_attribut:default|transform" in (attribut, alt_attribut_oder_None, default, transform_oder_None). Reihenfolge beim Parsen: erst am ":" in (attributteil, defaultteil) trennen (nur der erste ":" zaehlt, ein Default-Text soll selbst keinen ":" enthalten muessen), danach im defaultteil am LETZTEN "|" den Transform-Namen abtrennen (ein Default-Text wie "ohne HARTCOAT..." enthaelt kein "|"). attributteil wird am ERSTEN "|" in Attribut/ Fallback-Attribut gesplittet. """ roh = roh.strip() if ":" in roh: attribut_teil, rest = roh.split(":", 1) else: attribut_teil, rest = roh, "" transform = None default = rest.strip() if "|" in rest: default_teil, transform_teil = rest.rsplit("|", 1) default = default_teil.strip() transform = transform_teil.strip() or None attribut_teil = attribut_teil.strip() alt_attribut = None if "|" in attribut_teil: attribut, alt_attribut = (p.strip() for p in attribut_teil.split("|", 1)) else: attribut = attribut_teil alt_attribut = alt_attribut or None return (attribut or None, alt_attribut, default, transform) @functools.lru_cache(maxsize=None) def load_sivas_cfg(cfg_path): """Liest sivas.cfg einmal je Pfad (Prozess-Cache).""" parser = configparser.ConfigParser(inline_comment_prefixes=None) parser.optionxform = str # Keys (Merkmalnummern/Klartext-Keys) case-sensitive erhalten parser.read(cfg_path, encoding="utf-8") return parser def load_section_mapping(section, cfg_path=None): """Liest einen Abschnitt aus sivas.cfg. Rueckgabe: dict merkmal -> (attribut, alt_attribut, default, transform), wie von _parse_wert geliefert. Bei fehlender Datei/Sektion: leeres dict - Aufrufer bekommt dann einfach keine Merkmale aus der Config (bisheriges Verhalten vor der Config-Einfuehrung, kein harter Fehler). """ if cfg_path is None: cfg_path = cfg_path_from_env() parser = load_sivas_cfg(cfg_path) mapping = {} if parser.has_section(section): for merkmal, roh in parser.items(section): mapping[merkmal] = _parse_wert(roh) return mapping def load_flat_mapping(section, cfg_path=None): """Liest einen Abschnitt als einfaches dict key -> value (z.B. [geruest_typ_anzeige]: ASCII-Wert -> Sivas-Klartext), ohne das attribut/default/transform-Format von load_section_mapping.""" if cfg_path is None: cfg_path = cfg_path_from_env() parser = load_sivas_cfg(cfg_path) if parser.has_section(section): return {k: v.strip() for k, v in parser.items(section)} return {} def braucht_attribs(fn): """Markiert einen Transform, der NEBEN seinem Rohwert auch das komplette Attribut-Dict des Blocks braucht - er wird dann als fn(wert, attribs) aufgerufen statt als fn(wert). Noetig fuer Merkmale, deren Wert von einem ZWEITEN Attribut abhaengt: die AS-/ES-Seite (4428/4430) darf nur gefuellt werden, wenn der zugehoerige WINKEL_AS/WINKEL_ES ueberhaupt ein Element ausweist. Ohne diese Klammer muessten solche Merkmale wieder aus der Config heraus in Python-Code wandern - genau das soll sivas.cfg vermeiden. """ fn.braucht_attribs = True return fn def anwenden(section_mapping, attribs, transforms): """Wendet eine per load_section_mapping() gelesene Zuordnung auf ein Block-Attribut-Dict an. transforms: dict transform_name -> Funktion(wert) -> wert. Ein mit @braucht_attribs markierter Transform wird stattdessen als fn(wert, attribs) aufgerufen und sieht damit die uebrigen Attribute des Blocks. Ein in der Config genannter, aber hier unbekannter Transform-Name wird ignoriert (Rohwert bleibt unveraendert) statt abzubrechen - ein Tippfehler in sivas.cfg darf den Export nicht zum Stillstand bringen. Rueckgabe: dict merkmal -> wert. Ohne Transform wird der Rohwert zu str() gewandelt (bisheriges Verhalten der build_*_details-Funktionen: alles Merkmalnummer-Werte sind Strings). Ein Transform liefert seinen Rueckgabewert UNVERAENDERT (er ist selbst fuer den Typ verantwortlich). """ ergebnis = {} for merkmal, (attribut, alt_attribut, default, transform) in section_mapping.items(): if attribut is not None: wert = attribs.get(attribut) if wert is None and alt_attribut is not None: wert = attribs.get(alt_attribut) if wert is None: wert = default else: wert = default if transform: fn = transforms.get(transform) if fn is not None: if getattr(fn, "braucht_attribs", False): ergebnis[merkmal] = fn(wert, attribs) else: ergebnis[merkmal] = fn(wert) continue ergebnis[merkmal] = str(wert) return ergebnis