Files
dxfmakros/lib/merkmalstamm.py
T
Yuelin WangandClaude Sonnet 5 6f12cab86f refactor: Sivas-CSV-Export auf Merkmalnummern umgestellt, lesbare Zweitdatei
- ILS 2.0 Kreisel/Strecke(VF_N)/Gefaellestrecke/Automation: details-JSON
  nutzt jetzt Sivas-Merkmalnummern (z.B. "2952") statt beschreibender
  Klartext-Keys, abgeglichen mit dem Merkmalstamm in
  cfg/Merkmalsstamm_in_Sivas.xlsx.
- ILS 2.0 Zusatzmodul: BT-Beladung und Abwurfstationen laufen jetzt in
  einer gemeinsamen Zeile (Merkmale 3501-3504) statt zwei separaten
  Zeilen ohne details.
- Gefaellestrecke-Gruppe: Anzahl-Spalte bleibt fest 1, die reale
  Stueckzahl der Gruppe steht im Merkmal 4311 (anders als bei Kreisel,
  wo die Anzahl-Spalte die Stueckzahl traegt und 3511 fix "1" ist).
- Automation-Summenzeile: 4407 zaehlt jetzt echte Streckengruppen
  (VF_N-Bloecke) statt eines 0/1-Flags, 4408 (Motoren Vario-Foerderer)
  wird erstmals tatsaechlich berechnet (vorher nie inkrementiert), 4426
  steht fest auf "0" (Konzept "einzelner Variofoerderer" entfaellt).
- Neues Modul lib/merkmalstamm.py: liest Merkmalnummer->Bezeichnung aus
  der xlsx rein per Standard-Library (zipfile/xml.etree, keine neue
  pip-Abhaengigkeit fuer den ueber export.lsp aufgeloesten Interpreter).
- export_sivas.py schreibt bei jedem Lauf zusaetzlich eine zweite Datei
  "<name>_mit_bezeichnung.csv" (gleicher Inhalt, aber jeder
  Merkmalnummer-Key um "<Nummer> | <Bezeichnung>" erweitert) - reine
  Lesehilfe zur Pruefung, wird von Sivas nicht eingelesen.
- cfg/sivas_merkmal_zuordnung.md: Nachschlagetabelle Block-Attribut <->
  Merkmalnummer je Teileart, inkl. offener Punkte (Separator-/
  Scanner-Aufteilung Gefaelle-/Vario-Anteil noch Platzhalter).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 10:33:20 +02:00

93 lines
3.5 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
merkmalstamm.py - Liest Merkmalnummer -> Bezeichnung aus
cfg/Merkmalsstamm_in_Sivas.xlsx.
Bewusst ohne openpyxl (reine Standard-Library: zipfile + xml.etree) -
export.lsp/export:python-exe loest den Interpreter fuer EXPORTSIVAS ueber
Config/DXFM_PYTHON/Projekt-venv/PATH auf und garantiert dabei keine
zusaetzliche pip-Abhaengigkeit; ein .xlsx ist im Kern ein ZIP aus XML-
Dateien, das reicht hier ohne Zusatzpaket.
"""
import os
import re
import zipfile
from functools import lru_cache
from xml.etree import ElementTree as ET
_NS = {"main": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
def merkmalstamm_pfad():
"""Pfad zu Merkmalsstamm_in_Sivas.xlsx: DXFM_CFG env var, sonst <repo>/cfg."""
dxfm_cfg = os.environ.get("DXFM_CFG")
if dxfm_cfg:
return os.path.join(dxfm_cfg, "Merkmalsstamm_in_Sivas.xlsx")
basis = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
return os.path.join(basis, "cfg", "Merkmalsstamm_in_Sivas.xlsx")
def _spalte_zu_zahl(spalte):
zahl = 0
for zeichen in spalte:
zahl = zahl * 26 + (ord(zeichen) - ord("A") + 1)
return zahl
def _spalte_aus_zellref(ref):
treffer = re.match(r"([A-Z]+)\d+", ref)
return treffer.group(1) if treffer else "A"
@lru_cache(maxsize=None)
def lade_merkmalstamm(pfad=None):
"""Liest Sheet 1 (Spalte A=Merkmal-Nr., Spalte C=Bezeichnung) und liefert
ein Dict Merkmalnummer (str) -> Bezeichnung (str, von Excel-
Zeilenumbruch-Artefakten wie "_x000D_" bereinigt). Bei fehlender/kaputter
Datei: leeres Dict - Aufrufer faellt dann auf die reine Merkmalnummer
zurueck (siehe bezeichnung_fuer)."""
if pfad is None:
pfad = merkmalstamm_pfad()
if not os.path.exists(pfad):
return {}
try:
with zipfile.ZipFile(pfad) as z:
shared = []
if "xl/sharedStrings.xml" in z.namelist():
baum = ET.fromstring(z.read("xl/sharedStrings.xml"))
for si in baum.findall("main:si", _NS):
texte = si.findall(".//main:t", _NS)
shared.append("".join(t.text or "" for t in texte))
sheet = ET.fromstring(z.read("xl/worksheets/sheet1.xml"))
mapping = {}
for row in sheet.findall(".//main:sheetData/main:row", _NS):
zellen = {}
for c in row.findall("main:c", _NS):
spalte = _spalte_zu_zahl(_spalte_aus_zellref(c.get("r", "A1")))
v = c.find("main:v", _NS)
wert = v.text if v is not None else None
if c.get("t") == "s" and wert is not None:
wert = shared[int(wert)]
zellen[spalte] = wert
nummer = zellen.get(1)
bezeichnung = zellen.get(3)
if nummer and str(nummer).strip().isdigit() and bezeichnung:
bezeichnung = bezeichnung.replace("_x000D_", " ")
bezeichnung = re.sub(r"\s+", " ", bezeichnung).strip()
mapping[str(nummer).strip()] = bezeichnung
return mapping
except (KeyError, zipfile.BadZipFile, ET.ParseError):
return {}
def bezeichnung_fuer(nummer, mapping=None):
"""Bezeichnung zu einer Merkmalnummer, Fallback: die Nummer selbst
(z.B. bei fehlendem Eintrag im Merkmalstamm)."""
if mapping is None:
mapping = lade_merkmalstamm()
return mapping.get(str(nummer), str(nummer))