From 9ca6fd447c595cd0bf948616a252def8d8e838f2 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Thu, 4 Feb 2021 18:17:35 +0000 Subject: [PATCH] MDL-70796 count_words: match the count from LibraOffice & MS Word --- lib/moodlelib.php | 14 +++++------ lib/tests/moodlelib_test.php | 28 ++++++++++++++------- question/type/essay/tests/question_test.php | 2 +- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 66a4d2271be..4d34558a130 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -8378,14 +8378,14 @@ function count_words($string) { $string = strip_tags($string); // Decode HTML entities. $string = html_entity_decode($string); - // Replace underscores (which are classed as word characters) with spaces. - $string = preg_replace('/_/u', ' ', $string); - // Remove any characters that shouldn't be treated as word boundaries. - $string = preg_replace('/[\'"’-]/u', '', $string); - // Remove dots and commas from within numbers only. - $string = preg_replace('/([0-9])[.,]([0-9])/u', '$1$2', $string); - return count(preg_split('/\w\b/u', $string)) - 1; + // Now, the word count is the number of blocks of characters separated + // by any sort of space. That seems to be the definition used by all other systems. + // To be precise about what is considered to separate words: + // * Anything that Unicode considers a 'Separator' + // * Anything that Unicode considers a 'Control character' + // * An em- or en- dash. + return count(preg_split('~[\p{Z}\p{Cc}—–]+~u', $string, -1, PREG_SPLIT_NO_EMPTY)); } /** diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index 1b460683a1d..72bbdbc0a37 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -3808,22 +3808,27 @@ class core_moodlelib_testcase extends advanced_testcase { * @return array of test cases. */ public function count_words_testcases(): array { + // The counts here should match MS Word and Libre Office. return [ [0, ''], [4, 'one two three four'], - [3, "one two three'four"], - [3, 'one+two three’four'], - [2, 'one"two three-four'], - [4, 'one@two three_four'], - [4, 'one\two three/four'], + [1, "a'b"], + [1, '1+1=2'], + [1, ' one-sided '], + [2, 'one two'], + [1, 'email@example.com'], + [2, 'first\part second/part'], [4, '

one two

three four

'], [4, '

one two
three four

'], [4, '

one two
three four

'], // XHTML style. - [4, ' one ... two   three...four '], - [4, 'one.2 3,four'], + [3, ' one ... three '], + [1, 'just...one'], + [3, ' one & three '], + [1, 'just&one'], + [2, 'em—dash'], + [2, 'en–dash'], [4, '1³ £2 €3.45 $6,789'], - [4, 'one—two ブルース カンベッル'], - [4, 'one…two ブルース … カンベッル'], + [2, 'ブルース カンベッル'], // MS word counts this as 11, but we don't handle that yet. [4, '

one two

three four

'], [4, '

one two


three four

'], [4, '

one

four.

'], @@ -3832,7 +3837,12 @@ class core_moodlelib_testcase extends advanced_testcase { [1, '

emphasis.

'], [1, '

emphasis.

'], [2, "one\ntwo"], + [2, "one\rtwo"], + [2, "one\ttwo"], + [2, "one\vtwo"], + [2, "one\ftwo"], [1, "SO42-"], + [6, '4+4=8 i.e. O(1) a,b,c,d I’m black&blue_really'], ]; } diff --git a/question/type/essay/tests/question_test.php b/question/type/essay/tests/question_test.php index b42f2417256..eed59fcdd0b 100644 --- a/question/type/essay/tests/question_test.php +++ b/question/type/essay/tests/question_test.php @@ -295,7 +295,7 @@ class qtype_essay_question_test extends advanced_testcase { public function test_get_validation_error(int $responserequired, int $minwordlimit, int $maxwordlimit, string $expected): void { $question = test_question_maker::make_an_essay_question(); - $response = ['answer' => 'In this essay, I will be testing a function called check_input_word_count().']; + $response = ['answer' => 'One two three four five six seven eight nine ten eleven twelve thirteen fourteen.']; $question->responserequired = $responserequired; $question->minwordlimit = $minwordlimit; $question->maxwordlimit = $maxwordlimit;