139 lines
4.1 KiB
Python
139 lines
4.1 KiB
Python
"""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_<svname>`` 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"<br\s*/?>", "\n", t, flags=re.I)
|
|
t = re.sub(r"</?h1[^>]*>", "\n", t, flags=re.I)
|
|
t = re.sub(r"</?font[^>]*>", "", 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_]+\}")
|