Coverage Report

Created: 2026-07-30 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}