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