1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
# -*- coding: utf-8 -*-
'''
Math extension for Python-Markdown
==================================
Adds support for displaying math formulas using
[MathJax](http://www.mathjax.org/).
Author: 2015-2020, Dmitry Shachnev <mitya57@gmail.com>.
'''
from xml.etree.ElementTree import Element
from markdown.inlinepatterns import InlineProcessor
from markdown.extensions import Extension
from markdown.preprocessors import Preprocessor
from markdown.util import AtomicString
def _wrap_node(node, preview_text, wrapper_tag):
preview = Element('span', {'class': 'MathJax_Preview'})
preview.text = AtomicString(preview_text)
wrapper = Element(wrapper_tag)
wrapper.extend([preview, node])
return wrapper
class InlineMathPattern(InlineProcessor):
def handleMatch(self, m, data):
node = Element('script')
node.set('type', self._content_type)
node.text = AtomicString(m.group(2))
if self._add_preview:
node = _wrap_node(node, m.group(0), 'span')
return node, m.start(0), m.end(0)
class DisplayMathPattern(InlineProcessor):
def handleMatch(self, m, data):
node = Element('script')
node.set('type', '%s; mode=display' % self._content_type)
if '\\begin' in m.group(1):
node.text = AtomicString(m.group(0))
else:
node.text = AtomicString(m.group(2))
if self._add_preview:
node = _wrap_node(node, m.group(0), 'div')
return node, m.start(0), m.end(0)
class GitLabPreprocessor(Preprocessor):
"""
Preprocessor for GitLab-style standalone syntax:
```math
math goes here
```
"""
def run(self, lines):
inside_math_block = False
math_block_start = None
math_blocks = []
for line_number, line in enumerate(lines):
if line.strip() == '```math' and not inside_math_block:
math_block_start = line_number
inside_math_block = True
if line.strip() == '```' and inside_math_block:
math_blocks.append((math_block_start, line_number))
inside_math_block = False
for math_block_start, math_block_end in reversed(math_blocks):
math_lines = lines[math_block_start + 1:math_block_end]
math_content = '\n'.join(math_lines)
html = '<script type="%s; mode=display">\n%s\n</script>\n'
html %= (self._content_type, math_content)
placeholder = self.md.htmlStash.store(html)
lines[math_block_start:math_block_end + 1] = [placeholder]
return lines
class MathExtension(Extension):
def __init__(self, *args, **kwargs):
self.config = {
'enable_dollar_delimiter':
[False, 'Enable single-dollar delimiter'],
'add_preview': [False, 'Add a preview node before each math node'],
'use_asciimath':
[False, 'Use AsciiMath syntax instead of TeX syntax'],
'use_gitlab_delimiters':
[False, 'Use GitLab-style $`...`$ delimiters'],
}
super(MathExtension, self).__init__(*args, **kwargs)
def extendMarkdown(self, md):
add_preview = self.getConfig('add_preview')
use_asciimath = self.getConfig('use_asciimath')
use_gitlab_delimiters = self.getConfig('use_gitlab_delimiters')
content_type = 'math/asciimath' if use_asciimath else 'math/tex'
inlinemathpatterns = (
InlineMathPattern(r'(?<!\\|\$)(\$)([^\$]+)(\$)'), # $...$
InlineMathPattern(r'(?<!\\)(\\\()(.+?)(\\\))') # \(...\)
)
mathpatterns = (
DisplayMathPattern(r'(?<!\\)(\$\$)([^\$]+)(\$\$)'), # $$...$$
DisplayMathPattern(r'(?<!\\)(\\\[)(.+?)(\\\])'), # \[...\]
DisplayMathPattern( # \begin...\end
r'(?<!\\)(\\begin{([a-z]+?\*?)})(.+?)(\\end{\2})')
)
if not self.getConfig('enable_dollar_delimiter'):
inlinemathpatterns = inlinemathpatterns[1:]
if use_asciimath:
mathpatterns = mathpatterns[:-1] # \begin...\end is TeX only
if use_gitlab_delimiters:
# https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md#math
inlinemathpatterns = (
InlineMathPattern(r'(?<!\\)(\$`)([^`]+)(`\$)'), # $`...`$
)
mathpatterns = ()
preprocessor = GitLabPreprocessor(md)
preprocessor._content_type = content_type
# we should have higher priority than 'fenced_code_block' which
# has 25
md.preprocessors.register(preprocessor, 'math-gitlab', 27)
for i, pattern in enumerate(mathpatterns):
pattern._add_preview = add_preview
pattern._content_type = content_type
# we should have higher priority than 'escape' which has 180
md.inlinePatterns.register(pattern, 'math-%d' % i, 185)
for i, pattern in enumerate(inlinemathpatterns):
pattern._add_preview = add_preview
pattern._content_type = content_type
# to use gitlab delimiters, we should have higher priority than
# 'backtick' which has 190
priority = 195 if use_gitlab_delimiters else 185
md.inlinePatterns.register(pattern, 'math-inline-%d' % i, priority)
if self.getConfig('enable_dollar_delimiter'):
md.ESCAPED_CHARS.append('$')
def makeExtension(*args, **kwargs):
return MathExtension(*args, **kwargs)
|