3ea469c54b194c1e20dbec16244d777d7e05a560
[platform/upstream/aspell.git] / lib / find_speller.cpp
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   {
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   }
54
55   extern "C" 
56   Speller * libaspell_speller_default_LTX_new_speller_class(SpellerLtHandle);
57   
58   PosibErr<Speller *> get_speller_class(Config * config)
59   {
60     String name = config->retrieve("module");
61     assert(name == "default");
62     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   }
95
96   // Note this writes all over str
97   static void split_string_list(StringList & list, ParmString str)
98   {
99     const char * s0 = str;
100     const char * s1;
101     while (true) {
102       while (*s0 != '\0' && asc_isspace(*s0)) ++s0;
103       if (*s0 == '\0') break;
104       s1 = s0;
105       while (!asc_isspace(*s1)) ++s1;
106       String temp(s0,s1-s0);
107       list.add(temp);
108       if (*s1 != '\0')
109         s0 = s1 + 1;
110     }
111   }
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   Better::~Better() {}
128
129   IsBetter Better::better_match (IsBetter prev)
130   {
131     if (prev == WorseMatch)
132       return prev;
133     set_cur_rank();
134     if (cur_rank >= worst_rank)
135       return WorseMatch;
136     else if (cur_rank < best_rank)
137       return BetterMatch;
138     else if (cur_rank == best_rank)
139       return prev;
140     else // cur_rank > best_rank
141       if (prev == SameMatch)
142         return WorseMatch;
143       else
144         return BetterMatch;
145   }
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   {
160   }
161
162   void BetterList::init() {
163     StringListEnumeration es = list.elements_obj();
164     worst_rank = 0;
165     while ( (es.next()) != 0)
166       ++worst_rank;
167     best_rank = worst_rank;
168   }
169
170   void BetterList::set_best_from_cur() 
171   {
172     best_rank = cur_rank;
173     best = cur;
174   }
175
176   void BetterList::set_cur_rank() 
177   {
178     StringListEnumeration es = list.elements_obj();
179     const char * m;
180     cur_rank = 0;
181     while ( (m = es.next()) != 0 && strcmp(m, cur) != 0)
182       ++cur_rank;
183   }
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   void BetterSize::init() {
201     worst_rank = 0xFFF;
202     best_rank = worst_rank;
203   }
204
205   void BetterSize::set_best_from_cur() 
206   {
207     best_rank = cur_rank;
208     best = cur;
209     best_str = cur_str;
210   }
211
212   void BetterSize::set_cur_rank() 
213   {
214     int diff = cur - requested;
215     int sign;
216     if (diff < 0) {
217       cur_rank = -diff;
218       sign = -1;
219     } else {
220       cur_rank = diff;
221       sign = 1;
222     }
223     cur_rank <<= 1;
224     if ((sign == -1 && req_type == '+') || (sign == 1 && req_type == '-'))
225       cur_rank |= 0x1;
226     else if ((sign == -1 && req_type == '>') || (sign == 1 && req_type == '<'))
227       cur_rank |= 0x100;
228   }
229
230   struct BetterVariety : public Better
231   {
232     const char *         cur;
233     StringList           list;
234     const char *         best;
235     BetterVariety() {}
236     void init();
237     void set_best_from_cur();
238     void set_cur_rank();
239   };
240
241   void BetterVariety::init() {
242     worst_rank = 3;
243     best_rank = 3;
244   }
245
246   void BetterVariety::set_best_from_cur() 
247   {
248     best_rank = cur_rank;
249     best = cur;
250   }
251
252   void BetterVariety::set_cur_rank() 
253   {
254     if (strlen(cur) == 0) {
255       cur_rank = 2; 
256     } else {
257       StringListEnumeration es = list.elements_obj();
258       const char * m;
259       cur_rank = 3;
260       unsigned list_size = 0, num = 0;
261       while ( (m = es.next()) != 0 ) {
262         ++list_size;
263         unsigned s = strlen(m);
264         const char * c = cur;
265         unsigned p;
266         bool match = false;
267         num = 0;
268         for (; *c != '\0'; c += p) {
269           ++num;
270           p = strcspn(c, "-");
271           if (p == s && memcmp(m, c, s) == 0) {match = true; break;}
272           if (c[p] == '-') p++;
273         }
274         if (!match) goto fail;
275         cur_rank = 0;
276       }
277       if (cur_rank == 0 && num != list_size) cur_rank = 1;
278     }
279     return;
280   fail:
281     cur_rank = 3;
282   }
283
284   PosibErr<Config *> find_word_list(Config * c) 
285   {
286     Config * config = new_config();
287     RET_ON_ERR(config->read_in_settings(c));
288     String dict_name;
289
290     if (config->have("master")) {
291       dict_name = config->retrieve("master");
292
293     } 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       BetterList b_code;
306       //BetterList b_jargon;
307       BetterVariety b_variety;
308       BetterList b_module;
309       BetterSize b_size;
310       Better * better[4] = {&b_code,&b_variety,&b_module,&b_size};
311       const DictInfo * best = 0;
312
313       //
314       // retrieve and normalize code
315       //
316       const char * p;
317       String code;
318       PosibErr<String> str = config->retrieve("lang");
319       p = str.data.c_str();
320       while (asc_isalpha(*p))
321         code += asc_tolower(*p++);
322       String lang = code;
323       bool have_country = false;
324       if (*p == '-' || *p == '_') {
325         ++p;
326         have_country = true;
327         code += '_'; 
328         while (asc_isalpha(*p))
329           code += asc_toupper(*p++);
330       }
331   
332       //
333       // Retrieve acceptable code search orders
334       //
335       String lang_country_list;
336       if (have_country) {
337         lang_country_list = code;
338         lang_country_list += ' ';
339       }
340       String lang_only_list = lang;
341       lang_only_list += ' ';
342
343       // read retrieve lang_country_list and lang_only_list from file(s)
344       // FIXME: Write Me
345
346       //
347       split_string_list(b_code.list, lang_country_list);
348       split_string_list(b_code.list, lang_only_list);
349       b_code.init();
350
351       //
352       // Retrieve Variety
353       // 
354       config->retrieve_list("variety", &b_variety.list);
355       if (b_variety.list.empty() && config->have("jargon")) 
356         b_variety.list.add(config->retrieve("jargon"));
357       b_variety.init();
358       str.data.clear();
359
360       //
361       // Retrieve module list
362       //
363       if (config->have("module"))
364         b_module.list.add(config->retrieve("module"));
365       else if (config->have("module-search-order"))
366         config->retrieve_list("module-search-order", &b_module.list);
367       {
368         StackPtr<ModuleInfoEnumeration> els(get_module_info_list(config)->elements());
369         const ModuleInfo * entry;
370         while ( (entry = els->next()) != 0)
371           b_module.list.add(entry->name);
372       }
373       b_module.init();
374
375       //
376       // Retrieve size
377       //
378       str = config->retrieve("size");
379       p = str.data.c_str();
380       if (p[0] == '+' || p[0] == '-' || p[0] == '<' || p[0] == '>') {
381         b_size.req_type = p[0];
382         ++p;
383       } else {
384         b_size.req_type = '+';
385       }
386       if (!asc_isdigit(p[0]) || !asc_isdigit(p[1]) || p[2] != '\0')
387         abort(); //FIXME: create an error condition here
388       b_size.requested = atoi(p);
389       b_size.init();
390
391       //
392       // 
393       //
394
395       const DictInfoList * dlist = get_dict_info_list(config);
396       DictInfoEnumeration * dels = dlist->elements();
397       const DictInfo * entry;
398
399       while ( (entry = dels->next()) != 0) {
400
401         b_code  .cur = entry->code;
402         b_module.cur = entry->module->name;
403
404         b_variety.cur = entry->variety;
405     
406         b_size.cur_str = entry->size_str;
407         b_size.cur     = entry->size;
408
409         //
410         // check to see if we got a better match than the current
411         // best_match if any
412         //
413
414         IsBetter is_better = SameMatch;
415         for (int i = 0; i != 4; ++i)
416           is_better = better[i]->better_match(is_better);
417     
418         if (is_better == BetterMatch) {
419           for (int i = 0; i != 4; ++i)
420             better[i]->set_best_from_cur();
421           best = entry;
422         }
423       }
424
425       delete dels;
426
427       //
428       // set config to best match
429       //
430       if (best != 0) {
431         String main_wl,flags;
432         PosibErrBase ret = get_dict_file_name(best, main_wl, flags);
433         if (ret.has_err()) {
434           delete config;
435           return ret;
436         }
437         dict_name = best->name;
438         config->replace("lang", b_code.best);
439         config->replace("language-tag", b_code.best);
440         config->replace("master", main_wl.c_str());
441         config->replace("master-flags", flags.c_str());
442         config->replace("module", b_module.best);
443         config->replace("jargon", b_variety.best);
444         config->replace("clear-variety", "");
445         unsigned p;
446         for (const char * c = b_module.best; *c != '\0'; c += p) {
447           p = strcspn(c, "-");
448           config->replace("add-variety", String(c, p));
449         }
450         config->replace("size", b_size.best_str);
451       } else {
452         delete config;
453         return make_err(no_wordlist_for_lang, code);
454       }
455     }
456
457     const StringMap * dict_aliases = get_dict_aliases(config);
458     const char * val = dict_aliases->lookup(dict_name);
459     if (val) config->replace("master", val);
460     return config;
461   }
462
463   PosibErr<void> reload_filters(Speller * m) 
464   {
465     m->to_internal_->filter.clear();
466     m->from_internal_->filter.clear();
467     // Add enocder and decoder filters if any
468     RET_ON_ERR(setup_filter(m->to_internal_->filter, m->config(), 
469                             true, false, false));
470     RET_ON_ERR(setup_filter(m->from_internal_->filter, m->config(), 
471                             false, false, true));
472     return no_err;
473   }
474
475   PosibErr<Speller *> new_speller(Config * c0) 
476   {
477     aspell_gettext_init();
478
479     RET_ON_ERR_SET(find_word_list(c0), Config *, c);
480     StackPtr<Speller> m(get_speller_class(c));
481     RET_ON_ERR(m->setup(c));
482
483     RET_ON_ERR(reload_filters(m));
484     
485     return m.release();
486   }
487
488   void delete_speller(Speller * m) 
489   {
490     SpellerLtHandle h = ((Speller *)(m))->lt_handle();
491     delete m;
492     if (h != 0) free_lt_handle(h);
493   }
494 }