diff --git a/common/lib/chem/chem/chemcalc.py b/common/lib/chem/chem/chemcalc.py index dd4abefed2c0dad3c85a29eeed8ac506597250b0..196c99ae5ce0ef1dce153dad581ba587d77eb23c 100644 --- a/common/lib/chem/chem/chemcalc.py +++ b/common/lib/chem/chem/chemcalc.py @@ -2,10 +2,12 @@ from __future__ import division from fractions import Fraction +import markupsafe import nltk from nltk.tree import Tree from pyparsing import Literal, OneOrMore, ParseException, StringEnd + ARROWS = ('<->', '->') # Defines a simple pyparsing tokenizer for chemical equations @@ -26,6 +28,16 @@ phases = ["(s)", "(l)", "(g)", "(aq)"] tokens = reduce(lambda a, b: a ^ b, map(Literal, elements + digits + symbols + phases)) tokenizer = OneOrMore(tokens) + StringEnd() +# HTML, Text are temporarily copied from openedx.core.djangolib.markup +# These libraries need to be moved out of edx-platform to be used by +# other applications. +# See LEARNER-5853 for more details. +Text = markupsafe.escape # pylint: disable=invalid-name + + +def HTML(html): # pylint: disable=invalid-name + return markupsafe.Markup(html) + def _orjoin(l): return "'" + "' | '".join(l) + "'" @@ -166,20 +178,20 @@ def _render_to_html(tree): return tree[0][0] # If a fraction, return the fraction if len(tree) == 3: - return " <sup>{num}</sup>⁄<sub>{den}</sub> ".format(num=tree[0][0], den=tree[2][0]) + return HTML(" <sup>{num}</sup>⁄<sub>{den}</sub> ").format(num=tree[0][0], den=tree[2][0]) return "Error" def subscript(tree, children): - return "<sub>{sub}</sub>".format(sub=children) + return HTML("<sub>{sub}</sub>").format(sub=children) def superscript(tree, children): - return "<sup>{sup}</sup>".format(sup=children) + return HTML("<sup>{sup}</sup>").format(sup=children) def round_brackets(tree, children): - return "({insider})".format(insider=children) + return HTML("({insider})").format(insider=children) def square_brackets(tree, children): - return "[{insider}]".format(insider=children) + return HTML("[{insider}]").format(insider=children) dispatch = {'count': molecule_count, 'number_suffix': subscript, @@ -190,7 +202,7 @@ def _render_to_html(tree): if isinstance(tree, str): return tree else: - children = "".join(map(_render_to_html, tree)) + children = HTML("").join(map(_render_to_html, tree)) if tree.label() in dispatch: return dispatch[tree.label()](tree, children) else: @@ -207,20 +219,20 @@ def render_to_html(eq): """ Render as an error span """ - return '<span class="inline-error inline">{0}</span>'.format(s) + return HTML('<span class="inline-error inline">{0}</span>').format(s) def render_arrow(arrow): """ Turn text arrows into pretty ones """ if arrow == '->': - return u'\u2192' + return HTML(u'\u2192') if arrow == '<->': - return u'\u2194' + return HTML(u'\u2194') # this won't be reached unless we add more arrow types, but keep it to avoid explosions when - # that happens. - return arrow + # that happens. HTML-escape this unknown arrow just in case. + return Text(arrow) def render_expression(ex): """ @@ -232,7 +244,7 @@ def render_to_html(eq): return err(ex) def spanify(s): - return u'<span class="math">{0}</span>'.format(s) + return HTML(u'<span class="math">{0}</span>').format(s) left, arrow, right = split_on_arrow(eq) if arrow == '': diff --git a/common/lib/chem/chem/tests.py b/common/lib/chem/chem/tests.py index 93f6132798af6d8bc3367a061d3d606c814618d4..7625095aa9963dad4b675695cbac4b15251de085 100644 --- a/common/lib/chem/chem/tests.py +++ b/common/lib/chem/chem/tests.py @@ -296,13 +296,20 @@ class Test_Render_Equations(unittest.TestCase): log(out + ' ------- ' + correct, 'html') self.assertEqual(out, correct) - def test_render_simple_brackets(self): + def test_render_simple_round_brackets(self): test_string = "(Ar)" out = render_to_html(test_string) correct = u'<span class="math">(Ar)</span>' log(out + ' ------- ' + correct, 'html') self.assertEqual(out, correct) + def test_render_simple_square_brackets(self): + test_string = "[Ar]" + out = render_to_html(test_string) + correct = u'<span class="math">[Ar]</span>' + log(out + ' ------- ' + correct, 'html') + self.assertEqual(out, correct) + def test_render_eq1(self): test_string = "H^+ + OH^- -> H2O" out = render_to_html(test_string) @@ -320,7 +327,24 @@ class Test_Render_Equations(unittest.TestCase): def test_render_eq3(self): test_string = "H^+ + OH^- <= H2O" # unsupported arrow out = render_to_html(test_string) - correct = u'<span class="math"><span class="inline-error inline">H^+ + OH^- <= H2O</span></span>' + correct = u'<span class="math"><span class="inline-error inline">H^+ + OH^- <= H2O</span></span>' + log(out + ' ------- ' + correct, 'html') + self.assertEqual(out, correct) + + def test_render_eq4(self): + test_string = "[H^+] + OH^- <-> (H2O)" # with brackets + out = render_to_html(test_string) + correct = u'<span class="math">[H<sup>+</sup>]+OH<sup>-</sup>\u2194(H<sub>2</sub>O)</span>' + log(out + ' ------- ' + correct, 'html') + self.assertEqual(out, correct) + + def test_escaping(self): + """ + Tests that invalid input is escaped. + """ + test_string = "<script>f()</script>" + out = render_to_html(test_string) + correct = u'<span class="math"><span class="inline-error inline"><script>f()</script></span></span>' log(out + ' ------- ' + correct, 'html') self.assertEqual(out, correct) diff --git a/common/lib/chem/setup.py b/common/lib/chem/setup.py index 005836c1f0994f1b9f0784198d40884c88a097d9..12f8bddefd7a756f9bfe249ebf04267b73a9fedb 100644 --- a/common/lib/chem/setup.py +++ b/common/lib/chem/setup.py @@ -2,12 +2,13 @@ from setuptools import setup setup( name="chem", - version="0.1.2", + version="0.2.0", packages=["chem"], install_requires=[ "pyparsing==2.2.0", "numpy==1.6.2", "scipy==0.14.0", "nltk", + "markupsafe", # Should be replaced by other utilities. See LEARNER-5853 for more details. ], )