2 * \file AspellChecker.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Kevin Atkinson
9 * Full author contact details are available in file CREDITS.
14 #include "AspellChecker.h"
16 #include "WordLangTuple.h"
18 #include "support/lassert.h"
19 #include "support/debug.h"
20 #include "support/docstring_list.h"
22 #include "support/filetools.h"
23 #include "support/Package.h"
24 #include "support/FileName.h"
25 #include "support/Path.h"
33 using namespace lyx::support;
40 AspellConfig * config;
41 AspellCanHaveError * e_speller;
44 typedef std::map<std::string, Speller> Spellers;
48 struct AspellChecker::Private
54 /// add a speller of the given language and variety
55 AspellSpeller * addSpeller(string const & lang,
56 string const & variety = string());
59 AspellSpeller * speller(string const & lang,
60 string const & variety);
62 /// create a unique ID from lang code and variety
63 string const spellerID(string const & lang,
64 string const & variety);
66 bool isValidDictionary(AspellConfig * config,
67 string const & lang, string const & variety);
68 bool checkAspellData(AspellConfig * config,
69 string const & basepath, string const & datapath, string const & dictpath,
70 string const & lang, string const & variety);
71 AspellConfig * getConfig(string const & lang, string const & variety);
76 /// the location below system/user directory
77 /// there the rws files lookup will happen
78 const string dictDirectory(void) { return "dict"; }
79 /// there the dat+cmap files lookup will happen
80 const string dataDirectory(void) { return "data"; }
81 /// os package directory constants
82 /// macports on Mac OS X or
83 /// aspell rpms on Linux
84 const string osPackageBase(void) {
85 #ifdef USE_MACOSX_PACKAGING
91 const string osPackageDictDirectory(void) {
92 #ifdef USE_MACOSX_PACKAGING
93 return "/share/aspell";
95 return "/lib/aspell-0.60";
98 const string osPackageDataDirectory(void) { return "/lib/aspell-0.60"; }
103 AspellChecker::Private::~Private()
105 Spellers::iterator it = spellers_.begin();
106 Spellers::iterator end = spellers_.end();
108 for (; it != end; ++it) {
109 if (it->second.e_speller) {
110 AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
111 aspell_speller_save_all_word_lists(speller);
112 delete_aspell_can_have_error(it->second.e_speller);
114 delete_aspell_config(it->second.config);
119 bool AspellChecker::Private::isValidDictionary(AspellConfig * config,
120 string const & lang, string const & variety)
123 // code taken from aspell's list-dicts example
124 // the returned pointer should _not_ need to be deleted
125 AspellDictInfoList * dlist = get_aspell_dict_info_list(config);
126 AspellDictInfoEnumeration * dels = aspell_dict_info_list_elements(dlist);
127 const AspellDictInfo * entry;
129 while (0 != (entry = aspell_dict_info_enumeration_next(dels))) {
130 LYXERR(Debug::DEBUG, "aspell dict:"
131 << " name=" << entry->name
132 << ",code=" << entry->code
133 << ",variety=" << entry->jargon);
134 if (entry->code == lang && (variety.empty() || entry->jargon == variety)) {
139 delete_aspell_dict_info_enumeration(dels);
140 LYXERR(Debug::FILES, "aspell dictionary: " << lang << (have ? " yes" : " no"));
145 bool AspellChecker::Private::checkAspellData(AspellConfig * config,
146 string const & basepath, string const & datapath, string const & dictpath,
147 string const & lang, string const & variety)
149 FileName base(basepath);
150 bool have_dict = base.isDirectory() ;
153 FileName data(addPath(base.absFileName(), datapath));
154 FileName dict(addPath(base.absFileName(), dictpath));
155 have_dict = dict.isDirectory() && data.isDirectory();
157 LYXERR(Debug::FILES, "aspell dict-dir: " << dict);
158 LYXERR(Debug::FILES, "aspell data-dir: " << data);
159 aspell_config_replace(config, "dict-dir", dict.absFileName().c_str());
160 aspell_config_replace(config, "data-dir", data.absFileName().c_str());
161 have_dict = isValidDictionary(config, lang, variety);
168 AspellConfig * AspellChecker::Private::getConfig(string const & lang, string const & variety)
170 AspellConfig * config = new_aspell_config();
171 bool have_dict = false;
172 string const sysdir = lyx::support::package().system_support().absFileName() ;
173 string const userdir = lyx::support::package().user_support().absFileName() ;
175 LYXERR(Debug::FILES, "aspell user dir: " << userdir);
176 have_dict = checkAspellData(config, userdir, dataDirectory(), dictDirectory(), lang, variety);
178 LYXERR(Debug::FILES, "aspell sysdir dir: " << sysdir);
179 have_dict = checkAspellData(config, sysdir, dataDirectory(), dictDirectory(), lang, variety);
182 // check for package data of OS installation
183 have_dict = checkAspellData(config, osPackageBase(), osPackageDataDirectory(), osPackageDictDirectory(), lang, variety);
189 AspellSpeller * AspellChecker::Private::addSpeller(string const & lang,
190 string const & variety)
194 m.config = getConfig(lang, variety);
195 // Aspell supports both languages and varieties (such as German
196 // old vs. new spelling). The respective naming convention is
197 // lang_REGION-variety (e.g. de_DE-alt).
198 aspell_config_replace(m.config, "lang", lang.c_str());
199 if (!variety.empty())
200 aspell_config_replace(m.config, "variety", variety.c_str());
201 // Set the encoding to utf-8.
202 // aspell does also understand "ucs-4", so we would not need a
203 // conversion in theory, but if this is used it expects all
204 // char const * arguments to be a cast from uint const *, and it
205 // seems that this uint is not compatible with our char_type on some
206 // platforms (cygwin, OS X). Therefore we use utf-8, that does
208 aspell_config_replace(m.config, "encoding", "utf-8");
209 if (lyxrc.spellchecker_accept_compound)
210 // Consider run-together words as legal compounds
211 aspell_config_replace(m.config, "run-together", "true");
213 // Report run-together words as errors
214 aspell_config_replace(m.config, "run-together", "false");
216 m.e_speller = new_aspell_speller(m.config);
217 if (aspell_error_number(m.e_speller) != 0) {
218 // FIXME: We should indicate somehow that this language is not supported.
219 LYXERR(Debug::FILES, "aspell error: " << aspell_error_message(m.e_speller));
220 delete_aspell_can_have_error(m.e_speller);
221 delete_aspell_config(m.config);
226 spellers_[spellerID(lang, variety)] = m;
227 return m.e_speller ? to_aspell_speller(m.e_speller) : 0;
231 AspellSpeller * AspellChecker::Private::speller(string const & lang,
232 string const & variety)
234 Spellers::iterator it = spellers_.find(spellerID(lang, variety));
235 if (it != spellers_.end())
236 return to_aspell_speller(it->second.e_speller);
238 return addSpeller(lang, variety);
242 string const AspellChecker::Private::spellerID(string const & lang,
243 string const & variety)
247 return lang + "-" + variety;
251 AspellChecker::AspellChecker(): d(new Private)
256 AspellChecker::~AspellChecker()
262 SpellChecker::Result AspellChecker::check(WordLangTuple const & word)
266 d->speller(word.lang()->code(), word.lang()->variety());
271 if (word.word().empty())
272 // MSVC compiled Aspell doesn't like it.
275 string const word_str = to_utf8(word.word());
276 int const word_ok = aspell_speller_check(m, word_str.c_str(), -1);
277 LASSERT(word_ok != -1, /**/);
279 return (word_ok) ? WORD_OK : UNKNOWN_WORD;
283 void AspellChecker::advanceChangeNumber()
289 void AspellChecker::insert(WordLangTuple const & word)
291 Spellers::iterator it = d->spellers_.find(
292 d->spellerID(word.lang()->code(), word.lang()->variety()));
293 if (it != d->spellers_.end()) {
294 AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
295 aspell_speller_add_to_personal(speller, to_utf8(word.word()).c_str(), -1);
296 advanceChangeNumber();
301 void AspellChecker::accept(WordLangTuple const & word)
303 Spellers::iterator it = d->spellers_.find(
304 d->spellerID(word.lang()->code(), word.lang()->variety()));
305 if (it != d->spellers_.end()) {
306 AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
307 aspell_speller_add_to_session(speller, to_utf8(word.word()).c_str(), -1);
308 advanceChangeNumber();
313 void AspellChecker::suggest(WordLangTuple const & wl,
314 docstring_list & suggestions)
318 d->speller(wl.lang()->code(), wl.lang()->variety());
323 AspellWordList const * sugs =
324 aspell_speller_suggest(m, to_utf8(wl.word()).c_str(), -1);
325 LASSERT(sugs != 0, /**/);
326 AspellStringEnumeration * els = aspell_word_list_elements(sugs);
327 if (!els || aspell_word_list_empty(sugs))
331 char const * str = aspell_string_enumeration_next(els);
334 suggestions.push_back(from_utf8(str));
337 delete_aspell_string_enumeration(els);
341 bool AspellChecker::hasDictionary(Language const * lang) const
344 Spellers::iterator it = d->spellers_.begin();
345 Spellers::iterator end = d->spellers_.end();
348 for (; it != end && !have; ++it) {
349 have = it->second.config && d->isValidDictionary(it->second.config, lang->code(), lang->variety());
352 AspellConfig * config = d->getConfig(lang->code(), lang->variety());
353 have = d->isValidDictionary(config, lang->code(), lang->variety());
354 delete_aspell_config(config);
361 docstring const AspellChecker::error()
363 Spellers::iterator it = d->spellers_.begin();
364 Spellers::iterator end = d->spellers_.end();
365 char const * err = 0;
367 for (; it != end && 0 == err; ++it) {
368 if (it->second.e_speller && aspell_error_number(it->second.e_speller) != 0)
369 err = aspell_error_message(it->second.e_speller);
372 // FIXME UNICODE: err is not in UTF8, but probably the locale encoding
373 return (err ? from_utf8(err) : docstring());