/src/aspell/lib/find_speller.cpp
Line | Count | Source |
1 | | // This file is part of The New Aspell |
2 | | // Copyright (C) 2000-2001 by Kevin Atkinson under the GNU LGPL |
3 | | // license version 2.0 or 2.1. You should have received a copy of the |
4 | | // LGPL license along with this library if you did not you can find it |
5 | | // at http://www.gnu.org/. |
6 | | |
7 | | #include <assert.h> |
8 | | #include <string.h> |
9 | | |
10 | | // POSIX includes |
11 | | #include <sys/types.h> |
12 | | #include <dirent.h> |
13 | | |
14 | | #include "asc_ctype.hpp" |
15 | | #include "can_have_error.hpp" |
16 | | #include "config.hpp" |
17 | | #include "convert.hpp" |
18 | | #include "enumeration.hpp" |
19 | | #include "errors.hpp" |
20 | | #include "filter.hpp" |
21 | | #include "fstream.hpp" |
22 | | #include "getdata.hpp" |
23 | | #include "info.hpp" |
24 | | #include "speller.hpp" |
25 | | #include "stack_ptr.hpp" |
26 | | #include "string_enumeration.hpp" |
27 | | #include "string_list.hpp" |
28 | | #include "string_map.hpp" |
29 | | |
30 | | #include "gettext.h" |
31 | | |
32 | | #if 0 |
33 | | #include "preload.h" |
34 | | #define LT_NON_POSIX_NAMESPACE 1 |
35 | | #ifdef USE_LTDL |
36 | | #include <ltdl.h> |
37 | | #endif |
38 | | #endif |
39 | | |
40 | | using namespace acommon; |
41 | | |
42 | | namespace acommon { |
43 | | |
44 | | static void free_lt_handle(SpellerLtHandle h) |
45 | 0 | { |
46 | | #ifdef USE_LTDL |
47 | | int s; |
48 | | s = lt_dlclose((lt_dlhandle)h); |
49 | | assert (s == 0); |
50 | | s = lt_dlexit(); |
51 | | assert (s == 0); |
52 | | #endif |
53 | 0 | } |
54 | | |
55 | | extern "C" |
56 | | Speller * libaspell_speller_default_LTX_new_speller_class(SpellerLtHandle); |
57 | | |
58 | | PosibErr<Speller *> get_speller_class(Config * config) |
59 | 966 | { |
60 | 966 | String name = config->retrieve("module"); |
61 | 966 | assert(name == "default"); |
62 | 966 | return libaspell_speller_default_LTX_new_speller_class(0); |
63 | | #if 0 |
64 | | unsigned int i; |
65 | | for (i = 0; i != aspell_speller_funs_size; ++i) { |
66 | | if (strcmp(name.c_str(), aspell_speller_funs[i].name) == 0) { |
67 | | return (*aspell_speller_funs[i].fun)(config, 0); |
68 | | } |
69 | | } |
70 | | |
71 | | #ifdef USE_LTDL |
72 | | int s = lt_dlinit(); |
73 | | assert(s == 0); |
74 | | String libname; |
75 | | libname = LIBDIR "/libaspell_"; |
76 | | libname += name; |
77 | | libname += ".la"; |
78 | | lt_dlhandle h = lt_dlopen (libname.c_str()); |
79 | | if (h == 0) |
80 | | return (new CanHaveErrorImpl()) |
81 | | ->set_error(cant_load_module, name.c_str()); |
82 | | lt_ptr_t fun = lt_dlsym (h, "new_aspell_speller_class"); |
83 | | assert (fun != 0); |
84 | | CanHaveError * m = (*(NewSpellerClass)(fun))(config, h); |
85 | | assert (m != 0); |
86 | | if (m->error_number() != 0) |
87 | | free_lt_handle(h); |
88 | | return m; |
89 | | #else |
90 | | return (new CanHaveErrorImpl()) |
91 | | ->set_error(cant_load_module, name.c_str()); |
92 | | #endif |
93 | | #endif |
94 | 966 | } |
95 | | |
96 | | // Note this writes all over str |
97 | | static void split_string_list(StringList & list, ParmString str) |
98 | 1.99k | { |
99 | 1.99k | const char * s0 = str; |
100 | 1.99k | const char * s1; |
101 | 3.92k | while (true) { |
102 | 3.94k | while (*s0 != '\0' && asc_isspace(*s0)) ++s0; |
103 | 3.92k | if (*s0 == '\0') break; |
104 | 1.93k | s1 = s0; |
105 | 28.2k | while (!asc_isspace(*s1)) ++s1; |
106 | 1.93k | String temp(s0,s1-s0); |
107 | 1.93k | list.add(temp); |
108 | 1.93k | if (*s1 != '\0') |
109 | 1.93k | s0 = s1 + 1; |
110 | 1.93k | } |
111 | 1.99k | } |
112 | | |
113 | | enum IsBetter {BetterMatch, WorseMatch, SameMatch}; |
114 | | |
115 | | struct Better |
116 | | { |
117 | | unsigned int cur_rank; |
118 | | unsigned int best_rank; |
119 | | unsigned int worst_rank; |
120 | | virtual void init() = 0; |
121 | | virtual void set_best_from_cur() = 0; |
122 | | virtual void set_cur_rank() = 0; |
123 | | IsBetter better_match(IsBetter prev); |
124 | | virtual ~Better(); |
125 | | }; |
126 | | |
127 | 3.98k | Better::~Better() {} |
128 | | |
129 | | IsBetter Better::better_match (IsBetter prev) |
130 | 198k | { |
131 | 198k | if (prev == WorseMatch) |
132 | 109k | return prev; |
133 | 89.6k | set_cur_rank(); |
134 | 89.6k | if (cur_rank >= worst_rank) |
135 | 39.1k | return WorseMatch; |
136 | 50.4k | else if (cur_rank < best_rank) |
137 | 5.02k | return BetterMatch; |
138 | 45.3k | else if (cur_rank == best_rank) |
139 | 45.2k | return prev; |
140 | 181 | else // cur_rank > best_rank |
141 | 181 | if (prev == SameMatch) |
142 | 170 | return WorseMatch; |
143 | 11 | else |
144 | 11 | return BetterMatch; |
145 | 89.6k | } |
146 | | |
147 | | struct BetterList : public Better |
148 | | { |
149 | | const char * cur; |
150 | | StringList list; |
151 | | const char * best; |
152 | | BetterList(); |
153 | | void init(); |
154 | | void set_best_from_cur(); |
155 | | void set_cur_rank(); |
156 | | }; |
157 | | |
158 | | BetterList::BetterList() |
159 | 1.99k | { |
160 | 1.99k | } |
161 | | |
162 | 1.98k | void BetterList::init() { |
163 | 1.98k | StringListEnumeration es = list.elements_obj(); |
164 | 1.98k | worst_rank = 0; |
165 | 4.90k | while ( (es.next()) != 0) |
166 | 2.92k | ++worst_rank; |
167 | 1.98k | best_rank = worst_rank; |
168 | 1.98k | } |
169 | | |
170 | | void BetterList::set_best_from_cur() |
171 | 3.87k | { |
172 | 3.87k | best_rank = cur_rank; |
173 | 3.87k | best = cur; |
174 | 3.87k | } |
175 | | |
176 | | void BetterList::set_cur_rank() |
177 | 60.0k | { |
178 | 60.0k | StringListEnumeration es = list.elements_obj(); |
179 | 60.0k | const char * m; |
180 | 60.0k | cur_rank = 0; |
181 | 130k | while ( (m = es.next()) != 0 && strcmp(m, cur) != 0) |
182 | 69.9k | ++cur_rank; |
183 | 60.0k | } |
184 | | |
185 | | struct BetterSize : public Better |
186 | | { |
187 | | unsigned int cur; |
188 | | const char * cur_str; |
189 | | char req_type; |
190 | | unsigned int requested; |
191 | | unsigned int size; |
192 | | unsigned int best; |
193 | | const char * best_str; |
194 | | void init(); |
195 | | void set_best_from_cur(); |
196 | | void set_cur_rank(); |
197 | | }; |
198 | | |
199 | | |
200 | 986 | void BetterSize::init() { |
201 | 986 | worst_rank = 0xFFF; |
202 | 986 | best_rank = worst_rank; |
203 | 986 | } |
204 | | |
205 | | void BetterSize::set_best_from_cur() |
206 | 1.93k | { |
207 | 1.93k | best_rank = cur_rank; |
208 | 1.93k | best = cur; |
209 | 1.93k | best_str = cur_str; |
210 | 1.93k | } |
211 | | |
212 | | void BetterSize::set_cur_rank() |
213 | 10.3k | { |
214 | 10.3k | int diff = cur - requested; |
215 | 10.3k | int sign; |
216 | 10.3k | if (diff < 0) { |
217 | 212 | cur_rank = -diff; |
218 | 212 | sign = -1; |
219 | 10.1k | } else { |
220 | 10.1k | cur_rank = diff; |
221 | 10.1k | sign = 1; |
222 | 10.1k | } |
223 | 10.3k | cur_rank <<= 1; |
224 | 10.3k | if ((sign == -1 && req_type == '+') || (sign == 1 && req_type == '-')) |
225 | 162 | cur_rank |= 0x1; |
226 | 10.1k | else if ((sign == -1 && req_type == '>') || (sign == 1 && req_type == '<')) |
227 | 140 | cur_rank |= 0x100; |
228 | 10.3k | } |
229 | | |
230 | | struct BetterVariety : public Better |
231 | | { |
232 | | const char * cur; |
233 | | StringList list; |
234 | | const char * best; |
235 | 997 | BetterVariety() {} |
236 | | void init(); |
237 | | void set_best_from_cur(); |
238 | | void set_cur_rank(); |
239 | | }; |
240 | | |
241 | 997 | void BetterVariety::init() { |
242 | 997 | worst_rank = 3; |
243 | 997 | best_rank = 3; |
244 | 997 | } |
245 | | |
246 | | void BetterVariety::set_best_from_cur() |
247 | 1.93k | { |
248 | 1.93k | best_rank = cur_rank; |
249 | 1.93k | best = cur; |
250 | 1.93k | } |
251 | | |
252 | | void BetterVariety::set_cur_rank() |
253 | 19.1k | { |
254 | 19.1k | if (strlen(cur) == 0) { |
255 | 10.1k | cur_rank = 2; |
256 | 10.1k | } else { |
257 | 9.04k | StringListEnumeration es = list.elements_obj(); |
258 | 9.04k | const char * m; |
259 | 9.04k | cur_rank = 3; |
260 | 9.04k | unsigned list_size = 0, num = 0; |
261 | 9.45k | while ( (m = es.next()) != 0 ) { |
262 | 1.54k | ++list_size; |
263 | 1.54k | unsigned s = strlen(m); |
264 | 1.54k | const char * c = cur; |
265 | 1.54k | unsigned p; |
266 | 1.54k | bool match = false; |
267 | 1.54k | num = 0; |
268 | 4.23k | for (; *c != '\0'; c += p) { |
269 | 3.09k | ++num; |
270 | 3.09k | p = strcspn(c, "-"); |
271 | 3.09k | if (p == s && memcmp(m, c, s) == 0) {match = true; break;} |
272 | 2.68k | if (c[p] == '-') p++; |
273 | 2.68k | } |
274 | 1.54k | if (!match) goto fail; |
275 | 405 | cur_rank = 0; |
276 | 405 | } |
277 | 7.90k | if (cur_rank == 0 && num != list_size) cur_rank = 1; |
278 | 7.90k | } |
279 | 18.0k | return; |
280 | 18.0k | fail: |
281 | 1.14k | cur_rank = 3; |
282 | 1.14k | } |
283 | | |
284 | | PosibErr<Config *> find_word_list(Config * c) |
285 | 1.04k | { |
286 | 1.04k | StackPtr<Config> config(new_config()); |
287 | 1.04k | RET_ON_ERR(config->read_in_settings(c)); |
288 | 1.00k | String dict_name; |
289 | | |
290 | 1.00k | if (config->have("master")) { |
291 | 6 | dict_name = config->retrieve("master"); |
292 | | |
293 | 997 | } else { |
294 | | |
295 | | //////////////////////////////////////////////////////////////////// |
296 | | // |
297 | | // Give first preference to an exact match for the language-country |
298 | | // code, then give preference to those in the alternate code list |
299 | | // in the order they are presented, then if there is no match |
300 | | // look for one for just language. If that fails give up. |
301 | | // Once the best matching code is found, try to find a matching |
302 | | // variety if one exists, other wise look for one with no variety. |
303 | | // |
304 | | |
305 | 997 | BetterList b_code; |
306 | | //BetterList b_jargon; |
307 | 997 | BetterVariety b_variety; |
308 | 997 | BetterList b_module; |
309 | 997 | BetterSize b_size; |
310 | 997 | Better * better[4] = {&b_code,&b_variety,&b_module,&b_size}; |
311 | 997 | const DictInfo * best = 0; |
312 | | |
313 | | // |
314 | | // retrieve and normalize code |
315 | | // |
316 | 997 | const char * p; |
317 | 997 | String code; |
318 | 997 | PosibErr<String> str = config->retrieve("lang"); |
319 | 997 | p = str.data.c_str(); |
320 | 17.1k | while (asc_isalpha(*p)) |
321 | 16.1k | code += asc_tolower(*p++); |
322 | 997 | String lang = code; |
323 | 997 | bool have_country = false; |
324 | 997 | if (*p == '-' || *p == '_') { |
325 | 949 | ++p; |
326 | 949 | have_country = true; |
327 | 949 | code += '_'; |
328 | 5.44k | while (asc_isalpha(*p)) |
329 | 4.49k | code += asc_toupper(*p++); |
330 | 949 | } |
331 | | |
332 | | // |
333 | | // Retrieve acceptable code search orders |
334 | | // |
335 | 997 | String lang_country_list; |
336 | 997 | if (have_country) { |
337 | 949 | lang_country_list = code; |
338 | 949 | lang_country_list += ' '; |
339 | 949 | } |
340 | 997 | String lang_only_list = lang; |
341 | 997 | lang_only_list += ' '; |
342 | | |
343 | | // read retrieve lang_country_list and lang_only_list from file(s) |
344 | | // FIXME: Write Me |
345 | | |
346 | | // |
347 | 997 | split_string_list(b_code.list, lang_country_list); |
348 | 997 | split_string_list(b_code.list, lang_only_list); |
349 | 997 | b_code.init(); |
350 | | |
351 | | // |
352 | | // Retrieve Variety |
353 | | // |
354 | 997 | config->retrieve_list("variety", &b_variety.list); |
355 | 997 | if (b_variety.list.empty() && config->have("jargon")) |
356 | 124 | b_variety.list.add(config->retrieve("jargon")); |
357 | 997 | b_variety.init(); |
358 | 997 | str.data.clear(); |
359 | | |
360 | | // |
361 | | // Retrieve module list |
362 | | // |
363 | 997 | if (config->have("module")) |
364 | 1 | b_module.list.add(config->retrieve("module")); |
365 | 996 | else if (config->have("module-search-order")) |
366 | 0 | config->retrieve_list("module-search-order", &b_module.list); |
367 | 997 | { |
368 | 997 | RET_ON_ERR_SET(get_module_info_list(config), const ModuleInfoList *, modules); |
369 | 986 | StackPtr<ModuleInfoEnumeration> els(modules->elements()); |
370 | 986 | const ModuleInfo * entry; |
371 | 1.97k | while ( (entry = els->next()) != 0) |
372 | 986 | b_module.list.add(entry->name); |
373 | 986 | } |
374 | 0 | b_module.init(); |
375 | | |
376 | | // |
377 | | // Retrieve size |
378 | | // |
379 | 986 | str = config->retrieve("size"); |
380 | 986 | p = str.data.c_str(); |
381 | 986 | if (p[0] == '+' || p[0] == '-' || p[0] == '<' || p[0] == '>') { |
382 | 986 | b_size.req_type = p[0]; |
383 | 986 | ++p; |
384 | 986 | } else { |
385 | 0 | b_size.req_type = '+'; |
386 | 0 | } |
387 | 986 | if (!asc_isdigit(p[0]) || !asc_isdigit(p[1]) || p[2] != '\0') |
388 | 0 | return make_err(aerror_bad_value, "size", str, "valid"); |
389 | 986 | b_size.requested = atoi(p); |
390 | 986 | b_size.init(); |
391 | | |
392 | | // |
393 | | // |
394 | | // |
395 | | |
396 | 986 | const DictInfoList * dlist = get_dict_info_list(config); |
397 | 986 | DictInfoEnumeration * dels = dlist->elements(); |
398 | 986 | const DictInfo * entry; |
399 | | |
400 | 50.7k | while ( (entry = dels->next()) != 0) { |
401 | | |
402 | 49.7k | b_code .cur = entry->code; |
403 | 49.7k | b_module.cur = entry->module->name; |
404 | | |
405 | 49.7k | b_variety.cur = entry->variety; |
406 | | |
407 | 49.7k | b_size.cur_str = entry->size_str; |
408 | 49.7k | b_size.cur = entry->size; |
409 | | |
410 | | // |
411 | | // check to see if we got a better match than the current |
412 | | // best_match if any |
413 | | // |
414 | | |
415 | 49.7k | IsBetter is_better = SameMatch; |
416 | 248k | for (int i = 0; i != 4; ++i) |
417 | 198k | is_better = better[i]->better_match(is_better); |
418 | | |
419 | 49.7k | if (is_better == BetterMatch) { |
420 | 9.69k | for (int i = 0; i != 4; ++i) |
421 | 7.75k | better[i]->set_best_from_cur(); |
422 | 1.93k | best = entry; |
423 | 1.93k | } |
424 | 49.7k | } |
425 | | |
426 | 986 | delete dels; |
427 | | |
428 | | // |
429 | | // set config to best match |
430 | | // |
431 | 986 | if (best != 0) { |
432 | 961 | String main_wl,flags; |
433 | 961 | RET_ON_ERR(get_dict_file_name(best, main_wl, flags)); |
434 | 961 | dict_name = best->name; |
435 | 961 | config->replace("lang", b_code.best); |
436 | 961 | config->replace("language-tag", b_code.best); |
437 | 961 | config->replace("master", main_wl.c_str()); |
438 | 961 | config->replace("master-flags", flags.c_str()); |
439 | 961 | config->replace("module", b_module.best); |
440 | 961 | config->replace("jargon", b_variety.best); |
441 | 961 | config->replace("clear-variety", ""); |
442 | 961 | unsigned p; |
443 | 1.92k | for (const char * c = b_module.best; *c != '\0'; c += p) { |
444 | 961 | p = strcspn(c, "-"); |
445 | 961 | config->replace("add-variety", String(c, p)); |
446 | 961 | } |
447 | 961 | config->replace("size", b_size.best_str); |
448 | 961 | } else { |
449 | 25 | return make_err(no_wordlist_for_lang, code); |
450 | 25 | } |
451 | 986 | } |
452 | | |
453 | 967 | RET_ON_ERR_SET(get_dict_aliases(config), const StringMap *, dict_aliases); |
454 | 966 | const char * val = dict_aliases->lookup(dict_name); |
455 | 966 | if (val) config->replace("master", val); |
456 | 966 | return config.release(); |
457 | 967 | } |
458 | | |
459 | | PosibErr<void> reload_filters(Speller * m) |
460 | 909 | { |
461 | 909 | m->to_internal_->filter.clear(); |
462 | 909 | m->from_internal_->filter.clear(); |
463 | | // Add enocder and decoder filters if any |
464 | 909 | RET_ON_ERR(setup_filter(m->to_internal_->filter, m->config(), |
465 | 909 | true, false, false)); |
466 | 904 | RET_ON_ERR(setup_filter(m->from_internal_->filter, m->config(), |
467 | 904 | false, false, true)); |
468 | 904 | return no_err; |
469 | 904 | } |
470 | | |
471 | | PosibErr<Speller *> new_speller(Config * c0) |
472 | 1.04k | { |
473 | 1.04k | aspell_gettext_init(); |
474 | | |
475 | 1.04k | RET_ON_ERR_SET(find_word_list(c0), Config *, c); |
476 | 966 | StackPtr<Speller> m(get_speller_class(c)); |
477 | 966 | RET_ON_ERR(m->setup(c)); |
478 | | |
479 | 909 | RET_ON_ERR(reload_filters(m)); |
480 | | |
481 | 904 | return m.release(); |
482 | 909 | } |
483 | | |
484 | | void delete_speller(Speller * m) |
485 | 0 | { |
486 | 0 | SpellerLtHandle h = ((Speller *)(m))->lt_handle(); |
487 | 0 | delete m; |
488 | 0 | if (h != 0) free_lt_handle(h); |
489 | 0 | } |
490 | | } |