123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- """
- Latex Plugin For Pelican
- ========================
- This plugin allows you to write mathematical equations in your articles using Latex.
- It uses the MathJax Latex JavaScript library to render latex that is embedded in
- between `$..$` for inline math and `$$..$$` for displayed math. It also allows for
- writing equations in by using `\begin{equation}`...`\end{equation}`. No
- alteration to a template is required for this plugin to work, just install and
- use.
- Typogrify Compatibility
- -----------------------
- This plugin now plays nicely with typogrify, but it requires
- typogrify version 2.07 or above.
- User Settings
- -------------
- Users are also able to pass a dictionary of settings in the settings file which
- will control how the mathjax library renders thing. This could be very useful
- for template builders that want to adjust look and feel of the math.
- See README for more details.
- """
- from pelican import signals
- from pelican import contents
- import re
- _WRAP_TAG = None
- _LATEX_REGEX = re.compile(r'(\$\$|\$|\\begin\{(.+?)\}).*?\1|\\end\{\2\}', re.DOTALL | re.IGNORECASE)
- _LATEX_SUMMARY_REGEX = None
- _LATEX_PARTIAL_REGEX = None
- _MATHJAX_SETTINGS = {}
- _MATHJAX_SCRIPT="""
- <script type= "text/javascript">
- if (!document.getElementById('mathjaxscript_pelican')) {{
- var s = document.createElement('script');
- s.id = 'mathjaxscript_pelican';
- s.type = 'text/javascript'; s.src = 'https:' == document.location.protocol ? 'https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js' : 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
- s[(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: '{preview}'," +
- " }}, " +
- " 'HTML-CSS': {{ " +
- " styles: {{ '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {{color: '{color} ! important'}} }}" +
- " }} " +
- "}}); ";
- (document.body || document.getElementsByTagName('head')[0]).appendChild(s);
- }}
- </script>
- """
- 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
-
-
- 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 = []
-
-
- ignore_regex = re.compile(r'<(pre|code).*?>.*?</(\1)>', re.DOTALL | re.IGNORECASE)
- for match in ignore_regex.finditer(content):
- ignore_within.append(match.span())
- return ignore_within
- def wrap_latex(content, ignore_within):
- """Wraps latex in user specified tags.
- This is needed for typogrify to play nicely with latex but it can also be
- styled by template providers
- """
- wrap_latex.foundlatex = False
- def math_tag_wrap(match):
- """function for use in re.sub"""
-
- ignore = binary_search(match.span(1), ignore_within) and binary_search(match.span(2), ignore_within)
- if ignore:
- return match.group(0)
- else:
- wrap_latex.foundlatex = True
- return '<%s>%s</%s>' % (_WRAP_TAG, match.group(0), _WRAP_TAG)
- return (_LATEX_REGEX.sub(math_tag_wrap, content), wrap_latex.foundlatex)
- 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_script = False
- endtag = '</%s>' % _WRAP_TAG if _WRAP_TAG != None else ''
-
- summary = instance._get_summary()
-
-
- mathitem = None
- for mathitem in _LATEX_SUMMARY_REGEX.finditer(summary):
- if binary_search(mathitem.span(), ignore_within):
- mathitem = None
- else:
- insert_mathjax_script = True
-
-
- if mathitem and mathitem.group(4) == ' ...':
- end = r'\end{%s}' % mathitem.group(3) if mathitem.group(3) is not None else mathitem.group(2)
- latex_match = re.search('%s.*?%s' % (re.escape(mathitem.group(1)), re.escape(end)), instance._content, re.DOTALL | re.IGNORECASE)
- new_summary = summary.replace(mathitem.group(0), latex_match.group(0)+'%s ...' % endtag)
- if new_summary != summary:
- return new_summary+_MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
- def partial_regex(match):
- """function for use in re.sub"""
- if binary_search(match.span(), ignore_within):
- return match.group(0)
- process_summary.altered_summary = True
- return match.group(1) + match.group(4)
-
- summary = _LATEX_PARTIAL_REGEX.sub(partial_regex, summary)
- if process_summary.altered_summary:
- return summary+_MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS) if insert_mathjax_script else summary
- return summary+_MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS) if insert_mathjax_script else None
- def process_settings(settings):
- """Sets user specified MathJax settings (see README for more details)"""
- global _MATHJAX_SETTINGS
-
-
-
-
-
- _MATHJAX_SETTINGS['align'] = 'center'
- _MATHJAX_SETTINGS['indent'] = '0em'
- _MATHJAX_SETTINGS['show_menu'] = 'true'
- _MATHJAX_SETTINGS['process_escapes'] = 'true'
- _MATHJAX_SETTINGS['preview'] = 'TeX'
- _MATHJAX_SETTINGS['color'] = 'black'
- if not isinstance(settings, dict):
- return
-
-
-
- for key, value in ((key, settings[key]) for key in settings):
- if key == 'align' and isinstance(value, str):
- if value == 'left' or value == 'right' or value == 'center':
- _MATHJAX_SETTINGS[key] = value
- else:
- _MATHJAX_SETTINGS[key] = 'center'
- if key == 'indent':
- _MATHJAX_SETTINGS[key] = value
- if key == 'show_menu' and isinstance(value, bool):
- _MATHJAX_SETTINGS[key] = 'true' if value else 'false'
- if key == 'process_escapes' and isinstance(value, bool):
- _MATHJAX_SETTINGS[key] = 'true' if value else 'false'
- if key == 'preview' and isinstance(value, str):
- _MATHJAX_SETTINGS[key] = value
- if key == 'color' and isinstance(value, str):
- _MATHJAX_SETTINGS[key] = value
- def process_content(instance):
- """Processes content, with logic to ensure that typogrify does not clash
- with latex.
- In addition, mathjax script is inserted at the end of the content thereby
- making it independent of the template
- """
- if not instance._content:
- return
- ignore_within = ignore_content(instance._content)
- if _WRAP_TAG:
- instance._content, latex = wrap_latex(instance._content, ignore_within)
- else:
- latex = True if _LATEX_REGEX.search(instance._content) else False
-
-
-
-
- if process_content.typogrify:
-
- ignore_tags = [_WRAP_TAG] if _WRAP_TAG else None
-
- from typogrify.filters import typogrify
- instance._content = typogrify(instance._content, ignore_tags)
- instance.metadata['title'] = typogrify(instance.metadata['title'], ignore_tags)
- if latex:
-
-
- instance._content += _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
-
-
- summary = process_summary(instance, ignore_within)
- if summary != None:
- instance._summary = summary
- def pelican_init(pelicanobj):
- """Intialializes certain global variables and sets typogogrify setting to
- False should it be set to True.
- """
- global _WRAP_TAG
- global _LATEX_SUMMARY_REGEX
- global _LATEX_PARTIAL_REGEX
- try:
- settings = pelicanobj.settings['LATEX']
- except:
- settings = None
- process_settings(settings)
-
- pelicanobj.settings['MATHJAXSCRIPT'] = _MATHJAX_SCRIPT.format(**_MATHJAX_SETTINGS)
-
-
- try:
- if pelicanobj.settings['TYPOGRIFY'] == True:
- pelicanobj.settings['TYPOGRIFY'] = False
- _WRAP_TAG = 'mathjax'
- process_content.typogrify = True
- except KeyError:
- pass
-
-
- try:
- if pelicanobj.settings['LATEX']['wrap']:
- _WRAP_TAG = pelicanobj.settings['LATEX']['wrap']
- except (KeyError, TypeError):
- pass
-
- tagstart = r'<%s>' % _WRAP_TAG if not _WRAP_TAG is None else ''
- tagend = r'</%s>' % _WRAP_TAG if not _WRAP_TAG is None else ''
- latex_summary_regex = r'((\$\$|\$|\\begin\{(.+?)\}).+?)(\2|\\end\{\3\}|\s?\.\.\.)(%s)?' % tagend
- latex_partial_regex = r'(.*)(%s)(\\.*?|\$.*?)(\s?\.\.\.)(%s)' % (tagstart, tagend)
- _LATEX_SUMMARY_REGEX = re.compile(latex_summary_regex, re.DOTALL | re.IGNORECASE)
- _LATEX_PARTIAL_REGEX = re.compile(latex_partial_regex, re.DOTALL | re.IGNORECASE)
- def register():
- """Plugin registration"""
- signals.initialized.connect(pelican_init)
- signals.content_object_init.connect(process_content)
|