1r"""
2Arithmatex.
3
4pymdownx.arithmatex
5Extension that preserves the following for MathJax use:
6
7```
8$Equation$, \(Equation\)
9
10$$
11  Display Equations
12$$
13
14\[
15  Display Equations
16\]
17
18\begin{align}
19  Display Equations
20\end{align}
21```
22
23and `$Inline MathJax Equations$`
24
25Inline and display equations are converted to scripts tags. You can optionally generate previews.
26
27MIT license.
28
29Copyright (c) 2014 - 2017 Isaac Muse <isaacmuse@gmail.com>
30
31Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
32documentation files (the "Software"), to deal in the Software without restriction, including without limitation
33the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
34and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
35
36The above copyright notice and this permission notice shall be included in all copies or substantial portions
37of the Software.
38
39THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
40TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
41THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
42CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
43DEALINGS IN THE SOFTWARE.
44"""
45from markdown import Extension
46from markdown.inlinepatterns import InlineProcessor
47from markdown.blockprocessors import BlockProcessor
48from markdown import util as md_util
49from functools import partial
50import xml.etree.ElementTree as etree
51from . import util
52import re
53
54RE_SMART_DOLLAR_INLINE = r'(?:(?<!\\)((?:\\{2})+)(?=\$)|(?<!\\)(\$)(?!\s)((?:\\.|[^\\$])+?)(?<!\s)(?:\$))'
55RE_DOLLAR_INLINE = r'(?:(?<!\\)((?:\\{2})+)(?=\$)|(?<!\\)(\$)((?:\\.|[^\\$])+?)(?:\$))'
56RE_BRACKET_INLINE = r'(?:(?<!\\)((?:\\{2})+?)(?=\\\()|(?<!\\)(\\\()((?:\\[^)]|[^\\])+?)(?:\\\)))'
57
58RE_DOLLAR_BLOCK = r'(?P<dollar>[$]{2})(?P<math>((?:\\.|[^\\])+?))(?P=dollar)'
59RE_TEX_BLOCK = r'(?P<math2>\\begin\{(?P<env>[a-z]+\*?)\}(?:\\.|[^\\])+?\\end\{(?P=env)\})'
60RE_BRACKET_BLOCK = r'\\\[(?P<math3>(?:\\[^\]]|[^\\])+?)\\\]'
61
62
63def _escape(txt):
64    """Basic html escaping."""
65
66    txt = txt.replace('&', '&amp;')
67    txt = txt.replace('<', '&lt;')
68    txt = txt.replace('>', '&gt;')
69    txt = txt.replace('"', '&quot;')
70    return txt
71
72
73# Formatters usable with InlineHilite
74@util.deprecated(
75    "The inline MathJax Preview formatter has been deprecated in favor of the configurable 'arithmatex_fenced_format'. "
76    "Please see relevant documentation for more information on how to switch before this function is "
77    "removed in the future."
78)
79def inline_mathjax_preview_format(math, language='math', class_name='arithmatex', md=None):
80    """Inline math formatter with preview."""
81
82    return _inline_mathjax_format(math, preview=True)
83
84
85@util.deprecated(
86    "The inline MathJax formatter has been deprecated in favor of the configurable 'arithmatex_fenced_format'. "
87    "Please see relevant documentation for more information on how to switch before this function is "
88    "removed in the future."
89)
90def inline_mathjax_format(math, language='math', class_name='arithmatex', md=None):
91    """Inline math formatter."""
92
93    return _inline_mathjax_format(math, preview=False)
94
95
96@util.deprecated(
97    "The inline generic math formatter has been deprecated in favor of the configurable 'arithmatex_inline_format'. "
98    "Please see relevant documentation for more information on how to switch before this function is "
99    "removed in the future."
100)
101def inline_generic_format(math, language='math', class_name='arithmatex', md=None, **kwargs):
102    """Inline generic formatter."""
103
104    return _inline_generic_format(math, language, class_name, md, **kwargs)
105
106
107def _inline_mathjax_format(math, language='math', class_name='arithmatex', md=None, tag='span', preview=False):
108    """Inline math formatter."""
109
110    el = etree.Element(tag, {'class': 'arithmatex'})
111    if preview:
112        pre = etree.SubElement(el, 'span', {'class': 'MathJax_Preview'})
113        pre.text = md_util.AtomicString(math)
114    script = etree.SubElement(el, 'script', {'type': 'math/tex'})
115    script.text = md_util.AtomicString(math)
116    return el
117
118
119def _inline_generic_format(math, language='math', class_name='arithmatex', md=None, wrap='\\({}\\)', tag='span'):
120    """Inline generic formatter."""
121
122    el = etree.Element(tag, {'class': class_name})
123    el.text = md_util.AtomicString(wrap.format(math))
124    return el
125
126
127def arithmatex_inline_format(**kwargs):
128    """Specify which type of formatter you want and the wrapping tag."""
129
130    mode = kwargs.get('mode', 'generic')
131    tag = kwargs.get('tag', 'span')
132    preview = kwargs.get('preview', False)
133
134    if mode == 'generic':
135        return partial(_inline_generic_format, tag=tag)
136    elif mode == 'mathjax':
137        return partial(_inline_mathjax_format, preview=preview)
138
139
140# Formatters usable with SuperFences
141@util.deprecated(
142    "The fenced MathJax preview formatter has been deprecated in favor of the configurable 'arithmatex_fenced_format'. "
143    "Please see relevant documentation for more information on how to switch before this function is "
144    "removed in the future."
145)
146def fence_mathjax_preview_format(math, language='math', class_name='arithmatex', options=None, md=None, **kwargs):
147    """Block MathJax formatter with preview."""
148
149    return _fence_mathjax_format(math, preview=True)
150
151
152@util.deprecated(
153    "The fenced MathJax preview formatter has been deprecated in favor of the configurable 'arithmatex_fenced_format'. "
154    "Please see relevant documentation for more information on how to switch before this function is "
155    "removed in the future."
156)
157def fence_mathjax_format(math, language='math', class_name='arithmatex', options=None, md=None, **kwargs):
158    """Block MathJax formatter."""
159
160    return _fence_mathjax_format(math, preview=False)
161
162
163@util.deprecated(
164    "The generic math formatter has been deprecated in favor of the configurable 'arithmatex_fenced_format'. "
165    "Please see relevant documentation for more information on how to switch before this function is "
166    "removed in the future."
167)
168def fence_generic_format(math, language='math', class_name='arithmatex', options=None, md=None, **kwargs):
169    """Generic block formatter."""
170
171    return _fence_generic_format(math, language, class_name, options, md, **kwargs)
172
173
174def _fence_mathjax_format(
175    math, language='math', class_name='arithmatex', options=None, md=None, preview=False, tag="div", **kwargs
176):
177    """Block math formatter."""
178
179    text = '<{} class="arithmatex">\n'.format(tag)
180    if preview:
181        text += (
182            '<div class="MathJax_Preview">\n' +
183            _escape(math) +
184            '\n</div>\n'
185        )
186
187    text += (
188        '<script type="math/tex; mode=display">\n' +
189        math +
190        '\n</script>\n'
191    )
192    text += '</div>'
193
194    return text
195
196
197def _fence_generic_format(
198    math, language='math', class_name='arithmatex', options=None, md=None, wrap='\\[\n{}\n\\]', tag='div', **kwargs
199):
200    """Generic block formatter."""
201
202    classes = kwargs['classes']
203    id_value = kwargs['id_value']
204    attrs = kwargs['attrs']
205
206    classes.insert(0, class_name)
207
208    id_value = ' id="{}"'.format(id_value) if id_value else ''
209    classes = ' class="{}"'.format(' '.join(classes))
210    attrs = ' ' + ' '.join('{k}="{v}"'.format(k=k, v=v) for k, v in attrs.items()) if attrs else ''
211
212    return '<{}{}{}{}>{}</{}>'.format(tag, id_value, classes, attrs, wrap.format(math), tag)
213
214
215def arithmatex_fenced_format(**kwargs):
216    """Specify which type of formatter you want and the wrapping tag."""
217
218    mode = kwargs.get('mode', 'generic')
219    tag = kwargs.get('tag', 'div')
220    preview = kwargs.get('preview', False)
221
222    if mode == 'generic':
223        return partial(_fence_generic_format, tag=tag)
224    elif mode == 'mathjax':
225        return partial(_fence_mathjax_format, tag=tag, preview=preview)
226
227
228class InlineArithmatexPattern(InlineProcessor):
229    """Arithmatex inline pattern handler."""
230
231    ESCAPED_BSLASH = '{}{}{}'.format(md_util.STX, ord('\\'), md_util.ETX)
232
233    def __init__(self, pattern, config):
234        """Initialize."""
235
236        # Generic setup
237        self.generic = config.get('generic', False)
238        wrap = config.get('tex_inline_wrap', ["\\(", "\\)"])
239        self.wrap = (
240            wrap[0].replace('{', '}}').replace('}', '}}') + '{}' + wrap[1].replace('{', '}}').replace('}', '}}')
241        )
242        self.inline_tag = config.get('inline_tag', 'span')
243
244        # Default setup
245        self.preview = config.get('preview', True)
246        InlineProcessor.__init__(self, pattern)
247
248    def handleMatch(self, m, data):
249        """Handle notations and switch them to something that will be more detectable in HTML."""
250
251        # Handle escapes
252        escapes = m.group(1)
253        if not escapes:
254            escapes = m.group(4)
255        if escapes:
256            return escapes.replace('\\\\', self.ESCAPED_BSLASH), m.start(0), m.end(0)
257
258        # Handle Tex
259        math = m.group(3)
260        if not math:
261            math = m.group(6)
262
263        if self.generic:
264            return _inline_generic_format(math, wrap=self.wrap, tag=self.inline_tag), m.start(0), m.end(0)
265        else:
266            return _inline_mathjax_format(math, tag=self.inline_tag, preview=self.preview), m.start(0), m.end(0)
267
268
269class BlockArithmatexProcessor(BlockProcessor):
270    """MathJax block processor to find $$MathJax$$ content."""
271
272    def __init__(self, pattern, config, md):
273        """Initialize."""
274
275        # Generic setup
276        self.generic = config.get('generic', False)
277        wrap = config.get('tex_block_wrap', ['\\[', '\\]'])
278        self.wrap = (
279            wrap[0].replace('{', '}}').replace('}', '}}') + '{}' + wrap[1].replace('{', '}}').replace('}', '}}')
280        )
281        self.block_tag = config.get('block_tag', 'div')
282
283        # Default setup
284        self.preview = config.get('preview', False)
285
286        self.match = None
287        self.pattern = re.compile(pattern)
288
289        BlockProcessor.__init__(self, md.parser)
290
291    def test(self, parent, block):
292        """Return 'True' for future Python Markdown block compatibility."""
293
294        self.match = self.pattern.match(block) if self.pattern is not None else None
295        return self.match is not None
296
297    def mathjax_output(self, parent, math):
298        """Default MathJax output."""
299
300        grandparent = parent
301        parent = etree.SubElement(grandparent, self.block_tag, {'class': 'arithmatex'})
302        if self.preview:
303            preview = etree.SubElement(parent, 'div', {'class': 'MathJax_Preview'})
304            preview.text = md_util.AtomicString(math)
305        el = etree.SubElement(parent, 'script', {'type': 'math/tex; mode=display'})
306        el.text = md_util.AtomicString(math)
307
308    def generic_output(self, parent, math):
309        """Generic output."""
310
311        el = etree.SubElement(parent, self.block_tag, {'class': 'arithmatex'})
312        el.text = md_util.AtomicString(self.wrap.format(math))
313
314    def run(self, parent, blocks):
315        """Find and handle block content."""
316
317        blocks.pop(0)
318
319        math = self.match.group('math')
320        if not math:
321            math = self.match.group('math2')
322        if not math:
323            math = self.match.group('math3')
324
325        if self.generic:
326            self.generic_output(parent, math)
327        else:
328            self.mathjax_output(parent, math)
329
330        return True
331
332
333class ArithmatexExtension(Extension):
334    """Adds delete extension to Markdown class."""
335
336    def __init__(self, *args, **kwargs):
337        """Initialize."""
338
339        self.config = {
340            'tex_inline_wrap': [
341                ["\\(", "\\)"],
342                "Wrap inline content with the provided text ['open', 'close'] - Default: ['', '']"
343            ],
344            'tex_block_wrap': [
345                ["\\[", "\\]"],
346                "Wrap blick content with the provided text ['open', 'close'] - Default: ['', '']"
347            ],
348            "smart_dollar": [True, "Use Arithmatex's smart dollars - Default True"],
349            "block_syntax": [
350                ['dollar', 'square', 'begin'],
351                'Enable block syntax: "dollar" ($$...$$), "square" (\\[...\\]), and '
352                '"begin" (\\begin{env}...\\end{env}). - Default: ["dollar", "square", "begin"]'
353            ],
354            "inline_syntax": [
355                ['dollar', 'round'],
356                'Enable block syntax: "dollar" ($$...$$), "bracket" (\\(...\\)) '
357                ' - Default: ["dollar", "round"]'
358            ],
359            'generic': [False, "Output in a generic format for non MathJax libraries - Default: False"],
360            'preview': [
361                True,
362                "Insert a preview for scripts. - Default: False"
363            ],
364            'block_tag': ['div', "Specify wrapper tag - Default 'div'"],
365            'inline_tag': ['span', "Specify wrapper tag - Default 'span'"]
366        }
367
368        super(ArithmatexExtension, self).__init__(*args, **kwargs)
369
370    def extendMarkdown(self, md):
371        """Extend the inline and block processor objects."""
372
373        md.registerExtension(self)
374        util.escape_chars(md, ['$'])
375
376        config = self.getConfigs()
377
378        # Inline patterns
379        allowed_inline = set(config.get('inline_syntax', ['dollar', 'round']))
380        smart_dollar = config.get('smart_dollar', True)
381        inline_patterns = []
382        if 'dollar' in allowed_inline:
383            inline_patterns.append(RE_SMART_DOLLAR_INLINE if smart_dollar else RE_DOLLAR_INLINE)
384        if 'round' in allowed_inline:
385            inline_patterns.append(RE_BRACKET_INLINE)
386        if inline_patterns:
387            inline = InlineArithmatexPattern('(?:%s)' % '|'.join(inline_patterns), config)
388            md.inlinePatterns.register(inline, 'arithmatex-inline', 189.9)
389
390        # Block patterns
391        allowed_block = set(config.get('block_syntax', ['dollar', 'square', 'begin']))
392        block_pattern = []
393        if 'dollar' in allowed_block:
394            block_pattern.append(RE_DOLLAR_BLOCK)
395        if 'square' in allowed_block:
396            block_pattern.append(RE_BRACKET_BLOCK)
397        if 'begin' in allowed_block:
398            block_pattern.append(RE_TEX_BLOCK)
399        if block_pattern:
400            block = BlockArithmatexProcessor(r'(?s)^(?:%s)[ ]*$' % '|'.join(block_pattern), config, md)
401            md.parser.blockprocessors.register(block, "arithmatex-block", 79.9)
402
403
404def makeExtension(*args, **kwargs):
405    """Return extension."""
406
407    return ArithmatexExtension(*args, **kwargs)
408