#!/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 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 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 (z.B. build_eckrad_details erwartet fuer "Geruest fuer Einzelmodul" ein echtes bool, kein "True"/"False"-String - json.dumps schreibt dann true/false statt eines Strings). """ 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: ergebnis[merkmal] = fn(wert) continue ergebnis[merkmal] = str(wert) return ergebnis