Give numerical response tolerance as a range (for example [5,7)) (BLD-25)
This commit is contained in:
@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
|
||||
in roughly chronological order, most recent first. Add your entries at or near
|
||||
the top. Include a label indicating the component affected.
|
||||
|
||||
Blades: Give numerical response tolerance as a range. BLD-25.
|
||||
|
||||
Blades: Allow user with BetaTester role correctly use LTI. BLD-641.
|
||||
|
||||
Blades: Video player persist speed preferences between videos. BLD-237.
|
||||
|
||||
@@ -87,6 +87,7 @@ or= mouse</code></pre>
|
||||
</div>
|
||||
<div class="col">
|
||||
<pre><code>= 3.14 +- 2%</code></pre>
|
||||
<pre><code>= [3.14, 3.15)</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
@@ -26,6 +26,8 @@ import subprocess
|
||||
import textwrap
|
||||
import traceback
|
||||
import xml.sax.saxutils as saxutils
|
||||
from cmath import isnan
|
||||
from sys import float_info
|
||||
|
||||
from collections import namedtuple
|
||||
from shapely.geometry import Point, MultiPoint
|
||||
@@ -36,8 +38,10 @@ from . import correctmap
|
||||
from .registry import TagRegistry
|
||||
from datetime import datetime
|
||||
from pytz import UTC
|
||||
from .util import (compare_with_tolerance, contextualize_text, convert_files_to_filenames,
|
||||
is_list_of_files, find_with_default, default_tolerance)
|
||||
from .util import (
|
||||
compare_with_tolerance, contextualize_text, convert_files_to_filenames,
|
||||
is_list_of_files, find_with_default, default_tolerance
|
||||
)
|
||||
from lxml import etree
|
||||
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
|
||||
import capa.xqueue_interface as xqueue_interface
|
||||
@@ -846,39 +850,57 @@ class NumericalResponse(LoncapaResponse):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.correct_answer = ''
|
||||
self.tolerance = default_tolerance
|
||||
self.range_tolerance = False
|
||||
self.answer_range = self.inclusion = None
|
||||
super(NumericalResponse, self).__init__(*args, **kwargs)
|
||||
|
||||
def setup_response(self):
|
||||
xml = self.xml
|
||||
context = self.context
|
||||
self.correct_answer = contextualize_text(xml.get('answer'), context)
|
||||
answer = xml.get('answer')
|
||||
|
||||
# Find the tolerance
|
||||
tolerance_xml = xml.xpath(
|
||||
'//*[@id=$id]//responseparam[@type="tolerance"]/@default',
|
||||
id=xml.get('id')
|
||||
)
|
||||
if tolerance_xml: # If it isn't an empty list...
|
||||
self.tolerance = contextualize_text(tolerance_xml[0], context)
|
||||
if answer.startswith(('[', '(')) and answer.endswith((']', ')')): # range tolerance case
|
||||
self.range_tolerance = True
|
||||
self.inclusion = (
|
||||
True if answer.startswith('[') else False, True if answer.endswith(']') else False
|
||||
)
|
||||
try:
|
||||
self.answer_range = [contextualize_text(x, context) for x in answer[1:-1].split(',')]
|
||||
self.correct_answer = answer[0] + self.answer_range[0] + ', ' + self.answer_range[1] + answer[-1]
|
||||
except Exception:
|
||||
log.debug("Content error--answer '%s' is not a valid range tolerance answer", answer)
|
||||
_ = self.capa_system.i18n.ugettext
|
||||
raise StudentInputError(
|
||||
_("There was a problem with the staff answer to this problem.")
|
||||
)
|
||||
else:
|
||||
self.correct_answer = contextualize_text(answer, context)
|
||||
|
||||
def get_staff_ans(self):
|
||||
# Find the tolerance
|
||||
tolerance_xml = xml.xpath(
|
||||
'//*[@id=$id]//responseparam[@type="tolerance"]/@default',
|
||||
id=xml.get('id')
|
||||
)
|
||||
if tolerance_xml: # If it isn't an empty list...
|
||||
self.tolerance = contextualize_text(tolerance_xml[0], context)
|
||||
|
||||
def get_staff_ans(self, answer):
|
||||
"""
|
||||
Given the staff answer as a string, find its float value.
|
||||
|
||||
Use `evaluator` for this, but for backward compatability, try the
|
||||
built-in method `complex` (which used to be the standard).
|
||||
"""
|
||||
|
||||
try:
|
||||
correct_ans = complex(self.correct_answer)
|
||||
correct_ans = complex(answer)
|
||||
except ValueError:
|
||||
# When `correct_answer` is not of the form X+Yj, it raises a
|
||||
# `ValueError`. Then test if instead it is a math expression.
|
||||
# `complex` seems to only generate `ValueErrors`, only catch these.
|
||||
try:
|
||||
correct_ans = evaluator({}, {}, self.correct_answer)
|
||||
correct_ans = evaluator({}, {}, answer)
|
||||
except Exception:
|
||||
log.debug("Content error--answer '%s' is not a valid number", self.correct_answer)
|
||||
log.debug("Content error--answer '%s' is not a valid number", answer)
|
||||
_ = self.capa_system.i18n.ugettext
|
||||
raise StudentInputError(
|
||||
_("There was a problem with the staff answer to this problem.")
|
||||
@@ -887,11 +909,11 @@ class NumericalResponse(LoncapaResponse):
|
||||
return correct_ans
|
||||
|
||||
def get_score(self, student_answers):
|
||||
"""Grade a numeric response"""
|
||||
"""
|
||||
Grade a numeric response.
|
||||
"""
|
||||
student_answer = student_answers[self.answer_id]
|
||||
|
||||
correct_float = self.get_staff_ans()
|
||||
|
||||
_ = self.capa_system.i18n.ugettext
|
||||
general_exception = StudentInputError(
|
||||
_(u"Could not interpret '{student_answer}' as a number.").format(student_answer=cgi.escape(student_answer))
|
||||
@@ -924,10 +946,34 @@ class NumericalResponse(LoncapaResponse):
|
||||
except Exception:
|
||||
raise general_exception
|
||||
# End `evaluator` block -- we figured out the student's answer!
|
||||
|
||||
correct = compare_with_tolerance(
|
||||
student_float, correct_float, self.tolerance
|
||||
)
|
||||
if self.range_tolerance:
|
||||
if isinstance(student_float, complex):
|
||||
raise StudentInputError(_(u"You may not use complex numbers in range tolerance problems"))
|
||||
if isnan(student_float):
|
||||
raise general_exception
|
||||
boundaries = []
|
||||
for inclusion, answer in zip(self.inclusion, self.answer_range):
|
||||
boundary = self.get_staff_ans(answer)
|
||||
if boundary.imag != 0:
|
||||
raise StudentInputError(_("There was a problem with the staff answer to this problem: complex boundary."))
|
||||
if isnan(boundary):
|
||||
raise StudentInputError(_("There was a problem with the staff answer to this problem: empty boundary."))
|
||||
boundaries.append(boundary.real)
|
||||
if compare_with_tolerance(
|
||||
student_float,
|
||||
boundary,
|
||||
tolerance=float_info.epsilon,
|
||||
relative_tolerance=True
|
||||
):
|
||||
correct = inclusion
|
||||
break
|
||||
else:
|
||||
correct = boundaries[0] < student_float < boundaries[1]
|
||||
else:
|
||||
correct_float = self.get_staff_ans(self.correct_answer)
|
||||
correct = compare_with_tolerance(
|
||||
student_float, correct_float, self.tolerance
|
||||
)
|
||||
if correct:
|
||||
return CorrectMap(self.answer_id, 'correct')
|
||||
else:
|
||||
@@ -1062,7 +1108,7 @@ class StringResponse(LoncapaResponse):
|
||||
if self.regexp: # regexp match
|
||||
flags = re.IGNORECASE if self.case_insensitive else 0
|
||||
try:
|
||||
regexp = re.compile('^'+ '|'.join(expected) + '$', flags=flags | re.UNICODE)
|
||||
regexp = re.compile('^' + '|'.join(expected) + '$', flags=flags | re.UNICODE)
|
||||
result = re.search(regexp, given)
|
||||
except Exception as err:
|
||||
msg = '[courseware.capa.responsetypes.stringresponse] error: {}'.format(err.message)
|
||||
@@ -1075,7 +1121,6 @@ class StringResponse(LoncapaResponse):
|
||||
else:
|
||||
return given in expected
|
||||
|
||||
|
||||
def check_hint_condition(self, hxml_set, student_answers):
|
||||
given = student_answers[self.answer_id].strip()
|
||||
hints_to_show = []
|
||||
|
||||
@@ -1061,6 +1061,48 @@ class NumericalResponseTest(ResponseTest):
|
||||
# We blend the line between integration (using evaluator) and exclusively
|
||||
# unit testing the NumericalResponse (mocking out the evaluator)
|
||||
# For simple things its not worth the effort.
|
||||
|
||||
def test_grade_range_tolerance(self):
|
||||
problem_setup = [
|
||||
# [given_asnwer, [list of correct responses], [list of incorrect responses]]
|
||||
['[5, 7)', ['5', '6', '6.999'], ['4.999', '7']],
|
||||
['[1.6e-5, 1.9e24)', ['0.000016', '1.6*10^-5', '1.59e24'], ['1.59e-5', '1.9e24', '1.9*10^24']],
|
||||
['[0, 1.6e-5]', ['1.6*10^-5'], ["2"]],
|
||||
['(1.6e-5, 10]', ["2"], ['1.6*10^-5']],
|
||||
]
|
||||
for given_answer, correct_responses, incorrect_responses in problem_setup:
|
||||
problem = self.build_problem(answer=given_answer)
|
||||
self.assert_multiple_grade(problem, correct_responses, incorrect_responses)
|
||||
|
||||
def test_grade_range_tolerance_exceptions(self):
|
||||
# no complex number in range tolerance staff answer
|
||||
problem = self.build_problem(answer='[1j, 5]')
|
||||
input_dict = {'1_2_1': '3'}
|
||||
with self.assertRaises(StudentInputError):
|
||||
problem.grade_answers(input_dict)
|
||||
|
||||
# no complex numbers in student ansers to range tolerance problems
|
||||
problem = self.build_problem(answer='(1, 5)')
|
||||
input_dict = {'1_2_1': '1*J'}
|
||||
with self.assertRaises(StudentInputError):
|
||||
problem.grade_answers(input_dict)
|
||||
|
||||
# test isnan variable
|
||||
problem = self.build_problem(answer='(1, 5)')
|
||||
input_dict = {'1_2_1': ''}
|
||||
with self.assertRaises(StudentInputError):
|
||||
problem.grade_answers(input_dict)
|
||||
|
||||
# test invalid range tolerance answer
|
||||
with self.assertRaises(StudentInputError):
|
||||
problem = self.build_problem(answer='(1 5)')
|
||||
|
||||
# test empty boundaries
|
||||
problem = self.build_problem(answer='(1, ]')
|
||||
input_dict = {'1_2_1': '3'}
|
||||
with self.assertRaises(StudentInputError):
|
||||
problem.grade_answers(input_dict)
|
||||
|
||||
def test_grade_exact(self):
|
||||
problem = self.build_problem(answer=4)
|
||||
correct_responses = ["4", "4.0", "4.00"]
|
||||
@@ -1084,17 +1126,17 @@ class NumericalResponseTest(ResponseTest):
|
||||
Default tolerance for all responsetypes is 1e-3%.
|
||||
"""
|
||||
problem_setup = [
|
||||
#[given_asnwer, [list of correct responses], [list of incorrect responses]]
|
||||
[1, ["1"], ["1.1"],],
|
||||
[2.0, ["2.0"], ["1.0"],],
|
||||
[4, ["4.0", "4.00004"], ["4.00005"]],
|
||||
# [given_answer, [list of correct responses], [list of incorrect responses]]
|
||||
[1, ["1"], ["1.1"]],
|
||||
[2.0, ["2.0"], ["1.0"]],
|
||||
[4, ["4.0", "4.00004"], ["4.00005"]],
|
||||
[0.00016, ["1.6*10^-4"], [""]],
|
||||
[0.000016, ["1.6*10^-5"], ["0.000165"]],
|
||||
[1.9e24, ["1.9*10^24"], ["1.9001*10^24"]],
|
||||
[2e-15, ["2*10^-15"], [""]],
|
||||
[3141592653589793238., ["3141592653589793115."], [""]],
|
||||
[0.1234567, ["0.123456", "0.1234561"], ["0.123451"]],
|
||||
[1e-5, ["1e-5", "1.0e-5"], ["-1e-5", "2*1e-5"]],
|
||||
[0.1234567, ["0.123456", "0.1234561"], ["0.123451"]],
|
||||
[1e-5, ["1e-5", "1.0e-5"], ["-1e-5", "2*1e-5"]],
|
||||
]
|
||||
for given_answer, correct_responses, incorrect_responses in problem_setup:
|
||||
problem = self.build_problem(answer=given_answer)
|
||||
|
||||
@@ -7,19 +7,21 @@ from cmath import isinf
|
||||
default_tolerance = '0.001%'
|
||||
|
||||
|
||||
def compare_with_tolerance(v1, v2, tol=default_tolerance):
|
||||
def compare_with_tolerance(complex1, complex2, tolerance=default_tolerance, relative_tolerance=False):
|
||||
"""
|
||||
Compare v1 to v2 with maximum tolerance tol.
|
||||
Compare complex1 to complex2 with maximum tolerance tol.
|
||||
|
||||
tol is relative if it ends in %; otherwise, it is absolute.
|
||||
If tolerance is type string, then it is counted as relative if it ends in %; otherwise, it is absolute.
|
||||
|
||||
- v1 : student result (float complex number)
|
||||
- v2 : instructor result (float complex number)
|
||||
- tol : tolerance (string representing a number)
|
||||
- complex1 : student result (float complex number)
|
||||
- complex2 : instructor result (float complex number)
|
||||
- tolerance : string representing a number or float
|
||||
- relative_tolerance: bool, used when`tolerance` is float to explicitly use passed tolerance as relative.
|
||||
|
||||
Default tolerance of 1e-3% is added to compare two floats for near-equality
|
||||
(to handle machine representation errors).
|
||||
It is relative, as the acceptable difference between two floats depends on the magnitude of the floats.
|
||||
Default tolerance of 1e-3% is added to compare two floats for
|
||||
near-equality (to handle machine representation errors).
|
||||
Default tolerance is relative, as the acceptable difference between two
|
||||
floats depends on the magnitude of the floats.
|
||||
(http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
|
||||
Examples:
|
||||
In [183]: 0.000016 - 1.6*10**-5
|
||||
@@ -27,22 +29,23 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
|
||||
In [212]: 1.9e24 - 1.9*10**24
|
||||
Out[212]: 268435456.0
|
||||
"""
|
||||
relative = tol.endswith('%')
|
||||
if relative:
|
||||
tolerance_rel = evaluator(dict(), dict(), tol[:-1]) * 0.01
|
||||
tolerance = tolerance_rel * max(abs(v1), abs(v2))
|
||||
if relative_tolerance:
|
||||
tolerance = tolerance * max(abs(complex1), abs(complex2))
|
||||
elif tolerance.endswith('%'):
|
||||
tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01
|
||||
tolerance = tolerance * max(abs(complex1), abs(complex2))
|
||||
else:
|
||||
tolerance = evaluator(dict(), dict(), tol)
|
||||
tolerance = evaluator(dict(), dict(), tolerance)
|
||||
|
||||
if isinf(v1) or isinf(v2):
|
||||
# If an input is infinite, we can end up with `abs(v1-v2)` and
|
||||
if isinf(complex1) or isinf(complex2):
|
||||
# If an input is infinite, we can end up with `abs(complex1-complex2)` and
|
||||
# `tolerance` both equal to infinity. Then, below we would have
|
||||
# `inf <= inf` which is a fail. Instead, compare directly.
|
||||
return v1 == v2
|
||||
return complex1 == complex2
|
||||
else:
|
||||
# v1 and v2 are, in general, complex numbers:
|
||||
# there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()).
|
||||
return abs(v1 - v2) <= tolerance
|
||||
return abs(complex1 - complex2) <= tolerance
|
||||
|
||||
|
||||
def contextualize_text(text, context): # private
|
||||
|
||||
@@ -105,6 +105,14 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
Enter the number of fingers on a human hand:
|
||||
= 5
|
||||
|
||||
Range tolerance case
|
||||
= [6, 7]
|
||||
= (1, 2)
|
||||
|
||||
If first and last symbols are not brackets, or they are not closed, stringresponse will appear.
|
||||
= (7), 7
|
||||
= (1+2
|
||||
|
||||
[Explanation]
|
||||
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
|
||||
|
||||
@@ -135,6 +143,22 @@ describe 'MarkdownEditingDescriptor', ->
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
|
||||
<p>Range tolerance case</p>
|
||||
<numericalresponse answer="[6, 7]">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
<numericalresponse answer="(1, 2)">
|
||||
<formulaequationinput />
|
||||
</numericalresponse>
|
||||
|
||||
<p>If first and last symbols are not brackets, or they are not closed, stringresponse will appear.</p>
|
||||
<stringresponse answer="(7), 7" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
<stringresponse answer="(1+2" type="ci" >
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
|
||||
@@ -186,124 +186,175 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
|
||||
#}
|
||||
#
|
||||
@markdownToXml: (markdown)->
|
||||
toXml = `function(markdown) {
|
||||
var xml = markdown;
|
||||
toXml = `function (markdown) {
|
||||
var xml = markdown,
|
||||
i, splits, scriptFlag;
|
||||
|
||||
// replace headers
|
||||
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h1>$1</h1>');
|
||||
xml = xml.replace(/\n^\=\=+$/gm, '');
|
||||
|
||||
// group multiple choice answers
|
||||
xml = xml.replace(/(^\s*\(.?\).*?$\n*)+/gm, function(match, p) {
|
||||
var groupString = '<multiplechoiceresponse>\n';
|
||||
groupString += ' <choicegroup type="MultipleChoice">\n';
|
||||
var options = match.split('\n');
|
||||
for(var i = 0; i < options.length; i++) {
|
||||
if(options[i].length > 0) {
|
||||
var value = options[i].split(/^\s*\(.?\)\s*/)[1];
|
||||
var correct = /^\s*\(x\)/i.test(options[i]);
|
||||
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
|
||||
xml = xml.replace(/(^\s*\(.?\).*?$\n*)+/gm, function (match) {
|
||||
var groupString = '<multiplechoiceresponse>\n',
|
||||
value, correct, options;
|
||||
|
||||
groupString += ' <choicegroup type="MultipleChoice">\n';
|
||||
options = match.split('\n');
|
||||
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
if(options[i].length > 0) {
|
||||
value = options[i].split(/^\s*\(.?\)\s*/)[1];
|
||||
correct = /^\s*\(x\)/i.test(options[i]);
|
||||
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
groupString += ' </choicegroup>\n';
|
||||
groupString += '</multiplechoiceresponse>\n\n';
|
||||
return groupString;
|
||||
|
||||
groupString += ' </choicegroup>\n';
|
||||
groupString += '</multiplechoiceresponse>\n\n';
|
||||
|
||||
return groupString;
|
||||
});
|
||||
|
||||
// group check answers
|
||||
xml = xml.replace(/(^\s*\[.?\].*?$\n*)+/gm, function(match, p) {
|
||||
var groupString = '<choiceresponse>\n';
|
||||
groupString += ' <checkboxgroup direction="vertical">\n';
|
||||
var options = match.split('\n');
|
||||
for(var i = 0; i < options.length; i++) {
|
||||
if(options[i].length > 0) {
|
||||
var value = options[i].split(/^\s*\[.?\]\s*/)[1];
|
||||
var correct = /^\s*\[x\]/i.test(options[i]);
|
||||
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
|
||||
xml = xml.replace(/(^\s*\[.?\].*?$\n*)+/gm, function(match) {
|
||||
var groupString = '<choiceresponse>\n',
|
||||
options, value, correct;
|
||||
|
||||
groupString += ' <checkboxgroup direction="vertical">\n';
|
||||
options = match.split('\n');
|
||||
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
if(options[i].length > 0) {
|
||||
value = options[i].split(/^\s*\[.?\]\s*/)[1];
|
||||
correct = /^\s*\[x\]/i.test(options[i]);
|
||||
groupString += ' <choice correct="' + correct + '">' + value + '</choice>\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
groupString += ' </checkboxgroup>\n';
|
||||
groupString += '</choiceresponse>\n\n';
|
||||
return groupString;
|
||||
|
||||
groupString += ' </checkboxgroup>\n';
|
||||
groupString += '</choiceresponse>\n\n';
|
||||
|
||||
return groupString;
|
||||
});
|
||||
|
||||
// replace string and numerical
|
||||
xml = xml.replace(/(^\=\s*(.*?$)(\n*or\=\s*(.*?$))*)+/gm, function(match, p) {
|
||||
var string,
|
||||
answersList = p.replace(/^(or)?=\s*/gm, '').split('\n'),
|
||||
floatValue = parseFloat(answersList[0]);
|
||||
// Split answers
|
||||
var answersList = p.replace(/^(or)?=\s*/gm, '').split('\n'),
|
||||
|
||||
if(!isNaN(floatValue)) {
|
||||
// Tries to extract parameters from string like 'expr +- tolerance'
|
||||
var params = /(.*?)\+\-\s*(.*?$)/.exec(answersList[0]),
|
||||
answer = answersList[0].replace(/\s+/g, '');
|
||||
if(params) {
|
||||
answer = params[1].replace(/\s+/g, '');
|
||||
string = '<numericalresponse answer="' + answer + '">\n';
|
||||
string += ' <responseparam type="tolerance" default="' + params[2] + '" />\n';
|
||||
} else {
|
||||
string = '<numericalresponse answer="' + answer + '">\n';
|
||||
}
|
||||
string += ' <formulaequationinput />\n';
|
||||
string += '</numericalresponse>\n\n';
|
||||
} else {
|
||||
var firstAnswer = answersList.shift();
|
||||
if (firstAnswer[0] === '|') { // this is regexp case
|
||||
string = '<stringresponse answer="' + firstAnswer.slice(1).trim() + '" type="ci regexp" >\n'
|
||||
}
|
||||
else {
|
||||
string = '<stringresponse answer="' + firstAnswer + '" type="ci" >\n'
|
||||
}
|
||||
for(var i = 0; i < answersList.length; i++) {
|
||||
string += ' <additional_answer>' + answersList[i] + '</additional_answer>\n'
|
||||
}
|
||||
string += ' <textline size="20"/>\n</stringresponse>\n\n';
|
||||
}
|
||||
return string;
|
||||
});
|
||||
processNumericalResponse = function (value) {
|
||||
var params, answer, string;
|
||||
|
||||
if (_.contains([ '[', '(' ], value[0]) && _.contains([ ']', ')' ], value[value.length-1]) ) {
|
||||
// [5, 7) or (5, 7), or (1.2345 * (2+3), 7*4 ] - range tolerance case
|
||||
// = (5*2)*3 should not be used as range tolerance
|
||||
string = '<numericalresponse answer="' + value + '">\n';
|
||||
string += ' <formulaequationinput />\n';
|
||||
string += '</numericalresponse>\n\n';
|
||||
return string;
|
||||
}
|
||||
|
||||
if (isNaN(parseFloat(value))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tries to extract parameters from string like 'expr +- tolerance'
|
||||
params = /(.*?)\+\-\s*(.*?$)/.exec(value);
|
||||
|
||||
if(params) {
|
||||
answer = params[1].replace(/\s+/g, ''); // support inputs like 5*2 +- 10
|
||||
string = '<numericalresponse answer="' + answer + '">\n';
|
||||
string += ' <responseparam type="tolerance" default="' + params[2] + '" />\n';
|
||||
} else {
|
||||
answer = value.replace(/\s+/g, ''); // support inputs like 5*2
|
||||
string = '<numericalresponse answer="' + answer + '">\n';
|
||||
}
|
||||
|
||||
string += ' <formulaequationinput />\n';
|
||||
string += '</numericalresponse>\n\n';
|
||||
|
||||
return string;
|
||||
},
|
||||
|
||||
processStringResponse = function (values) {
|
||||
var firstAnswer = values.shift(), string;
|
||||
|
||||
if (firstAnswer[0] === '|') { // this is regexp case
|
||||
string = '<stringresponse answer="' + firstAnswer.slice(1).trim() + '" type="ci regexp" >\n';
|
||||
} else {
|
||||
string = '<stringresponse answer="' + firstAnswer + '" type="ci" >\n';
|
||||
}
|
||||
|
||||
for (i = 0; i < values.length; i += 1) {
|
||||
string += ' <additional_answer>' + values[i] + '</additional_answer>\n';
|
||||
}
|
||||
|
||||
string += ' <textline size="20"/>\n</stringresponse>\n\n';
|
||||
|
||||
return string;
|
||||
};
|
||||
|
||||
return processNumericalResponse(answersList[0]) || processStringResponse(answersList);
|
||||
});
|
||||
|
||||
// replace selects
|
||||
xml = xml.replace(/\[\[(.+?)\]\]/g, function(match, p) {
|
||||
var selectString = '\n<optionresponse>\n';
|
||||
selectString += ' <optioninput options="(';
|
||||
var options = p.split(/\,\s*/g);
|
||||
for(var i = 0; i < options.length; i++) {
|
||||
selectString += "'" + options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, '$1') + "'" + (i < options.length -1 ? ',' : '');
|
||||
}
|
||||
selectString += ')" correct="';
|
||||
var correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(p);
|
||||
if (correct) selectString += correct[1];
|
||||
selectString += '"></optioninput>\n';
|
||||
selectString += '</optionresponse>\n\n';
|
||||
return selectString;
|
||||
var selectString = '\n<optionresponse>\n',
|
||||
correct, options;
|
||||
|
||||
selectString += ' <optioninput options="(';
|
||||
options = p.split(/\,\s*/g);
|
||||
|
||||
for (i = 0; i < options.length; i += 1) {
|
||||
selectString += "'" + options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, '$1') + "'" + (i < options.length -1 ? ',' : '');
|
||||
}
|
||||
|
||||
selectString += ')" correct="';
|
||||
correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(p);
|
||||
|
||||
if (correct) {
|
||||
selectString += correct[1];
|
||||
}
|
||||
|
||||
selectString += '"></optioninput>\n';
|
||||
selectString += '</optionresponse>\n\n';
|
||||
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// replace explanations
|
||||
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gmi, function(match, p1) {
|
||||
var selectString = '<solution>\n<div class="detailed-solution">\nExplanation\n\n' + p1 + '\n</div>\n</solution>';
|
||||
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// replace code blocks
|
||||
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) {
|
||||
var selectString = '<pre><code>\n' + p1 + '</code></pre>';
|
||||
|
||||
return selectString;
|
||||
});
|
||||
|
||||
// split scripts and preformatted sections, and wrap paragraphs
|
||||
var splits = xml.split(/(\<\/?(?:script|pre).*?\>)/g);
|
||||
var scriptFlag = false;
|
||||
for(var i = 0; i < splits.length; i++) {
|
||||
if(/\<(script|pre)/.test(splits[i])) {
|
||||
scriptFlag = true;
|
||||
}
|
||||
if(!scriptFlag) {
|
||||
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, '<p>$1</p>');
|
||||
}
|
||||
if(/\<\/(script|pre)/.test(splits[i])) {
|
||||
scriptFlag = false;
|
||||
}
|
||||
splits = xml.split(/(\<\/?(?:script|pre).*?\>)/g);
|
||||
scriptFlag = false;
|
||||
|
||||
for (i = 0; i < splits.length; i += 1) {
|
||||
if(/\<(script|pre)/.test(splits[i])) {
|
||||
scriptFlag = true;
|
||||
}
|
||||
|
||||
if(!scriptFlag) {
|
||||
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, '<p>$1</p>');
|
||||
}
|
||||
|
||||
if(/\<\/(script|pre)/.test(splits[i])) {
|
||||
scriptFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
xml = splits.join('');
|
||||
|
||||
// rid white space
|
||||
@@ -313,7 +364,6 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
|
||||
xml = '<problem>\n' + xml + '\n</problem>';
|
||||
|
||||
return xml;
|
||||
}
|
||||
`
|
||||
}`
|
||||
return toXml markdown
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ task :builddocs, [:type, :quiet] do |t, args|
|
||||
end
|
||||
end
|
||||
|
||||
desc "Show docs in browser (mac and ubuntu)."
|
||||
desc "Show docs in browser: dev, author, data."
|
||||
task :showdocs, [:options] do |t, args|
|
||||
if args.options == 'dev'
|
||||
path = "docs/en_us/developers"
|
||||
|
||||
Reference in New Issue
Block a user