]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
Update tex2lyx test files
[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 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 4; }
87         const string HunspellDictionaryName(Language const * lang) {
88                 return lang->variety().empty() 
89                         ? lang->code()
90                         : lang->code() + "-" + lang->variety();
91         }
92         const string osPackageDictDirectory(void) {
93                 return "/usr/share/myspell";
94         }
95 };
96
97
98 HunspellChecker::Private::Private()
99 {
100         setUserPath(lyxrc.hunspelldir_path);
101 }
102
103
104 HunspellChecker::Private::~Private()
105 {
106         cleanCache();
107 }
108
109
110 void HunspellChecker::Private::setUserPath(std::string path)
111 {
112         if (user_path_ != lyxrc.hunspelldir_path) {
113                 cleanCache();
114                 user_path_ = path;
115         }
116 }
117
118
119 void HunspellChecker::Private::cleanCache()
120 {
121         Spellers::iterator it = spellers_.begin();
122         Spellers::iterator end = spellers_.end();
123
124         for (; it != end; ++it) {
125                 delete it->second;
126                 it->second = 0;
127         }
128
129         LangPersonalWordList::const_iterator pdit = personal_.begin();
130         LangPersonalWordList::const_iterator pdet = personal_.end();
131
132         for (; pdit != pdet; ++pdit) {
133                 if ( 0 == pdit->second)
134                         continue;
135                 PersonalWordList * pd = pdit->second;
136                 pd->save();
137                 delete pd;
138         }
139 }
140
141
142 bool HunspellChecker::Private::haveLanguageFiles(string const & hpath)
143 {
144         FileName const affix(hpath + ".aff");
145         FileName const dict(hpath + ".dic");
146         return affix.isReadableFile() && dict.isReadableFile();
147 }
148
149
150 const string HunspellChecker::Private::dictPath(int selector)
151 {
152         switch (selector) {
153         case 3:
154                 return addName(osPackageDictDirectory(),dictDirectory());
155                 break;
156         case 2:
157                 return addName(package().system_support().absFileName(),dictDirectory());
158                 break;
159         case 1:
160                 return addName(package().user_support().absFileName(),dictDirectory());
161                 break;
162         default:
163                 return user_path_;
164         }
165 }
166
167
168 bool HunspellChecker::Private::haveDictionary(Language const * lang, string & hpath)
169 {
170         if (hpath.empty()) {
171                 return false;
172         }
173
174         LYXERR(Debug::FILES, "check hunspell path: " << hpath
175                                 << " for language " << (lang ? lang->lang() : "NULL" ));
176
177         string h_path = addName(hpath, HunspellDictionaryName(lang));
178         // first we try lang code+variety
179         if (haveLanguageFiles(h_path)) {
180                 LYXERR(Debug::FILES, "  found " << h_path);
181                 hpath = h_path;
182                 return true;
183         }
184         // another try with code, '_' replaced by '-'
185         h_path = addName(hpath, subst(lang->code(), '_', '-'));
186         if (!haveLanguageFiles(h_path)) {
187                 return false;
188         }
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         }
219         return addSpeller(lang);
220 }
221
222
223 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang,string & path)
224 {
225         if (!haveDictionary(lang, path)) {
226                 spellers_[lang->lang()] = 0;
227                 return 0;
228         }
229
230         FileName const affix(path + ".aff");
231         FileName const dict(path + ".dic");
232         Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
233         LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
234         spellers_[lang->lang()] = h;
235         return h;
236 }
237
238
239 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang)
240 {
241         Hunspell * h = 0;
242         for ( int p = 0; p < maxLookupSelector() && 0 == h; p++ ) {
243                 string lpath = dictPath(p);
244                 h = addSpeller(lang, lpath);
245         }
246         if (0 != h) {
247                 string const encoding = h->get_dic_encoding();
248                 PersonalWordList * pd = new PersonalWordList(lang->lang());
249                 pd->load();
250                 personal_[lang->lang()] = pd;
251                 docstring_list::const_iterator it = pd->begin();
252                 docstring_list::const_iterator et = pd->end();
253                 for (; it != et; ++it) {
254                         string const word_to_add = to_iconv_encoding(*it, encoding);
255                         h->add(word_to_add.c_str());
256                 }
257         }
258         return h;
259 }
260
261
262 int HunspellChecker::Private::numDictionaries() const
263 {
264         int result = 0;
265         Spellers::const_iterator it = spellers_.begin();
266         Spellers::const_iterator et = spellers_.end();
267
268         for (; it != et; ++it) {
269                 result += it->second != 0;
270         }
271         return result;
272 }
273
274
275 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
276 {
277         IgnoreList::const_iterator it = ignored_.begin();
278         for (; it != ignored_.end(); ++it) {
279                 if ((*it).lang()->code() != wl.lang()->code())
280                         continue;
281                 if ((*it).word() == wl.word())
282                         return true;
283         }
284         return false;
285 }
286
287 /// personal word list interface
288 void HunspellChecker::Private::remove(WordLangTuple const & wl)
289 {
290         Hunspell * h = speller(wl.lang());
291         if (!h)
292                 return;
293         string const encoding = h->get_dic_encoding();
294         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
295         h->remove(word_to_check.c_str());
296         PersonalWordList * pd = personal_[wl.lang()->lang()];
297         if (!pd)
298                 return;
299         pd->remove(wl.word());
300 }
301
302
303 void HunspellChecker::Private::insert(WordLangTuple const & wl)
304 {
305         Hunspell * h = speller(wl.lang());
306         if (!h)
307                 return;
308         string const encoding = h->get_dic_encoding();
309         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
310         h->add(word_to_check.c_str());
311         PersonalWordList * pd = personal_[wl.lang()->lang()];
312         if (!pd)
313                 return;
314         pd->insert(wl.word());
315 }
316
317
318 bool HunspellChecker::Private::learned(WordLangTuple const & wl)
319 {
320         PersonalWordList * pd = personal_[wl.lang()->lang()];
321         if (!pd)
322                 return false;
323         return pd->exists(wl.word());
324 }
325
326
327 HunspellChecker::HunspellChecker(): d(new Private)
328 {
329 }
330
331
332 HunspellChecker::~HunspellChecker()
333 {
334         delete d;
335 }
336
337
338 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
339 {
340         if (d->isIgnored(wl))
341                 return WORD_OK;
342
343         Hunspell * h = d->speller(wl.lang());
344         if (!h)
345                 return NO_DICTIONARY;
346         int info;
347
348         string const encoding = h->get_dic_encoding();
349         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
350
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::FILES, "Hunspell compound word found " << word_to_check);
357         }
358         if (info & SPELL_FORBIDDEN) {
359                 // This was removed from personal dictionary
360                 LYXERR(Debug::FILES, "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