00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013 #include "answervalidatorold.h"
00014
00015 #include "prefs.h"
00016
00017 #include <keduvocexpression.h>
00018 #include <keduvocdocument.h>
00019 #include <keduvocidentifier.h>
00020
00021 #include <KLocalizedString>
00022 #include <sonnet/speller.h>
00023 #include <kdebug.h>
00024
00027 namespace ParleyStringHandlerOld {
00028 QString stripAccents(const QString& original){
00029 QString noAccents;
00030 QString decomposed = original.normalized(QString::NormalizationForm_D);
00031 for (int i = 0; i < decomposed.length(); ++i) {
00032 if ( decomposed[i].category() != 1 ) {
00033 noAccents.append(decomposed[i]);
00034 }
00035 }
00036 kDebug() << original << " without accents: " << noAccents;
00037 return noAccents;
00038 }
00039 }
00040
00041
00042 const double AnswerValidatorOld::LEVENSHTEIN_THRESHOLD = 0.2;
00043 const double AnswerValidatorOld::UNRELATED_WORD_GRADE = 0.0;
00044 const double AnswerValidatorOld::FALSE_FRIEND_GRADE = 0.0;
00045 const double AnswerValidatorOld::SPELLING_MISTAKE_PER_LETTER_PUNISHMENT = 0.2;
00046 const double AnswerValidatorOld::CAPITALIZATION_MISTAKE_PUNISHMENT = 0.1;
00047 const double AnswerValidatorOld::ACCENT_MISTAKE_PUNISHMENT = 0.1;
00048 const double AnswerValidatorOld::WRONG_ARTICLE_PUNISHMENT = 0.1;
00049
00050 AnswerValidatorOld::AnswerValidatorOld(KEduVocDocument* doc)
00051 {
00052 m_doc = doc;
00053 m_entry = 0;
00054 m_speller = 0;
00055 m_spellerAvailable = false;
00056 }
00057
00058 AnswerValidatorOld::~AnswerValidatorOld()
00059 {
00060 delete m_speller;
00061 }
00062
00063
00064 void AnswerValidatorOld::setLanguage(int translation)
00065 {
00066 m_translation = translation;
00067
00068
00069 if ( !m_speller ) {
00070 m_speller = new Sonnet::Speller(m_doc->identifier(translation).locale());
00071 } else {
00072 m_speller->setLanguage(m_doc->identifier(translation).locale());
00073 }
00074
00075
00076 if ( !m_speller->isValid() ) {
00077 m_speller->setLanguage(m_doc->identifier(translation).name());
00078 }
00079
00080 if ( !m_speller->isValid() ) {
00081 kDebug() << "No spellchecker for current language found: " << m_doc->identifier(m_translation).locale();
00082 kDebug() << "Available dictionaries: " << m_speller->availableLanguages()
00083 << "\n names: " << m_speller->availableLanguageNames()
00084 << "\n backends: " << m_speller->availableBackends();
00085 m_spellerAvailable = false;
00086 } else {
00087 m_spellerAvailable = true;
00088 }
00089 }
00090
00091
00092 void AnswerValidatorOld::setTestEntry(TestEntry * entry)
00093 {
00094 m_entry = entry;
00095 if (m_entry) {
00096 m_solution = m_entry->entry()->translation(m_translation)->text();
00097 }
00098 }
00099
00100 int AnswerValidatorOld::levenshteinDistance(const QString& s, const QString& t)
00101 {
00102 int m = s.length();
00103 int n = t.length();
00104
00105 int dWidth = m+1 ;
00106
00107
00108 if( m_d.size() < (m+1) * (n+1)) {
00109 m_d.resize( (m+1) * (n+1) );
00110 }
00111
00112 int i;
00113 int j;
00114
00115
00116 for ( i = 0; i <= m; i++ )
00117 {
00118 m_d[i + 0*dWidth] = i;
00119 }
00120 for ( j = 0; j <= n; j++ ) {
00121 m_d[0 + j*dWidth] = j;
00122 }
00123
00124 int cost;
00125 for (i = 1; i <= m; i++) {
00126 for (j = 1; j <= n; j++) {
00127 if ( s[i-1] == t[j-1] ) {
00128
00129 cost = 0;
00130 } else {
00131 cost = 1;
00132 }
00133 m_d[i + j*dWidth] = qMin( qMin (
00134 m_d[i-1 + (j )*dWidth] + 1,
00135 m_d[i + (j-1)*dWidth] + 1),
00136 m_d[i-1 + (j-1)*dWidth] + cost);
00137 }
00138 }
00139 return m_d[m + n*dWidth];
00140 }
00141
00142 bool AnswerValidatorOld::spellcheckerMisspelled(const QString& userAnswer)
00143 {
00144 if (!m_spellerAvailable) {
00145 return true;
00146 }
00147 return m_speller->isMisspelled(userAnswer);
00148 }
00149
00150 bool AnswerValidatorOld::spellcheckerInSuggestionList(const QString& solution, const QString& userAnswer)
00151 {
00152 if ( !m_spellerAvailable ) {
00153 return false;
00154 }
00155
00156 kDebug() << "entered: " << userAnswer << " misspelled: " << m_speller->isMisspelled(userAnswer) << " suggestions: " << m_speller->suggest(userAnswer);
00157
00158 if ( m_speller->suggest(userAnswer).contains(solution) ) {
00159 kDebug() << "I think this is a spelling error.";
00160 return true;
00161 } else {
00162 kDebug() << "No, this is a different word I think.";
00163 return false;
00164 }
00165 }
00166
00167
00168 void AnswerValidatorOld::simpleCorrector()
00169 {
00170 kDebug() << "simpleCorrector";
00171 if ( m_entry == 0 ) {
00172 kError() << "No entry set, cannot verify answer.";
00173 return;
00174 }
00175
00177 if ( m_solution == m_userAnswer ) {
00178 m_entry->setLastErrors(TestEntry::Correct);
00179 m_entry->setLastPercentage(1.0);
00180
00181 kDebug() << "right";
00182 return;
00183 }
00184
00185 TestEntry::ErrorTypes errorTypes = TestEntry::UnknownMistake;
00186
00187 foreach(KEduVocTranslation *synonym, m_entry->entry()->translation(m_translation)->synonyms()) {
00188 if (synonym->text() == m_userAnswer) {
00189 m_entry->setLastErrors(TestEntry::Synonym);
00190 if ( Prefs::countSynonymsAsCorrect() ) {
00191 m_entry->setLastPercentage(1.0);
00192 } else {
00193 m_entry->setLastPercentage(0.0);
00194 }
00195 return;
00196 }
00197 }
00198
00199 int levensthein = levenshteinDistance( m_solution, m_userAnswer );
00200
00201 m_entry->setLastPercentage(1.0 - ((double)levensthein/ qMax(m_solution.length(), m_userAnswer.length())));
00202
00203 kDebug() << "simpleCorrector" << m_userAnswer << "-" << m_solution << "has levensthein distance: " << levensthein << " grade: " << m_entry->lastPercentage();
00204 }
00205
00206
00207 void AnswerValidatorOld::defaultCorrector()
00208 {
00211 if ( m_solution == m_userAnswer ) {
00212 m_entry->setLastErrors(TestEntry::Correct);
00213 m_entry->setLastPercentage(1.0);
00214 return;
00215 }
00216
00217 if ( m_userAnswer.isEmpty() ) {
00218 m_entry->setLastErrors(TestEntry::Empty);
00219 m_entry->setLastPercentage(0.0);
00220 return;
00221 }
00222
00223 if ( m_solution.toLower() == m_userAnswer.toLower() ) {
00224 if (Prefs::ignoreCapitalizationMistakes()) {
00225 m_entry->setLastPercentage(1.0);
00226 } else {
00227 m_entry->setLastPercentage(1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT);
00228 }
00229 m_entry->setLastErrors(TestEntry::CapitalizationMistake);
00230 return ;
00231 }
00232
00233 if ( ParleyStringHandlerOld::stripAccents(m_solution) == ParleyStringHandlerOld::stripAccents(m_userAnswer) ) {
00234 if (Prefs::ignoreAccentMistakes()) {
00235 m_entry->setLastPercentage(1.0);
00236 } else {
00237 m_entry->setLastPercentage(1.0 - ACCENT_MISTAKE_PUNISHMENT);
00238 }
00239 m_entry->setLastErrors(TestEntry::AccentMistake);
00240 return ;
00241 }
00242
00243 foreach(KEduVocTranslation *synonym, m_entry->entry()->translation(m_translation)->synonyms()) {
00244 if (synonym->text() == m_userAnswer) {
00245 m_entry->setLastErrors(TestEntry::Synonym);
00246 if ( Prefs::countSynonymsAsCorrect() ) {
00247 m_entry->setLastPercentage(1.0);
00248 } else {
00249 m_entry->setLastPercentage(0.0);
00250 }
00251 return;
00252 }
00253 }
00254
00255 int numberSolutionWords = m_solution.simplified().split(' ').count();
00256 int numberAnswerWords = m_userAnswer.simplified().split(' ').count();
00257
00258 if ( numberSolutionWords == 1 ) {
00259 double grade;
00260 TestEntry::ErrorTypes errors;
00261 wordCompare(m_solution, m_userAnswer, grade, errors);
00262 m_entry->setLastPercentage(grade);
00263 m_entry->setLastErrors(errors);
00264 return;
00265 }
00266
00267 if ( numberSolutionWords == 2 ) {
00268
00269 QStringList solutionWords = m_solution.simplified().split(' ');
00270
00271 if ( m_translation >= 0 ) {
00272 if (m_doc->identifier(m_translation).article().isArticle(solutionWords.value(0)) ) {
00273
00274 if ( numberAnswerWords == 1 ) {
00275 double percent;
00276 TestEntry::ErrorTypes errors;
00277 wordCompare(solutionWords.value(1), m_userAnswer.simplified(), percent, errors);
00278 m_entry->setLastPercentage(qMax(percent-WRONG_ARTICLE_PUNISHMENT, 0.0));
00279 m_entry->setLastErrors(errors|TestEntry::ArticleMissing);
00280 return;
00281 }
00282 if ( numberAnswerWords == 2 ) {
00283 double percent;
00284 TestEntry::ErrorTypes errors;
00285 wordCompare(solutionWords.value(1), m_userAnswer.simplified().split(' ').value(1), percent,
00286 errors);
00287
00288 if ( m_userAnswer.simplified().split(' ').value(0) == solutionWords.value(0) ) {
00289 m_entry->setLastErrors(errors);
00290 } else {
00291 m_entry->setLastPercentage(qMax(percent-WRONG_ARTICLE_PUNISHMENT, 0.0));
00292 m_entry->setLastErrors(errors|TestEntry::ArticleWrong);
00293 }
00294 return;
00295 }
00296 }
00297 }
00298 }
00299
00300
00301 sentenceAnalysis();
00302 }
00303
00304
00305
00306
00307 void AnswerValidatorOld::checkUserAnswer(const QString & userAnswer)
00308 {
00309 if ( m_entry == 0 ) {
00310 kError() << "Error: no entry set for validator.";
00311 return;
00312 }
00313
00314 m_userAnswer = userAnswer;
00315
00316
00317 defaultCorrector();
00318 }
00319
00320 void AnswerValidatorOld::checkUserAnswer(const QString & solution, const QString & userAnswer, const QString& language)
00321 {
00322 kDebug() << "CheckUserAnswer with two strings. The one string version is preferred.";
00323 if ( !language.isEmpty() ) {
00324
00325 } else {
00326 m_spellerAvailable = false;
00327 }
00328
00329 m_solution = solution;
00330
00331 checkUserAnswer(userAnswer);
00332 }
00333
00334
00335 void AnswerValidatorOld::wordCompare(const QString & solution, const QString & userWord, double& grade, TestEntry::ErrorTypes& errorTypes)
00336 {
00338
00339
00340 if ( solution == userWord ) {
00341 grade = 1.0;
00342 errorTypes = TestEntry::Correct;
00343 return;
00344 }
00345
00346 if ( solution.toLower() == userWord.toLower() ) {
00347 if (Prefs::ignoreCapitalizationMistakes()) {
00348 grade = 1.0;
00349 } else {
00350 grade = 1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT;
00351 }
00352 errorTypes = TestEntry::CapitalizationMistake;
00353 return ;
00354 }
00355
00356 if ( ParleyStringHandlerOld::stripAccents(solution) == ParleyStringHandlerOld::stripAccents(userWord) ) {
00357 if (Prefs::ignoreAccentMistakes()) {
00358 grade = 1.0;
00359 } else {
00360 grade = 1.0 - ACCENT_MISTAKE_PUNISHMENT;
00361 }
00362 errorTypes = TestEntry::AccentMistake;
00363 return ;
00364 }
00365
00366 int levenshtein = levenshteinDistance(solution, userWord);
00367
00368 if ( m_spellerAvailable ) {
00369 bool inSuggestions = false;
00370 bool isMisspelled = m_speller->isMisspelled( userWord );
00371 if ( m_speller->suggest(userWord).contains(solution) ) {
00372 inSuggestions = true;
00373 }
00374
00375 if ( isMisspelled && inSuggestions ) {
00376 grade = 1.0 - qMax (levenshtein * SPELLING_MISTAKE_PER_LETTER_PUNISHMENT, 1.0);
00377 errorTypes = TestEntry::SpellingMistake;
00378 return;
00379 }
00380
00381 if ( !isMisspelled && inSuggestions ) {
00382 grade = FALSE_FRIEND_GRADE;
00383
00384 errorTypes = errorTypes = TestEntry::FalseFriend;
00385 return ;
00386 }
00387
00388 if ( !isMisspelled && !inSuggestions ) {
00389 grade = UNRELATED_WORD_GRADE;
00390
00391 errorTypes = TestEntry::UnrelatedWord;
00392 return;
00393 }
00394
00395 if ( isMisspelled && !inSuggestions ) {
00396 if ( ((double)levenshtein/ qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD ) {
00397
00398 errorTypes = TestEntry::SpellingMistake;
00399 return;
00400 } else {
00401
00402 errorTypes = TestEntry::UnknownMistake;
00403 return;
00404 }
00405 }
00406 } else {
00407 if ( ((double)levenshtein/ qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD ) {
00408 grade = 1.0 - ((double)levenshtein/ qMax(solution.length(), userWord.length()));
00409
00410 errorTypes = TestEntry::SpellingMistake;
00411 return;
00412 } else {
00413 grade = 1.0 - ((double)levenshtein/ qMax(solution.length(), userWord.length()));
00414
00415 errorTypes = TestEntry::UnknownMistake;
00416 return;
00417 }
00418 }
00419
00420 errorTypes = TestEntry::UnknownMistake;
00421 return;
00422 }
00423
00424
00425 void AnswerValidatorOld::sentenceAnalysis()
00426 {
00427 QStringList solutionWords;
00428 QStringList userAnswerWords;
00429
00430
00431 solutionWords = m_solution.simplified().split(QRegExp("\\W"), QString::SkipEmptyParts);
00432 userAnswerWords = m_userAnswer.simplified().split(QRegExp("\\W"), QString::SkipEmptyParts);
00433 kDebug() << "Solution words: " << solutionWords;
00434 kDebug() << "Answer: " << userAnswerWords;
00435
00436 QStringList correctWords;
00437 QStringList wrongWords;
00438
00439 for ( int i = 0; i < userAnswerWords.count(); i++ ) {
00440 int pos = solutionWords.indexOf( userAnswerWords.value(i) );
00441 if ( pos >= 0 ) {
00442 correctWords.append(userAnswerWords.value(i));
00443 solutionWords.removeAt(pos);
00444 userAnswerWords.removeAt(i);
00445 } else {
00446 wrongWords.append(userAnswerWords.value(i));
00447 }
00448 }
00449
00450 kDebug() << " remaining: solution: " << solutionWords.count()
00451 << "user: " << userAnswerWords.count();
00452
00453 QList< QPair<QString, QString> > pairs = bestPairs(solutionWords, userAnswerWords);
00454
00455 for ( int i = 0; i < pairs.count(); i++ ) {
00456 kDebug() << "Possible pair: " << pairs.value(i).first << " and " << pairs.value(i).second;
00457 }
00458
00459
00460 QString correction;
00461 correction.append("Correct: ");
00462 foreach (const QString &correctWord, correctWords) {
00463 correction.append(QString::fromLatin1("<font color=\"#188C18\">") + correctWord + QString::fromLatin1("</font> "));
00464 }
00465
00466 correction.append(" Wrong: ");
00467 foreach (const QString &wrongWord, wrongWords) {
00468 correction.append(QString::fromLatin1("<font color=\"#8C1818\">") + wrongWord + QString::fromLatin1("</font> "));
00469 }
00470
00471 int levenshtein = levenshteinDistance(m_solution, m_userAnswer);
00472
00473 kDebug() << correction;
00474 kDebug() << "IMPLEMENT ME TO ACTUALLY EVALUATE THE ABOVE AND GENERATE A GRADE!";
00475 m_entry->setLastPercentage(1.0 - ((double)levenshtein/ qMax(m_solution.length(), m_userAnswer.length())));
00476 m_entry->setLastErrors(TestEntry::UnknownMistake);
00477 }
00478
00479
00480 QList< QPair < QString , QString > > AnswerValidatorOld::bestPairs(const QStringList& solutionWords , const QStringList& userAnswerWords )
00481 {
00482 int nSol = solutionWords.count();
00483 int nUser = userAnswerWords.count();
00484
00485 QByteArray d;
00486 d.resize( nSol * nUser );
00487
00488 QList< QPair < QString , QString > > pairList;
00489
00490
00491 for ( int i = 0; i < nSol; i++) {
00492 for ( int j = 0; j < nUser; j++) {
00493 d[i + nSol*j] = levenshteinDistance(solutionWords.value(i), userAnswerWords.value(j));
00494 }
00495 }
00496
00497 int MAX_LEVENSHTEIN = 5;
00498
00499
00500 int min;
00501 int posSol = -1;
00502 int posUser = -1;
00503 do {
00504 min = MAX_LEVENSHTEIN;
00505 for ( int i = 0; i < nSol; i++ ) {
00506 for ( int j = 0; j < nUser; j++) {
00507 if ( d.at(i + j*nSol ) < min ) {
00508 min = d.at(i + j*nSol );
00509 posSol = i;
00510 posUser = j;
00511 }
00512 }
00513 }
00514 if ( min < MAX_LEVENSHTEIN && posUser != -1 && posSol != -1) {
00515 pairList.append( qMakePair(solutionWords.value(posSol), userAnswerWords.value(posUser)) );
00516
00517
00518 d[posSol + posUser*nSol] = MAX_LEVENSHTEIN;
00519 }
00520 } while ( min < MAX_LEVENSHTEIN );
00521
00522 return pairList;
00523 }
00524
00525
00526 bool AnswerValidatorOld::spellcheckerAvailable()
00527 {
00528 return m_spellerAvailable;
00529 }