17 #include <keduvocexpression.h>
18 #include <keduvocdocument.h>
19 #include <keduvocidentifier.h>
21 #include <KLocalizedString>
22 #include <sonnet/speller.h>
27 namespace ParleyStringHandlerOld {
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]);
36 kDebug() << original <<
" without accents: " << noAccents;
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;
55 m_spellerAvailable =
false;
66 m_translation = translation;
70 m_speller =
new Sonnet::Speller(m_doc->identifier(translation).locale());
72 m_speller->setLanguage(m_doc->identifier(translation).locale());
76 if ( !m_speller->isValid() ) {
77 m_speller->setLanguage(m_doc->identifier(translation).name());
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;
87 m_spellerAvailable =
true;
96 m_solution = m_entry->
entry()->translation(m_translation)->text();
100 int AnswerValidatorOld::levenshteinDistance(
const QString& s,
const QString& t)
108 if( m_d.size() < (m+1) * (n+1)) {
109 m_d.resize( (m+1) * (n+1) );
116 for ( i = 0; i <= m; i++ )
118 m_d[i + 0*dWidth] = i;
120 for ( j = 0; j <= n; j++ ) {
121 m_d[0 + j*dWidth] = j;
125 for (i = 1; i <= m; i++) {
126 for (j = 1; j <= n; j++) {
127 if ( s[i-1] == t[j-1] ) {
133 m_d[i + j*dWidth] = qMin( qMin (
134 m_d[i-1 + (j )*dWidth] + 1,
135 m_d[i + (j-1)*dWidth] + 1),
136 m_d[i-1 + (j-1)*dWidth] + cost);
139 return m_d[m + n*dWidth];
142 bool AnswerValidatorOld::spellcheckerMisspelled(
const QString& userAnswer)
144 if (!m_spellerAvailable) {
147 return m_speller->isMisspelled(userAnswer);
150 bool AnswerValidatorOld::spellcheckerInSuggestionList(
const QString& solution,
const QString& userAnswer)
152 if ( !m_spellerAvailable ) {
156 kDebug() <<
"entered: " << userAnswer <<
" misspelled: " << m_speller->isMisspelled(userAnswer) <<
" suggestions: " << m_speller->suggest(userAnswer);
158 if ( m_speller->suggest(userAnswer).contains(solution) ) {
159 kDebug() <<
"I think this is a spelling error.";
162 kDebug() <<
"No, this is a different word I think.";
168 void AnswerValidatorOld::simpleCorrector()
170 kDebug() <<
"simpleCorrector";
171 if ( m_entry == 0 ) {
172 kError() <<
"No entry set, cannot verify answer.";
177 if ( m_solution == m_userAnswer ) {
185 TestEntry::ErrorTypes errorTypes = TestEntry::UnknownMistake;
187 foreach(KEduVocTranslation *synonym, m_entry->
entry()->translation(m_translation)->synonyms()) {
188 if (synonym->text() == m_userAnswer) {
199 int levensthein = levenshteinDistance( m_solution, m_userAnswer );
201 m_entry->
setLastPercentage(1.0 - ((
double)levensthein/ qMax(m_solution.length(), m_userAnswer.length())));
203 kDebug() <<
"simpleCorrector" << m_userAnswer <<
"-" << m_solution <<
"has levensthein distance: " << levensthein <<
" grade: " << m_entry->
lastPercentage();
207 void AnswerValidatorOld::defaultCorrector()
211 if ( m_solution == m_userAnswer ) {
217 if ( m_userAnswer.isEmpty() ) {
223 if ( m_solution.toLower() == m_userAnswer.toLower() ) {
243 foreach(KEduVocTranslation *synonym, m_entry->
entry()->translation(m_translation)->synonyms()) {
244 if (synonym->text() == m_userAnswer) {
255 int numberSolutionWords = m_solution.simplified().split(
' ').count();
256 int numberAnswerWords = m_userAnswer.simplified().split(
' ').count();
258 if ( numberSolutionWords == 1 ) {
260 TestEntry::ErrorTypes errors;
261 wordCompare(m_solution, m_userAnswer, grade, errors);
267 if ( numberSolutionWords == 2 ) {
269 QStringList solutionWords = m_solution.simplified().split(
' ');
271 if ( m_translation >= 0 ) {
272 if (m_doc->identifier(m_translation).article().isArticle(solutionWords.value(0)) ) {
274 if ( numberAnswerWords == 1 ) {
276 TestEntry::ErrorTypes errors;
277 wordCompare(solutionWords.value(1), m_userAnswer.simplified(), percent, errors);
282 if ( numberAnswerWords == 2 ) {
284 TestEntry::ErrorTypes errors;
285 wordCompare(solutionWords.value(1), m_userAnswer.simplified().split(
' ').value(1), percent,
288 if ( m_userAnswer.simplified().split(
' ').value(0) == solutionWords.value(0) ) {
309 if ( m_entry == 0 ) {
310 kError() <<
"Error: no entry set for validator.";
314 m_userAnswer = userAnswer;
322 kDebug() <<
"CheckUserAnswer with two strings. The one string version is preferred.";
323 if ( !language.isEmpty() ) {
326 m_spellerAvailable =
false;
329 m_solution = solution;
335 void AnswerValidatorOld::wordCompare(
const QString & solution,
const QString & userWord,
double& grade, TestEntry::ErrorTypes& errorTypes)
340 if ( solution == userWord ) {
346 if ( solution.toLower() == userWord.toLower() ) {
350 grade = 1.0 - CAPITALIZATION_MISTAKE_PUNISHMENT;
360 grade = 1.0 - ACCENT_MISTAKE_PUNISHMENT;
366 int levenshtein = levenshteinDistance(solution, userWord);
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;
375 if ( isMisspelled && inSuggestions ) {
376 grade = 1.0 - qMax (levenshtein * SPELLING_MISTAKE_PER_LETTER_PUNISHMENT, 1.0);
381 if ( !isMisspelled && inSuggestions ) {
382 grade = FALSE_FRIEND_GRADE;
388 if ( !isMisspelled && !inSuggestions ) {
389 grade = UNRELATED_WORD_GRADE;
395 if ( isMisspelled && !inSuggestions ) {
396 if ( ((
double)levenshtein/ qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD ) {
402 errorTypes = TestEntry::UnknownMistake;
407 if ( ((
double)levenshtein/ qMax(solution.length(), userWord.length())) < LEVENSHTEIN_THRESHOLD ) {
408 grade = 1.0 - ((double)levenshtein/ qMax(solution.length(), userWord.length()));
413 grade = 1.0 - ((double)levenshtein/ qMax(solution.length(), userWord.length()));
415 errorTypes = TestEntry::UnknownMistake;
420 errorTypes = TestEntry::UnknownMistake;
425 void AnswerValidatorOld::sentenceAnalysis()
427 QStringList solutionWords;
428 QStringList userAnswerWords;
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;
436 QStringList correctWords;
437 QStringList wrongWords;
439 for (
int i = 0; i < userAnswerWords.count(); i++ ) {
440 int pos = solutionWords.indexOf( userAnswerWords.value(i) );
442 correctWords.append(userAnswerWords.value(i));
443 solutionWords.removeAt(pos);
444 userAnswerWords.removeAt(i);
446 wrongWords.append(userAnswerWords.value(i));
450 kDebug() <<
" remaining: solution: " << solutionWords.count()
451 <<
"user: " << userAnswerWords.count();
453 QList< QPair<QString, QString> > pairs = bestPairs(solutionWords, userAnswerWords);
455 for (
int i = 0; i < pairs.count(); i++ ) {
456 kDebug() <<
"Possible pair: " << pairs.value(i).first <<
" and " << pairs.value(i).second;
461 correction.append(
"Correct: ");
462 foreach (
const QString &correctWord, correctWords) {
463 correction.append(QString::fromLatin1(
"<font color=\"#188C18\">") + correctWord + QString::fromLatin1(
"</font> "));
466 correction.append(
" Wrong: ");
467 foreach (
const QString &wrongWord, wrongWords) {
468 correction.append(QString::fromLatin1(
"<font color=\"#8C1818\">") + wrongWord + QString::fromLatin1(
"</font> "));
471 int levenshtein = levenshteinDistance(m_solution, m_userAnswer);
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())));
480 QList< QPair < QString , QString > > AnswerValidatorOld::bestPairs(
const QStringList& solutionWords ,
const QStringList& userAnswerWords )
482 int nSol = solutionWords.count();
483 int nUser = userAnswerWords.count();
486 d.resize( nSol * nUser );
488 QList< QPair < QString , QString > > pairList;
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));
497 int MAX_LEVENSHTEIN = 5;
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 );
514 if ( min < MAX_LEVENSHTEIN && posUser != -1 && posSol != -1) {
515 pairList.append( qMakePair(solutionWords.value(posSol), userAnswerWords.value(posUser)) );
518 d[posSol + posUser*nSol] = MAX_LEVENSHTEIN;
520 }
while ( min < MAX_LEVENSHTEIN );
528 return m_spellerAvailable;
< solution is correct with the article interchanged
void setTestEntry(TestEntry *entry)
Sets the current entry.
< an accent is missing or wrong (é)
< solution is correct with the article missing
< a synonym (may be correct)
void checkUserAnswer(const QString &userAnswer)
Checks the user answer.
< capitalization error (whAt)
< the part that was entered is right, but not complete
void setLanguage(int translation)
Set the language for the spell checker.
void setLastPercentage(double percent)
QString stripAccents(const QString &original)
bool spellcheckerAvailable()
void setLastErrors(ErrorTypes errorTypes)
static bool countSynonymsAsCorrect()
Get When the synonym instead of the word was entered, does it count as correct?
static bool ignoreAccentMistakes()
Get Count answers as right when only the accentuation is wrong.
AnswerValidatorOld(KEduVocDocument *doc)
static bool ignoreCapitalizationMistakes()
Get Count answers as right when only the capitalization is wrong.
KEduVocExpression * entry()