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('&', '&') 67 txt = txt.replace('<', '<') 68 txt = txt.replace('>', '>') 69 txt = txt.replace('"', '"') 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