Warning, file /education/parley/src/practice/writtenpracticevalidator.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2010 Benjamin Schleinzer <ben-kde@schleinzer.eu>
0003     SPDX-FileCopyrightText: 2007-2010 Frederik Gladhorn <gladhorn@kde.org>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "writtenpracticevalidator.h"
0008 #include "prefs.h"
0009 
0010 #include <KEduVocTranslation>
0011 #include <QDebug>
0012 
0013 /// temporary namespace for string manipulation functions
0014 /// could move into KStringHandler eventually
0015 namespace ParleyStringHandlerOld
0016 {
0017 QString stripAccents(const QString &original)
0018 {
0019     QString noAccents;
0020     QString decomposed = original.normalized(QString::NormalizationForm_D);
0021     for (int i = 0; i < decomposed.length(); ++i) {
0022         if (decomposed[i].category() != 1) {
0023             noAccents.append(decomposed[i]);
0024         }
0025     }
0026     qDebug() << original << " without accents: " << noAccents;
0027     return noAccents;
0028 }
0029 }
0030 
0031 using namespace Practice;
0032 
0033 WrittenPracticeValidator::WrittenPracticeValidator(int translation, KEduVocDocument *doc)
0034     : m_doc(doc)
0035     , m_error()
0036 {
0037     setLanguage(translation);
0038 }
0039 
0040 WrittenPracticeValidator::~WrittenPracticeValidator()
0041 {
0042     delete m_speller;
0043 }
0044 
0045 void WrittenPracticeValidator::setEntry(TestEntry *entry)
0046 {
0047     m_entry = entry;
0048 }
0049 
0050 void WrittenPracticeValidator::setLanguage(int translation)
0051 {
0052     m_translation = translation;
0053 
0054     // default: try locale
0055     if (!m_speller) {
0056         m_speller = new Sonnet::Speller(m_doc->identifier(translation).locale());
0057     } else {
0058         m_speller->setLanguage(m_doc->identifier(translation).locale());
0059     }
0060 
0061     // we might succeed with language name instead.
0062     if (!m_speller->isValid()) {
0063         m_speller->setLanguage(m_doc->identifier(translation).name());
0064     }
0065 
0066     if (!m_speller->isValid()) {
0067         qDebug() << "No spellchecker for current language found: " << m_doc->identifier(m_translation).locale();
0068         qDebug() << "Available dictionaries: " << m_speller->availableLanguages() << "\n names: " << m_speller->availableLanguageNames()
0069                  << "\n backends: " << m_speller->availableBackends();
0070         m_spellerAvailable = false;
0071     } else {
0072         m_spellerAvailable = true;
0073     }
0074 }
0075 
0076 bool WrittenPracticeValidator::spellcheckerAvailable()
0077 {
0078     return m_spellerAvailable;
0079 }
0080 
0081 void WrittenPracticeValidator::validateAnswer(const QString &answer)
0082 {
0083     if (m_entry == 0) {
0084         qCritical() << "No entry set, cannot verify answer.";
0085         return;
0086     }
0087 
0088     QString correct = m_entry->entry()->translation(m_entry->languageTo())->text();
0089 
0090     qDebug() << "Correct answer should be: " << correct;
0091     m_error = {};
0092 
0093     // Check for empty answers and valid answers first
0094     if (answer.isEmpty()) {
0095         m_error |= TestEntry::Wrong;
0096         qDebug() << "Empty answer ";
0097     } else if (isCorrect(correct, answer)) {
0098         m_error |= TestEntry::Correct;
0099     } else {
0100         // Check for all valid errors to build a list of
0101         // possible mistakes. This provides us with useful information
0102         // that we can use to give feedback to the user.
0103         if (isPunctuationMistake(correct, answer)) {
0104             m_error |= TestEntry::Correct;
0105         } else if (isCapitalizationMistake(correct, answer)) {
0106             m_error |= TestEntry::Correct;
0107         } else if (isAccentMistake(correct, answer)) {
0108             m_error |= TestEntry::Correct;
0109         } else if (isSynonymMistake(answer)) {
0110             m_error |= TestEntry::Correct;
0111         } else {
0112             m_error |= TestEntry::Wrong;
0113             qDebug() << "Wrong answer: " << answer;
0114         }
0115     }
0116     qDebug() << "Error code " << m_error;
0117 
0118     m_entry->setLastErrors(m_error);
0119 }
0120 
0121 QString WrittenPracticeValidator::getCorrectedAnswer()
0122 {
0123     return m_correctedAnswer;
0124 }
0125 
0126 bool WrittenPracticeValidator::isCorrect(const QString &correct, const QString &answer)
0127 {
0128     if (answer == correct) {
0129         qDebug() << "Correct answer was given";
0130         return true;
0131     }
0132     return false;
0133 }
0134 
0135 bool WrittenPracticeValidator::isSynonymMistake(const QString &answer)
0136 {
0137     const QList<KEduVocTranslation *> synonyms = m_entry->entry()->translation(m_entry->languageTo())->synonyms();
0138     for (KEduVocTranslation *synonym : synonyms) {
0139         if (synonym->text() == answer || (Prefs::ignoreCapitalizationMistakes() && isCapitalizationMistake(synonym->text(), answer))
0140             || (Prefs::ignoreAccentMistakes() && isAccentMistake(synonym->text(), answer))
0141             || (Prefs::ignorePunctuationMistakes() && isPunctuationMistake(synonym->text(), answer))) {
0142             qDebug() << "Synonym entered: " << synonym->text() << " answer: " << answer;
0143             m_correctedAnswer = synonym->text();
0144             m_error |= TestEntry::Synonym;
0145             // only return true if accept these kinds of mistakes
0146             // otherwise just set the error flag
0147             if (Prefs::countSynonymsAsCorrect()) {
0148                 return true;
0149             }
0150         }
0151     }
0152     return false;
0153 }
0154 
0155 bool WrittenPracticeValidator::isCapitalizationMistake(const QString &original, const QString &answer)
0156 {
0157     if (answer.toLower() == original.toLower() || (Prefs::ignorePunctuationMistakes() && isPunctuationMistake(original.toLower(), answer.toLower()))) {
0158         qDebug() << "CapitalizationMistake: " << original << " answer: " << answer;
0159         m_error |= TestEntry::CapitalizationMistake;
0160         m_correctedAnswer = answer;
0161         // only return true if accept these kinds of mistakes
0162         // otherwise just set the error flag
0163         if (Prefs::ignoreCapitalizationMistakes())
0164             return true;
0165     }
0166     return false;
0167 }
0168 
0169 bool WrittenPracticeValidator::isPunctuationMistake(const QString &original, const QString &answer)
0170 {
0171     QString ans = answer;
0172     QString orig = original;
0173     if (ans.remove(QRegExp(QStringLiteral("[^a-zA-ZƒŠŒŽšœžŸÀ-ÿ\\s]"))) == orig.remove(QRegExp(QStringLiteral("[^a-zA-ZƒŠŒŽšœžŸÀ-ÿ\\s]")))) {
0174         qDebug() << "PunctuationMistake: " << original << " answer: " << answer;
0175         m_error |= TestEntry::PunctuationMistake;
0176         m_correctedAnswer = answer;
0177         // only return true if accept these kinds of mistakes
0178         // otherwise just set the error flag
0179         if (Prefs::ignorePunctuationMistakes())
0180             return true;
0181     }
0182     return false;
0183 }
0184 
0185 bool WrittenPracticeValidator::isAccentMistake(const QString &original, const QString &answer)
0186 {
0187     QString stripedOriginal = ParleyStringHandlerOld::stripAccents(original);
0188     QString stripedAnswer = ParleyStringHandlerOld::stripAccents(answer);
0189     if (stripedOriginal == stripedAnswer || (Prefs::ignoreCapitalizationMistakes() && isCapitalizationMistake(stripedOriginal, stripedAnswer))
0190         || (Prefs::ignorePunctuationMistakes() && isPunctuationMistake(stripedOriginal, stripedAnswer))) {
0191         qDebug() << "AccentMistake: " << original << " answer: " << answer;
0192         m_error |= TestEntry::AccentMistake;
0193         m_correctedAnswer = answer;
0194         // only return true if accept these kinds of mistakes
0195         // otherwise just set the error flag
0196         if (Prefs::ignoreAccentMistakes()) {
0197             return true;
0198         }
0199     }
0200     return false;
0201 }