From bc1b0b7ec2ecd68638899aa286666ec310af56b7 Mon Sep 17 00:00:00 2001
From: Adrian <1917353+apetcu@users.noreply.github.com>
Date: Wed, 26 Aug 2026 23:01:40 +0300
Subject: [PATCH] fix(viewer): keep balanced nested emphasis working
Restricting emphasis content to non-delimiters made `**bold with *italic*
inside**` render its outer delimiters literally. Bold spans now only require
a non-delimiter at each end (so `******` still stays literal) and italic
content may contain complete .. spans, which keeps nesting working
while still ruling out crossed tags.
---
strix/interface/viewer/report_pdf.py | 15 ++++++++-------
tests/test_report_pdf.py | 6 ++++++
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/strix/interface/viewer/report_pdf.py b/strix/interface/viewer/report_pdf.py
index 358bd424..91436095 100644
--- a/strix/interface/viewer/report_pdf.py
+++ b/strix/interface/viewer/report_pdf.py
@@ -417,14 +417,15 @@ def _inline_md(text: str) -> str:
codes.append(match.group(1))
return f"\x00{len(codes) - 1}\x00"
- # Emphasis content may not contain its own delimiter or a tag, so the three passes can
- # never interleave into crossed markup (``..``), which reportlab rejects.
- # A run of asterisks such as a masked secret (``******``) therefore stays literal.
+ # Bold spans must start and end with a non-delimiter, so a run of asterisks such as a masked
+ # secret (``******``) stays literal. Italic content is plain text or complete ``..``
+ # spans, so the passes can nest (``**a *b* c**``, ``*a **b** c*``) but can never interleave
+ # into crossed markup (``..``), which reportlab rejects.
seg = html.escape(re.sub(r"`([^`]+)`", _stash, text))
- seg = re.sub(r"\*\*\*([^*<>\n]+?)\*\*\*", r"\1", seg)
- seg = re.sub(r"\*\*([^*<>\n]+?)\*\*", r"\1", seg)
- seg = re.sub(r"__([^_<>\n]+?)__", r"\1", seg)
- seg = re.sub(r"\*([^*<>\n]+?)\*", r"\1", seg)
+ seg = re.sub(r"\*\*\*(?=[^*])(.+?)(?<=[^*])\*\*\*", r"\1", seg)
+ seg = re.sub(r"\*\*(?=[^*])(.+?)(?<=[^*])\*\*", r"\1", seg)
+ seg = re.sub(r"__(?=[^_])(.+?)(?<=[^_])__", r"\1", seg)
+ seg = re.sub(r"\*((?:[^*<>\n]|[^<>*\n]*)+?)\*", r"\1", seg)
def _restore(match: re.Match[str]) -> str:
inner = html.escape(codes[int(match.group(1))])
diff --git a/tests/test_report_pdf.py b/tests/test_report_pdf.py
index c25b486b..35b103b0 100644
--- a/tests/test_report_pdf.py
+++ b/tests/test_report_pdf.py
@@ -156,3 +156,9 @@ def test_generate_report_pdf_with_masked_secret_in_summary(tmp_path: Path) -> No
def test_para_falls_back_to_plain_text_on_crossed_markup() -> None:
para = _para("x & y", ParagraphStyle("t"))
assert para.getPlainText() == "x & y"
+
+
+def test_inline_md_keeps_balanced_nested_emphasis() -> None:
+ assert _inline_md("**bold with *italic* inside**") == "bold with italic inside"
+ assert _inline_md("*outer **bold** inner*") == "outer bold inner"
+ assert "******" in _inline_md("(observed as '******')") # a masked secret stays literal