• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdeedu API Reference
  • KDE Home
  • Contact Us
 

parley

  • sources
  • kde-4.14
  • kdeedu
  • parley
  • src
  • practice
answervalidatorold.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  Copyright 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net>
3  ***************************************************************************/
4 
5 /***************************************************************************
6  * *
7  * This program is free software; you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation; either version 2 of the License, or *
10  * (at your option) any later version. *
11  * *
12  ***************************************************************************/
13 #include "answervalidatorold.h"
14 
15 #include "prefs.h"
16 
17 #include <keduvocexpression.h>
18 #include <keduvocdocument.h>
19 #include <keduvocidentifier.h>
20 
21 #include <KLocalizedString>
22 #include <sonnet/speller.h>
23 #include <kdebug.h>
24 
27 namespace ParleyStringHandlerOld
28 {
29 QString stripAccents(const QString& original)
30 {
31  QString noAccents;
32  QString decomposed = original.normalized(QString::NormalizationForm_D);
33  for (int i = 0; i < decomposed.length(); ++i) {
34  if (decomposed[i].category() != 1) {
35  noAccents.append(decomposed[i]);
36  }
37  }
38  kDebug() << original << " without accents: " << noAccents;
39  return noAccents;
40 }
41 }
42 
43 
44 const double AnswerValidatorOld::LEVENSHTEIN_THRESHOLD = 0.2;
45 const double AnswerValidatorOld::UNRELATED_WORD_GRADE = 0.0;
46 const double AnswerValidatorOld::FALSE_FRIEND_GRADE = 0.0;
47 const double AnswerValidatorOld::SPELLING_MISTAKE_PER_LETTER_PUNISHMENT = 0.2;
48 const double AnswerValidatorOld::CAPITALIZATION_MISTAKE_PUNISHMENT = 0.1;
49 const double AnswerValidatorOld::ACCENT_MISTAKE_PUNISHMENT = 0.1;
50 const double AnswerValidatorOld::WRONG_ARTICLE_PUNISHMENT = 0.1;
51 
52 AnswerValidatorOld::AnswerValidatorOld(KEduVocDocument* doc)
53 {
54  m_doc = doc;
55  m_entry = 0;
56  m_speller = 0;
57  m_spellerAvailable = false;
58 }
59 
60 AnswerValidatorOld::~AnswerValidatorOld()
61 {
62  delete m_speller;
63 }
64 
65 
66 void AnswerValidatorOld::setLanguage(int translation)
67 {
68  m_translation = translation;
69 
70  // default: try locale
71  if (!m_speller) {
72  m_speller = new Sonnet::Speller(m_doc->identifier(translation).locale());
73  } else {
74  m_speller->setLanguage(m_doc->identifier(translation).locale());
75  }
76 
77  // we might succeed with language name instead.
78  if (!m_speller->isValid()) {
79  m_speller->setLanguage(m_doc->identifier(translation).name());
80  }
81 
82  if (!m_speller->isValid()) {
83  kDebug() << "No spellchecker for current language found: " << m_doc->identifier(m_translation).locale();
84  kDebug() << "Available dictionaries: " << m_speller->availableLanguages()
85  << "\n names: " << m_speller->availableLanguageNames()
86  << "\n backends: " << m_speller->availableBackends();
87  m_spellerAvailable = false;
88  } else {
89  m_spellerAvailable = true;
90  }
91 }
92 
93 
94 void AnswerValidatorOld::setTestEntry(TestEntry * entry)
95 {
96  m_entry = entry;
97  if (m_entry) {
98  m_solution = m_entry->entry()->translation(m_translation)->text();
99  }
100 }
101 
102 int AnswerValidatorOld::levenshteinDistance(const QString& s, const QString& t)
103 {
104  int m = s.length();
105  int n = t.length();
106 
107  int dWidth = m + 1 ;
108 
109  // make sure the matrix is big enough
110  if (m_d.size() < (m + 1) * (n + 1)) {
111  m_d.resize((m + 1) * (n + 1));
112  }
113 
114  int i;
115  int j;
116 
117  // init 0..m, 0..n as starting values - distance to ""
118  for (i = 0; i <= m; i++) {
119  m_d[i + 0 * dWidth] = i;
120  }
121  for (j = 0; j <= n; j++) {
122  m_d[0 + j * dWidth] = j;
123  }
124 
125  int cost;
126  for (i = 1; i <= m; i++) {
127  for (j = 1; j <= n; j++) {
128  if (s[i - 1] == t[j - 1]) {
129  // if current char is equal, no cost for substitution
130  cost = 0;
131  } else {
132  cost = 1;
133  }
134  m_d[i + j * dWidth] = qMin(qMin( // min of three possibilities
135  m_d[i - 1 + (j) * dWidth] + 1, // deletion
136  m_d[i + (j - 1) * dWidth] + 1), // insertion
137  m_d[i - 1 + (j - 1) * dWidth] + cost); // substitution
138  }
139  }
140  return m_d[m + n * dWidth];
141 }
142 
143 bool AnswerValidatorOld::spellcheckerMisspelled(const QString& userAnswer)
144 {
145  if (!m_spellerAvailable) {
146  return true;
147  }
148  return m_speller->isMisspelled(userAnswer);
149 }
150 
151 bool AnswerValidatorOld::spellcheckerInSuggestionList(const QString& solution, const QString& userAnswer)
152 {
153  if (!m_spellerAvailable) {
154  return false;
155  }
156 
157  kDebug() << "entered: " << userAnswer << " misspelled: " << m_speller->isMisspelled(userAnswer) << " suggestions: " << m_speller->suggest(userAnswer);
158 
159  if (m_speller->suggest(userAnswer).contains(solution)) {
160  kDebug() << "I think this is a spelling error.";
161  return true;
162  } else {
163  kDebug() << "No, this is a different word I think.";
164  return false;
165  }
166 }
167 
168 
169 void AnswerValidatorOld::simpleCorrector()
170 {
171  kDebug() << "simpleCorrector";
172  if (m_entry == 0) {
173  kError() << "No entry set, cannot verify answer.";
174  return;
175  }
176 
178  if (m_solution == m_userAnswer) {
179  m_entry->setLastErrors(TestEntry::Correct);
180  m_entry->setLastPercentage(1.0);
181 // m_htmlCorrection = i18n("Your answer is right!");
182  kDebug() << "right";
183  return;
184  }
185 
186  TestEntry::ErrorTypes errorTypes = TestEntry::UnknownMistake;
187 
188  foreach(KEduVocTranslation * synonym, m_entry->entry()->translation(m_translation)->synonyms()) {
189  if (synonym->text() == m_userAnswer) {
190  m_entry->setLastErrors(TestEntry::Synonym);
191  if (Prefs::countSynonymsAsCorrect()) {
192  m_entry->setLastPercentage(1.0);
193  } else {
194  m_entry->setLastPercentage(0.0); // bit harsh maybe
195  }
196  return;
197  }
198  }
199 
200  int levensthein = levenshteinDistance(m_solution, m_userAnswer);
201 
202  m_entry->setLastPercentage(1.0 - ((double)levensthein / qMax(m_solution.length(), m_userAnswer.length())));
203 
204  kDebug() << "simpleCorrector" << m_userAnswer << "-" << m_solution << "has levensthein distance: " << levensthein << " grade: " << m_entry->lastPercentage();
205 }
206 
207 
208 void AnswerValidatorOld::defaultCorrector()
209 {
212  if (m_solution == m_userAnswer) {
213  m_entry->setLastErrors(TestEntry::Correct);
214  m_entry->setLastPercentage(1.0);
215  return;
216  }
217 
218  if (m_userAnswer.isEmpty()) {
219  m_entry->setLastErrors(TestEntry::Empty);
220  m_entry->setLastPercentage(0.0);
221  return;
222  }
223 
224  if (m_solution.toLower() == m_userAnswer.toLower()) {
225  if (Prefs::ignoreCapitalizationMistakes()) {
226  m_entry->setLastPercentage(1.0);
227  } else {
228  m_entry->setLastPercentage(1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT);
229  }
230  m_entry->setLastErrors(TestEntry::CapitalizationMistake);
231  return ;
232  }
233 
234  if (ParleyStringHandlerOld::stripAccents(m_solution) == ParleyStringHandlerOld::stripAccents(m_userAnswer)) {
235  if (Prefs::ignoreAccentMistakes()) {
236  m_entry->setLastPercentage(1.0);
237  } else {
238  m_entry->setLastPercentage(1.0 - ACCENT_MISTAKE_PUNISHMENT);
239  }
240  m_entry->setLastErrors(TestEntry::AccentMistake);
241  return ;
242  }
243 
244  foreach(KEduVocTranslation * synonym, m_entry->entry()->translation(m_translation)->synonyms()) {
245  if (synonym->text() == m_userAnswer) {
246  m_entry->setLastErrors(TestEntry::Synonym);
247  if (Prefs::countSynonymsAsCorrect()) {
248  m_entry->setLastPercentage(1.0);
249  } else {
250  m_entry->setLastPercentage(0.0); // bit harsh maybe
251  }
252  return;
253  }
254  }
255 
256  int numberSolutionWords = m_solution.simplified().split(' ').count();
257  int numberAnswerWords = m_userAnswer.simplified().split(' ').count();
258 
259  if (numberSolutionWords == 1) {
260  double grade;
261  TestEntry::ErrorTypes errors;
262  wordCompare(m_solution, m_userAnswer, grade, errors);
263  m_entry->setLastPercentage(grade);
264  m_entry->setLastErrors(errors);
265  return;
266  }
267 
268  if (numberSolutionWords == 2) {
269  // could be noun + article
270  QStringList solutionWords = m_solution.simplified().split(' ');
271 
272  if (m_translation >= 0) {
273  if (m_doc->identifier(m_translation).article().isArticle(solutionWords.value(0))) {
274  // yes, the answer is an article + noun
275  if (numberAnswerWords == 1) {
276  double percent;
277  TestEntry::ErrorTypes errors;
278  wordCompare(solutionWords.value(1), m_userAnswer.simplified(), percent, errors);
279  m_entry->setLastPercentage(qMax(percent - WRONG_ARTICLE_PUNISHMENT, 0.0));
280  m_entry->setLastErrors(errors | TestEntry::ArticleMissing);
281  return;
282  }
283  if (numberAnswerWords == 2) {
284  double percent;
285  TestEntry::ErrorTypes errors;
286  wordCompare(solutionWords.value(1), m_userAnswer.simplified().split(' ').value(1), percent,
287  errors);
288 
289  if (m_userAnswer.simplified().split(' ').value(0) == solutionWords.value(0)) {
290  m_entry->setLastErrors(errors);
291  } else {
292  m_entry->setLastPercentage(qMax(percent - WRONG_ARTICLE_PUNISHMENT, 0.0));
293  m_entry->setLastErrors(errors | TestEntry::ArticleWrong);
294  }
295  return;
296  }
297  }
298  }
299  }
300 
301  // ok, more than one word (or one+article)
302  sentenceAnalysis();
303 }
304 
305 
306 
307 
308 void AnswerValidatorOld::checkUserAnswer(const QString & userAnswer)
309 {
310  if (m_entry == 0) {
311  kError() << "Error: no entry set for validator.";
312  return;
313  }
314 
315  m_userAnswer = userAnswer;
316 
317 // simpleCorrector();
318  defaultCorrector();
319 }
320 
321 void AnswerValidatorOld::checkUserAnswer(const QString & solution, const QString & userAnswer, const QString& language)
322 {
323  kDebug() << "CheckUserAnswer with two strings. The one string version is preferred.";
324  if (!language.isEmpty()) {
325 
326  } else {
327  m_spellerAvailable = false;
328  }
329 
330  m_solution = solution;
331 
332  checkUserAnswer(userAnswer);
333 }
334 
335 
336 void AnswerValidatorOld::wordCompare(const QString & solution, const QString & userWord, double& grade, TestEntry::ErrorTypes& errorTypes)
337 {
339 
340  // nothing to be done here if it's right
341  if (solution == userWord) {
342  grade = 1.0;
343  errorTypes = TestEntry::Correct;
344  return;
345  }
346 
347  if (solution.toLower() == userWord.toLower()) {
348  if (Prefs::ignoreCapitalizationMistakes()) {
349  grade = 1.0;
350  } else {
351  grade = 1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT;
352  }
353  errorTypes = TestEntry::CapitalizationMistake;
354  return ;
355  }
356 
357  if (ParleyStringHandlerOld::stripAccents(solution) == ParleyStringHandlerOld::stripAccents(userWord)) {
358  if (Prefs::ignoreAccentMistakes()) {
359  grade = 1.0;
360  } else {
361  grade = 1.0 - ACCENT_MISTAKE_PUNISHMENT;
362  }
363  errorTypes = TestEntry::AccentMistake;
364  return ;
365  }
366 
367  int levenshtein = levenshteinDistance(solution, userWord);
368 
369  if (m_spellerAvailable) {
370  bool inSuggestions = false;
371  bool isMisspelled = m_speller->isMisspelled(userWord);
372  if (m_speller->suggest(userWord).contains(solution)) {
373  inSuggestions = true;
374  }
375  // probably misspelled
376  if (isMisspelled && inSuggestions) {
377  grade = 1.0 - qMax(levenshtein * SPELLING_MISTAKE_PER_LETTER_PUNISHMENT, 1.0);
378  errorTypes = TestEntry::SpellingMistake;
379  return;
380  }
381  // this is a different word but sounds similar!
382  if (!isMisspelled && inSuggestions) {
383  grade = FALSE_FRIEND_GRADE;
384 // htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">NOOOO! That was a false friend!</font> ");
385  errorTypes = errorTypes = TestEntry::FalseFriend;
386  return ;
387  }
388  // unrelated word
389  if (!isMisspelled && !inSuggestions) {
390  grade = UNRELATED_WORD_GRADE;
391 // htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">Do you have any idea what you are talking about? (Wrong word, you spelled it correct I guess.)</font> ");
392  errorTypes = TestEntry::UnrelatedWord;
393  return;
394  }
395  // complete nonsense, unless levenshtein comes to the rescue
396  if (isMisspelled && !inSuggestions) {
397  if (((double)levenshtein / qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD) {
398 // htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">Seems like you got the spellig wrong.</font> ");
399  errorTypes = TestEntry::SpellingMistake;
400  return;
401  } else {
402 // htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">I don't know that word and it is not similar to the solution.</font> ");
403  errorTypes = TestEntry::UnknownMistake;
404  return;
405  }
406  }
407  } else {
408  if (((double)levenshtein / qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD) {
409  grade = 1.0 - ((double)levenshtein / qMax(solution.length(), userWord.length()));
410 // htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">No spellchecker, but seems like a spelling error.</font> ");
411  errorTypes = TestEntry::SpellingMistake;
412  return;
413  } else {
414  grade = 1.0 - ((double)levenshtein / qMax(solution.length(), userWord.length()));
415 // htmlCorrection = QString::fromLatin1("<font color=\"#8C1818\">No dictionary and no clue.</font> ");
416  errorTypes = TestEntry::UnknownMistake;
417  return;
418  }
419  }
420 
421  errorTypes = TestEntry::UnknownMistake;
422  return;
423 }
424 
425 
426 void AnswerValidatorOld::sentenceAnalysis()
427 {
428  QStringList solutionWords;
429  QStringList userAnswerWords;
430 
431  // filter !?.,;:¿ etc and throw them away
432  solutionWords = m_solution.simplified().split(QRegExp("\\W"), QString::SkipEmptyParts);
433  userAnswerWords = m_userAnswer.simplified().split(QRegExp("\\W"), QString::SkipEmptyParts);
434  kDebug() << "Solution words: " << solutionWords;
435  kDebug() << "Answer: " << userAnswerWords;
436 
437  QStringList correctWords;
438  QStringList wrongWords;
439 
440  for (int i = 0; i < userAnswerWords.count(); i++) {
441  int pos = solutionWords.indexOf(userAnswerWords.value(i));
442  if (pos >= 0) {
443  correctWords.append(userAnswerWords.value(i));
444  solutionWords.removeAt(pos);
445  userAnswerWords.removeAt(i);
446  } else {
447  wrongWords.append(userAnswerWords.value(i));
448  }
449  }
450 
451  kDebug() << " remaining: solution: " << solutionWords.count()
452  << "user: " << userAnswerWords.count();
453 
454  QList< QPair<QString, QString> > pairs = bestPairs(solutionWords, userAnswerWords);
455 
456  for (int i = 0; i < pairs.count(); i++) {
457  kDebug() << "Possible pair: " << pairs.value(i).first << " and " << pairs.value(i).second;
458  }
459 
460 
461  QString correction;
462  correction.append("Correct: ");
463  foreach(const QString & correctWord, correctWords) {
464  correction.append(QString::fromLatin1("<font color=\"#188C18\">") + correctWord + QString::fromLatin1("</font> "));
465  }
466 
467  correction.append(" Wrong: ");
468  foreach(const QString & wrongWord, wrongWords) {
469  correction.append(QString::fromLatin1("<font color=\"#8C1818\">") + wrongWord + QString::fromLatin1("</font> "));
470  }
471 
472  int levenshtein = levenshteinDistance(m_solution, m_userAnswer);
473 
474  kDebug() << correction;
475  kDebug() << "IMPLEMENT ME TO ACTUALLY EVALUATE THE ABOVE AND GENERATE A CONFIDENCE!";
476  m_entry->setLastPercentage(1.0 - ((double)levenshtein / qMax(m_solution.length(), m_userAnswer.length())));
477  m_entry->setLastErrors(TestEntry::UnknownMistake);
478 }
479 
480 
481 QList< QPair < QString , QString > > AnswerValidatorOld::bestPairs(const QStringList& solutionWords , const QStringList& userAnswerWords)
482 {
483  int nSol = solutionWords.count();
484  int nUser = userAnswerWords.count();
485 
486  QByteArray d;
487  d.resize(nSol * nUser);
488 
489  QList< QPair < QString , QString > > pairList;
490 
491  // matrix of levenshteinDistances
492  for (int i = 0; i < nSol; i++) {
493  for (int j = 0; j < nUser; j++) {
494  d[i + nSol * j] = levenshteinDistance(solutionWords.value(i), userAnswerWords.value(j));
495  }
496  }
497 
498  int MAX_LEVENSHTEIN = 5;
499 
500  // check if another pair is possible
501  int min;
502  int posSol = -1;
503  int posUser = -1;
504  do {
505  min = MAX_LEVENSHTEIN;
506  for (int i = 0; i < nSol; i++) {
507  for (int j = 0; j < nUser; j++) {
508  if (d.at(i + j * nSol) < min) {
509  min = d.at(i + j * nSol);
510  posSol = i;
511  posUser = j;
512  }
513  }
514  }
515  if (min < MAX_LEVENSHTEIN && posUser != -1 && posSol != -1) {
516  pairList.append(qMakePair(solutionWords.value(posSol), userAnswerWords.value(posUser)));
517 
518  // taken
519  d[posSol + posUser * nSol] = MAX_LEVENSHTEIN;
520  }
521  } while (min < MAX_LEVENSHTEIN);
522 
523  return pairList;
524 }
525 
526 
527 bool AnswerValidatorOld::spellcheckerAvailable()
528 {
529  return m_spellerAvailable;
530 }
TestEntry::ArticleMissing
< solution is correct with the article interchanged
Definition: testentry.h:30
TestEntry::SpellingMistake
Definition: testentry.h:26
AnswerValidatorOld::setTestEntry
void setTestEntry(TestEntry *entry)
Sets the current entry.
Definition: answervalidatorold.cpp:94
answervalidatorold.h
TestEntry::entry
KEduVocExpression * entry() const
Definition: testentry.cpp:149
QString::append
QString & append(QChar ch)
TestEntry::lastPercentage
double lastPercentage()
Definition: testentry.cpp:144
TestEntry::ArticleWrong
< an accent is missing or wrong (é)
Definition: testentry.h:29
QByteArray
QByteArray::at
char at(int i) const
TestEntry::FalseFriend
< solution is correct with the article missing
Definition: testentry.h:31
TestEntry::Empty
< a synonym (may be correct)
Definition: testentry.h:33
QString::split
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QList::removeAt
void removeAt(int i)
QString::simplified
QString simplified() const
TestEntry::Synonym
< a false friend
Definition: testentry.h:32
prefs.h
AnswerValidatorOld::checkUserAnswer
void checkUserAnswer(const QString &userAnswer)
Checks the user answer.
Definition: answervalidatorold.cpp:308
QString::normalized
QString normalized(NormalizationForm mode) const
QList::value
T value(int i) const
QByteArray::resize
void resize(int size)
QRegExp
TestEntry::AccentMistake
< capitalization error (whAt)
Definition: testentry.h:28
QList::count
int count(const T &value) const
QList::append
void append(const T &value)
TestEntry::Correct
< the part that was entered is right, but not complete
Definition: testentry.h:36
AnswerValidatorOld::setLanguage
void setLanguage(int translation)
Set the language for the spell checker.
Definition: answervalidatorold.cpp:66
TestEntry::setLastPercentage
void setLastPercentage(double percent)
Definition: testentry.cpp:139
QString::isEmpty
bool isEmpty() const
ParleyStringHandlerOld::stripAccents
QString stripAccents(const QString &original)
Definition: answervalidatorold.cpp:29
AnswerValidatorOld::spellcheckerAvailable
bool spellcheckerAvailable()
Definition: answervalidatorold.cpp:527
TestEntry::UnrelatedWord
< empty answer string
Definition: testentry.h:34
AnswerValidatorOld::~AnswerValidatorOld
~AnswerValidatorOld()
Definition: answervalidatorold.cpp:60
QString
QList
QStringList
QString::toLower
QString toLower() const
TestEntry::setLastErrors
void setLastErrors(ErrorTypes errorTypes)
Definition: testentry.cpp:129
Prefs::countSynonymsAsCorrect
static bool countSynonymsAsCorrect()
Get When the synonym instead of the word was entered, does it count as correct?
Definition: prefs.h:678
Prefs::ignoreAccentMistakes
static bool ignoreAccentMistakes()
Get Count answers as right when only the accentuation is wrong.
Definition: prefs.h:507
QString::length
int length() const
QString::fromLatin1
QString fromLatin1(const char *str, int size)
QStringList::indexOf
int indexOf(const QRegExp &rx, int from) const
TestEntry::CapitalizationMistake
< misspelled
Definition: testentry.h:27
AnswerValidatorOld::AnswerValidatorOld
AnswerValidatorOld(KEduVocDocument *doc)
Definition: answervalidatorold.cpp:52
Prefs::ignoreCapitalizationMistakes
static bool ignoreCapitalizationMistakes()
Get Count answers as right when only the capitalization is wrong.
Definition: prefs.h:526
QByteArray::size
int size() const
TestEntry
Definition: testentry.h:22
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:15:56 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

parley

Skip menu "parley"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdeedu API Reference

Skip menu "kdeedu API Reference"
  • Analitza
  •     lib
  • kalgebra
  • kalzium
  •   libscience
  • kanagram
  • kig
  •   lib
  • klettres
  • marble
  • parley
  • rocs
  •   App
  •   RocsCore
  •   VisualEditor
  •   stepcore

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal