from __future__ import annotations import re _ALLOWED_TAGS = { "b", "strong", "i", "em", "u", "ins", "s", "strike", "del", "code", "pre", "a", } _TAG_PATTERN = re.compile(r"]*>") _TABLE_ROW_PATTERN = re.compile(r"^\|(.+)\|$", re.MULTILINE) _TABLE_SEPARATOR_PATTERN = re.compile(r"^\|[\s\-:|]+\|$", re.MULTILINE) def text_sanitizer(text: str) -> str: if not text: return "" text = text.replace(" ", " ") def _replace_tag(m: re.Match) -> str: tag = m.group(1).lower() if tag in _ALLOWED_TAGS: return m.group(0) return "" text = _TAG_PATTERN.sub(_replace_tag, text) text = text.strip() return text def markdown_to_whatsapp(md_text: str) -> str: if not md_text: return "" text = md_text text = re.sub(r"```(\w+)?\n(.*?)```", _code_block, text, flags=re.DOTALL) text = re.sub(r"`([^`]+)`", r"```\1```", text) text = _convert_tables(text) text = re.sub(r"\*\*\*(.+?)\*\*\*", r"*_\1_*", text) text = re.sub(r"___(.+?)___", r"*_\1_*", text) text = re.sub(r"\*\*(.+?)\*\*", r"*\1*", text) text = re.sub(r"__(.+?)__", r"*\1*", text) text = re.sub(r"(?", "\n") text = re.sub(r"\n{3,}", "\n\n", text) return text.strip() def _convert_tables(text: str) -> str: def _replace_table(m: re.Match) -> str: table_block = m.group(0) lines = table_block.strip().split("\n") if len(lines) < 2: return table_block data_lines = [] header = None for line in lines: stripped = line.strip() if not stripped.startswith("|"): continue if _TABLE_SEPARATOR_PATTERN.match(stripped): continue cells = [c.strip() for c in stripped[1:-1].split("|")] if header is None: header = " | ".join(cells) else: data_lines.append("• " + " | ".join(cells)) if header is None: return table_block result = [header] result.extend(data_lines) return "\n".join(result) text = re.sub( r"(?:^|\n)(\|.+\|\s*\n){2,}", _replace_table, text, flags=re.MULTILINE, ) return text def _code_block(m: re.Match) -> str: lang = m.group(1) or "" code = m.group(2).strip() prefix = f"[{lang}]\n" if lang else "" return f"```{prefix}{code}```" def _link(m: re.Match) -> str: text = m.group(1) url = m.group(2) if text == url: return url if not text.strip(): return url return f"{text}\n({url})"