]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
f5b9d0c61a88158662e9af684042eca3b9b59ceb
[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
15 #include "LyXRC.h"
16 #include "WordLangTuple.h"
17
18 #include "frontends/alert.h"
19
20 #include "support/debug.h"
21 #include "support/docstring_list.h"
22 #include "support/filetools.h"
23 #include "support/Package.h"
24 #include "support/FileName.h"
25 #include "support/gettext.h"
26 #include "support/lassert.h"
27 #include "support/lstrings.h"
28 #include "support/os.h"
29
30 #include <hunspell/hunspell.hxx>
31
32 #include <map>
33 #include <string>
34 #include <vector>
35
36 using namespace std;
37 using namespace lyx::support;
38 using namespace lyx::support::os;
39
40 namespace lyx {
41
42 namespace {
43
44 typedef map<std::string, Hunspell *> Spellers;
45 typedef vector<WordLangTuple> IgnoreList;
46
47 } // anon namespace
48
49
50 struct HunspellChecker::Private
51 {
52         Private() {}
53
54         ~Private();
55
56         const string dictPath(int selector);
57         bool haveLanguageFiles(string const & hpath);
58         bool haveDictionary(string const & lang, string & hpath);
59         bool haveDictionary(string const & lang);
60         Hunspell * addSpeller(string const & lang, string & hpath);
61         Hunspell * addSpeller(string const & lang);
62         Hunspell * speller(string const & lang);
63         /// ignored words
64         bool isIgnored(WordLangTuple const & wl) const;
65
66         /// the spellers
67         Spellers spellers_;
68         ///
69         IgnoreList ignored_;
70
71         /// the location below system/user directory
72         /// there the aff+dic files lookup will happen
73         const string dictDirectory(void) const { return "dict"; }
74         int maxLookupSelector(void) const { return 3; }
75 };
76
77
78 HunspellChecker::Private::~Private()
79 {
80         Spellers::iterator it = spellers_.begin();
81         Spellers::iterator end = spellers_.end();
82
83         for (; it != end; ++it) {
84                 if ( 0 != it->second) delete it->second;
85         }
86 }
87
88
89 bool HunspellChecker::Private::haveLanguageFiles(string const & hpath)
90 {
91         FileName const affix(hpath + ".aff");
92         FileName const dict(hpath + ".dic");
93         return affix.isReadableFile() && dict.isReadableFile();
94 }
95
96
97 const string HunspellChecker::Private::dictPath(int selector)
98 {
99         switch (selector) {
100         case 2:
101                 return addName(lyx::support::package().system_support().absFileName(),dictDirectory());
102                 break;
103         case 1:
104                 return addName(lyx::support::package().user_support().absFileName(),dictDirectory());
105                 break;
106         default:
107                 return lyxrc.hunspelldir_path;
108         }
109 }
110
111
112 bool HunspellChecker::Private::haveDictionary(string const & lang, string & hpath)
113 {
114         if (hpath.empty()) {
115                 return false;
116         }
117
118         LYXERR(Debug::FILES, "check hunspell path: " << hpath << " for language " << lang);
119         string h_path = addName(hpath, lang);
120         if (!haveLanguageFiles(h_path)) {
121                 // try with '_' replaced by '-'
122                 h_path = addName(hpath, subst(lang, '_', '-'));
123                 if (!haveLanguageFiles(h_path)) {
124                         // FIXME: We should indicate somehow that this language is not
125                         // supported, probably by popping a warning. But we'll need to
126                         // remember which warnings we've issued.
127                         return false;
128                 }
129         }
130         hpath = h_path;
131         return true;
132 }
133
134
135 bool HunspellChecker::Private::haveDictionary(string const & lang)
136 {
137         bool result = false;
138         for ( int p = 0; !result && p < maxLookupSelector(); p++ ) {
139                 string lpath = dictPath(p);
140                 result = haveDictionary(lang, lpath);
141         }
142         return result;
143 }
144
145
146 Hunspell * HunspellChecker::Private::speller(string const & lang)
147 {
148         Spellers::iterator it = spellers_.find(lang);
149         if (it != spellers_.end())
150                 return it->second;
151         
152         return addSpeller(lang);
153 }
154
155
156 Hunspell * HunspellChecker::Private::addSpeller(string const & lang,string & path)
157 {
158         if (!haveDictionary(lang, path)) {
159                 spellers_[lang] = 0;
160                 return 0;
161         }
162
163         FileName const affix(path + ".aff");
164         FileName const dict(path + ".dic");
165         Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str());
166         LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found");
167         spellers_[lang] = h;
168         return h;
169 }
170
171
172 Hunspell * HunspellChecker::Private::addSpeller(string const & lang)
173 {
174         Hunspell * h = 0;
175         for ( int p = 0; p < maxLookupSelector() && 0 == h; p++ ) {
176                 string lpath = dictPath(p);
177                 h = addSpeller(lang, lpath);
178         }
179         return h;
180 }
181
182
183 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
184 {
185         IgnoreList::const_iterator it = ignored_.begin();
186         for (; it != ignored_.end(); ++it) {
187                 if ((*it).lang()->code() != wl.lang()->code())
188                         continue;
189                 if ((*it).word() == wl.word())
190                         return true;
191         }
192         return false;
193 }
194
195
196 HunspellChecker::HunspellChecker(): d(new Private)
197 {
198 }
199
200
201 HunspellChecker::~HunspellChecker()
202 {
203         delete d;
204 }
205
206
207 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
208 {
209         if (d->isIgnored(wl))
210                 return OK;
211
212         Hunspell * h = d->speller(wl.lang()->code());
213         if (!h)
214                 return OK;
215         int info;
216
217         string const encoding = h->get_dic_encoding();
218         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
219         
220         if (h->spell(word_to_check.c_str(), &info))
221                 return OK;
222
223         if (info & SPELL_COMPOUND) {
224                 // FIXME: What to do with that?
225                 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
226         }
227         if (info & SPELL_FORBIDDEN) {
228                 // FIXME: What to do with that?
229                 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
230         }
231
232         return UNKNOWN_WORD;
233 }
234
235
236 void HunspellChecker::insert(WordLangTuple const & wl)
237 {
238         string const word_to_check = to_utf8(wl.word());
239         Hunspell * h = d->speller(wl.lang()->code());
240         if (!h)
241                 return;
242         h->add(word_to_check.c_str());
243 }
244
245
246 void HunspellChecker::accept(WordLangTuple const & wl)
247 {
248         d->ignored_.push_back(wl);
249 }
250
251
252 void HunspellChecker::suggest(WordLangTuple const & wl,
253         docstring_list & suggestions)
254 {
255         suggestions.clear();
256         Hunspell * h = d->speller(wl.lang()->code());
257         if (!h)
258                 return;
259         string const encoding = h->get_dic_encoding();
260         string const word_to_check = to_iconv_encoding(wl.word(), encoding);
261         char ** suggestion_list;
262         int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
263         if (suggestion_number <= 0)
264                 return;
265         for (int i = 0; i != suggestion_number; ++i)
266                 suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding));
267         h->free_list(&suggestion_list, suggestion_number);
268 }
269
270
271 bool HunspellChecker::hasDictionary(Language const * lang) const
272 {
273         if (!lang)
274                 return false;
275         return (d->haveDictionary(lang->code()));
276 }
277
278
279 docstring const HunspellChecker::error()
280 {
281         return docstring();
282 }
283
284
285 } // namespace lyx