diff options
Diffstat (limited to 'plugins/render_math/pelican_mathjax_markdown_extension.py')
-rwxr-xr-x | plugins/render_math/pelican_mathjax_markdown_extension.py | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/plugins/render_math/pelican_mathjax_markdown_extension.py b/plugins/render_math/pelican_mathjax_markdown_extension.py new file mode 100755 index 0000000..e739363 --- /dev/null +++ b/plugins/render_math/pelican_mathjax_markdown_extension.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +""" +Pelican Mathjax Markdown Extension +================================== +An extension for the Python Markdown module that enables +the Pelican python blog to process mathjax. This extension +gives Pelican the ability to use Mathjax as a "first class +citizen" of the blog +""" + +import markdown + +from markdown.util import etree +from markdown.util import AtomicString + +class PelicanMathJaxPattern(markdown.inlinepatterns.Pattern): + """Inline markdown processing that matches mathjax""" + + def __init__(self, pelican_mathjax_extension, tag, pattern): + super(PelicanMathJaxPattern,self).__init__(pattern) + self.math_tag_class = pelican_mathjax_extension.getConfig('math_tag_class') + self.pelican_mathjax_extension = pelican_mathjax_extension + self.tag = tag + + def handleMatch(self, m): + node = markdown.util.etree.Element(self.tag) + node.set('class', self.math_tag_class) + + prefix = '\\(' if m.group('prefix') == '$' else m.group('prefix') + suffix = '\\)' if m.group('suffix') == '$' else m.group('suffix') + node.text = markdown.util.AtomicString(prefix + m.group('math') + suffix) + + # If mathjax was successfully matched, then JavaScript needs to be added + # for rendering. The boolean below indicates this + self.pelican_mathjax_extension.mathjax_needed = True + return node + +class PelicanMathJaxCorrectDisplayMath(markdown.treeprocessors.Treeprocessor): + """Corrects invalid html that results from a <div> being put inside + a <p> for displayed math""" + + def __init__(self, pelican_mathjax_extension): + self.pelican_mathjax_extension = pelican_mathjax_extension + + def correct_html(self, root, children, div_math, insert_idx, text): + """Separates out <div class="math"> from the parent tag <p>. Anything + in between is put into its own parent tag of <p>""" + + current_idx = 0 + + for idx in div_math: + el = markdown.util.etree.Element('p') + el.text = text + el.extend(children[current_idx:idx]) + + # Test to ensure that empty <p> is not inserted + if len(el) != 0 or (el.text and not el.text.isspace()): + root.insert(insert_idx, el) + insert_idx += 1 + + text = children[idx].tail + children[idx].tail = None + root.insert(insert_idx, children[idx]) + insert_idx += 1 + current_idx = idx+1 + + el = markdown.util.etree.Element('p') + el.text = text + el.extend(children[current_idx:]) + + if len(el) != 0 or (el.text and not el.text.isspace()): + root.insert(insert_idx, el) + + def run(self, root): + """Searches for <div class="math"> that are children in <p> tags and corrects + the invalid HTML that results""" + + math_tag_class = self.pelican_mathjax_extension.getConfig('math_tag_class') + + for parent in root: + div_math = [] + children = list(parent) + + for div in parent.findall('div'): + if div.get('class') == math_tag_class: + div_math.append(children.index(div)) + + # Do not process further if no displayed math has been found + if not div_math: + continue + + insert_idx = list(root).index(parent) + self.correct_html(root, children, div_math, insert_idx, parent.text) + root.remove(parent) # Parent must be removed last for correct insertion index + + return root + +class PelicanMathJaxAddJavaScript(markdown.treeprocessors.Treeprocessor): + """Tree Processor for adding Mathjax JavaScript to the blog""" + + def __init__(self, pelican_mathjax_extension): + self.pelican_mathjax_extension = pelican_mathjax_extension + + def run(self, root): + # If no mathjax was present, then exit + if (not self.pelican_mathjax_extension.mathjax_needed): + return root + + # Add the mathjax script to the html document + mathjax_script = etree.Element('script') + mathjax_script.set('type','text/javascript') + mathjax_script.text = AtomicString(self.pelican_mathjax_extension.getConfig('mathjax_script')) + root.append(mathjax_script) + + # Reset the boolean switch to false so that script is only added + # to other pages if needed + self.pelican_mathjax_extension.mathjax_needed = False + return root + +class PelicanMathJaxExtension(markdown.Extension): + """A markdown extension enabling mathjax processing in Markdown for Pelican""" + def __init__(self, config): + + try: + # Needed for markdown versions >= 2.5 + self.config['mathjax_script'] = ['', 'Mathjax JavaScript script'] + self.config['math_tag_class'] = ['math', 'The class of the tag in which mathematics is wrapped'] + self.config['auto_insert'] = [True, 'Determines if mathjax script is automatically inserted into content'] + super(PelicanMathJaxExtension,self).__init__(**config) + except AttributeError: + # Markdown versions < 2.5 + config['mathjax_script'] = [config['mathjax_script'], 'Mathjax JavaScript script'] + config['math_tag_class'] = [config['math_tag_class'], 'The class of the tag in which mathematic is wrapped'] + config['auto_insert'] = [config['auto_insert'], 'Determines if mathjax script is automatically inserted into content'] + super(PelicanMathJaxExtension,self).__init__(config) + + # Used as a flag to determine if javascript + # needs to be injected into a document + self.mathjax_needed = False + + def extendMarkdown(self, md): + # Regex to detect mathjax + mathjax_inline_regex = r'(?P<prefix>\$)(?P<math>.+?)(?P<suffix>(?<!\s)\2)' + mathjax_display_regex = r'(?P<prefix>\$\$|\\begin\{(.+?)\})(?P<math>.+?)(?P<suffix>\2|\\end\{\3\})' + + # Process mathjax before escapes are processed since escape processing will + # intefer with mathjax. The order in which the displayed and inlined math + # is registered below matters: we should have higher priority than 'escape' which has 180 + md.inlinePatterns.register(PelicanMathJaxPattern(self, 'div', mathjax_display_regex), 'mathjax_displayed', 186) + md.inlinePatterns.register(PelicanMathJaxPattern(self, 'span', mathjax_inline_regex), 'mathjax_inlined', 185) + + # Correct the invalid HTML that results from teh displayed math (<div> tag within a <p> tag) + md.treeprocessors.register(PelicanMathJaxCorrectDisplayMath(self), 'mathjax_correctdisplayedmath', 15) + + # If necessary, add the JavaScript Mathjax library to the document. This must + # be last in the ordered dict (hence it is given the position '_end') + if self.getConfig('auto_insert'): + md.treeprocessors.register(PelicanMathJaxAddJavaScript(self), 'mathjax_addjavascript', 0) |