gwoe-antragspruefer/app/pm_render.py

82 lines
2.8 KiB
Python
Raw Normal View History

"""Mini-Markdown-Renderer für PM-Bodies — Python-Spiegelbild der
JS-Funktion ``renderPmBody`` in ``v2/screens/aktuelle-themen.html``.
Der Renderer interpretiert eine kleine Untermenge von Markdown:
- ``**bold**`` und ``__bold__`` ``<strong>...</strong>``
- ``*italic*`` und ``_italic_`` ``<em>...</em>`` (vorsichtig nur
wenn nicht zwischen Ziffern oder Word-Charakters)
- Zeilen, die mit ``- `` oder ``* `` beginnen ``<ul><li>...</li></ul>``
- Doppel-Newlines trennen Absätze ``<p>...</p>``
- Einzelne Newlines innerhalb eines Absatzes ``<br>``
- HTML-Special-Chars (``&``, ``<``, ``>``) werden escaped.
Die Python-Variante existiert primär zum Testen. Im Produktiv-Frontend
wird die JS-Variante benutzt (kein Round-Trip zum Server).
"""
from __future__ import annotations
import re
# 1) HTML-Escape (analog zur JS-Variante, in dieser Reihenfolge)
def _html_escape(s: str) -> str:
return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
# 2) Bold-Marker: **...** und __...__
_RE_BOLD_STAR = re.compile(r"\*\*([^*\n]+?)\*\*")
_RE_BOLD_UNDER = re.compile(r"__([^_\n]+?)__")
# 3) Italic-Marker: *…* und _…_, jeweils nicht zw. Word-Chars
_RE_ITALIC_STAR = re.compile(r"(?<![*\w])\*([^*\n]+?)\*(?![*\w])")
_RE_ITALIC_UNDER = re.compile(r"(?<![_\w])_([^_\n]+?)_(?![_\w])")
# 4) Listen-Bullet
_RE_BULLET = re.compile(r"^\s*[-*]\s+")
def render_pm_body(body: str) -> str:
"""Render Mini-Markdown zu HTML. Leere/None-Eingabe → leerer String."""
if not body:
return ""
s = _html_escape(body)
s = _RE_BOLD_STAR.sub(r"<strong>\1</strong>", s)
s = _RE_BOLD_UNDER.sub(r"<strong>\1</strong>", s)
s = _RE_ITALIC_STAR.sub(r"<em>\1</em>", s)
s = _RE_ITALIC_UNDER.sub(r"<em>\1</em>", s)
# Listen: konsekutive "- "/"* "-Zeilen zu einem <ul> zusammenfassen
out_lines = []
in_list = False
for line in s.split("\n"):
if _RE_BULLET.match(line):
if not in_list:
out_lines.append('<ul style="margin:0.5em 0;padding-left:1.4em;">')
in_list = True
out_lines.append("<li>" + _RE_BULLET.sub("", line) + "</li>")
else:
if in_list:
out_lines.append("</ul>")
in_list = False
out_lines.append(line)
if in_list:
out_lines.append("</ul>")
s = "\n".join(out_lines)
# Paragraphen-Split bei doppelten Newlines, einzelne → <br>
paras = re.split(r"\n\s*\n", s)
rendered = []
for p in paras:
trimmed = p.strip()
if not trimmed:
continue
if trimmed.startswith("<"):
rendered.append(trimmed)
else:
rendered.append(
'<p style="margin:0 0 0.9em;">'
+ trimmed.replace("\n", "<br>")
+ "</p>"
)
return "\n".join(rendered)