1
1
import re
2
- from typing import Callable
2
+ from typing import Any , Callable , Dict , Optional
3
3
4
4
from markdown_it import MarkdownIt
5
- from markdown_it .common .utils import isWhiteSpace
5
+ from markdown_it .common .utils import escapeHtml , isWhiteSpace
6
6
from markdown_it .rules_block import StateBlock
7
7
from markdown_it .rules_inline import StateInline
8
8
9
9
10
10
def dollarmath_plugin (
11
11
md : MarkdownIt ,
12
+ * ,
12
13
allow_labels : bool = True ,
13
14
allow_space : bool = True ,
14
15
allow_digits : bool = True ,
15
16
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 ,
16
20
) -> None :
17
21
"""Plugin for parsing dollar enclosed math,
18
22
e.g. inline: ``$a=1$``, block: ``$$b=2$$``
@@ -27,39 +31,64 @@ def dollarmath_plugin(
27
31
before/after the opening/closing ``$``, e.g. ``1$`` or ``$2``.
28
32
This is useful when also using currency.
29
33
: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
30
39
31
40
"""
41
+ if label_normalizer is None :
42
+ label_normalizer = lambda label : re .sub (r"\s+" , "-" , label )
32
43
33
44
md .inline .ruler .before (
34
45
"escape" ,
35
46
"math_inline" ,
36
47
math_inline_dollar (allow_space , allow_digits , double_inline ),
37
48
)
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
+ )
39
52
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
43
56
57
+ if renderer is None :
58
+ _renderer = lambda content , _ : escapeHtml (content )
59
+ else :
60
+ _renderer = renderer
44
61
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
47
68
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>'
48
72
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>'
53
76
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 '
54
80
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 '
57
86
87
+ md .add_render_rule ("math_inline" , render_math_inline )
88
+ md .add_render_rule ("math_inline_double" , render_math_inline_double )
58
89
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 )
63
92
64
93
65
94
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:
146
175
# find closing $
147
176
pos = state .pos + 1 + (1 if is_double else 0 )
148
177
found_closing = False
149
- while True :
178
+ while not found_closing :
150
179
try :
151
180
end = state .srcCharCode .index (0x24 , pos )
152
181
except ValueError :
@@ -167,7 +196,6 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
167
196
end += 1
168
197
169
198
found_closing = True
170
- break
171
199
172
200
if not found_closing :
173
201
return False
@@ -199,7 +227,9 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
199
227
return False
200
228
201
229
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
+ )
203
233
token .content = text
204
234
token .markup = "$$" if is_double else "$"
205
235
@@ -216,6 +246,7 @@ def _math_inline_dollar(state: StateInline, silent: bool) -> bool:
216
246
217
247
def math_block_dollar (
218
248
allow_labels : bool = True ,
249
+ label_normalizer : Optional [Callable [[str ], str ]] = None ,
219
250
) -> Callable [[StateBlock , int , int , bool ], bool ]:
220
251
"""Generate block dollar rule."""
221
252
@@ -249,7 +280,7 @@ def _math_block_dollar(
249
280
# search for end of block on same line
250
281
lineText = state .src [startPos :end ]
251
282
if len (lineText .strip ()) > 3 :
252
- lineText = state . src [ startPos : end ]
283
+
253
284
if lineText .strip ().endswith ("$$" ):
254
285
haveEndMarker = True
255
286
end = end - 2 - (len (lineText ) - len (lineText .strip ()))
@@ -295,13 +326,13 @@ def _math_block_dollar(
295
326
296
327
state .line = nextLine + (1 if haveEndMarker else 0 )
297
328
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 )
299
330
token .block = True
300
331
token .content = state .src [startPos + 2 : end ]
301
332
token .markup = "$$"
302
333
token .map = [startLine , state .line ]
303
334
if label :
304
- token .info = label
335
+ token .info = label if label_normalizer is None else label_normalizer ( label )
305
336
306
337
return True
307
338
0 commit comments