File indexing completed on 2024-05-05 04:01:24

0001 /*
0002  * kspell_hunspelldict.cpp
0003  *
0004  * SPDX-FileCopyrightText: 2009 Montel Laurent <montel@kde.org>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.1-or-later
0007  */
0008 
0009 #include "hunspelldict.h"
0010 
0011 #include "config-hunspell.h"
0012 #include "hunspelldebug.h"
0013 
0014 #include <QDir>
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QStandardPaths>
0018 #include <QTextStream>
0019 
0020 using namespace Sonnet;
0021 
0022 HunspellDict::HunspellDict(const QString &lang, const std::shared_ptr<Hunspell> &speller)
0023     : SpellerPlugin(lang)
0024 {
0025     if (!speller) {
0026         qCWarning(SONNET_HUNSPELL) << "Can't create a client without a speller";
0027         return;
0028     }
0029     m_decoder = QStringDecoder(speller->get_dic_encoding());
0030     if (!m_decoder.isValid()) {
0031         qCWarning(SONNET_HUNSPELL) << "Failed to find a text codec for name" << speller->get_dic_encoding() << "defaulting to locale text codec";
0032         m_decoder = QStringDecoder(QStringDecoder::System);
0033         Q_ASSERT(m_decoder.isValid());
0034     }
0035     m_encoder = QStringEncoder(speller->get_dic_encoding());
0036     if (!m_encoder.isValid()) {
0037         qCWarning(SONNET_HUNSPELL) << "Failed to find a text codec for name" << speller->get_dic_encoding() << "defaulting to locale text codec";
0038         m_encoder = QStringEncoder(QStringEncoder::System);
0039         Q_ASSERT(m_encoder.isValid());
0040     }
0041     m_speller = speller;
0042 
0043     const QString userDic = QDir::home().filePath(QLatin1String(".hunspell_") % lang);
0044     QFile userDicFile(userDic);
0045     if (userDicFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
0046         qCDebug(SONNET_HUNSPELL) << "Load a user dictionary" << userDic;
0047         QTextStream userDicIn(&userDicFile);
0048         while (!userDicIn.atEnd()) {
0049             const QString word = userDicIn.readLine();
0050             if (word.isEmpty()) {
0051                 continue;
0052             }
0053 
0054             if (word.contains(QLatin1Char('/'))) {
0055                 QStringList wordParts = word.split(QLatin1Char('/'));
0056                 speller->add_with_affix(toDictEncoding(wordParts.at(0)).constData(), toDictEncoding(wordParts.at(1)).constData());
0057             }
0058             if (word.at(0) == QLatin1Char('*')) {
0059                 speller->remove(toDictEncoding(word.mid(1)).constData());
0060             } else {
0061                 speller->add(toDictEncoding(word).constData());
0062             }
0063         }
0064         userDicFile.close();
0065     }
0066 }
0067 
0068 std::shared_ptr<Hunspell> HunspellDict::createHunspell(const QString &lang, QString path)
0069 {
0070     qCDebug(SONNET_HUNSPELL) << "Loading dictionary for" << lang << "from" << path;
0071 
0072     if (!path.endsWith(QLatin1Char('/'))) {
0073         path += QLatin1Char('/');
0074     }
0075     path += lang;
0076     QString dictionary = path + QStringLiteral(".dic");
0077     QString aff = path + QStringLiteral(".aff");
0078 
0079     if (!QFileInfo::exists(dictionary) || !QFileInfo::exists(aff)) {
0080         qCWarning(SONNET_HUNSPELL) << "Unable to find dictionary for" << lang << "in path" << path;
0081         return nullptr;
0082     }
0083 
0084     std::shared_ptr<Hunspell> speller = std::make_shared<Hunspell>(aff.toLocal8Bit().constData(), dictionary.toLocal8Bit().constData());
0085     qCDebug(SONNET_HUNSPELL) << "Created " << speller.get();
0086 
0087     return speller;
0088 }
0089 
0090 HunspellDict::~HunspellDict()
0091 {
0092 }
0093 
0094 QByteArray HunspellDict::toDictEncoding(const QString &word) const
0095 {
0096     if (m_encoder.isValid()) {
0097         return m_encoder.encode(word);
0098     }
0099     return {};
0100 }
0101 
0102 bool HunspellDict::isCorrect(const QString &word) const
0103 {
0104     qCDebug(SONNET_HUNSPELL) << " isCorrect :" << word;
0105     if (!m_speller) {
0106         return false;
0107     }
0108 
0109 #if USE_OLD_HUNSPELL_API
0110     int result = m_speller->spell(toDictEncoding(word).constData());
0111     qCDebug(SONNET_HUNSPELL) << " result :" << result;
0112     return result != 0;
0113 #else
0114     bool result = m_speller->spell(toDictEncoding(word).toStdString());
0115     qCDebug(SONNET_HUNSPELL) << " result :" << result;
0116     return result;
0117 #endif
0118 }
0119 
0120 QStringList HunspellDict::suggest(const QString &word) const
0121 {
0122     if (!m_speller) {
0123         return QStringList();
0124     }
0125 
0126     QStringList lst;
0127 #if USE_OLD_HUNSPELL_API
0128     char **selection;
0129     int nbWord = m_speller->suggest(&selection, toDictEncoding(word).constData());
0130     for (int i = 0; i < nbWord; ++i) {
0131         lst << m_decoder.decode(selection[i]);
0132     }
0133     m_speller->free_list(&selection, nbWord);
0134 #else
0135     const auto suggestions = m_speller->suggest(toDictEncoding(word).toStdString());
0136     for_each(suggestions.begin(), suggestions.end(), [this, &lst](const std::string &suggestion) {
0137         lst << m_decoder.decode(suggestion.c_str());
0138     });
0139 #endif
0140 
0141     return lst;
0142 }
0143 
0144 bool HunspellDict::storeReplacement(const QString &bad, const QString &good)
0145 {
0146     Q_UNUSED(bad);
0147     Q_UNUSED(good);
0148     if (!m_speller) {
0149         return false;
0150     }
0151     qCDebug(SONNET_HUNSPELL) << "HunspellDict::storeReplacement not implemented";
0152     return false;
0153 }
0154 
0155 bool HunspellDict::addToPersonal(const QString &word)
0156 {
0157     if (!m_speller) {
0158         return false;
0159     }
0160     m_speller->add(toDictEncoding(word).constData());
0161     const QString userDic = QDir::home().filePath(QLatin1String(".hunspell_") % language());
0162     QFile userDicFile(userDic);
0163     if (userDicFile.open(QIODevice::Append | QIODevice::Text)) {
0164         QTextStream out(&userDicFile);
0165         out << word << '\n';
0166         userDicFile.close();
0167         return true;
0168     }
0169 
0170     return false;
0171 }
0172 
0173 bool HunspellDict::addToSession(const QString &word)
0174 {
0175     if (!m_speller) {
0176         return false;
0177     }
0178     int r = m_speller->add(toDictEncoding(word).constData());
0179     return r == 0;
0180 }