Skip to content

Commit 93a53f3

Browse files
authored
Merge pull request #489 from validmind/cachafla/sc-15241/fix-markdown-table-ckeditor-table-parsing
Wrap markdown tables in <figure> for CKEditor compatibility
2 parents 7b3bf48 + 7589cfc commit 93a53f3

4 files changed

Lines changed: 140 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "validmind"
3-
version = "2.12.1"
3+
version = "2.12.2"
44
description = "ValidMind Library"
55
readme = "README.pypi.md"
66
requires-python = ">=3.9,<3.13"
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Tests for md_to_html in validmind.utils."""
2+
3+
from validmind.utils import md_to_html
4+
5+
6+
# ---------------------------------------------------------------------------
7+
# Basic Markdown conversion
8+
# ---------------------------------------------------------------------------
9+
10+
11+
def test_heading_converted():
12+
result = md_to_html("# Heading")
13+
assert "<h1>Heading</h1>" in result
14+
15+
16+
def test_paragraph_preserved():
17+
result = md_to_html("Hello world.")
18+
assert "<p>Hello world.</p>" in result
19+
20+
21+
def test_bold_converted():
22+
result = md_to_html("**bold**")
23+
assert "<strong>bold</strong>" in result
24+
25+
26+
def test_italic_converted():
27+
result = md_to_html("*italic*")
28+
assert "<em>italic</em>" in result
29+
30+
31+
def test_empty_string():
32+
result = md_to_html("")
33+
assert result.strip() == ""
34+
35+
36+
# ---------------------------------------------------------------------------
37+
# Table conversion — CKEditor compatibility
38+
# ---------------------------------------------------------------------------
39+
40+
41+
def test_table_wrapped_in_figure():
42+
md = "| A | B |\n|---|---|\n| 1 | 2 |"
43+
result = md_to_html(md)
44+
assert '<figure class="table"><table>' in result
45+
assert "</table></figure>" in result
46+
47+
48+
def test_table_cells_rendered():
49+
md = "| Name | Score |\n|------|-------|\n| Alice | 95 |"
50+
result = md_to_html(md)
51+
assert "Alice" in result
52+
assert "95" in result
53+
54+
55+
def test_table_header_rendered():
56+
md = "| H1 | H2 |\n|----|----|\n| a | b |"
57+
result = md_to_html(md)
58+
assert "<th>" in result
59+
60+
61+
def test_multiple_tables_both_wrapped():
62+
md = "| A |\n|---|\n| 1 |\n\nText\n\n| B |\n|---|\n| 2 |"
63+
result = md_to_html(md)
64+
assert result.count('<figure class="table">') == 2
65+
66+
67+
# ---------------------------------------------------------------------------
68+
# Math conversion (mathml=False — no math/tex rewriting)
69+
# ---------------------------------------------------------------------------
70+
71+
72+
def test_inline_math_without_mathml():
73+
"""Without mathml, mistune wraps inline math in <span class="math">."""
74+
result = md_to_html("Value $x + y$ here.")
75+
assert "x + y" in result
76+
77+
78+
def test_block_math_without_mathml():
79+
"""Without mathml, mistune wraps block math in <div class="math">."""
80+
result = md_to_html("$$E = mc^2$$")
81+
assert "E = mc^2" in result
82+
83+
84+
# ---------------------------------------------------------------------------
85+
# Math conversion (mathml=True — rewrite to math/tex script tags)
86+
# ---------------------------------------------------------------------------
87+
88+
89+
def test_block_math_converted_to_script_tag():
90+
# Mistune requires $$ on its own line for block-level math
91+
result = md_to_html("$$\nE = mc^2\n$$", mathml=True)
92+
assert '<script type="math/tex; mode=display">' in result
93+
assert "E = mc^2" in result
94+
95+
96+
def test_inline_math_converted_to_script_tag():
97+
result = md_to_html("Value $x + y$ here.", mathml=True)
98+
assert '<script type="math/tex">x + y</script>' in result
99+
100+
101+
def test_multiple_inline_math():
102+
result = md_to_html("$a$ and $b$", mathml=True)
103+
assert result.count('<script type="math/tex">') == 2
104+
105+
106+
# ---------------------------------------------------------------------------
107+
# Code blocks
108+
# ---------------------------------------------------------------------------
109+
110+
111+
def test_fenced_code_block():
112+
md = "```python\ndef hello():\n pass\n```"
113+
result = md_to_html(md)
114+
assert "code" in result
115+
assert "hello" in result
116+
117+
118+
def test_inline_code():
119+
result = md_to_html("Use `len()` to count.")
120+
assert "<code>" in result
121+
assert "len()" in result
122+
123+
124+
# ---------------------------------------------------------------------------
125+
# Mixed content
126+
# ---------------------------------------------------------------------------
127+
128+
129+
def test_mixed_text_table_and_math():
130+
md = "Formula $x^2$.\n\n| A | B |\n|---|---|\n| 1 | 2 |\n\nDone."
131+
result = md_to_html(md, mathml=True)
132+
assert '<script type="math/tex">x^2</script>' in result
133+
assert '<figure class="table">' in result
134+
assert "<p>Done.</p>" in result

validmind/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.12.1"
1+
__version__ = "2.12.2"

validmind/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,10 @@ def md_to_html(md: str, mathml=False) -> str:
564564
plugins=["math", "table", "strikethrough", "footnotes"]
565565
)(md)
566566

567+
# Wrap <table> elements in <figure class="table"> for CKEditor compatibility
568+
html = re.sub(r"<table>", '<figure class="table"><table>', html)
569+
html = re.sub(r"</table>", "</table></figure>", html)
570+
567571
if not mathml:
568572
return html
569573

0 commit comments

Comments
 (0)