Browse Source

Render math update

Barry Steyn 10 years ago
parent
commit
b2fb21704d

+ 50 - 110
render_math/Readme.md

@@ -1,26 +1,14 @@
 Math Render Plugin For Pelican
 ==============================
 This plugin gives pelican the ability to render mathematics. It accomplishes
-this by using the [MathJax](http://www.mathjax.org/) javascript engine. Both
-[LaTex](http://en.wikipedia.org/wiki/LaTeX) and [MathML](http://en.wikipedia.org/wiki/MathML) 
-can be rendered within the content.
+this by using the [MathJax](http://www.mathjax.org/) javascript engine.
 
-The plugin also ensures that pelican and recognized math "play" nicely together, by
-ensuring [Typogrify](https://github.com/mintchaos/typogrify) does not alter math content
-and summaries that get cut off are repaired.
+The plugin also ensures that Typogrify and recognized math "play" nicely together, by
+ensuring [Typogrify](https://github.com/mintchaos/typogrify) does not alter math content.
+It requires at a minimum Pelican version *3.5* and Typogrify version *2.0.7* to work.
+If these versions are not available, Typogrify will be disabled for the entire site.
 
-Recognized math in the context of this plugin is either LaTex or MathML as described below.
-
-### LaTex
-Anything between `$`...`$` (inline math) and `$$`..`$$` (displayed math) will be recognized as
-LaTex. In addition, anything the `\begin` and `\end` LaTex macros will also be 
-recognized as LaTex. For example, `\begin{equation}`...`\end{equation}` would be used to 
-render math equations with numbering.
-
-Within recognized LaTex as described above, any supported LaTex macro can be used.
-
-### MathML
-Anything between `<math>` and `</math>` tags will be recognized as MathML
+Both Markdown and reStructuredText is supported.
 
 Installation
 ------------
@@ -37,45 +25,16 @@ In the past, using [Typgogrify](https://github.com/mintchaos/typogrify) would al
 in math that could not be rendered by MathJax. The only option was to ensure
 that Typogrify was disabled in the settings.
 
-The problem has been recitified in this plugin, but it requires [Typogrify version 2.04](https://pypi.python.org/pypi/typogrify)
-(or higher). If this version of Typogrify is not present, the plugin will inform that an incorrect
-version of Typogrify is not present and disable Typogrify for the entire site
+The problem has been recitified in this plugin, but it requires [Typogrify version 2.0.7](https://pypi.python.org/pypi/typogrify)
+(or higher) and Pelican version 3.5 or higher. If these versions are not present, the plugin will disable
+Typogrify for the entire site
 
 Usage
 -----
-### Backward Compatibility
-This plugin is backward compatible in the sense that it
-will render previous setups correctly. This is because those
-settings and metadata information is ignored by this version. Therefore
-you can remove them to neaten up your site
-
 ### Templates
 No alteration is needed to a template for this plugin to work. Just install
 the plugin and start writing your Math. 
 
-If on the other hand, you are template designer and want total control
-over the MathJax JavaScript, you can set the `auto_insert` setting to 
-`False` which will cause no MathJax JavaScript to be added to the content.
-
-If you choose this option, you should really know what you are doing. Therefore
-only do this if you are designing your template. There is no real advantage to
-to letting template logic handle the insertion of the MathJax JavaScript other
-than it being slightly neater.
-
-By setting `auto_insert` to `False`, metadata with `key` value of `mathjax`
-will be present in all pages and articles where MathJax should be present.
-The template designer can detect this and then use the `MATHJAXSCRIPT` setting
-which will contain the user specified MathJax script to insert into the content.
-
-For example, this code could be used:
-```
-{% if not MATH['auto_insert'] %}
-    {% if page and page.mathjax or article and article.mathjax %}
-        {{ MATHJAXSCRIPT }}
-    {% endif %}
-{% endif %}
-```
-
 ### Settings
 Certain MathJax rendering options can be set. These options 
 are in a dictionary variable called `MATH` in the pelican
@@ -83,18 +42,6 @@ settings file.
 
 The dictionary can be set with the following keys:
 
- * `auto_insert`: controls whether plugin should automatically insert
-MathJax JavaScript in content that has Math. It is only recommended
-to set this to False if you are a template designer and you want
-extra control over where the MathJax JavaScript is renderd. **Default Value**:
-True
- * `wrap_latex`: controls the tags that LaTex math is wrapped with inside the resulting
-html. For example, setting `wrap_latex` to `mathjax` would wrap all LaTex math inside
-`<mathjax>...</mathjax>` tags. If typogrify is set to True, then math needs
-to be wrapped in tags and `wrap_latex` will therefore default to `mathjax` if not
-set. `wrap_latex` cannot be set to `'math'` because this tag is reserved for 
-mathml notation. **Default Value**: None unless Typogrify is enabled in which case, 
-it defaults to `mathjax`
  * `align`: controls how displayed math will be aligned. Can be set to either
 `left`, `right` or `center`. **Default Value**: `center`.
  * `indent`: if `align` not set to `center`, then this controls the indent
@@ -107,67 +54,60 @@ sequences. **Default Value**: True
 rendering LaTex. If set to `Tex`, then the TeX code is used as the preview 
 (which will be visible until it is processed by MathJax). **Default Value**: `Tex`
  * `color`: controls the color of the mathjax rendered font. **Default Value**: `black`
- * `ssl`: specifies if ssl should be used to load MathJax engine. Can be set to one
-of three things
-  * `auto`: **Default Value** will automatically determine what protodol to use 
-based on current protocol of the site. 
-  * `force`: will force ssl to be used.
-  * `off`: will ensure that ssl is not used
 
 For example, in settings.py, the following would make math render in blue and
 displaymath align to the left:
 
     MATH = {'color':'blue','align':left}
 
-LaTex Examples
---------------
-###Inline
-LaTex between `$`..`$`, for example, `$`x^2`$`, will be rendered inline
-with respect to the current html block.
+#### Resulting HTML
+Inlined math is wrapped in `span` tags, while displayed math is wrapped in `div` tags.
+These tags will have a class attribute that is set to `math` which 
+can be used by template designers to alter the display of the math.
+
+Markdown
+--------
+This plugin implements a custom extension for markdown resulting in math
+being a "first class citizen" for Pelican. 
+
+###Inlined Math
+Math between `$`..`$`, for example, `$`x^2`$`, will be rendered inline
+with respect to the current html block. Note: To use inline math, there
+must *not* be any whitespace before the ending `$`. So for example:
+
+ * **Relevant inline math**: `$e=mc^2$`
+ * **Will not render as inline math**: `$40 vs $50`
 
 ###Displayed Math
-LaTex between `$$`..`$$`, for example, `$$`x^2`$$`, will be rendered centered in a
+Math between `$$`..`$$`, for example, `$$`x^2`$$`, will be rendered centered in a
 new paragraph.
 
-###Equations
-LaTex between `\begin` and `\end`, for example, `begin{equation}` x^2 `\end{equation}`,
-will be rendered centered in a new paragraph with a right justified equation number
-at the top of the paragraph. This equation number can be referenced in the document.
+###Latex Macros
+Latex macros are supported, and are automatically treated like displayed math 
+(i.e. it is wrapped in `div` tag). For example, `begin{equation}` x^2 `\end{equation}`,
+will be rendered in its own block with a right justified equation number
+at the top of the block. This equation number can be referenced in the document.
 To do this, use a `label` inside of the equation format and then refer to that label
 using `ref`. For example: `begin{equation}` `\label{eq}` X^2 `\end{equation}`. Now
 refer to that equation number by `$`\ref{eq}`$`.
 
-MathML Examples
----------------
-The following will render the Quadratic formula:
+reStructuredText
+----------------
+If there is math detected in reStructuredText document, the plugin will automatically
+set the [math_output](http://docutils.sourceforge.net/docs/user/config.html#math-output) configuration setting to `MathJax`.
+
+###Inlined Math
+Inlined math needs to use the [math role](http://docutils.sourceforge.net/docs/ref/rst/roles.html#math):
+
+```
+The area of a circle is :math:`A_\text{c} = (\pi/4) d^2`.
+```
+
+###Displayed Math
+Displayed math uses the [math block](http://docutils.sourceforge.net/docs/ref/rst/directives.html#math):
+
 ```
-<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 
-  <mrow>
-    <mi>x</mi>
-    <mo>=</mo>
-    <mfrac>
-      <mrow>
-        <mo>&#x2212;</mo>
-        <mi>b</mi>
-        <mo>&#xB1;</mo>
-        <msqrt>
-          <mrow>
-            <msup>
-              <mi>b</mi>
-              <mn>2</mn>
-            </msup>
-            <mo>&#x2212;</mo>
-            <mn>4</mn>
-            <mi>a</mi>
-            <mi>c</mi>
-          </mrow>
-        </msqrt>
-      </mrow>
-      <mrow>
-        <mn>2</mn>
-        <mi>a</mi>
-      </mrow>
-    </mfrac>
-  </mrow>
-</math>
+.. math::
+
+  α_t(i) = P(O_1, O_2, … O_t, q_t = S_i λ)
 ```

+ 113 - 306
render_math/math.py

@@ -2,8 +2,17 @@
 """
 Math Render Plugin for Pelican
 ==============================
-This plugin allows your site to render Math. It supports both LaTeX and MathML
-using the MathJax JavaScript engine.
+This plugin allows your site to render Math. It uses
+the MathJax JavaScript engine.
+
+For markdown, the plugin works by creating a Markdown 
+extension which is used during the markdown compilation stage. 
+Math therefore gets treated like a "first class citizen" in Pelican
+
+For reStructuredText, the plugin instructs the rst engine
+to output Mathjax for for math.
+
+The mathjax script is automatically inserted into the HTML.
 
 Typogrify Compatibility
 -----------------------
@@ -19,176 +28,15 @@ See README for more details.
 """
 
 import os
-import re
+import sys
 
 from pelican import signals
-from pelican import contents
-
-
-# Global Variables
-_TYPOGRIFY = None  # if Typogrify is enabled, this is set to the typogrify.filter function
-_WRAP_LATEX = None  # the tag to wrap LaTeX math in (needed to play nicely with Typogrify or for template designers)
-_MATH_REGEX = re.compile(r'(\$\$|\$|\\begin\{(.+?)\}|<(math)(?:\s.*?)?>).*?(\1|\\end\{\2\}|</\3>)', re.DOTALL | re.IGNORECASE)  # used to detect math
-_MATH_SUMMARY_REGEX = None  # used to match math in summary
-_MATH_INCOMPLETE_TAG_REGEX = None  # used to match math that has been cut off in summary
-_MATHJAX_SETTINGS = {}  # settings that can be specified by the user, used to control mathjax script settings
-with open (os.path.dirname(os.path.realpath(__file__))+'/mathjax_script.txt', 'r') as mathjax_script:  # Read the mathjax javascript from file
-    _MATHJAX_SCRIPT=mathjax_script.read()
-
-
-# Python standard library for binary search, namely bisect is cool but I need
-# specific business logic to evaluate my search predicate, so I am using my
-# own version
-def binary_search(match_tuple, ignore_within):
-    """Determines if t is within tupleList. Using the fact that tupleList is
-    ordered, binary search can be performed which is O(logn)
-    """
-
-    ignore = False
-    if ignore_within == []:
-        return False
-
-    lo = 0
-    hi = len(ignore_within)-1
-
-    # Find first value in array where predicate is False
-    # predicate function: tupleList[mid][0] < t[index]
-    while lo < hi:
-        mid = lo + (hi-lo+1)//2
-        if ignore_within[mid][0] < match_tuple[0]:
-            lo = mid
-        else:
-            hi = mid-1
-
-    if lo >= 0 and lo <= len(ignore_within)-1:
-        ignore = (ignore_within[lo][0] <= match_tuple[0] and ignore_within[lo][1] >= match_tuple[1])
-
-    return ignore
-
-
-def ignore_content(content):
-    """Creates a list of match span tuples for which content should be ignored
-    e.g. <pre> and <code> tags
-    """
-    ignore_within = []
-
-    # used to detect all <pre> and <code> tags. NOTE: Alter this regex should
-    # additional tags need to be ignored
-    ignore_regex = re.compile(r'<(pre|code)(?:\s.*?)?>.*?</(\1)>', re.DOTALL | re.IGNORECASE)
-
-    for match in ignore_regex.finditer(content):
-        ignore_within.append(match.span())
-
-    return ignore_within
-
-
-def wrap_math(content, ignore_within):
-    """Wraps math in user specified tags.
-
-    This is needed for Typogrify to play nicely with math but it can also be
-    styled by template providers
-    """
-
-    wrap_math.found_math = False
-
-    def math_tag_wrap(match):
-        """function for use in re.sub"""
-
-        # determine if the tags are within <pre> and <code> blocks
-        ignore = binary_search(match.span(1), ignore_within) or binary_search(match.span(4), ignore_within)
-
-        if ignore or match.group(3) == 'math':
-            if match.group(3) == 'math':
-                # Will detect mml, but not wrap anything around it
-                wrap_math.found_math = True
-
-            return match.group(0)
-        else:
-            wrap_math.found_math = True
-            return '<%s>%s</%s>' % (_WRAP_LATEX, match.group(0), _WRAP_LATEX)
-
-    return (_MATH_REGEX.sub(math_tag_wrap, content), wrap_math.found_math)
-
-
-def process_summary(instance, ignore_within):
-    """Summaries need special care. If Latex is cut off, it must be restored.
-
-    In addition, the mathjax script must be included if necessary thereby
-    making it independent to the template
-    """
-
-    process_summary.altered_summary = False
-    insert_mathjax = False
-    end_tag = '</%s>' % _WRAP_LATEX if _WRAP_LATEX is not None else ''
-
-    # use content's _get_summary method to obtain summary
-    summary = instance._get_summary()
-
-    # Determine if there is any math in the summary which are not within the
-    # ignore_within tags
-    math_item = None
-    for math_item in _MATH_SUMMARY_REGEX.finditer(summary):
-        ignore = binary_search(math_item.span(2), ignore_within)
-        if '...' not in math_item.group(5):
-            ignore = ignore or binary_search(math_item.span(5), ignore_within)
-        else:
-            ignore = ignore or binary_search(math_item.span(6), ignore_within)
-
-        if ignore:
-            math_item = None # In <code> or <pre> tags, so ignore
-        else:
-            insert_mathjax = True
-
-    # Repair the math if it was cut off math_item will be the final math
-    # code  matched that is not within <pre> or <code> tags
-    if math_item and '...' in math_item.group(5):
-        if math_item.group(3) is not None:
-            end = r'\end{%s}' % math_item.group(3)
-        elif math_item.group(4) is not None:
-            end = r'</math>'
-        elif math_item.group(2) is not None:
-            end = math_item.group(2)
-
-        search_regex = r'%s(%s.*?%s)' % (re.escape(instance._content[0:math_item.start(1)]), re.escape(math_item.group(1)), re.escape(end))
-        math_match = re.search(search_regex, instance._content, re.DOTALL | re.IGNORECASE)
-
-        if math_match:
-            new_summary = summary.replace(math_item.group(0), math_match.group(1)+'%s ...' % end_tag)
-
-            if new_summary != summary:
-                if _MATHJAX_SETTINGS['auto_insert']:
-                    return new_summary+_MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
-                else:
-                    instance.mathjax = True
-                    return new_summary
-
-    def incomplete_end_latex_tag(match):
-        """function for use in re.sub"""
-        if binary_search(match.span(3), ignore_within):
-            return match.group(0)
-
-        process_summary.altered_summary = True
-        return match.group(1) + match.group(4)
-
-    # check for partial math tags at end. These must be removed
-    summary = _MATH_INCOMPLETE_TAG_REGEX.sub(incomplete_end_latex_tag, summary)
-
-    if process_summary.altered_summary or insert_mathjax:
-        if insert_mathjax:
-            if _MATHJAX_SETTINGS['auto_insert']:
-                summary+= _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
-            else:
-                instance.mathjax = True
-
-        return summary
+from . pelican_mathjax_markdown_extension import PelicanMathJaxExtension
 
-    return None  # Making it explicit that summary was not altered
-
-
-def process_settings(settings):
+def process_settings(pelicanobj):
     """Sets user specified MathJax settings (see README for more details)"""
 
-    global _MATHJAX_SETTINGS
+    mathjax_settings = {}
 
     # NOTE TO FUTURE DEVELOPERS: Look at the README and what is happening in
     # this function if any additional changes to the mathjax settings need to
@@ -196,184 +44,143 @@ def process_settings(settings):
     # will be used for
 
     # Default settings
-    _MATHJAX_SETTINGS['align'] = 'center'  # controls alignment of of displayed equations (values can be: left, right, center)
-    _MATHJAX_SETTINGS['indent'] = '0em'  # if above is not set to 'center', then this setting acts as an indent
-    _MATHJAX_SETTINGS['show_menu'] = 'true'  # controls whether to attach mathjax contextual menu
-    _MATHJAX_SETTINGS['process_escapes'] = 'true'  # controls whether escapes are processed
-    _MATHJAX_SETTINGS['latex_preview'] = 'TeX'  # controls what user sees while waiting for LaTex to render
-    _MATHJAX_SETTINGS['color'] = 'black'  # controls color math is rendered in
-
-    # Source for MathJax: default (below) is to automatically determine what protocol to use
-    _MATHJAX_SETTINGS['source'] = """'https:' == document.location.protocol
-                ? 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
-                : 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"""
-
-    # This next setting controls whether the mathjax script should be automatically
-    # inserted into the content. The mathjax script will not be inserted into
-    # the content if no math is detected. For summaries that are present in the
-    # index listings, mathjax script will also be automatically inserted.
-    # Setting this value to false means the template must be altered if this
-    # plugin is to work, and so it is only recommended for the template
-    # designer who wants maximum control.
-    _MATHJAX_SETTINGS['auto_insert'] = True  # controls whether mathjax script is automatically inserted into the content
+    mathjax_settings['align'] = 'center'  # controls alignment of of displayed equations (values can be: left, right, center)
+    mathjax_settings['indent'] = '0em'  # if above is not set to 'center', then this setting acts as an indent
+    mathjax_settings['show_menu'] = 'true'  # controls whether to attach mathjax contextual menu
+    mathjax_settings['process_escapes'] = 'true'  # controls whether escapes are processed
+    mathjax_settings['latex_preview'] = 'TeX'  # controls what user sees while waiting for LaTex to render
+    mathjax_settings['color'] = 'black'  # controls color math is rendered in
+
+    # Source for MathJax: Works boths for http and https (see http://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn)
+    mathjax_settings['source'] = "'//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"
+    
+    # Get the user specified settings
+    try:
+        settings = pelicanobj.settings['MATH_JAX']
+    except:
+        settings = None
 
+    # If no settings have been specified, then return the defaults
     if not isinstance(settings, dict):
-        return
+        return mathjax_settings
 
     # The following mathjax settings can be set via the settings dictionary
-    # Iterate over dictionary in a way that is compatible with both version 2
-    # and 3 of python
     for key, value in ((key, settings[key]) for key in settings):
-        if key == 'auto_insert' and isinstance(value, bool):
-            _MATHJAX_SETTINGS[key] = value
-
+        # Iterate over dictionary in a way that is compatible with both version 2
+        # and 3 of python
         if key == 'align' and isinstance(value, str):
             if value == 'left' or value == 'right' or value == 'center':
-                _MATHJAX_SETTINGS[key] = value
+                mathjax_settings[key] = value
             else:
-                _MATHJAX_SETTINGS[key] = 'center'
+                mathjax_settings[key] = 'center'
 
         if key == 'indent':
-            _MATHJAX_SETTINGS[key] = value
+            mathjax_settings[key] = value
 
         if key == 'show_menu' and isinstance(value, bool):
-            _MATHJAX_SETTINGS[key] = 'true' if value else 'false'
+            mathjax_settings[key] = 'true' if value else 'false'
 
         if key == 'process_escapes' and isinstance(value, bool):
-            _MATHJAX_SETTINGS[key] = 'true' if value else 'false'
+            mathjax_settings[key] = 'true' if value else 'false'
 
         if key == 'latex_preview' and isinstance(value, str):
-            _MATHJAX_SETTINGS[key] = value
+            mathjax_settings[key] = value
 
         if key == 'color' and isinstance(value, str):
-            _MATHJAX_SETTINGS[key] = value
+            mathjax_settings[key] = value
 
-        if key == 'ssl' and isinstance(value, str):
-            if value == 'off':
-                _MATHJAX_SETTINGS['source'] = "'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"
+    return mathjax_settings
 
-            if value == 'force':
-                _MATHJAX_SETTINGS['source'] = "'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"
+def configure_typogrify(pelicanobj, mathjax_settings):
+    """Instructs Typogrify to ignore math tags - which allows Typogfrify
+    to play nicely with math related content"""
+    
+    # If Typogrify is not being used, then just exit
+    if not pelicanobj.settings.get('TYPOGRIFY', False):
+        return
 
+    try:
+        import typogrify
+        from distutils.version import LooseVersion
+        
+        if LooseVersion(typogrify.__version__) < LooseVersion('2.0.7'):
+            raise TypeError('Incorrect version of Typogrify')
 
-def process_content(instance):
-    """Processes content, with logic to ensure that Typogrify does not clash
-    with math.
+        from typogrify.filters import typogrify
 
-    In addition, mathjax script is inserted at the end of the content thereby
-    making it independent of the template
-    """
+        # At this point, we are happy to use Typogrify, meaning
+        # it is installed and it is a recent enough version
+        # that can be used to ignore all math
+        # Instantiate markdown extension and append it to the current extensions
+        pelicanobj.settings['TYPOGRIFY_IGNORE_TAGS'].extend(['.math', 'script'])  # ignore math class and script
 
-    if not instance._content:
-        return
+    except (ImportError, TypeError, KeyError) as e:
+        pelicanobj.settings['TYPOGRIFY'] = False  # disable Typogrify
 
-    ignore_within = ignore_content(instance._content)
-
-    if _WRAP_LATEX:
-        instance._content, math = wrap_math(instance._content, ignore_within)
-    else:
-        math = True if _MATH_REGEX.search(instance._content) else False
-
-    # The user initially set Typogrify to be True, but since it would clash
-    # with math, we set it to False. This means that the default reader will
-    # not call Typogrify, so it is called here, where we are able to control
-    # logic for it ignore math if necessary
-    if _TYPOGRIFY:
-        # Tell Typogrify to ignore the tags that math has been wrapped in
-        # also, Typogrify must always ignore mml (math) tags
-        ignore_tags = [_WRAP_LATEX,'math'] if _WRAP_LATEX else ['math']
-
-        # Exact copy of the logic as found in the default reader
-        instance._content = _TYPOGRIFY(instance._content, ignore_tags)
-        instance.metadata['title'] = _TYPOGRIFY(instance.metadata['title'], ignore_tags)
-
-    if math:
-        if _MATHJAX_SETTINGS['auto_insert']:
-            # Mathjax script added to content automatically. Now it
-            # does not need to be explicitly added to the template
-            instance._content += _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
-        else:
-            # Place the burden on ensuring mathjax script is available to
-            # browser on the template designer (see README for more details)
-            instance.mathjax = True
-
-        # The summary needs special care because math math cannot just be cut
-        # off
-        summary = process_summary(instance, ignore_within)
-        if summary is not None:
-            instance._summary = summary
+        if isinstance(e, ImportError):
+            print("\nTypogrify is not installed, so it is being ignored.\nIf you want to use it, please install via: pip install typogrify\n")
 
+        if isinstance(e, TypeError):
+            print("\nA more recent version of Typogrify is needed for the render_math module.\nPlease upgrade Typogrify to the latest version (anything equal or above version 2.0.7 is okay).\nTypogrify will be turned off due to this reason.\n")
 
-def pelican_init(pelicanobj):
-    """Intialializes certain global variables and sets typogogrify setting to
-    False should it be set to True.
-    """
+        if isinstance(e, KeyError):
+            print("\nA more recent version of Pelican is needed for Typogrify to work with render_math.\nPlease upgrade Pelican to the latest version or clone it directly from the master GitHub branch\nTypogrify will be turned off due to this reason\n")
+
+def process_mathjax_script(mathjax_settings):
+    """Load the mathjax script template from file, and render with the settings"""
 
-    global _TYPOGRIFY
-    global _WRAP_LATEX
-    global _MATH_SUMMARY_REGEX
-    global _MATH_INCOMPLETE_TAG_REGEX
+    # Read the mathjax javascript template from file
+    with open (os.path.dirname(os.path.realpath(__file__))+'/mathjax_script_template', 'r') as mathjax_script_template:
+        mathjax_template = mathjax_script_template.read()
 
+    return mathjax_template.format(**mathjax_settings)
+
+def mathjax_for_markdown(pelicanobj, mathjax_settings):
+    """Instantiates a customized markdown extension for handling mathjax
+    related content"""
+
+    # Create the configuration for the markdown template
+    config = {}
+    config['mathjax_script'] = [process_mathjax_script(mathjax_settings),'Mathjax JavaScript script']
+    config['math_tag_class'] = ['math', 'The class of the tag in which mathematics is wrapped']
+    
+    # Instantiate markdown extension and append it to the current extensions
     try:
-        settings = pelicanobj.settings['MATH']
+        pelicanobj.settings['MD_EXTENSIONS'].append(PelicanMathJaxExtension(config))
     except:
-        settings = None
+        print("\nError - the pelican mathjax markdown extension was not configured, so mathjax will not be work.\nThe error message was as follows - [%s]" % sys.exc_info()[0])
 
-    process_settings(settings)
+def mathjax_for_rst(pelicanobj, mathjax_settings):
+    pelicanobj.settings['DOCUTILS_SETTINGS'] = {'math_output': 'MathJax'}
+    rst_add_mathjax.mathjax_script = process_mathjax_script(mathjax_settings)
 
-    # Allows MathJax script to be accessed from template should it be needed
-    pelicanobj.settings['MATHJAXSCRIPT'] = _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
+def pelican_init(pelicanobj):
+    """Loads the mathjax script according to the settings. Instantiate the Python
+    markdown extension, passing in the mathjax script as config parameter
+    """
 
-    # If Typogrify set to True, then we need to handle it manually so it does
-    # not conflict with LaTeX
-    try:
-        if pelicanobj.settings['TYPOGRIFY'] is True:
-            pelicanobj.settings['TYPOGRIFY'] = False
-            try:
-                from typogrify.filters import typogrify
-
-                # Determine if this is the correct version of Typogrify to use
-                import inspect
-                typogrify_args = inspect.getargspec(typogrify).args
-                if len(typogrify_args) < 2 or 'ignore_tags' not in typogrify_args:
-                    raise TypeError('Incorrect version of Typogrify')
-
-                # At this point, we are happy to use Typogrify, meaning
-                # it is installed and it is a recent enough version
-                # that can be used to ignore all math
-                _TYPOGRIFY = typogrify
-                _WRAP_LATEX = 'mathjax' # default to wrap mathjax content inside of
-            except ImportError:
-                print("\nTypogrify is not installed, so it is being ignored.\nIf you want to use it, please install via: pip install typogrify\n")
-            except TypeError:
-                print("\nA more recent version of Typogrify is needed for the render_math module.\nPlease upgrade Typogrify to the latest version (anything above version 2.04 is okay).\nTypogrify will be turned off due to this reason.\n")
-    except KeyError:
-        pass
-
-    # Set _WRAP_LATEX to the settings tag if defined. The idea behind this is
-    # to give template designers control over how math would be rendered
-    try:
-        if pelicanobj.settings['MATH']['wrap_latex']:
-            _WRAP_LATEX = pelicanobj.settings['MATH']['wrap_latex']
-    except (KeyError, TypeError):
-        pass
+    # Process settings
+    mathjax_settings = process_settings(pelicanobj)
 
-    # regular expressions that depend on _WRAP_LATEX are set here
-    tag_start= r'<%s>' % _WRAP_LATEX if not _WRAP_LATEX is None else ''
-    tag_end = r'</%s>' % _WRAP_LATEX if not _WRAP_LATEX is None else ''
-    math_summary_regex = r'((\$\$|\$|\\begin\{(.+?)\}|<(math)(?:\s.*?)?>).+?)(\2|\\end\{\3\}|</\4>|\s?\.\.\.)(%s|</\4>)?' % tag_end
+    # Configure Typogrify
+    configure_typogrify(pelicanobj, mathjax_settings)
 
-    # NOTE: The logic in _get_summary will handle <math> correctly because it
-    # is perceived as an html tag. Therefore we are only interested in handling
-    # non mml (i.e. LaTex)
-    incomplete_end_latex_tag = r'(.*)(%s)(\\\S*?|\$)\s*?(\s?\.\.\.)(%s)?$' % (tag_start, tag_end)
+    # Configure Mathjax For Markdown
+    mathjax_for_markdown(pelicanobj, mathjax_settings)
 
-    _MATH_SUMMARY_REGEX = re.compile(math_summary_regex, re.DOTALL | re.IGNORECASE)
-    _MATH_INCOMPLETE_TAG_REGEX = re.compile(incomplete_end_latex_tag, re.DOTALL | re.IGNORECASE)
+    # Configure Mathjax For RST
+    mathjax_for_rst(pelicanobj, mathjax_settings)
 
+def rst_add_mathjax(instance):
+    _, ext = os.path.splitext(os.path.basename(instance.source_path))
+    if ext != '.rst':
+        return
+
+    # If math class is present in text, add the javascript
+    if 'class="math"' in instance._content:
+        instance._content += "<script type='text/javascript'>%s</script>" % rst_add_mathjax.mathjax_script
 
 def register():
     """Plugin registration"""
-
     signals.initialized.connect(pelican_init)
-    signals.content_object_init.connect(process_content)
+    signals.content_object_init.connect(rst_add_mathjax)

+ 0 - 28
render_math/mathjax_script.txt

@@ -1,28 +0,0 @@
-<script type= "text/javascript">
-    if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {{
-        var mathjaxscript = document.createElement('script');
-        mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
-        mathjaxscript.type = 'text/javascript';
-        mathjaxscript.src = {source};
-        mathjaxscript[(window.opera ? "innerHTML" : "text")] =
-            "MathJax.Hub.Config({{" +
-            "    config: ['MMLorHTML.js']," +
-            "    TeX: {{ extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: {{ autoNumber: 'AMS' }} }}," +
-            "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
-            "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
-            "    displayAlign: '{align}'," +
-            "    displayIndent: '{indent}'," +
-            "    showMathMenu: {show_menu}," +
-            "    tex2jax: {{ " +
-            "        inlineMath: [ ['$','$'] ], " +
-            "        displayMath: [ ['$$','$$'] ]," +
-            "        processEscapes: {process_escapes}," +
-            "        preview: '{latex_preview}'," +
-            "    }}, " +
-            "    'HTML-CSS': {{ " +
-            "        styles: {{ '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {{color: '{color} ! important'}} }}" +
-            "    }} " +
-            "}}); ";
-        (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
-    }}
-</script>

+ 26 - 0
render_math/mathjax_script_template

@@ -0,0 +1,26 @@
+if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {{
+    var mathjaxscript = document.createElement('script');
+    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
+    mathjaxscript.type = 'text/javascript';
+    mathjaxscript.src = {source};
+    mathjaxscript[(window.opera ? "innerHTML" : "text")] =
+        "MathJax.Hub.Config({{" +
+        "    config: ['MMLorHTML.js']," +
+        "    TeX: {{ extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: {{ autoNumber: 'AMS' }} }}," +
+        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
+        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
+        "    displayAlign: '{align}'," +
+        "    displayIndent: '{indent}'," +
+        "    showMathMenu: {show_menu}," +
+        "    tex2jax: {{ " +
+        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
+        "        displayMath: [ ['$$','$$'] ]," +
+        "        processEscapes: {process_escapes}," +
+        "        preview: '{latex_preview}'," +
+        "    }}, " +
+        "    'HTML-CSS': {{ " +
+        "        styles: {{ '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {{color: '{color} ! important'}} }}" +
+        "    }} " +
+        "}}); ";
+    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
+}}

+ 79 - 0
render_math/pelican_mathjax_markdown_extension.py

@@ -0,0 +1,79 @@
+# -*- 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):
+    """Pattern for matching 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 PelicanMathJaxTreeProcessor(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
+
+        # 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
+
+class PelicanMathJaxExtension(markdown.Extension):
+    """A markdown extension enabling mathjax processing in Markdown for Pelican"""
+    def __init__(self, config):
+        super(PelicanMathJaxExtension,self).__init__(config)
+
+        # Regex to detect mathjax
+        self.mathjax_inline_regex = r'(?P<prefix>\$)(?P<math>.+?)(?P<suffix>(?<!\s)\2)'
+        self.mathjax_display_regex = r'(?P<prefix>(?:\$\$)|\\begin\{(.+?)\})(?P<math>.+?)(?P<suffix>\2|\\end\{\3\})'
+        self.mathjax_needed = False
+
+    def extendMarkdown(self, md, md_globals):
+        # 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
+        md.inlinePatterns.add('mathjax_displayed', PelicanMathJaxPattern(self, 'div', self.mathjax_display_regex), '<escape')
+        md.inlinePatterns.add('mathjax_inlined', PelicanMathJaxPattern(self, 'span', self.mathjax_inline_regex), '<escape')
+
+        # 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')
+        md.treeprocessors.add('mathjax', PelicanMathJaxTreeProcessor(self), '_end')