Skip to content

Commit 0f89d23

Browse files
committed
👌 IMPROVE: dollarmath plugin rendering
Syncs with: executablebooks/markdown-it-dollarmath@543108e
1 parent 70a756d commit 0f89d23

File tree

5 files changed

+188
-161
lines changed

5 files changed

+188
-161
lines changed

‎mdit_py_plugins/dollarmath/index.py

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import re
2-
from typing import Callable
2+
from typing import Any, Callable, Dict, Optional
33

44
from markdown_it import MarkdownIt
5-
from markdown_it.common.utils import isWhiteSpace
5+
from markdown_it.common.utils import escapeHtml, isWhiteSpace
66
from markdown_it.rules_block import StateBlock
77
from markdown_it.rules_inline import StateInline
88

99

1010
def dollarmath_plugin(
1111
md: MarkdownIt,
12+
*,
1213
allow_labels: bool = True,
1314
allow_space: bool = True,
1415
allow_digits: bool = True,
1516
double_inline: bool = False,
17+
label_normalizer: Optional[Callable[[str], str]] = None,
18+
renderer: Optional[Callable[[str, Dict[str, Any]], str]] = None,
19+
label_renderer: Optional[Callable[[str], str]] = None,
1620
) -> None:
1721
"""Plugin for parsing dollar enclosed math,
1822
e.g. inline: ``$a=1$``, block: ``$$b=2$$``
@@ -27,39 +31,64 @@ def dollarmath_plugin(
2731
before/after the opening/closing ``$``, e.g. ``1$`` or ``$2``.
2832
This is useful when also using currency.
2933
:param double_inline: Search for double-dollar math within inline contexts
34+
:param label_normalizer: Function to normalize the label,
35+
by default replaces whitespace with `-`
36+
:param renderer: Function to render content: `(str, {"display_mode": bool}) -> str`,
37+
by default escapes HTML
38+
:param label_renderer: Function to render labels, by default creates anchor
3039
3140
"""
41+
if label_normalizer is None:
42+
label_normalizer = lambda label: re.sub(r"\s+", "-", label)
3243

3344
md.inline.ruler.before(
3445
"escape",
3546
"math_inline",
3647
math_inline_dollar(allow_space, allow_digits, double_inline),
3748
)
38-
md.add_render_rule("math_inline", render_math_inline)
49+
md.block.ruler.before(
50+
"fence", "math_block", math_block_dollar(allow_labels, label_normalizer)
51+
)
3952

40-
md.block.ruler.before("fence", "math_block", math_block_dollar(allow_labels))
41-
md.add_render_rule("math_block", render_math_block)
42-
md.add_render_rule("math_block_eqno", render_math_block_eqno)
53+
# TODO the current render rules are really just for testing
54+
# would be good to allow "proper" math rendering,
55+
# e.g. https://github.com/roniemartinez/latex2mathml
4356

57+
if renderer is None:
58+
_renderer = lambda content, _: escapeHtml(content)
59+
else:
60+
_renderer = renderer
4461

45-
# TODO the current render rules are really just for testing
46-
# would be good to allow "proper" math rendering, e.g. https://github.com/roniemartinez/latex2mathml
62+
if label_renderer is None:
63+
_label_renderer = (
64+
lambda label: f'<a href="#{label}" class="mathlabel" title="Permalink to this equation">¶</a>' # noqa: E501
65+
)
66+
else:
67+
_label_renderer = label_renderer
4768

69+
def render_math_inline(self, tokens, idx, options, env) -> str:
70+
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": False})
71+
return f'<span class="math inline">{content}</span>'
4872

49-
def render_math_inline(self, tokens, idx, options, env) -> str:
50-
return "<{0}>{1}</{0}>".format(
51-
"eqn" if tokens[idx].markup == "$$" else "eq", tokens[idx].content
52-
)
73+
def render_math_inline_double(self, tokens, idx, options, env) -> str:
74+
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
75+
return f'<div class="math inline">{content}</div>'
5376

77+
def render_math_block(self, tokens, idx, options, env) -> str:
78+
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
79+
return f'<div class="math block">\n{content}\n</div>\n'
5480

55-
def render_math_block(self, tokens, idx, options, env) -> str:
56-
return "<section>\n<eqn>{0}</eqn>\n</section>\n".format(tokens[idx].content)
81+
def render_math_block_label(self, tokens, idx, options, env) -> str:
82+
content = _renderer(str(tokens[idx].content).strip(), {"display_mode": True})
83+
_id = tokens[idx].info
84+
label = _label_renderer(tokens[idx].info)
85+
return f'<div id="{_id}" class="math block">\n{label}\n{content}\n</div>\n'
5786

87+
md.add_render_rule("math_inline", render_math_inline)
88+
md.add_render_rule("math_inline_double", render_math_inline_double)
5889

59-
def render_math_block_eqno(self, tokens, idx, options, env) -> str:
60-
return '<section>\n<eqn>{0}</eqn>\n<span class="eqno">({1})</span>\n</section>\n'.format(
61-
tokens[idx].content, tokens[idx].info
62-
)
90+
md.add_render_rule("math_block", render_math_block)
91+
md.add_render_rule("math_block_label", render_math_block_label)
6392

6493

6594
def is_escaped(state: StateInline, back_pos: int, mod: int = 0) -> bool:
@@ -146,7 +175,7 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
146175
# find closing $
147176
pos = state.pos + 1 + (1 if is_double else 0)
148177
found_closing = False
149-
while True:
178+
while not found_closing:
150179
try:
151180
end = state.srcCharCode.index(0x24, pos)
152181
except ValueError:
@@ -167,7 +196,6 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
167196
end += 1
168197

169198
found_closing = True
170-
break
171199

172200
if not found_closing:
173201
return False
@@ -199,7 +227,9 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
199227
return False
200228

201229
if not silent:
202-
token = state.push("math_inline", "math", 0)
230+
token = state.push(
231+
"math_inline_double" if is_double else "math_inline", "math", 0
232+
)
203233
token.content = text
204234
token.markup = "$$" if is_double else "$"
205235

@@ -216,6 +246,7 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
216246

217247
def math_block_dollar(
218248
allow_labels: bool = True,
249+
label_normalizer: Optional[Callable[[str], str]] = None,
219250
) -> Callable[[StateBlock, int, int, bool], bool]:
220251
"""Generate block dollar rule."""
221252

@@ -249,7 +280,7 @@ def _math_block_dollar(
249280
# search for end of block on same line
250281
lineText = state.src[startPos:end]
251282
if len(lineText.strip()) > 3:
252-
lineText = state.src[startPos:end]
283+
253284
if lineText.strip().endswith("$$"):
254285
haveEndMarker = True
255286
end = end - 2 - (len(lineText) - len(lineText.strip()))
@@ -295,13 +326,13 @@ def _math_block_dollar(
295326

296327
state.line = nextLine + (1 if haveEndMarker else 0)
297328

298-
token = state.push("math_block_eqno" if label else "math_block", "math", 0)
329+
token = state.push("math_block_label" if label else "math_block", "math", 0)
299330
token.block = True
300331
token.content = state.src[startPos + 2 : end]
301332
token.markup = "$$"
302333
token.map = [startLine, state.line]
303334
if label:
304-
token.info = label
335+
token.info = label if label_normalizer is None else label_normalizer(label)
305336

306337
return True
307338

‎setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ strict_equality = True
6060

6161
[flake8]
6262
max-line-length = 100
63-
extend-ignore = E203
63+
extend-ignore = E203,E731

0 commit comments

Comments
 (0)