"""Shared Valve loc formatting: strip HTML and fill %token% / {s:token}.""" from __future__ import annotations import re from html import unescape def fmt_num(v: float) -> str: if abs(v - round(v)) < 1e-6: return str(int(round(v))) return f"{v:g}" def sv_lookup( special_values: list | None, prefer: str | None = None ) -> dict[str, list[float]]: """prefer: None | 'scepter' | 'shard' — choose values_* channel when present. Keys are stored casefolded so Valve Chinese locs (lowercase tokens) match PascalCase special_values names. """ out: dict[str, list[float]] = {} for sv in special_values or []: if not isinstance(sv, dict): continue name = str(sv.get("name") or "").strip() if not name: continue base = sv.get("values_float") or [] if not isinstance(base, list): base = [] sc = sv.get("values_scepter") or [] sh = sv.get("values_shard") or [] if not isinstance(sc, list): sc = [] if not isinstance(sh, list): sh = [] chosen = base if prefer == "scepter" and sc: chosen = sc elif prefer == "shard" and sh: chosen = sh floats = [float(x) for x in chosen if isinstance(x, (int, float))] out[name.casefold()] = floats if sc: out[("scepter_" + name).casefold()] = [ float(x) for x in sc if isinstance(x, (int, float)) ] if sh: out[("shard_" + name).casefold()] = [ float(x) for x in sh if isinstance(x, (int, float)) ] return out def _resolve_vals( lookup: dict[str, list[float]], key: str, prefer: str | None, ) -> list[float] | None: """Resolve a loc token against the casefolded SV lookup. Handles ``bonus_`` upgrade tokens that point at the base SV's ``values_shard`` / ``values_scepter`` (or ``shard_``/``scepter_`` aliases), including an extra ``bonus_`` when the SV name already starts with bonus_. """ key_cf = key.casefold() vals = lookup.get(key_cf) if vals: return vals if prefer: vals = lookup.get(f"{prefer}_{key}".casefold()) if vals: return vals if not key_cf.startswith("bonus_"): return None base = key_cf[len("bonus_") :] if not base: return None vals = lookup.get(base) if vals: return vals if prefer: vals = lookup.get(f"{prefer}_{base}".casefold()) if vals: return vals for ch in ("shard", "scepter"): vals = lookup.get(f"{ch}_{base}".casefold()) if vals: return vals return None def strip_html(text: str) -> str: """Remove HTML tags and collapse whitespace (no token filling).""" if not text: return "" t = unescape(text) t = re.sub(r"", "\n", t, flags=re.I) t = re.sub(r"]*>", "\n", t, flags=re.I) t = re.sub(r"]*>", "", t, flags=re.I) t = re.sub(r"<[^>]+>", " ", t) return re.sub(r"[ \t]+", " ", t).strip() def format_loc( text: str, special_values: list | None = None, prefer: str | None = None, ) -> str: """Strip HTML and fill %token% / {s:token} from special_values when possible.""" if not text: return "" t = strip_html(text) t = re.sub(r"[ \t]+\n", "\n", t) lookup = sv_lookup(special_values or [], prefer=prefer) def repl_pct(m: re.Match) -> str: vals = _resolve_vals(lookup, m.group(1), prefer) if not vals: return "?" if len(vals) == 1: return fmt_num(vals[0]) if len(vals) <= 4: return " / ".join(fmt_num(v) for v in vals) return fmt_num(vals[0]) t = re.sub(r"%([A-Za-z0-9_]+)%", repl_pct, t) t = re.sub(r"\{s:([A-Za-z0-9_]+)\}", repl_pct, t) t = t.replace("%%", "%") t = re.sub(r"[ \t]+\n", "\n", t) t = re.sub(r"\n{3,}", "\n\n", t) t = re.sub(r"[ \t]{2,}", " ", t).strip() return t HAS_PLACEHOLDER = re.compile(r"%[A-Za-z0-9_]+%|\{s:[A-Za-z0-9_]+\}")