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