]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
Split pdf format as discussed on the list
[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 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 const & 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         LYXERR(Debug::FILES, "check hunspell path: " << hpath
174                                 << " for language " << (lang ? lang->lang() : "NULL" ));
175
176         string h_path = addName(hpath, HunspellDictionaryName(lang));
177         // first we try lang code+variety
178         if (haveLanguageFiles(h_path)) {
179                 LYXERR(Debug::FILES, "  found " << h_path);
180                 hpath = h_path;
181                 return true;
182         }
183         // another try with code, '_' replaced by '-'
184         h_path = addName(hpath, subst(lang->code(), '_', '-'));
185         if (!haveLanguageFiles(h_path))
186                 return false;
187         LYXERR(Debug::FILES, "  found " << h_path);
188         hpath = h_path;
189         return true;
190 }
191
192
193 bool HunspellChecker::Private::haveDictionary(Language const * lang)
194 {
195         bool result = false;
196
197         setUserPath(lyxrc.hunspelldir_path);
198         for (int p = 0; !result && p < maxLookupSelector(); ++p) {
199                 string lpath = dictPath(p);
200                 result = haveDictionary(lang, lpath);
201         }
202         // FIXME: if result is false... 
203         // we should indicate somehow that this language is not
204         // supported, probably by popping a warning. But we'll need to
205         // remember which warnings we've issued.
206         return result;
207 }
208
209
210 Hunspell * HunspellChecker::Private::speller(Language const * lang)
211 {
212         setUserPath(lyxrc.hunspelldir_path);
213         Spellers::iterator it = spellers_.find(lang->lang());
214         if (it != spellers_.end())
215                 return it->second;
216         return addSpeller(lang);
217 }
218
219
220 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang,string & path)
221 {
222         if (!haveDictionary(lang, path)) {
223                 spellers_[lang->lang()] = 0;
224                 return 0;
225         }
226
227         FileName const affix(path + ".aff");
228         FileName const dict(path + ".dic");
229         Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
230         LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
231         spellers_[lang->lang()] = h;
232         return h;
233 }
234
235
236 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang)
237 {
238         Hunspell * h = 0;
239         for (int p = 0; p < maxLookupSelector() && 0 == h; ++p) {
240                 string lpath = dictPath(p);
241                 h = addSpeller(lang, lpath);
242         }
243         if (h) {
244                 string const encoding = h->get_dic_encoding();
245                 PersonalWordList * pd = new PersonalWordList(lang->lang());
246                 pd->load();
247                 personal_[lang->lang()] = pd;
248                 docstring_list::const_iterator it = pd->begin();
249                 docstring_list::const_iterator et = pd->end();
250                 for (; it != et; ++it) {
251                         string const word_to_add = to_iconv_encoding(*it, encoding);
252                         h->add(word_to_add.c_str());
253                 }
254         }
255         return h;
256 }
257
258
259 int HunspellChecker::Private::numDictionaries() const
260 {
261         int result = 0;
262         Spellers::const_iterator it = spellers_.begin();
263         Spellers::const_iterator et = spellers_.end();
264
265         for (; it != et; ++it)
266                 result += it->second != 0;
267         return result;
268 }
269
270
271 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
272 {
273         IgnoreList::const_iterator it = ignored_.begin();
274         for (; it != ignored_.end(); ++it) {
275                 if (it->lang()->code() != wl.lang()->code())
276                         continue;
277                 if (it->word() == wl.word())
278                         return true;
279         }
280         return false;
281 }
282
283 /// personal word list interface
284 void HunspellChecker::Private::remove(WordLangTuple const & wl)
285 {
286         Hunspell * h = speller(wl.lang());
287         if (!h)
288                 return;
289         string const encoding = h->get_dic_encoding();
290         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
291         h->remove(word_to_check.c_str());
292         PersonalWordList * pd = personal_[wl.lang()->lang()];
293         if (!pd)
294                 return;
295         pd->remove(wl.word());
296 }
297
298
299 void HunspellChecker::Private::insert(WordLangTuple const & wl)
300 {
301         Hunspell * h = speller(wl.lang());
302         if (!h)
303                 return;
304         string const encoding = h->get_dic_encoding();
305         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
306         h->add(word_to_check.c_str());
307         PersonalWordList * pd = personal_[wl.lang()->lang()];
308         if (!pd)
309                 return;
310         pd->insert(wl.word());
311 }
312
313
314 bool HunspellChecker::Private::learned(WordLangTuple const & wl)
315 {
316         PersonalWordList * pd = personal_[wl.lang()->lang()];
317         if (!pd)
318                 return false;
319         return pd->exists(wl.word());
320 }
321
322
323 HunspellChecker::HunspellChecker()
324         : d(new Private)
325 {}
326
327
328 HunspellChecker::~HunspellChecker()
329 {
330         delete d;
331 }
332
333
334 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
335 {
336         if (d->isIgnored(wl))
337                 return WORD_OK;
338
339         Hunspell * h = d->speller(wl.lang());
340         if (!h)
341                 return NO_DICTIONARY;
342         int info;
343
344         string const encoding = h->get_dic_encoding();
345         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
346
347         if (h->spell(word_to_check.c_str(), &info))
348                 return d->learned(wl) ? LEARNED_WORD : WORD_OK;
349
350         if (info & SPELL_COMPOUND) {
351                 // FIXME: What to do with that?
352                 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
353         }
354         if (info & SPELL_FORBIDDEN) {
355                 // This was removed from personal dictionary
356                 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
357         }
358
359         return UNKNOWN_WORD;
360 }
361
362
363 void HunspellChecker::advanceChangeNumber()
364 {
365         nextChangeNumber();
366 }
367
368
369 void HunspellChecker::insert(WordLangTuple const & wl)
370 {
371         d->insert(wl);
372         LYXERR(Debug::GUI, "learn word: \"" << wl.word() << "\"") ;
373         advanceChangeNumber();
374 }
375
376
377 void HunspellChecker::remove(WordLangTuple const & wl)
378 {
379         d->remove(wl);
380         LYXERR(Debug::GUI, "unlearn word: \"" << wl.word() << "\"") ;
381         advanceChangeNumber();
382 }
383
384
385 void HunspellChecker::accept(WordLangTuple const & wl)
386 {
387         d->ignored_.push_back(wl);
388         LYXERR(Debug::GUI, "ignore word: \"" << wl.word() << "\"") ;
389         advanceChangeNumber();
390 }
391
392
393 void HunspellChecker::suggest(WordLangTuple const & wl,
394         docstring_list & suggestions)
395 {
396         suggestions.clear();
397         Hunspell * h = d->speller(wl.lang());
398         if (!h)
399                 return;
400         string const encoding = h->get_dic_encoding();
401         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
402         char ** suggestion_list;
403         int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
404         if (suggestion_number <= 0)
405                 return;
406         for (int i = 0; i != suggestion_number; ++i)
407                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
408         h->free_list(&suggestion_list, suggestion_number);
409 }
410
411
412 void HunspellChecker::stem(WordLangTuple const & wl,
413         docstring_list & suggestions)
414 {
415         suggestions.clear();
416         Hunspell * h = d->speller(wl.lang());
417         if (!h)
418                 return;
419         string const encoding = h->get_dic_encoding();
420         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
421         char ** suggestion_list;
422         int const suggestion_number = h->stem(&suggestion_list, word_to_check.c_str());
423         if (suggestion_number <= 0)
424                 return;
425         for (int i = 0; i != suggestion_number; ++i)
426                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
427         h->free_list(&suggestion_list, suggestion_number);
428 }
429
430
431 bool HunspellChecker::hasDictionary(Language const * lang) const
432 {
433         if (!lang)
434                 return false;
435         return d->haveDictionary(lang);
436 }
437
438
439 int HunspellChecker::numDictionaries() const
440 {
441         return d->numDictionaries();
442 }
443
444
445 docstring const HunspellChecker::error()
446 {
447         return docstring();
448 }
449
450
451 } // namespace lyx