File indexing completed on 2024-04-28 07:39:31

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