]> git.lyx.org Git - lyx.git/blob - src/HunspellChecker.cpp
5d42421c2c6dc8bbb8c032195e3a6e38919fd70c
[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/FileName.h"
24 #include "support/gettext.h"
25 #include "support/lassert.h"
26 #include "support/lstrings.h"
27 #include "support/os.h"
28
29 #include <hunspell/hunspell.hxx>
30
31 #include <map>
32 #include <string>
33 #include <vector>
34
35 using namespace std;
36 using namespace lyx::support;
37 using namespace lyx::support::os;
38
39 namespace lyx {
40
41 namespace {
42
43 typedef map<std::string, Hunspell *> Spellers;
44 typedef vector<WordLangTuple> IgnoreList;
45
46 } // anon namespace
47
48 struct HunspellChecker::Private
49 {
50         Private() {}
51
52         ~Private();
53
54         bool haveDictionary(string const & lang) const;
55         Hunspell * addSpeller(string const & lang);
56         Hunspell * speller(string const & lang);
57         /// ignored words
58         bool isIgnored(WordLangTuple const & wl) const;
59
60         /// the spellers
61         Spellers spellers_;
62         ///
63         IgnoreList ignored_;
64 };
65
66
67 HunspellChecker::Private::~Private()
68 {
69         Spellers::iterator it = spellers_.begin();
70         Spellers::iterator end = spellers_.end();
71
72         for (; it != end; ++it) {
73                 delete it->second;
74         }
75 }
76
77
78 namespace {
79 bool haveLanguageFiles(string const & hpath)
80 {
81         FileName const affix(hpath + ".aff");
82         FileName const dict(hpath + ".dic");
83         if (!affix.isReadableFile()) {
84                 // FIXME: We should indicate somehow that this language is not
85                 // supported.
86                 LYXERR(Debug::FILES, "Hunspell affix file " << affix << " does not exist");
87                 return false;
88         }
89         if (!dict.isReadableFile()) {
90                 LYXERR(Debug::FILES, "Hunspell dictionary file " << dict << " does not exist");
91                 return false;
92         }
93         return true;
94 }
95 }
96
97
98 bool HunspellChecker::Private::haveDictionary(string const & lang) const
99 {
100         string hunspell_path = lyxrc.hunspelldir_path;
101         LYXERR(Debug::FILES, "hunspell path: " << external_path(hunspell_path));
102         if (hunspell_path.empty()) {
103                 // FIXME We'd like to issue a better error message here, but there seems
104                 // to be a problem about thread safety, or something of the sort. If
105                 // we issue the message using frontend::Alert, then the code comes
106                 // back through here while the box is waiting, and causes some kind
107                 // of crash. 
108                 static bool warned = false;
109                 if (!warned) {
110                         warned = true;
111                         LYXERR0("Hunspell path not set.");
112                         //frontend::Alert::error(_("Hunspell Path Not Found"), 
113                         //              _("You must set the Hunspell dictionary path in Tools>Preferences>Paths."));
114                 }
115                 return false;
116         }
117
118         hunspell_path = external_path(addName(hunspell_path, lang));
119         if (!haveLanguageFiles(hunspell_path)) {
120                 // try with '_' replaced by '-'
121                 hunspell_path = subst(hunspell_path, '_', '-');
122                 if (!haveLanguageFiles(hunspell_path)) {
123                         // FIXME: We should indicate somehow that this language is not
124                         // supported, probably by popping a warning. But we'll need to
125                         // remember which warnings we've issued.
126                         return false;
127                 }
128         }
129         return true;
130 }
131
132
133 Hunspell * HunspellChecker::Private::addSpeller(string const & lang)
134 {
135         string hunspell_path = lyxrc.hunspelldir_path;
136
137         if (!haveDictionary(lang))
138                 return 0;
139
140         FileName const affix(hunspell_path + ".aff");
141         FileName const dict(hunspell_path + ".dic");
142         Hunspell * h = new Hunspell(affix.absFilename().c_str(), dict.absFilename().c_str());
143         spellers_[lang] = h;
144         return h;
145 }
146
147
148 Hunspell * HunspellChecker::Private::speller(string const & lang)
149 {
150         Spellers::iterator it = spellers_.find(lang);
151         if (it != spellers_.end())
152                 return it->second;
153         
154         return addSpeller(lang);
155 }
156
157
158 bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const
159 {
160         IgnoreList::const_iterator it = ignored_.begin();
161         for (; it != ignored_.end(); ++it) {
162                 if ((*it).lang()->code() != wl.lang()->code())
163                         continue;
164                 if ((*it).word() == wl.word())
165                         return true;
166         }
167         return false;
168 }
169
170
171 HunspellChecker::HunspellChecker(): d(new Private)
172 {
173 }
174
175
176 HunspellChecker::~HunspellChecker()
177 {
178         delete d;
179 }
180
181
182 SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
183 {
184         if (d->isIgnored(wl))
185                 return OK;
186
187         string const word_to_check = to_utf8(wl.word());
188         Hunspell * h = d->speller(wl.lang()->code());
189         if (!h)
190                 return OK;
191         int info;
192         if (h->spell(word_to_check.c_str(), &info))
193                 return OK;
194
195         if (info & SPELL_COMPOUND) {
196                 // FIXME: What to do with that?
197                 LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check);
198         }
199         if (info & SPELL_FORBIDDEN) {
200                 // FIXME: What to do with that?
201                 LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check);
202         }
203
204         return UNKNOWN_WORD;
205 }
206
207
208 void HunspellChecker::insert(WordLangTuple const & wl)
209 {
210         string const word_to_check = to_utf8(wl.word());
211         Hunspell * h = d->speller(wl.lang()->code());
212         if (!h)
213                 return;
214         h->add(word_to_check.c_str());
215 }
216
217
218 void HunspellChecker::accept(WordLangTuple const & wl)
219 {
220         d->ignored_.push_back(wl);
221 }
222
223
224 void HunspellChecker::suggest(WordLangTuple const & wl,
225         docstring_list & suggestions)
226 {
227         suggestions.clear();
228         string const word_to_check = to_utf8(wl.word());
229         Hunspell * h = d->speller(wl.lang()->code());
230         if (!h)
231                 return;
232         char ** suggestion_list;
233         int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str());
234         if (suggestion_number <= 0)
235                 return;
236         for (int i = 0; i != suggestion_number; ++i)
237                 suggestions.push_back(from_utf8(suggestion_list[i]));
238         h->free_list(&suggestion_list, suggestion_number);
239 }
240
241
242 bool HunspellChecker::hasDictionary(Language const * lang) const
243 {
244         if (!lang)
245                 return false;
246         return (d->haveDictionary(lang->code()));
247 }
248
249
250 docstring const HunspellChecker::error()
251 {
252         return docstring();
253 }
254
255
256 } // namespace lyx