Skip to content

Commit f94e848

Browse files
authored
better handling of quotes and strings in student answers (#257)
1 parent 6a94b26 commit f94e848

File tree

5 files changed

+43
-10
lines changed

5 files changed

+43
-10
lines changed

classes/external/answervalidation.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ public static function validate_student_answer(string $answer, int $answertype,
100100
$parser = new answer_parser($number);
101101
// As we are in a try-catch block anyway, we can just throw an Exception if the answer
102102
// is not acceptable.
103-
if (!$parser->is_acceptable_for_answertype($answertype)) {
103+
$isacceptable = $parser->is_acceptable_for_answertype($answertype);
104+
$containsquote = preg_match('/[\'"]/', $number);
105+
if (!$isacceptable || $containsquote) {
104106
$answertypestrings = [
105107
qtype_formulas::ANSWER_TYPE_NUMBER => 'number',
106108
qtype_formulas::ANSWER_TYPE_NUMERIC => 'numeric',

question.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,10 +1335,12 @@ public function get_evaluated_answers(): array {
13351335
*/
13361336
private static function wrap_algebraic_formulas_in_quotes(array $formulas): array {
13371337
foreach ($formulas as &$formula) {
1338-
// If the formula is aready wrapped in quotes (e. g. after an earlier call to this
1339-
// function), there is nothing to do.
1338+
// If the formula is aready wrapped in quotes, we throw an Exception, because that
1339+
// should not happen. It will happen, if the student puts quotes around their response, but
1340+
// we want that to be graded wrong. The exception will be caught and dealt with upstream,
1341+
// so we do not need to be more precise.
13401342
if (preg_match('/^\"[^\"]*\"$/', $formula)) {
1341-
continue;
1343+
throw new Exception();
13421344
}
13431345

13441346
$formula = '"' . $formula . '"';
@@ -1495,7 +1497,12 @@ public function grade(array $response, bool $finalsubmit = false): array {
14951497
// formulas, we must wrap the answers in quotes before we move on. Also, we reset the conversion
14961498
// factor, because it is not needed for algebraic answers.
14971499
if ($isalgebraic) {
1498-
$response = self::wrap_algebraic_formulas_in_quotes($response);
1500+
try {
1501+
$response = self::wrap_algebraic_formulas_in_quotes($response);
1502+
} catch (Throwable $t) {
1503+
// TODO: convert to non-capturing catch.
1504+
return ['answer' => 0, 'unit' => $unitcorrect];
1505+
}
14991506
$conversionfactor = 1;
15001507
}
15011508

tests/answer_parser_test.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static function provide_numbers(): array {
9090
[false, '#'],
9191
[false, ''],
9292
[false, '""'],
93+
[false, '"foo"'],
9394
];
9495
}
9596

@@ -110,7 +111,6 @@ public static function provide_algebraic_formulas(): array {
110111
[qtype_formulas::ANSWER_TYPE_NUMERICAL_FORMULA, 'sin(3)-3+exp(4)'],
111112
[qtype_formulas::ANSWER_TYPE_NUMERICAL_FORMULA, '3+exp(4+5)^sin(6+7)'],
112113
[qtype_formulas::ANSWER_TYPE_NUMERIC, '3+4^-(9)'],
113-
[qtype_formulas::ANSWER_TYPE_ALGEBRAIC, '"foo"'],
114114
[qtype_formulas::ANSWER_TYPE_ALGEBRAIC, 'sin(a)-a+exp(b)'],
115115
[qtype_formulas::ANSWER_TYPE_ALGEBRAIC, '3e 10'],
116116
[qtype_formulas::ANSWER_TYPE_NUMERIC, '3e8(4.e8+2)(.5e8/2)5'],
@@ -173,6 +173,7 @@ public static function provide_algebraic_formulas(): array {
173173
[false, '\sin(pi)'],
174174
[false, '1+sin'],
175175
[false, '""'],
176+
[false, '"foo"'],
176177
[false, '5 "foo"'],
177178
];
178179
}

tests/question_test.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,11 +1445,11 @@ public static function provide_formulas_for_wrapping(): array {
14451445
return [
14461446
[['""'], ['']],
14471447
[['"a"'], ['a']],
1448-
[['"a"'], ['"a"']],
1448+
['!', ['"a"']],
14491449
[['"1"', '"2"'], ['1', '2']],
14501450
[['""', '""'], ['', '']],
1451-
[['""', '""'], ['""', '""']],
1452-
[['"1"', '"x"'], ['"1"', '"x"']],
1451+
['!', ['""', '""']],
1452+
['!', ['"1"', '"x"']],
14531453
];
14541454
}
14551455

@@ -1465,7 +1465,17 @@ public function test_wrap_algebraic_formulas_in_quotes($expected, $input): void
14651465
$func = new \ReflectionMethod(qtype_formulas_part::class, 'wrap_algebraic_formulas_in_quotes');
14661466
$func->setAccessible(true);
14671467

1468-
$wrapped = $func->invoke(null, $input);
1468+
$error = false;
1469+
try {
1470+
$wrapped = $func->invoke(null, $input);
1471+
} catch (Exception $e) {
1472+
$error = true;
1473+
}
1474+
1475+
if ($expected === '!') {
1476+
self::assertTrue($error);
1477+
return;
1478+
}
14691479

14701480
foreach ($expected as $i => $exp) {
14711481
self::assertEquals($exp, $wrapped[$i]);

tests/renderer_test.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,19 @@ public function test_render_question_with_algebraic_answer(): void {
213213
$this->check_output_contains_lang_string('correctansweris', 'qtype_formulas', '5 * x^2');
214214
$this->check_output_does_not_contain('a*x^2');
215215

216+
// Submit correct answer in quotes, which will be graded wrong.
217+
$this->start_attempt_at_question($q, 'immediatefeedback', 1);
218+
$this->process_submission(['0_0' => '"5x^2"', '-submit' => 1]);
219+
$this->check_current_state(question_state::$gradedwrong);
220+
$this->check_current_mark(0);
221+
$this->check_current_output(
222+
$this->get_contains_mark_summary(0),
223+
);
224+
$this->render();
225+
$this->check_output_contains_text_input('0_0', '"5x^2"', false);
226+
$this->check_output_contains_lang_string('correctansweris', 'qtype_formulas', '5 * x^2');
227+
$this->check_output_does_not_contain('a*x^2');
228+
216229
// Submit right answer.
217230
$this->start_attempt_at_question($q, 'immediatefeedback', 1);
218231
$this->process_submission(['0_0' => '5*x^2', '-submit' => 1]);

0 commit comments

Comments
 (0)