"""Utility functions shared across renderers. Mirrors PHP helpers: h(), formatMoneyFigures(), handbook_anchor(), etc. """ from __future__ import annotations import html import re def h(s: str) -> str: """HTML-escape (mirrors PHP h()).""" return html.escape(str(s), quote=True) def is_assoc(a: list | dict) -> bool: """Check if an array is associative (dict-like) vs sequential list.""" return isinstance(a, dict) def hb_slug(s: str) -> str: """Slug helper for anchors.""" tmp = s.lower().strip() tmp = re.sub(r"[^a-z0-9]+", "_", tmp, flags=re.IGNORECASE) tmp = re.sub(r"_+", "_", tmp) return tmp.strip("_") def handbook_anchor(prefix: str, text: str, idx: int) -> str: """Normalise a string into a safe anchor id. Mirrors PHP handbook_anchor.""" base = text.lower().strip() base = re.sub(r"[^a-z0-9]+", "-", base, flags=re.IGNORECASE) base = base.strip("-") if not base: base = f"{prefix}-{idx}" return f"{prefix}-{base}-{idx}" def is_truthy(val) -> bool: """Mirrors PHP handbook_true.""" if isinstance(val, bool): return val if isinstance(val, int): return val != 0 v = str(val).lower().strip() return v not in ("0", "false", "") def format_money_figures(text: str) -> str: """Mirrors PHP formatMoneyFigures(). - Strips "USD " prefix - Adds $ to large numbers - Formats with commas """ if not text: return text # Remove "USD " prefix text = re.sub(r"\bUSD\s*", "", text, flags=re.IGNORECASE) def _format_match(m: re.Match) -> str: num_str = m.group(1).replace(",", "") dec = m.group(2) if m.group(2) else "" num = float(num_str) if dec: formatted = f"{num:,.{len(dec)}f}" else: formatted = f"{num:,.0f}" return "$" + formatted # Add $ to large numbers (with optional decimals) text = re.sub( r"(? str: num_str = m.group(1).replace(",", "") formatted = f"{float(num_str):,.0f}" return "$" + formatted text = re.sub( r"(? list[dict]: """Stable sort: sort_order ASC, then id ASC, then insertion order.""" for i, s in enumerate(sections): s.setdefault("_i", i) def sort_key(s: dict): so = s.get("sort_order") sid = s.get("id") so_key = (0, so) if so is not None else (1, 0) sid_key = (0, sid) if sid is not None else (1, 0) return (so_key, sid_key, s.get("_i", 0)) sections.sort(key=sort_key) for s in sections: s.pop("_i", None) return sections def get_any(d: dict, keys: list[str]) -> str: """Return the first non-empty string value found for one of the keys.""" for k in keys: v = d.get(k) if v is None or isinstance(v, (dict, list)): continue t = str(v).strip() if t: return t return "" def emphasize_keywords(text: str) -> str: """Add bold HTML emphasis to key handbook terms in already-escaped text. Bolds: REGULAR, PRIME, dollar amounts ($X,XXX), and other critical terms. Input must already be HTML-escaped. Returns HTML with tags. """ if not text: return text escaped = h(text) # Bold REGULAR and PRIME (case-insensitive, whole word) escaped = re.sub( r'\b(REGULAR|PRIME)\b', r'\1', escaped, flags=re.IGNORECASE, ) # Bold dollar amounts like $1,000 or $500 escaped = re.sub( r'(\$[\d,]+(?:\.\d+)?)', r'\1', escaped, ) return escaped