2 * \file HunspellChecker.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Abdelrazak Younes
8 * Full author contact details are available in file CREDITS.
13 #include "HunspellChecker.h"
16 #include "WordLangTuple.h"
18 #include "frontends/alert.h"
20 #include "support/debug.h"
21 #include "support/docstring_list.h"
22 #include "support/filetools.h"
23 #include "support/FileName.h"
24 #include "support/gettext.h"
25 #include "support/lassert.h"
26 #include "support/lstrings.h"
27 #include "support/os.h"
29 #include <hunspell/hunspell.hxx>
36 using namespace lyx::support;
37 using namespace lyx::support::os;
43 typedef map<std::string, Hunspell *> Spellers;
44 typedef vector<WordLangTuple> IgnoreList;
48 struct HunspellChecker::Private
54 bool haveDictionary(string const & lang, string & hpath);
55 Hunspell * addSpeller(string const & lang);
56 Hunspell * speller(string const & lang);
58 bool isIgnored(WordLangTuple const & wl) const;
67 HunspellChecker::Private::~Private()
69 Spellers::iterator it = spellers_.begin();
70 Spellers::iterator end = spellers_.end();
72 for (; it != end; ++it) {
79 bool haveLanguageFiles(string const & hpath)
81 FileName const affix(hpath + ".aff");
82 FileName const dict(hpath + ".dic");
83 if (!affix.isReadableFile()) {
84 // FIXME: We should indicate somehow that this language is not
86 LYXERR(Debug::FILES, "Hunspell affix file " << affix << " does not exist");
89 if (!dict.isReadableFile()) {
90 LYXERR(Debug::FILES, "Hunspell dictionary file " << dict << " does not exist");
98 bool HunspellChecker::Private::haveDictionary(string const & lang, string & hunspell_path)
100 LYXERR(Debug::FILES, "hunspell path: " << external_path(hunspell_path));
101 if (hunspell_path.empty()) {
102 // FIXME We'd like to issue a better error message here, but there seems
103 // to be a problem about thread safety, or something of the sort. If
104 // we issue the message using frontend::Alert, then the code comes
105 // back through here while the box is waiting, and causes some kind
107 static bool warned = false;
110 LYXERR0("Hunspell path not set.");
111 //frontend::Alert::error(_("Hunspell Path Not Found"),
112 // _("You must set the Hunspell dictionary path in Tools>Preferences>Paths."));
117 hunspell_path = external_path(addName(hunspell_path, lang));
118 if (!haveLanguageFiles(hunspell_path)) {
119 // try with '_' replaced by '-'
120 hunspell_path = subst(hunspell_path, '_', '-');
121 if (!haveLanguageFiles(hunspell_path)) {
122 // FIXME: We should indicate somehow that this language is not
123 // supported, probably by popping a warning. But we'll need to
124 // remember which warnings we've issued.
132 Hunspell * HunspellChecker::Private::addSpeller(string const & lang)
134 string hunspell_path = lyxrc.hunspelldir_path;
136 if (!haveDictionary(lang, hunspell_path))
139 FileName const affix(hunspell_path + ".aff");
140 FileName const dict(hunspell_path + ".dic");
141 Hunspell * h = new Hunspell(affix.absFilename().c_str(), dict.absFilename().c_str());
147 Hunspell * HunspellChecker::Private::speller(string const & lang)
149 Spellers::iterator it = spellers_.find(lang);
150 if (it != spellers_.end())
153 return addSpeller(lang);
157 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
159 IgnoreList::const_iterator it = ignored_.begin();
160 for (; it != ignored_.end(); ++it) {
161 if ((*it).lang()->code() != wl.lang()->code())
163 if ((*it).word() == wl.word())
170 HunspellChecker::HunspellChecker(): d(new Private)
175 HunspellChecker::~HunspellChecker()
181 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
183 if (d->isIgnored(wl))
186 Hunspell * h = d->speller(wl.lang()->code());
191 string const encoding = h->get_dic_encoding();
192 string const word_to_check = to_iconv_encoding(wl.word(), encoding);
194 if (h->spell(word_to_check.c_str(), &info))
197 if (info & SPELL_COMPOUND) {
198 // FIXME: What to do with that?
199 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
201 if (info & SPELL_FORBIDDEN) {
202 // FIXME: What to do with that?
203 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
210 void HunspellChecker::insert(WordLangTuple const & wl)
212 string const word_to_check = to_utf8(wl.word());
213 Hunspell * h = d->speller(wl.lang()->code());
216 h->add(word_to_check.c_str());
220 void HunspellChecker::accept(WordLangTuple const & wl)
222 d->ignored_.push_back(wl);
226 void HunspellChecker::suggest(WordLangTuple const & wl,
227 docstring_list & suggestions)
230 Hunspell * h = d->speller(wl.lang()->code());
233 string const encoding = h->get_dic_encoding();
234 string const word_to_check = to_iconv_encoding(wl.word(), encoding);
235 char ** suggestion_list;
236 int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
237 if (suggestion_number <= 0)
239 for (int i = 0; i != suggestion_number; ++i)
240 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
241 h->free_list(&suggestion_list, suggestion_number);
245 bool HunspellChecker::hasDictionary(Language const * lang) const
249 string hunspell_path = lyxrc.hunspelldir_path;
250 return (d->haveDictionary(lang->code(), hunspell_path));
254 docstring const HunspellChecker::error()