]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
tex2lyx/Preamble.cpp: forgot a package in previous commit
[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         void cleanCache();
59         void setUserPath(std::string const & path);
60         const string dictPath(int selector);
61         bool haveLanguageFiles(string const & hpath);
62         bool haveDictionary(Language const * lang, string & hpath);
63         bool haveDictionary(Language const * lang);
64         int numDictionaries() const;
65         Hunspell * addSpeller(Language const * lang, string & hpath);
66         Hunspell * addSpeller(Language const * lang);
67         Hunspell * speller(Language const * lang);
68         /// ignored words
69         bool isIgnored(WordLangTuple const & wl) const;
70         /// personal word list interface
71         void remove(WordLangTuple const & wl);
72         void insert(WordLangTuple const & wl);
73         bool learned(WordLangTuple const & wl);
74         /// the spellers
75         Spellers spellers_;
76         ///
77         IgnoreList ignored_;
78         ///
79         LangPersonalWordList personal_;
80         ///
81         std::string user_path_;
82
83         /// the location below system/user directory
84         /// there the aff+dic files lookup will happen
85         const string dictDirectory(void) const { return "dicts"; }
86         int maxLookupSelector(void) const { return 5; }
87         const string HunspellDictionaryName(Language const * lang) {
88                 return lang->variety().empty() 
89                         ? lang->code()
90                         : lang->code() + "-" + lang->variety();
91         }
92         const string myspellPackageDictDirectory(void) {
93                 return "/usr/share/myspell";
94         }
95         const string hunspellPackageDictDirectory(void) {
96                 return "/usr/share/hunspell";
97         }
98 };
99
100
101 HunspellChecker::Private::Private()
102 {
103         setUserPath(lyxrc.hunspelldir_path);
104 }
105
106
107 HunspellChecker::Private::~Private()
108 {
109         cleanCache();
110 }
111
112
113 void HunspellChecker::Private::setUserPath(std::string const & path)
114 {
115         if (user_path_ != lyxrc.hunspelldir_path) {
116                 cleanCache();
117                 user_path_ = path;
118         }
119 }
120
121
122 void HunspellChecker::Private::cleanCache()
123 {
124         Spellers::iterator it = spellers_.begin();
125         Spellers::iterator end = spellers_.end();
126
127         for (; it != end; ++it) {
128                 delete it->second;
129                 it->second = 0;
130         }
131
132         LangPersonalWordList::const_iterator pdit = personal_.begin();
133         LangPersonalWordList::const_iterator pdet = personal_.end();
134
135         for (; pdit != pdet; ++pdit) {
136                 if ( 0 == pdit->second)
137                         continue;
138                 PersonalWordList * pd = pdit->second;
139                 pd->save();
140                 delete pd;
141         }
142 }
143
144
145 bool HunspellChecker::Private::haveLanguageFiles(string const & hpath)
146 {
147         FileName const affix(hpath + ".aff");
148         FileName const dict(hpath + ".dic");
149         return affix.isReadableFile() && dict.isReadableFile();
150 }
151
152
153 const string HunspellChecker::Private::dictPath(int selector)
154 {
155         switch (selector) {
156         case 4:
157                 return addName(hunspellPackageDictDirectory(),dictDirectory());
158         case 3:
159                 return addName(myspellPackageDictDirectory(),dictDirectory());
160         case 2:
161                 return addName(package().system_support().absFileName(),dictDirectory());
162         case 1:
163                 return addName(package().user_support().absFileName(),dictDirectory());
164         default:
165                 return user_path_;
166         }
167 }
168
169
170 bool HunspellChecker::Private::haveDictionary(Language const * lang, string & hpath)
171 {
172         if (hpath.empty())
173                 return false;
174
175         LYXERR(Debug::FILES, "check hunspell path: " << hpath
176                                 << " for language " << (lang ? lang->lang() : "NULL" ));
177
178         string h_path = addName(hpath, HunspellDictionaryName(lang));
179         // first we try lang code+variety
180         if (haveLanguageFiles(h_path)) {
181                 LYXERR(Debug::FILES, "  found " << h_path);
182                 hpath = h_path;
183                 return true;
184         }
185         // another try with code, '_' replaced by '-'
186         h_path = addName(hpath, subst(lang->code(), '_', '-'));
187         if (!haveLanguageFiles(h_path))
188                 return false;
189         LYXERR(Debug::FILES, "  found " << h_path);
190         hpath = h_path;
191         return true;
192 }
193
194
195 bool HunspellChecker::Private::haveDictionary(Language const * lang)
196 {
197         bool result = false;
198
199         setUserPath(lyxrc.hunspelldir_path);
200         for (int p = 0; !result && p < maxLookupSelector(); ++p) {
201                 string lpath = dictPath(p);
202                 result = haveDictionary(lang, lpath);
203         }
204         // FIXME: if result is false... 
205         // we should indicate somehow that this language is not
206         // supported, probably by popping a warning. But we'll need to
207         // remember which warnings we've issued.
208         return result;
209 }
210
211
212 Hunspell * HunspellChecker::Private::speller(Language const * lang)
213 {
214         setUserPath(lyxrc.hunspelldir_path);
215         Spellers::iterator it = spellers_.find(lang->lang());
216         if (it != spellers_.end())
217                 return it->second;
218         return addSpeller(lang);
219 }
220
221
222 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang,string & path)
223 {
224         if (!haveDictionary(lang, path)) {
225                 spellers_[lang->lang()] = 0;
226                 return 0;
227         }
228
229         FileName const affix(path + ".aff");
230         FileName const dict(path + ".dic");
231         Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
232         LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
233         spellers_[lang->lang()] = h;
234         return h;
235 }
236
237
238 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang)
239 {
240         Hunspell * h = 0;
241         for (int p = 0; p < maxLookupSelector() && 0 == h; ++p) {
242                 string lpath = dictPath(p);
243                 h = addSpeller(lang, lpath);
244         }
245         if (h) {
246                 string const encoding = h->get_dic_encoding();
247                 PersonalWordList * pd = new PersonalWordList(lang->lang());
248                 pd->load();
249                 personal_[lang->lang()] = pd;
250                 docstring_list::const_iterator it = pd->begin();
251                 docstring_list::const_iterator et = pd->end();
252                 for (; it != et; ++it) {
253                         string const word_to_add = to_iconv_encoding(*it, encoding);
254                         h->add(word_to_add.c_str());
255                 }
256         }
257         return h;
258 }
259
260
261 int HunspellChecker::Private::numDictionaries() const
262 {
263         int result = 0;
264         Spellers::const_iterator it = spellers_.begin();
265         Spellers::const_iterator et = spellers_.end();
266
267         for (; it != et; ++it)
268                 result += it->second != 0;
269         return result;
270 }
271
272
273 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
274 {
275         IgnoreList::const_iterator it = ignored_.begin();
276         for (; it != ignored_.end(); ++it) {
277                 if (it->lang()->code() != wl.lang()->code())
278                         continue;
279                 if (it->word() == wl.word())
280                         return true;
281         }
282         return false;
283 }
284
285 /// personal word list interface
286 void HunspellChecker::Private::remove(WordLangTuple const & wl)
287 {
288         Hunspell * h = speller(wl.lang());
289         if (!h)
290                 return;
291         string const encoding = h->get_dic_encoding();
292         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
293         h->remove(word_to_check.c_str());
294         PersonalWordList * pd = personal_[wl.lang()->lang()];
295         if (!pd)
296                 return;
297         pd->remove(wl.word());
298 }
299
300
301 void HunspellChecker::Private::insert(WordLangTuple const & wl)
302 {
303         Hunspell * h = speller(wl.lang());
304         if (!h)
305                 return;
306         string const encoding = h->get_dic_encoding();
307         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
308         h->add(word_to_check.c_str());
309         PersonalWordList * pd = personal_[wl.lang()->lang()];
310         if (!pd)
311                 return;
312         pd->insert(wl.word());
313 }
314
315
316 bool HunspellChecker::Private::learned(WordLangTuple const & wl)
317 {
318         PersonalWordList * pd = personal_[wl.lang()->lang()];
319         if (!pd)
320                 return false;
321         return pd->exists(wl.word());
322 }
323
324
325 HunspellChecker::HunspellChecker()
326         : d(new Private)
327 {}
328
329
330 HunspellChecker::~HunspellChecker()
331 {
332         delete d;
333 }
334
335
336 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
337 {
338         if (d->isIgnored(wl))
339                 return WORD_OK;
340
341         Hunspell * h = d->speller(wl.lang());
342         if (!h)
343                 return NO_DICTIONARY;
344         int info;
345
346         string const encoding = h->get_dic_encoding();
347         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
348
349         LYXERR(Debug::GUI, "spellCheck: \"" <<
350                    wl.word() << "\", lang = " << wl.lang()->lang()) ;
351         if (h->spell(word_to_check.c_str(), &info))
352                 return d->learned(wl) ? LEARNED_WORD : WORD_OK;
353
354         if (info & SPELL_COMPOUND) {
355                 // FIXME: What to do with that?
356                 LYXERR(Debug::GUI, "Hunspell compound word found " << word_to_check);
357         }
358         if (info & SPELL_FORBIDDEN) {
359                 // This was removed from personal dictionary
360                 LYXERR(Debug::GUI, "Hunspell explicit forbidden word found " << word_to_check);
361         }
362
363         return UNKNOWN_WORD;
364 }
365
366
367 void HunspellChecker::advanceChangeNumber()
368 {
369         nextChangeNumber();
370 }
371
372
373 void HunspellChecker::insert(WordLangTuple const & wl)
374 {
375         d->insert(wl);
376         LYXERR(Debug::GUI, "learn word: \"" << wl.word() << "\"") ;
377         advanceChangeNumber();
378 }
379
380
381 void HunspellChecker::remove(WordLangTuple const & wl)
382 {
383         d->remove(wl);
384         LYXERR(Debug::GUI, "unlearn word: \"" << wl.word() << "\"") ;
385         advanceChangeNumber();
386 }
387
388
389 void HunspellChecker::accept(WordLangTuple const & wl)
390 {
391         d->ignored_.push_back(wl);
392         LYXERR(Debug::GUI, "ignore word: \"" << wl.word() << "\"") ;
393         advanceChangeNumber();
394 }
395
396
397 void HunspellChecker::suggest(WordLangTuple const & wl,
398         docstring_list & suggestions)
399 {
400         suggestions.clear();
401         Hunspell * h = d->speller(wl.lang());
402         if (!h)
403                 return;
404         string const encoding = h->get_dic_encoding();
405         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
406         char ** suggestion_list;
407         int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
408         if (suggestion_number <= 0)
409                 return;
410         for (int i = 0; i != suggestion_number; ++i)
411                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
412         h->free_list(&suggestion_list, suggestion_number);
413 }
414
415
416 void HunspellChecker::stem(WordLangTuple const & wl,
417         docstring_list & suggestions)
418 {
419         suggestions.clear();
420         Hunspell * h = d->speller(wl.lang());
421         if (!h)
422                 return;
423         string const encoding = h->get_dic_encoding();
424         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
425         char ** suggestion_list;
426         int const suggestion_number = h->stem(&suggestion_list, word_to_check.c_str());
427         if (suggestion_number <= 0)
428                 return;
429         for (int i = 0; i != suggestion_number; ++i)
430                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
431         h->free_list(&suggestion_list, suggestion_number);
432 }
433
434
435 bool HunspellChecker::hasDictionary(Language const * lang) const
436 {
437         if (!lang)
438                 return false;
439         return d->haveDictionary(lang);
440 }
441
442
443 int HunspellChecker::numDictionaries() const
444 {
445         return d->numDictionaries();
446 }
447
448
449 docstring const HunspellChecker::error()
450 {
451         return docstring();
452 }
453
454
455 } // namespace lyx