]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
Forgot this part.
[lyx.git] / src / HunspellChecker.cpp
1 /**
2  * \file HunspellChecker.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Abdelrazak Younes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "HunspellChecker.h"
14 #include "PersonalWordList.h"
15
16 #include "LyXRC.h"
17 #include "WordLangTuple.h"
18
19 #include "frontends/alert.h"
20
21 #include "support/debug.h"
22 #include "support/docstring_list.h"
23 #include "support/filetools.h"
24 #include "support/Package.h"
25 #include "support/FileName.h"
26 #include "support/gettext.h"
27 #include "support/lassert.h"
28 #include "support/lstrings.h"
29 #include "support/os.h"
30
31 #include <hunspell/hunspell.hxx>
32
33 #include <map>
34 #include <string>
35 #include <vector>
36
37 using namespace std;
38 using namespace lyx::support;
39 using namespace lyx::support::os;
40
41 namespace lyx {
42
43 namespace {
44
45 typedef map<std::string, Hunspell *> Spellers;
46 typedef map<std::string, PersonalWordList *> LangPersonalWordList;
47
48 typedef vector<WordLangTuple> IgnoreList;
49
50 } // anon namespace
51
52
53 struct HunspellChecker::Private
54 {
55         Private();
56         ~Private();
57
58         const string dictPath(int selector);
59         bool haveLanguageFiles(string const & hpath);
60         bool haveDictionary(string const & lang, string & hpath);
61         bool haveDictionary(string const & lang);
62         Hunspell * addSpeller(string const & lang, string & hpath);
63         Hunspell * addSpeller(Language const * lang);
64         Hunspell * speller(Language const * lang);
65         /// ignored words
66         bool isIgnored(WordLangTuple const & wl) const;
67         /// personal word list interface
68         void remove(WordLangTuple const & wl);
69         void insert(WordLangTuple const & wl);
70         bool learned(WordLangTuple const & wl);
71         /// the spellers
72         Spellers spellers_;
73         ///
74         IgnoreList ignored_;
75         ///
76         LangPersonalWordList personal_;
77
78         /// the location below system/user directory
79         /// there the aff+dic files lookup will happen
80         const string dictDirectory(void) const { return "dict"; }
81         int maxLookupSelector(void) const { return 3; }
82 };
83
84
85 HunspellChecker::Private::Private()
86 {
87 }
88
89
90 HunspellChecker::Private::~Private()
91 {
92         Spellers::iterator it = spellers_.begin();
93         Spellers::iterator end = spellers_.end();
94
95         for (; it != end; ++it) {
96                 if ( 0 != it->second) delete it->second;
97         }
98
99         LangPersonalWordList::const_iterator pdit = personal_.begin();
100         LangPersonalWordList::const_iterator pdet = personal_.end();
101
102         for (; pdit != pdet; ++pdit) {
103                 if ( 0 == pdit->second)
104                         continue;
105                 PersonalWordList * pd = pdit->second;
106                 pd->save();
107                 delete pd;
108         }
109 }
110
111
112 bool HunspellChecker::Private::haveLanguageFiles(string const & hpath)
113 {
114         FileName const affix(hpath + ".aff");
115         FileName const dict(hpath + ".dic");
116         return affix.isReadableFile() && dict.isReadableFile();
117 }
118
119
120 const string HunspellChecker::Private::dictPath(int selector)
121 {
122         switch (selector) {
123         case 2:
124                 return addName(package().system_support().absFileName(),dictDirectory());
125                 break;
126         case 1:
127                 return addName(package().user_support().absFileName(),dictDirectory());
128                 break;
129         default:
130                 return lyxrc.hunspelldir_path;
131         }
132 }
133
134
135 bool HunspellChecker::Private::haveDictionary(string const & lang, string & hpath)
136 {
137         if (hpath.empty()) {
138                 return false;
139         }
140
141         LYXERR(Debug::FILES, "check hunspell path: " << hpath << " for language " << lang);
142         string h_path = addName(hpath, lang);
143         if (!haveLanguageFiles(h_path)) {
144                 // try with '_' replaced by '-'
145                 h_path = addName(hpath, subst(lang, '_', '-'));
146                 if (!haveLanguageFiles(h_path)) {
147                         // FIXME: We should indicate somehow that this language is not
148                         // supported, probably by popping a warning. But we'll need to
149                         // remember which warnings we've issued.
150                         return false;
151                 }
152         }
153         hpath = h_path;
154         return true;
155 }
156
157
158 bool HunspellChecker::Private::haveDictionary(string const & lang)
159 {
160         bool result = false;
161         for ( int p = 0; !result && p < maxLookupSelector(); p++ ) {
162                 string lpath = dictPath(p);
163                 result = haveDictionary(lang, lpath);
164         }
165         return result;
166 }
167
168
169 Hunspell * HunspellChecker::Private::speller(Language const * lang)
170 {
171         Spellers::iterator it = spellers_.find(lang->code());
172         if (it != spellers_.end())
173                 return it->second;
174
175         return addSpeller(lang);
176 }
177
178
179 Hunspell * HunspellChecker::Private::addSpeller(string const & lang,string & path)
180 {
181         if (!haveDictionary(lang, path)) {
182                 spellers_[lang] = 0;
183                 return 0;
184         }
185
186         FileName const affix(path + ".aff");
187         FileName const dict(path + ".dic");
188         Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
189         LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
190         spellers_[lang] = h;
191         return h;
192 }
193
194
195 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang)
196 {
197         Hunspell * h = 0;
198         for ( int p = 0; p < maxLookupSelector() && 0 == h; p++ ) {
199                 string lpath = dictPath(p);
200                 h = addSpeller(lang->code(), lpath);
201         }
202         if (0 != h) {
203                 string const encoding = h->get_dic_encoding();
204                 PersonalWordList * pd = new PersonalWordList(lang->lang());
205                 pd->load();
206                 personal_[lang->lang()] = pd;
207                 docstring_list::const_iterator it = pd->begin();
208                 docstring_list::const_iterator et = pd->end();
209                 for (; it != et; ++it) {
210                         string const word_to_add = to_iconv_encoding(*it, encoding);
211                         h->add(word_to_add.c_str());
212                 }
213         }
214         return h;
215 }
216
217
218 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
219 {
220         IgnoreList::const_iterator it = ignored_.begin();
221         for (; it != ignored_.end(); ++it) {
222                 if ((*it).lang()->code() != wl.lang()->code())
223                         continue;
224                 if ((*it).word() == wl.word())
225                         return true;
226         }
227         return false;
228 }
229
230 /// personal word list interface
231 void HunspellChecker::Private::remove(WordLangTuple const & wl)
232 {
233         Hunspell * h = speller(wl.lang());
234         if (!h)
235                 return;
236         string const encoding = h->get_dic_encoding();
237         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
238         h->remove(word_to_check.c_str());
239         PersonalWordList * pd = personal_[wl.lang()->lang()];
240         if (!pd)
241                 return;
242         pd->remove(wl.word());
243 }
244
245
246 void HunspellChecker::Private::insert(WordLangTuple const & wl)
247 {
248         Hunspell * h = speller(wl.lang());
249         if (!h)
250                 return;
251         string const encoding = h->get_dic_encoding();
252         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
253         h->add(word_to_check.c_str());
254         PersonalWordList * pd = personal_[wl.lang()->lang()];
255         if (!pd)
256                 return;
257         pd->insert(wl.word());
258 }
259
260
261 bool HunspellChecker::Private::learned(WordLangTuple const & wl)
262 {
263         PersonalWordList * pd = personal_[wl.lang()->lang()];
264         if (!pd)
265                 return false;
266         return pd->exists(wl.word());
267 }
268
269
270 HunspellChecker::HunspellChecker(): d(new Private)
271 {
272 }
273
274
275 HunspellChecker::~HunspellChecker()
276 {
277         delete d;
278 }
279
280
281 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
282 {
283         if (d->isIgnored(wl))
284                 return WORD_OK;
285
286         Hunspell * h = d->speller(wl.lang());
287         if (!h)
288                 return WORD_OK;
289         int info;
290
291         string const encoding = h->get_dic_encoding();
292         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
293
294         if (h->spell(word_to_check.c_str(), &info))
295                 return d->learned(wl) ? LEARNED_WORD : WORD_OK;
296
297         if (info & SPELL_COMPOUND) {
298                 // FIXME: What to do with that?
299                 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
300         }
301         if (info & SPELL_FORBIDDEN) {
302                 // This was removed from personal dictionary
303                 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
304         }
305
306         return UNKNOWN_WORD;
307 }
308
309
310 void HunspellChecker::advanceChangeNumber()
311 {
312         nextChangeNumber();
313 }
314
315
316 void HunspellChecker::insert(WordLangTuple const & wl)
317 {
318         d->insert(wl);
319         LYXERR(Debug::GUI, "learn word: \"" << wl.word() << "\"") ;
320         advanceChangeNumber();
321 }
322
323
324 void HunspellChecker::remove(WordLangTuple const & wl)
325 {
326         d->remove(wl);
327         LYXERR(Debug::GUI, "unlearn word: \"" << wl.word() << "\"") ;
328         advanceChangeNumber();
329 }
330
331
332 void HunspellChecker::accept(WordLangTuple const & wl)
333 {
334         d->ignored_.push_back(wl);
335         LYXERR(Debug::GUI, "ignore word: \"" << wl.word() << "\"") ;
336         advanceChangeNumber();
337 }
338
339
340 void HunspellChecker::suggest(WordLangTuple const & wl,
341         docstring_list & suggestions)
342 {
343         suggestions.clear();
344         Hunspell * h = d->speller(wl.lang());
345         if (!h)
346                 return;
347         string const encoding = h->get_dic_encoding();
348         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
349         char ** suggestion_list;
350         int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
351         if (suggestion_number <= 0)
352                 return;
353         for (int i = 0; i != suggestion_number; ++i)
354                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
355         h->free_list(&suggestion_list, suggestion_number);
356 }
357
358
359 bool HunspellChecker::hasDictionary(Language const * lang) const
360 {
361         if (!lang)
362                 return false;
363         return (d->haveDictionary(lang->code()));
364 }
365
366
367 docstring const HunspellChecker::error()
368 {
369         return docstring();
370 }
371
372
373 } // namespace lyx