diff --git a/common/forms.py b/common/forms.py index 0d6f4fc3059e475a36373824c6e9aff87ccf2e58..9a532ad6c551c00b4fd665e08f1a5abeceafb282 100644 --- a/common/forms.py +++ b/common/forms.py @@ -132,9 +132,15 @@ class MarkupTextForm(forms.Form): def get_processed_markup(self): text = self.cleaned_data['markup_text'] + # Detect text format - language = detect_markup_language(text) + markup_detector = detect_markup_language(text) + language = markup_detector['language'] print('language: %s' % language) + + if markup_detector['errors']: + return markup_detector + if language == 'reStructuredText': # This performs the same actions as the restructuredtext filter of app scipost from io import StringIO @@ -160,6 +166,7 @@ class MarkupTextForm(forms.Form): 'language': language, 'errors': warnStream.getvalue() } + # at this point, language is assumed to be plain text from django.template.defaultfilters import linebreaksbr return { 'language': language, diff --git a/common/utils.py b/common/utils.py index aec3ed4aa7c11eca6015971517ee395223ce3a2c..950f46544de2f9a4866faccdd9e26b92fb0b728c 100644 --- a/common/utils.py +++ b/common/utils.py @@ -3,6 +3,7 @@ __license__ = "AGPL v3" from datetime import timedelta +import re from django.core.mail import EmailMultiAlternatives from django.db.models import Q @@ -140,13 +141,65 @@ def detect_markup_language(text): """ Detect which markup language is being used. - Possible return values: - * plain - * reStructuredText + This method returns a dictionary containing: + + * language + * errors + + Language can be one of: plain, reStructuredText + + The criteria used are: + + * if the ``math`` role or directive is found together with $...$, return error + * if the ``math`` role or directive is found, return ReST + + Assumptions: + + * MathJax is set up with $...$ for inline, \[...\] for online equations. """ - rst_headers = ["####", "****", "====", "----", "^^^^", "\"\"\"\"",] + + # Inline maths + inline_math = re.search("\$[^$]+\$", text) + if inline_math: + print('inline math: %s' % inline_math.group(0)) + # Online maths is of the form \[ ... \] + # The re.DOTALL is to also capture newline chars with the . (any single character) + online_math = re.search(r'[\\][[].+[\\][\]]', text, re.DOTALL) + if online_math: + print('online math: %s' % online_math.group(0)) + + rst_math = '.. math::' in text or ':math:`' in text + + # Normal inline/online maths cannot be used simultaneously with ReST math. + # If this is detected, language is set to plain, and errors are reported. + # Otherwise if math present in ReST but not in/online math, assume ReST. + if rst_math: + if inline_math: + return { + 'language': 'plain', + 'errors': ('Cannot determine whether this is plain text or reStructuredText.\n' + 'You have mixed inline maths ($...$) with reStructuredText markup.' + '\n\nPlease use one or the other, but not both!') + } + elif online_math: + return { + 'language': 'plain', + 'errors': ('Cannot determine whether this is plain text or reStructuredText.\n' + 'You have mixed online maths (\[...\]) with reStructuredText markup.' + '\n\nPlease use one or the other, but not both!') + } + else: # assume ReST + return { + 'language': 'reStructuredText', + 'errors': None + } + + # reStructuredText header patterns + rst_header_patterns = [ + "^#{2,}$", "^\*{2,}$", "^={2,}$", "^-{2,}$", "^\^{2,}$", "^\"{2,}$",] # See list of reStructuredText directives at # http://docutils.sourceforge.net/0.4/docs/ref/rst/directives.html + # We don't include the math one here since we covered it above. rst_directives = [ "attention", "caution", "danger", "error", "hint", "important", "note", "tip", "warning", "admonition", @@ -156,28 +209,50 @@ def detect_markup_language(text): "contents", "sectnum", "section-autonumbering", "header", "footer", "target-notes", "replace", "unicode", "date", "class", "role", "default-role", - "math",] + ] # See list at http://docutils.sourceforge.net/0.4/docs/ref/rst/roles.html rst_roles = [ "emphasis", "literal", "pep-reference", "rfc-reference", "strong", "subscript", "superscript", "title-reference", - "math",] - nr_rst_roles = 0 + ] nr_rst_headers = 0 - for header in rst_headers: - if header in text: - nr_rst_headers += 1 + for header_pattern in rst_header_patterns: + matches = re.findall(header_pattern, text, re.MULTILINE) + print ('%s matched %d times' % (header_pattern, len(matches))) + nr_rst_headers += len(matches) nr_rst_directives = 0 for directive in rst_directives: if ('.. %s::' % directive) in text: nr_rst_directives += 1 + nr_rst_roles = 0 for role in rst_roles: if (':%s:`' % role) in text: nr_rst_roles += 1 if (nr_rst_headers > 0 or nr_rst_directives > 0 or nr_rst_roles > 0): - return 'reStructuredText' - return 'plain' + if inline_math: + return { + 'language': 'plain', + 'errors': ('Cannot determine whether this is plain text or reStructuredText.\n' + 'You have mixed inline maths ($...$) with reStructuredText markup.' + '\n\nPlease use one or the other, but not both!') + } + elif online_math: + return { + 'language': 'plain', + 'errors': ('Cannot determine whether this is plain text or reStructuredText.\n' + 'You have mixed online maths (\[...\]) with reStructuredText markup.' + '\n\nPlease use one or the other, but not both!') + } + else: + return { + 'language': 'reStructuredText', + 'errors': None + } + return { + 'language': 'plain', + 'errors': None + } diff --git a/scipost/static/scipost/ticket-preview.js b/scipost/static/scipost/ticket-preview.js index 8b37958876f29caa73107709d9f9510a20db4e85..f1973bb83fd48e6f4eab4aa158fa68bc7dcf342e 100644 --- a/scipost/static/scipost/ticket-preview.js +++ b/scipost/static/scipost/ticket-preview.js @@ -31,14 +31,14 @@ $('#runPreviewButton').on('click', function(){ $('#preview-description').css('background', '#feebce'); $('#submitButton').hide(); $('#runPreviewButton').show(); - alert("An error has occurred while processing the ReStructuredText:\n\n" + data.errors); + alert("An error has occurred while processing the text:\n\n" + data.errors); } $('#preview-description').html(data.processed_markup); let preview = document.getElementById('preview-description'); MathJax.Hub.Queue(["Typeset",MathJax.Hub, preview]); }, error: function(data) { - alert("An error has occurred while processing the ReStructuredText."); + alert("An error has occurred while processing the text."); } }); $('#runPreviewButton').hide();