import re
from typing import Optional
from markdown_it import MarkdownIt
from markdown_it.common.utils import charCodeAt
[docs]def texmath_plugin(md: MarkdownIt, delimiters="dollars", macros: Optional[dict] = None):
"""Plugin ported from
`markdown-it-texmath <https://github.com/goessner/markdown-it-texmath>`__.
It parses TeX math equations set inside opening and closing delimiters:
.. code-block:: md
$\\alpha = \\frac{1}{2}$
:param delimiters: one of: brackets, dollars, gitlab, julia, kramdown
"""
macros = macros or {}
if delimiters in rules:
for rule_inline in rules[delimiters]["inline"]:
md.inline.ruler.before(
"escape", rule_inline["name"], make_inline_func(rule_inline)
)
def render_math_inline(self, tokens, idx, options, env):
return rule_inline["tmpl"].format( # noqa: B023
render(tokens[idx].content, False, macros)
)
md.add_render_rule(rule_inline["name"], render_math_inline)
for rule_block in rules[delimiters]["block"]:
md.block.ruler.before(
"fence", rule_block["name"], make_block_func(rule_block)
)
def render_math_block(self, tokens, idx, options, env):
return rule_block["tmpl"].format( # noqa: B023
render(tokens[idx].content, True, macros), tokens[idx].info
)
md.add_render_rule(rule_block["name"], render_math_block)
def applyRule(rule, string: str, begin, inBlockquote):
if not (
string.startswith(rule["tag"], begin)
and (rule["pre"](string, begin) if "pre" in rule else True)
):
return False
match = rule["rex"].match(string[begin:]) # type: re.Match
if not match or match.start() != 0:
return False
lastIndex = match.end() + begin - 1
if "post" in rule and not (
rule["post"](string, lastIndex) # valid post-condition
# remove evil blockquote bug (https:#github.com/goessner/mdmath/issues/50)
and (not inBlockquote or "\n" not in match.group(1))
):
return False
return match
def make_inline_func(rule):
def _func(state, silent):
res = applyRule(rule, state.src, state.pos, False)
if res:
if not silent:
token = state.push(rule["name"], "math", 0)
token.content = res[1] # group 1 from regex ..
token.markup = rule["tag"]
state.pos += res.end()
return bool(res)
return _func
def make_block_func(rule):
def _func(state, begLine, endLine, silent):
begin = state.bMarks[begLine] + state.tShift[begLine]
res = applyRule(rule, state.src, begin, state.parentType == "blockquote")
if res:
if not silent:
token = state.push(rule["name"], "math", 0)
token.block = True
token.content = res[1]
token.info = res[len(res.groups())]
token.markup = rule["tag"]
line = begLine
endpos = begin + res.end() - 1
while line < endLine:
if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
# line for end of block math found ...
state.line = line + 1
break
line += 1
state.pos = begin + res.end()
return bool(res)
return _func
def dollar_pre(str, beg):
prv = charCodeAt(str[beg - 1], 0) if beg > 0 else False
return (
(not prv) or prv != 0x5C and (prv < 0x30 or prv > 0x39) # no backslash,
) # no decimal digit .. before opening '$'
def dollar_post(string, end):
try:
nxt = string[end + 1] and charCodeAt(string[end + 1], 0)
except IndexError:
return True
return (
(not nxt) or (nxt < 0x30) or (nxt > 0x39)
) # no decimal digit .. after closing '$'
def render(tex, displayMode, macros):
return tex
# TODO better HTML renderer port for math
# try:
# res = katex.renderToString(tex,{throwOnError:False,displayMode,macros})
# except:
# res = tex+": "+err.message.replace("<","<")
# return res
# def use(katex): # math renderer used ...
# texmath.katex = katex; # ... katex solely at current ...
# return texmath;
# }
# All regexes areg global (g) and sticky (y), see:
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky
rules: dict = {
"brackets": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\\\((.+?)\\\)", re.DOTALL),
"tmpl": "<eq>{0}</eq>",
"tag": "\\(",
}
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(
r"^\\\[(((?!\\\]|\\\[)[\s\S])+?)\\\]\s*?\(([^)$\r\n]+?)\)", re.M
),
"tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
"tag": "\\[",
},
{
"name": "math_block",
"rex": re.compile(r"^\\\[([\s\S]+?)\\\]", re.M),
"tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
"tag": "\\[",
},
],
},
"gitlab": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\$`(.+?)`\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$`",
}
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(
r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M
),
"tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n', # noqa: E501
"tag": "```math",
},
{
"name": "math_block",
"rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M),
"tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
"tag": "```math",
},
],
},
"julia": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^`{2}([^`]+?)`{2}"),
"tmpl": "<eq>{0}</eq>",
"tag": "``",
},
{
"name": "math_inline",
"rex": re.compile(r"^\$(\S[^$\r\n]*?[^\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
{
"name": "math_single",
"rex": re.compile(r"^\$([^$\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(
r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M
),
"tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
"tag": "```math",
},
{
"name": "math_block",
"rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M),
"tmpl": "<section><eqn>{0}</eqn></section>",
"tag": "```math",
},
],
},
"kramdown": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\${2}([^$\r\n]*?)\${2}"),
"tmpl": "<eq>{0}</eq>",
"tag": "$$",
}
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M),
"tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>',
"tag": "$$",
},
{
"name": "math_block",
"rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M),
"tmpl": "<section><eqn>{0}</eqn></section>",
"tag": "$$",
},
],
},
"dollars": {
"inline": [
{
"name": "math_inline",
"rex": re.compile(r"^\$(\S[^$]*?[^\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
{
"name": "math_single",
"rex": re.compile(r"^\$([^$\s\\]{1}?)\$"),
"tmpl": "<eq>{0}</eq>",
"tag": "$",
"pre": dollar_pre,
"post": dollar_post,
},
],
"block": [
{
"name": "math_block_eqno",
"rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M),
"tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n', # noqa: E501
"tag": "$$",
},
{
"name": "math_block",
"rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M),
"tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n",
"tag": "$$",
},
],
},
}