from typing import Any CELL_GAP = " | " _TEXT_KEYS = ("summary", "text", "expression", "credit") _INLINE_KEYS = ("text", "alternative_text", "expression") def _button(button: dict[str, Any]) -> str: label = _inline(button.get("text")) url = button.get("url") return f"{label} ({url})" if isinstance(url, str) else label def _inline(value: object) -> str: if isinstance(value, str): return value if isinstance(value, list): return "".join(_inline(item) for item in value) if not isinstance(value, dict): return "" button = value.get("button") if isinstance(button, dict): return _button(button) return next((_inline(value[key]) for key in _INLINE_KEYS if key in value), "") def _children(block: dict[str, Any], key: str) -> list[Any]: value = block.get(key) return value if isinstance(value, list) else [] def _row(row: object) -> str: cells = row if isinstance(row, list) else [] return CELL_GAP.join( _inline(cell.get("text")) for cell in cells if isinstance(cell, dict) ) def _block(block: object) -> list[str]: if not isinstance(block, dict): return [] lines = [_inline(block[key]) for key in _TEXT_KEYS if key in block] lines += [_row(row) for row in _children(block, "cells")] lines += [ _button(button) for button in _children(block, "buttons") if isinstance(button, dict) ] for key in ("items", "blocks"): for child in _children(block, key): lines += _block(child) lines += _block(block.get("caption")) lines = [line for line in lines if line] label = block.get("label") if isinstance(label, str) and lines: lines[0] = f"{label} {lines[0]}" return lines def rich_text(rich: object) -> str | None: return "\n".join(_block(rich)) or None