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

parley

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