| """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 |
|
|
| |
| 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 |
|
|
| |
| text = re.sub( |
| r"(?<![$0-9])(?<!\$)((?:\d{1,3}(?:,\d{3})+)|(?:\d{4,}))(?:\.(\d+))?(?![%\d/])", |
| _format_match, |
| text, |
| ) |
|
|
| |
| def _format_remaining(m: re.Match) -> str: |
| num_str = m.group(1).replace(",", "") |
| formatted = f"{float(num_str):,.0f}" |
| return "$" + formatted |
|
|
| text = re.sub( |
| r"(?<!\$)\b(\d{1,3}(?:,\d{3})+|\d{4,})(?![%\d/])", |
| _format_remaining, |
| text, |
| ) |
|
|
| return text |
|
|
|
|
| def sort_sections_stable(sections: list[dict]) -> 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 <strong> tags. |
| """ |
| if not text: |
| return text |
|
|
| escaped = h(text) |
|
|
| |
| escaped = re.sub( |
| r'\b(REGULAR|PRIME)\b', |
| r'<strong>\1</strong>', |
| escaped, |
| flags=re.IGNORECASE, |
| ) |
|
|
| |
| escaped = re.sub( |
| r'(\$[\d,]+(?:\.\d+)?)', |
| r'<strong>\1</strong>', |
| escaped, |
| ) |
|
|
| return escaped |
|
|