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/Package.h"
24 #include "support/FileName.h"
25 #include "support/gettext.h"
26 #include "support/lassert.h"
27 #include "support/lstrings.h"
28 #include "support/os.h"
30 #include <hunspell/hunspell.hxx>
37 using namespace lyx::support;
38 using namespace lyx::support::os;
44 typedef map<std::string, Hunspell *> Spellers;
45 typedef vector<WordLangTuple> IgnoreList;
50 struct HunspellChecker::Private
56 const string dictPath(int selector);
57 bool haveLanguageFiles(string const & hpath);
58 bool haveDictionary(string const & lang, string & hpath);
59 bool haveDictionary(string const & lang);
60 Hunspell * addSpeller(string const & lang, string & hpath);
61 Hunspell * addSpeller(string const & lang);
62 Hunspell * speller(string const & lang);
64 bool isIgnored(WordLangTuple const & wl) const;
71 /// the location below system/user directory
72 /// there the aff+dic files lookup will happen
73 const string dictDirectory(void) const { return "dict"; }
74 int maxLookupSelector(void) const { return 3; }
78 HunspellChecker::Private::~Private()
80 Spellers::iterator it = spellers_.begin();
81 Spellers::iterator end = spellers_.end();
83 for (; it != end; ++it) {
84 if ( 0 != it->second) delete it->second;
89 bool HunspellChecker::Private::haveLanguageFiles(string const & hpath)
91 FileName const affix(hpath + ".aff");
92 FileName const dict(hpath + ".dic");
93 return affix.isReadableFile() && dict.isReadableFile();
97 const string HunspellChecker::Private::dictPath(int selector)
101 return addName(lyx::support::package().system_support().absFileName(),dictDirectory());
104 return addName(lyx::support::package().user_support().absFileName(),dictDirectory());
107 return lyxrc.hunspelldir_path;
112 bool HunspellChecker::Private::haveDictionary(string const & lang, string & hpath)
118 LYXERR(Debug::FILES, "check hunspell path: " << hpath << " for language " << lang);
119 string h_path = addName(hpath, lang);
120 if (!haveLanguageFiles(h_path)) {
121 // try with '_' replaced by '-'
122 h_path = addName(hpath, subst(lang, '_', '-'));
123 if (!haveLanguageFiles(h_path)) {
124 // FIXME: We should indicate somehow that this language is not
125 // supported, probably by popping a warning. But we'll need to
126 // remember which warnings we've issued.
135 bool HunspellChecker::Private::haveDictionary(string const & lang)
138 for ( int p = 0; !result && p < maxLookupSelector(); p++ ) {
139 string lpath = dictPath(p);
140 result = haveDictionary(lang, lpath);
146 Hunspell * HunspellChecker::Private::speller(string const & lang)
148 Spellers::iterator it = spellers_.find(lang);
149 if (it != spellers_.end())
152 return addSpeller(lang);
156 Hunspell * HunspellChecker::Private::addSpeller(string const & lang,string & path)
158 if (!haveDictionary(lang, path)) {
163 FileName const affix(path + ".aff");
164 FileName const dict(path + ".dic");
165 Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
166 LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
172 Hunspell * HunspellChecker::Private::addSpeller(string const & lang)
175 for ( int p = 0; p < maxLookupSelector() && 0 == h; p++ ) {
176 string lpath = dictPath(p);
177 h = addSpeller(lang, lpath);
183 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
185 IgnoreList::const_iterator it = ignored_.begin();
186 for (; it != ignored_.end(); ++it) {
187 if ((*it).lang()->code() != wl.lang()->code())
189 if ((*it).word() == wl.word())
196 HunspellChecker::HunspellChecker(): d(new Private)
201 HunspellChecker::~HunspellChecker()
207 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
209 if (d->isIgnored(wl))
212 Hunspell * h = d->speller(wl.lang()->code());
217 string const encoding = h->get_dic_encoding();
218 string const word_to_check = to_iconv_encoding(wl.word(), encoding);
220 if (h->spell(word_to_check.c_str(), &info))
223 if (info & SPELL_COMPOUND) {
224 // FIXME: What to do with that?
225 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
227 if (info & SPELL_FORBIDDEN) {
228 // FIXME: What to do with that?
229 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
236 void HunspellChecker::insert(WordLangTuple const & wl)
238 string const word_to_check = to_utf8(wl.word());
239 Hunspell * h = d->speller(wl.lang()->code());
242 h->add(word_to_check.c_str());
246 void HunspellChecker::accept(WordLangTuple const & wl)
248 d->ignored_.push_back(wl);
252 void HunspellChecker::suggest(WordLangTuple const & wl,
253 docstring_list & suggestions)
256 Hunspell * h = d->speller(wl.lang()->code());
259 string const encoding = h->get_dic_encoding();
260 string const word_to_check = to_iconv_encoding(wl.word(), encoding);
261 char ** suggestion_list;
262 int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
263 if (suggestion_number <= 0)
265 for (int i = 0; i != suggestion_number; ++i)
266 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
267 h->free_list(&suggestion_list, suggestion_number);
271 bool HunspellChecker::hasDictionary(Language const * lang) const
275 return (d->haveDictionary(lang->code()));
279 docstring const HunspellChecker::error()