Skip to content

Commit fe0c575

Browse files
danghieu1407AnupamaSarjoshi
danghieu1407
authored andcommitted
Crossword: must support apostrophes in the answer
1 parent 5068a30 commit fe0c575

16 files changed

+109
-58
lines changed

amd/build/crossword_grid.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

amd/build/crossword_grid.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

amd/build/crossword_question.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

amd/build/crossword_question.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

amd/src/crossword_grid.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class CrosswordGrid extends CrosswordQuestion {
9696
return;
9797
}
9898
for (let i = 0; i < words.length; i++) {
99-
const answer = words[i].answer.trim().replace(/-|\s/g, '');
99+
const answer = words[i].answer.trim().replace(/-|\s|\'||/g, '');
100100
let row = words[i].startrow + 1;
101101
let column = words[i].startcolumn + 1;
102102
let answerLength = answer.length;

amd/src/crossword_question.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ export class CrosswordQuestion {
7878
coordinates: '',
7979
maxSizeCell: 50,
8080
minSizeCell: 30,
81-
specialCharacters: {hyphen: '-', space: ' '},
81+
specialCharacters: {
82+
hyphen: '-',
83+
space: ' ',
84+
straightsinglequote: '\'',
85+
openingsinglequote: '‘',
86+
closingsinglequote: '’',
87+
},
8288
};
8389
// Merge options.
8490
defaultOption = {...defaultOption, ...options};
@@ -220,7 +226,7 @@ export class CrosswordQuestion {
220226
* Calculate and retreive the letter index.
221227
*
222228
* @param {Number} letterIndex The current letter index.
223-
* @param {Array} ignoreList The ignore list; If the letter contains space or hyphen
229+
* @param {Array} ignoreList The ignore list; If the letter contains space or hyphen or apostrophes.
224230
* @param {Number} wordLength The word length.
225231
* characters. We have to ignore it.
226232
* @return {Number} The new letter index.

classes/answer.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,14 @@ public function generate_answer_hint(): array {
123123
$count = 0;
124124
$answerhint = '';
125125
$ignorecharcterindex = [];
126-
// Allow space and hyphen only.
127-
$listspecialcharacters = ['space' => ' ', 'hyphen' => '-'];
126+
// Allow space, hyphen and apostrophes only.
127+
$listspecialcharacters = [
128+
'space' => ' ',
129+
'hyphen' => '-',
130+
'straightsinglequote' => '\'',
131+
'openingsinglequote' => '',
132+
'closingsinglequote' => '',
133+
];
128134
// Retrieve the answer length (answers that still contain spaces and hyphens).
129135
$length = \core_text::strlen($this->answer);
130136
// Loop the answer by letter.
@@ -134,14 +140,14 @@ public function generate_answer_hint(): array {
134140
// In case the character is a space or a hyphen, we need to handle it further.
135141
if (in_array($letter, array_values($listspecialcharacters))) {
136142
// Get type of the special character.
137-
// It should return 'space' or 'hyphen'.
143+
// It should return 'space' or 'hyphen' or 'apostrophes'.
138144
$character = array_search($letter, $listspecialcharacters);
139145
if ($character < -1) {
140146
continue;
141147
}
142148
// Store index of special character.
143149
$ignorecharcterindex[$character][] = $index;
144-
// Prevents the value 0 when double spaces/hyphen exist.
150+
// Prevents the value 0 when double spaces/hyphen/apostrophes exist.
145151
// E.g: The result should be 1, 2 instead of 1, 0, 2.
146152
if ($count > 0) {
147153
// Generate answer hint.

classes/util.php

+28-3
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ public static function safe_normalize(string $string, int $normalizeform = Norma
5050
}
5151

5252
/**
53-
* Remove the work-break characters '-' and ' ' from an answer.
53+
* Remove the work-break characters '-' and ' ' and apostrophes from an answer.
5454
*
5555
* @param string $text Full answer.
5656
* @return string Answer with just the letters remaining.
5757
*/
5858
public static function remove_break_characters(string $text): string {
59-
// Remove hyphen and space from text.
60-
return preg_replace('/-|\s/', '', $text);
59+
// Remove hyphen, space and apostrophes from text.
60+
return preg_replace('/-|\s|\'|‘|’/', '', $text);
6161
}
6262

6363
/**
@@ -135,4 +135,29 @@ public static function update_answer_list(array $answers): array {
135135

136136
return $answerresponse;
137137
}
138+
139+
/**
140+
* Convert smart quotes to straight quotes, handling recursion for arrays.
141+
*
142+
* @param mixed $input Form input data can be a string / number / array.
143+
* @return mixed
144+
*/
145+
public static function convert_quote_to_straight_quote(mixed $input): mixed {
146+
if (is_array($input)) {
147+
// If input is an array, process each element recursively.
148+
foreach ($input as $key => $subvalue) {
149+
$input[$key] = self::convert_quote_to_straight_quote($subvalue);
150+
}
151+
} else if (is_string($input)) {
152+
// If input is a string, convert quotes.
153+
// Replace smart quotes with straight quotes.
154+
$input = str_replace(
155+
['&lsquo;', '&rsquo;', '&ldquo;', '&rdquo;', '', '', '', ''], // HTML entities and smart quotes.
156+
["'", "'", '"', '"', "'", "'", '"', '"'], // Corresponding straight quotes.
157+
$input
158+
);
159+
}
160+
161+
return $input;
162+
}
138163
}

edit_crossword_form.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ protected function data_preprocessing_words(stdClass $question): stdClass {
315315
$question->numrows = $question->options->numrows;
316316
$question->numcolumns = $question->options->numcolumns;
317317
$question->accentgradingtype = $question->options->accentgradingtype;
318+
$question->quotematching = $question->options->quotematching;
318319
$question->accentpenalty = $question->options->accentpenalty;
319320
}
320321
$question->answer = $answer;
@@ -332,8 +333,11 @@ public function validation($data, $files): array {
332333
$answercount = 0;
333334
$answers = $data['answer'];
334335
$clues = $data['clue'];
336+
if (isset($data['quotematching']) && $data['quotematching'] == 0) {
337+
$data = util::convert_quote_to_straight_quote($data);
338+
}
335339
// phpcs:ignore
336-
$regex = '/([^\p{L}\p{N}\-\s]+)/u';
340+
$regex = '/([^\p{L}\p{N}\s\-‘’\']+)/u';
337341
$except = [];
338342
for ($i = 0; $i < count($answers); $i++) {
339343
// Skip the invalid word.

lang/en/qtype_crossword.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
$string['down'] = 'Down';
3939
$string['inputlabel'] = '{$a->number} {$a->orientation}. {$a->clue} Answer length {$a->length}';
4040
$string['missingresponse'] = '-';
41-
$string['mustbealphanumeric'] = 'The answer must be alphanumeric characters only';
41+
$string['mustbealphanumeric'] = 'The answer must contain alphanumeric characters. Special characters allowed are hyphens and apostrophes.';
4242
$string['notenoughwords'] = 'This type of question requires at least {$a} word';
4343
$string['numberofcolumns'] = 'Number of columns';
4444
$string['numberofrows'] = 'Number of rows';

questiontype.php

+1-26
Original file line numberDiff line numberDiff line change
@@ -94,31 +94,6 @@ protected function create_default_options($question): object {
9494
return $options;
9595
}
9696

97-
/**
98-
* Convert smart quotes to straight quotes, handling recursion for arrays.
99-
*
100-
* @param mixed $input Form input data can be a string / number / array.
101-
* @return mixed
102-
*/
103-
public function convert_quote_to_straight_quote(mixed $input): mixed {
104-
if (is_array($input)) {
105-
// If input is an array, process each element recursively.
106-
foreach ($input as $key => $subvalue) {
107-
$input[$key] = $this->convert_quote_to_straight_quote($subvalue);
108-
}
109-
} else if (is_string($input)) {
110-
// If input is a string, convert quotes.
111-
// Replace smart quotes with straight quotes.
112-
$input = str_replace(
113-
['&lsquo;', '&rsquo;', '&ldquo;', '&rdquo;', '', '', '', ''], // HTML entities and smart quotes.
114-
["'", "'", '"', '"', "'", "'", '"', '"'], // Corresponding straight quotes.
115-
$input
116-
);
117-
}
118-
119-
return $input;
120-
}
121-
12297
#[\Override]
12398
public function save_question($question, $form) {
12499
// For MVP version, default mark will be set automatically.
@@ -133,7 +108,7 @@ public function save_question($question, $form) {
133108
if (!$form->quotematching) {
134109
foreach ($form as $property => $value) {
135110
if (isset($value)) {
136-
$form->{$property} = $this->convert_quote_to_straight_quote($value);
111+
$form->{$property} = util::convert_quote_to_straight_quote($value);
137112
}
138113
}
139114
}

tests/answer_test.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ final class answer_test extends \advanced_testcase {
3838
*/
3939
public function test_is_correct(array $answerdata): void {
4040
// Create a normal crossword question.
41-
$q = \test_question_maker::make_question('crossword', 'normal_with_hyphen_and_space');
41+
$q = \test_question_maker::make_question('crossword', 'normal_with_hyphen_space_and_apostrophes');
4242
foreach ($q->answers as $key => $answer) {
4343
$this->assertTrue($answer->is_correct($answerdata[$key]));
4444
}
@@ -52,10 +52,10 @@ public function test_is_correct(array $answerdata): void {
5252
public static function is_correct_test_provider(): array {
5353
return [
5454
'Normal case' => [
55-
['TIM BERNERS-LEE', 'GORDON BROWN', 'DAVID ATTENBOROUGH'],
55+
['TIM BERNERS-LEE', 'GORDON BROWN', 'DAVID ATTENBOROUGH', "ALBERT EINSTEIN'S THEORY"],
5656
],
5757
'With Underscore' => [
58-
['TIM_BERNERS-LEE', 'GORDON_BROWN', 'DAVID_ATTENBOROUGH'],
58+
['TIM_BERNERS-LEE', 'GORDON_BROWN', 'DAVID_ATTENBOROUGH', "ALBERT_EINSTEIN'S_THEORY"],
5959
],
6060
];
6161
}
@@ -67,11 +67,12 @@ public static function is_correct_test_provider(): array {
6767
*/
6868
public function test_generate_answer_hint(): void {
6969
// Create a normal crossword question.
70-
$q = \test_question_maker::make_question('crossword', 'normal_with_hyphen_and_space');
70+
$q = \test_question_maker::make_question('crossword', 'normal_with_hyphen_space_and_apostrophes');
7171
$expecteddata = [
7272
['3, 7-3', ['space' => [3], 'hyphen' => [11]]],
7373
['6, 5', ['space' => [6]]],
7474
['5, 12', ['space' => [5]]],
75+
['6, 8\'1, 6', ['space' => [6, 17], 'straightsinglequote' => [15]]],
7576
];
7677
foreach ($q->answers as $key => $answer) {
7778
$this->assertEquals($expecteddata[$key], $answer->generate_answer_hint());

tests/behat/preview.feature

+9-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Feature: Preview a Crossword question
2424
| Test questions | crossword | crossword-003 | different_codepoint |
2525
| Test questions | crossword | crossword-004 | sampleimage |
2626
| Test questions | crossword | crossword-005 | clear_incorrect_response |
27-
| Test questions | crossword | crossword-006 | normal_with_hyphen_and_space |
27+
| Test questions | crossword | crossword-006 | normal_with_hyphen_space_and_apostrophes |
2828
| Test questions | crossword | crossword-007 | accept_wrong_accents_but_subtract_point |
2929
| Test questions | crossword | crossword-008 | accept_wrong_accents_but_not_subtract_point |
3030
| Test questions | crossword | crossword-009 | not_accept_wrong_accents |
@@ -226,14 +226,16 @@ Feature: Preview a Crossword question
226226
And the field "3 Across. Where is the Leaning Tower of Pisa? Answer length 5" matches value "ITALY"
227227

228228
@javascript
229-
Scenario: For answers that contain spaces or hyphens, the answer hint will not count those characters.
229+
Scenario: For answers that contain spaces or hyphens or apostrophes, the answer hint will not count those characters.
230230
When I am on the "crossword-006" "core_question > preview" page logged in as teacher
231231
And I expand all fieldsets
232232
And I set the field "How questions behave" to "Interactive with multiple tries"
233233
And I press "id_saverestart"
234234
Then I should see "(5, 12)"
235235
And I should see "(6, 5)"
236236
And I should see "(3, 7-3)"
237+
And I should see "(6, 8'1, 6)"
238+
And I should see "(7, 7’1, 4)"
237239

238240
@javascript
239241
Scenario: Preview a Crossword question and submit a correct response with mobile input.
@@ -329,6 +331,11 @@ Feature: Preview a Crossword question
329331
And I set the field "1 Down. Engineer, computer scientist and inventor of the World Wide Web? Answer length 3, 7-3" to "TIMBERNERSLEE"
330332
And I set the field "2 Down. Former Prime Minister of the United Kingdom? Answer length 6, 5" to "GORDONBROWN"
331333
And I set the field "3 Across. British broadcaster and naturalist, famous for his voice-overs of nature programmes? Answer length 5, 12" to "DAVIDATTENBOROUGH"
334+
And I set the field "3 Across. British broadcaster and naturalist, famous for his voice-overs of nature programmes? Answer length 5, 12" to "DAVIDATTENBOROUGH"
335+
And I set the field "4 Down. Physicist known for black hole research and author of \"A Brief History of Time\"? Answer length 7, 7’1, 4" to "STEPHENHAWKINGSWORK"
336+
And I set the field "5 Down. Famous physicist known for his theory of relativity? Answer length 6, 8'1, 6" to "ALBERTEINSTEINSTHEORY"
332337
Then the field "1 Down. Engineer, computer scientist and inventor of the World Wide Web? Answer length 3, 7-3" matches value "TIM BERNERS-LEE"
333338
And the field "2 Down. Former Prime Minister of the United Kingdom? Answer length 6, 5" matches value "GORDON BROWN"
334339
And the field "3 Across. British broadcaster and naturalist, famous for his voice-overs of nature programmes? Answer length 5, 12" matches value "DAVID ATTENBOROUGH"
340+
And the field "4 Down. Physicist known for black hole research and author of \"A Brief History of Time\"? Answer length 7, 7’1, 4" matches value "STEPHEN HAWKING’S WORK"
341+
And the field "5 Down. Famous physicist known for his theory of relativity? Answer length 6, 8'1, 6" matches value "ALBERT EINSTEIN'S THEORY"

tests/form_test.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public static function form_validation_testcases(): array {
115115
'answer[0]' => get_string('overflowposition', 'qtype_crossword'),
116116
],
117117
],
118-
'The answer must be alphanumeric characters only' => [
118+
'The answer must contain alphanumeric characters' => [
119119
[
120120
'noanswers' => 3,
121121
'answer' => ['Speci@al char*', 'BBB', 'CCC'],

tests/helper.php

+35-8
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class qtype_crossword_test_helper extends question_test_helper {
4545
public function get_test_questions(): array {
4646
// phpcs:disable NormalizedArrays.Arrays.CommaAfterLast.MissingMultiLine
4747
return ['normal', 'unicode', 'different_codepoint', 'sampleimage',
48-
'clear_incorrect_response', 'normal_with_hyphen_and_space',
48+
'clear_incorrect_response', 'normal_with_hyphen_space_and_apostrophes',
4949
'not_accept_wrong_accents', 'accept_wrong_accents_but_subtract_point',
5050
'accept_wrong_accents_but_not_subtract_point'];
5151
// phpcs:enable
@@ -488,7 +488,7 @@ public static function question_edit_contexts(\context $context): object {
488488
*
489489
* @return qtype_crossword_question
490490
*/
491-
public function make_crossword_question_normal_with_hyphen_and_space() {
491+
public function make_crossword_question_normal_with_hyphen_space_and_apostrophes() {
492492
question_bank::load_question_definition_classes('crossword');
493493
$cw = new qtype_crossword_question();
494494
test_question_maker::initialise_a_question($cw);
@@ -544,6 +544,19 @@ public function make_crossword_question_normal_with_hyphen_and_space() {
544544
'feedbackformat' => FORMAT_HTML,
545545
'answernumber' => 3,
546546
],
547+
(object) [
548+
'id' => 4,
549+
'questionid' => 1,
550+
'clue' => "Famous physicist known for his theory of relativity?",
551+
'clueformat' => FORMAT_HTML,
552+
'answer' => "ALBERT EINSTEIN'S THEORY",
553+
'startcolumn' => 5,
554+
'startrow' => 2,
555+
'orientation' => 1,
556+
'feedback' => '',
557+
'feedbackformat' => FORMAT_HTML,
558+
'answernumber' => 4,
559+
],
547560
];
548561

549562
foreach ($answerslist as $answer) {
@@ -566,7 +579,7 @@ public function make_crossword_question_normal_with_hyphen_and_space() {
566579
/**
567580
* Makes a normal crossword question with answer contains hyphen and space.
568581
*/
569-
public function get_crossword_question_form_data_normal_with_hyphen_and_space() {
582+
public function get_crossword_question_form_data_normal_with_hyphen_space_and_apostrophes() {
570583
$fromform = new stdClass();
571584
$fromform->name = 'Cross word question';
572585
$fromform->questiontext = ['text' => 'Crossword question text', 'format' => FORMAT_HTML];
@@ -575,7 +588,13 @@ public function get_crossword_question_form_data_normal_with_hyphen_and_space()
575588
$fromform->incorrectfeedback = ['text' => 'Incorrect feedback.', 'format' => FORMAT_HTML];
576589
$fromform->penalty = 1;
577590
$fromform->defaultmark = 1;
578-
$fromform->answer = ['TIM BERNERS-LEE', 'GORDON BROWN', 'DAVID ATTENBOROUGH'];
591+
$fromform->answer = [
592+
'TIM BERNERS-LEE',
593+
'GORDON BROWN',
594+
'DAVID ATTENBOROUGH',
595+
'ALBERT EINSTEIN\'S THEORY',
596+
'STEPHEN HAWKING’S WORK',
597+
];
579598
$fromform->clue = [
580599
[
581600
'text' => 'Engineer, computer scientist and inventor of the World Wide Web?',
@@ -589,15 +608,23 @@ public function get_crossword_question_form_data_normal_with_hyphen_and_space()
589608
'text' => 'British broadcaster and naturalist, famous for his voice-overs of nature programmes?',
590609
'format' => FORMAT_HTML,
591610
],
611+
[
612+
'text' => 'Famous physicist known for his theory of relativity?',
613+
'format' => FORMAT_HTML,
614+
],
615+
[
616+
'text' => 'Physicist known for black hole research and author of "A Brief History of Time"?',
617+
'format' => FORMAT_HTML,
618+
],
592619
];
593-
$fromform->orientation = [1, 1, 0];
594-
$fromform->startrow = [0, 0, 1];
595-
$fromform->startcolumn = [3, 11, 0];
620+
$fromform->orientation = [1, 1, 0, 1, 1];
621+
$fromform->startrow = [0, 0, 1, 2, 2];
622+
$fromform->startcolumn = [3, 11, 0, 5, 2];
596623
$fromform->numrows = 13;
597624
$fromform->numcolumns = 17;
598625
$fromform->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT;
599626
$fromform->accentpenalty = 0;
600-
$fromform->quotematching = 0;
627+
$fromform->quotematching = 1;
601628
return $fromform;
602629
}
603630

tests/question_type_test.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ public function test_convert_quote_to_straight_quote(): void {
250250
'arrayelement' => ['hasrecursion' => '‘ single smart quote ’ and “ double smart quote ”'],
251251
'test' => '&lsquo; HTML entities single quote &rsquo; and &ldquo; HTML entities double quote &rdquo;',
252252
];
253-
$result = $this->qtype->convert_quote_to_straight_quote($data);
253+
$result = util::convert_quote_to_straight_quote($data);
254254
$this->assertEquals($result['arrayelement']['hasrecursion'], "' single smart quote ' and " . '" double smart quote "');
255255
$this->assertEquals($result['test'], "' HTML entities single quote ' and " . '" HTML entities double quote "');
256256
}

0 commit comments

Comments
 (0)