#!/usr/bin/env python3 """Parse assets/libraries/btp-service-icons-all-size-M.xml and emit icon-index.json. Run once (or whenever the upstream icon library is refreshed). Output: assets/icon-index.json with { "": { "label": "", "aliases": ["", ...], "style": "" } } No image payload is inlined (the library XML already holds them); the extract script (extract_icon.py) returns the full image-data URI on demand. """ from __future__ import annotations import json import re import sys from pathlib import Path from xml.sax.saxutils import unescape HERE = Path(__file__).resolve().parent LIB = HERE.parent / "assets" / "libraries" / "btp-service-icons-all-size-M.xml" OUT = HERE.parent / "assets" / "icon-index.json" def clean(label: str) -> str: label = label.replace("
", " ").replace("\\n", " ") label = re.sub(r"\s+", " ", label).strip() return label def display_from_title(title: str | None) -> str: if not title: return "" title = re.sub(r"^\d+-", "", title) title = re.sub(r"_sd$", "", title) title = title.replace("-", " ") title = re.sub(r"\s+", " ", title).strip() acronyms = {"sap": "SAP", "btp": "BTP", "hana": "HANA", "abap": "ABAP"} return " ".join(acronyms.get(part, part.capitalize()) for part in title.split()) def slugify(name: str) -> str: s = name.lower() s = re.sub(r"[^a-z0-9]+", "-", s) return s.strip("-") def build() -> dict[str, dict]: raw = LIB.read_text(encoding="utf-8") # strip the ... shell and the stray comments raw = re.sub(r"", "", raw, flags=re.S) raw = raw.strip() assert raw.startswith("") and raw.endswith(""), raw[:80] body = raw[len("") : -len("")].strip() entries = json.loads(body) index: dict[str, dict] = {} for entry in entries: xml_encoded = entry["xml"] xml = unescape(xml_encoded) # find the cell = re.search(r']*value="([^"]*)"[^>]*style="([^"]*)"', xml) if not cell: continue raw_label = cell.group(1) style = cell.group(2) label = clean(raw_label) or display_from_title(entry.get("title")) slug = slugify(label) if not slug: continue aliases = {slug} # common abbreviations / renamings people use when describing diagrams noise = ["sap-", "service-for-sap-btp", "-service", "-on-sap-btp"] short = slug for n in noise: short = short.replace(n, "-") short = re.sub(r"-+", "-", short).strip("-") if short and short != slug: aliases.add(short) # word-only alias (drops "-service" tails) aliases.add(re.sub(r"-service$", "", slug)) index[slug] = { "label": raw_label, "display": label, "aliases": sorted(aliases - {slug}), "style": style, } return index def main() -> int: if not LIB.exists(): print(f"missing {LIB}", file=sys.stderr) return 1 index = build() OUT.write_text(json.dumps(index, indent=2, ensure_ascii=False, sort_keys=True) + "\n", encoding="utf-8") print(f"wrote {OUT} — {len(index)} icons") return 0 if __name__ == "__main__": sys.exit(main())