]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
In InsetText::fixParagraphsFont, differentiate the case of pass thru paragraphs for...
[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(Language const * lang, string & hpath);
61         bool haveDictionary(Language const * lang);
62         Hunspell * addSpeller(Language 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 "dicts"; }
81         int maxLookupSelector(void) const { return 3; }
82         const string HunspellDictionaryName(Language const * lang) {
83                 return lang->variety().empty() 
84                         ? lang->code()
85                         : lang->code() + "-" + lang->variety();
86         }
87 };
88
89
90 HunspellChecker::Private::Private()
91 {
92 }
93
94
95 HunspellChecker::Private::~Private()
96 {
97         Spellers::iterator it = spellers_.begin();
98         Spellers::iterator end = spellers_.end();
99
100         for (; it != end; ++it) {
101                 if ( 0 != it->second) delete it->second;
102         }
103
104         LangPersonalWordList::const_iterator pdit = personal_.begin();
105         LangPersonalWordList::const_iterator pdet = personal_.end();
106
107         for (; pdit != pdet; ++pdit) {
108                 if ( 0 == pdit->second)
109                         continue;
110                 PersonalWordList * pd = pdit->second;
111                 pd->save();
112                 delete pd;
113         }
114 }
115
116
117 bool HunspellChecker::Private::haveLanguageFiles(string const & hpath)
118 {
119         FileName const affix(hpath + ".aff");
120         FileName const dict(hpath + ".dic");
121         return affix.isReadableFile() && dict.isReadableFile();
122 }
123
124
125 const string HunspellChecker::Private::dictPath(int selector)
126 {
127         switch (selector) {
128         case 2:
129                 return addName(package().system_support().absFileName(),dictDirectory());
130                 break;
131         case 1:
132                 return addName(package().user_support().absFileName(),dictDirectory());
133                 break;
134         default:
135                 return lyxrc.hunspelldir_path;
136         }
137 }
138
139
140 bool HunspellChecker::Private::haveDictionary(Language const * lang, string & hpath)
141 {
142         if (hpath.empty()) {
143                 return false;
144         }
145
146         LYXERR(Debug::FILES, "check hunspell path: " << hpath << " for language " << lang);
147         string h_path = addName(hpath, HunspellDictionaryName(lang));
148         // first we try lang code+variety
149         if (haveLanguageFiles(h_path)) {
150                 hpath = h_path;
151                 return true;
152         }
153         // another try with code, '_' replaced by '-'
154         h_path = addName(hpath, subst(lang->code(), '_', '-'));
155         if (!haveLanguageFiles(h_path)) {
156                 return false;
157         }
158         hpath = h_path;
159         return true;
160 }
161
162
163 bool HunspellChecker::Private::haveDictionary(Language const * lang)
164 {
165         bool result = false;
166         for ( int p = 0; !result && p < maxLookupSelector(); p++ ) {
167                 string lpath = dictPath(p);
168                 result = haveDictionary(lang, lpath);
169         }
170         // FIXME: if result is false... 
171         // we should indicate somehow that this language is not
172         // supported, probably by popping a warning. But we'll need to
173         // remember which warnings we've issued.
174         return result;
175 }
176
177
178 Hunspell * HunspellChecker::Private::speller(Language const * lang)
179 {
180         Spellers::iterator it = spellers_.find(lang->lang());
181         if (it != spellers_.end())
182                 return it->second;
183
184         return addSpeller(lang);
185 }
186
187
188 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang,string & path)
189 {
190         if (!haveDictionary(lang, path)) {
191                 spellers_[lang->lang()] = 0;
192                 return 0;
193         }
194
195         FileName const affix(path + ".aff");
196         FileName const dict(path + ".dic");
197         Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
198         LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
199         spellers_[lang->lang()] = h;
200         return h;
201 }
202
203
204 Hunspell * HunspellChecker::Private::addSpeller(Language const * lang)
205 {
206         Hunspell * h = 0;
207         for ( int p = 0; p < maxLookupSelector() && 0 == h; p++ ) {
208                 string lpath = dictPath(p);
209                 h = addSpeller(lang, lpath);
210         }
211         if (0 != h) {
212                 string const encoding = h->get_dic_encoding();
213                 PersonalWordList * pd = new PersonalWordList(lang->lang());
214                 pd->load();
215                 personal_[lang->lang()] = pd;
216                 docstring_list::const_iterator it = pd->begin();
217                 docstring_list::const_iterator et = pd->end();
218                 for (; it != et; ++it) {
219                         string const word_to_add = to_iconv_encoding(*it, encoding);
220                         h->add(word_to_add.c_str());
221                 }
222         }
223         return h;
224 }
225
226
227 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
228 {
229         IgnoreList::const_iterator it = ignored_.begin();
230         for (; it != ignored_.end(); ++it) {
231                 if ((*it).lang()->code() != wl.lang()->code())
232                         continue;
233                 if ((*it).word() == wl.word())
234                         return true;
235         }
236         return false;
237 }
238
239 /// personal word list interface
240 void HunspellChecker::Private::remove(WordLangTuple const & wl)
241 {
242         Hunspell * h = speller(wl.lang());
243         if (!h)
244                 return;
245         string const encoding = h->get_dic_encoding();
246         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
247         h->remove(word_to_check.c_str());
248         PersonalWordList * pd = personal_[wl.lang()->lang()];
249         if (!pd)
250                 return;
251         pd->remove(wl.word());
252 }
253
254
255 void HunspellChecker::Private::insert(WordLangTuple const & wl)
256 {
257         Hunspell * h = speller(wl.lang());
258         if (!h)
259                 return;
260         string const encoding = h->get_dic_encoding();
261         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
262         h->add(word_to_check.c_str());
263         PersonalWordList * pd = personal_[wl.lang()->lang()];
264         if (!pd)
265                 return;
266         pd->insert(wl.word());
267 }
268
269
270 bool HunspellChecker::Private::learned(WordLangTuple const & wl)
271 {
272         PersonalWordList * pd = personal_[wl.lang()->lang()];
273         if (!pd)
274                 return false;
275         return pd->exists(wl.word());
276 }
277
278
279 HunspellChecker::HunspellChecker(): d(new Private)
280 {
281 }
282
283
284 HunspellChecker::~HunspellChecker()
285 {
286         delete d;
287 }
288
289
290 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
291 {
292         if (d->isIgnored(wl))
293                 return WORD_OK;
294
295         Hunspell * h = d->speller(wl.lang());
296         if (!h)
297                 return WORD_OK;
298         int info;
299
300         string const encoding = h->get_dic_encoding();
301         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
302
303         if (h->spell(word_to_check.c_str(), &info))
304                 return d->learned(wl) ? LEARNED_WORD : WORD_OK;
305
306         if (info & SPELL_COMPOUND) {
307                 // FIXME: What to do with that?
308                 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
309         }
310         if (info & SPELL_FORBIDDEN) {
311                 // This was removed from personal dictionary
312                 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
313         }
314
315         return UNKNOWN_WORD;
316 }
317
318
319 void HunspellChecker::advanceChangeNumber()
320 {
321         nextChangeNumber();
322 }
323
324
325 void HunspellChecker::insert(WordLangTuple const & wl)
326 {
327         d->insert(wl);
328         LYXERR(Debug::GUI, "learn word: \"" << wl.word() << "\"") ;
329         advanceChangeNumber();
330 }
331
332
333 void HunspellChecker::remove(WordLangTuple const & wl)
334 {
335         d->remove(wl);
336         LYXERR(Debug::GUI, "unlearn word: \"" << wl.word() << "\"") ;
337         advanceChangeNumber();
338 }
339
340
341 void HunspellChecker::accept(WordLangTuple const & wl)
342 {
343         d->ignored_.push_back(wl);
344         LYXERR(Debug::GUI, "ignore word: \"" << wl.word() << "\"") ;
345         advanceChangeNumber();
346 }
347
348
349 void HunspellChecker::suggest(WordLangTuple const & wl,
350         docstring_list & suggestions)
351 {
352         suggestions.clear();
353         Hunspell * h = d->speller(wl.lang());
354         if (!h)
355                 return;
356         string const encoding = h->get_dic_encoding();
357         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
358         char ** suggestion_list;
359         int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
360         if (suggestion_number <= 0)
361                 return;
362         for (int i = 0; i != suggestion_number; ++i)
363                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
364         h->free_list(&suggestion_list, suggestion_number);
365 }
366
367
368 bool HunspellChecker::hasDictionary(Language const * lang) const
369 {
370         if (!lang)
371                 return false;
372         return (d->haveDictionary(lang));
373 }
374
375
376 docstring const HunspellChecker::error()
377 {
378         return docstring();
379 }
380
381
382 } // namespace lyx