Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * spell.c
4 : * Normalizing word with ISpell
5 : *
6 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
7 : *
8 : * Ispell dictionary
9 : * -----------------
10 : *
11 : * Rules of dictionaries are defined in two files with .affix and .dict
12 : * extensions. They are used by spell checker programs Ispell and Hunspell.
13 : *
14 : * An .affix file declares morphological rules to get a basic form of words.
15 : * The format of an .affix file has different structure for Ispell and Hunspell
16 : * dictionaries. The Hunspell format is more complicated. But when an .affix
17 : * file is imported and compiled, it is stored in the same structure AffixNode.
18 : *
19 : * A .dict file stores a list of basic forms of words with references to
20 : * affix rules. The format of a .dict file has the same structure for Ispell
21 : * and Hunspell dictionaries.
22 : *
23 : * Compilation of a dictionary
24 : * ---------------------------
25 : *
26 : * A compiled dictionary is stored in the IspellDict structure. Compilation of
27 : * a dictionary is divided into the several steps:
28 : * - NIImportDictionary() - stores each word of a .dict file in the
29 : * temporary Spell field.
30 : * - NIImportAffixes() - stores affix rules of an .affix file in the
31 : * Affix field (not temporary) if an .affix file has the Ispell format.
32 : * -> NIImportOOAffixes() - stores affix rules if an .affix file has the
33 : * Hunspell format. The AffixData field is initialized if AF parameter
34 : * is defined.
35 : * - NISortDictionary() - builds a prefix tree (Trie) from the words list
36 : * and stores it in the Dictionary field. The words list is got from the
37 : * Spell field. The AffixData field is initialized if AF parameter is not
38 : * defined.
39 : * - NISortAffixes():
40 : * - builds a list of compound affixes from the affix list and stores it
41 : * in the CompoundAffix.
42 : * - builds prefix trees (Trie) from the affix list for prefixes and suffixes
43 : * and stores them in Suffix and Prefix fields.
44 : * The affix list is got from the Affix field.
45 : *
46 : * Memory management
47 : * -----------------
48 : *
49 : * The IspellDict structure has the Spell field which is used only in compile
50 : * time. The Spell field stores a words list. It can take a lot of memory.
51 : * Therefore when a dictionary is compiled this field is cleared by
52 : * NIFinishBuild().
53 : *
54 : * All resources which should cleared by NIFinishBuild() is initialized using
55 : * tmpalloc() and tmpalloc0().
56 : *
57 : * IDENTIFICATION
58 : * src/backend/tsearch/spell.c
59 : *
60 : *-------------------------------------------------------------------------
61 : */
62 :
63 : #include "postgres.h"
64 :
65 : #include "catalog/pg_collation.h"
66 : #include "tsearch/dicts/spell.h"
67 : #include "tsearch/ts_locale.h"
68 : #include "utils/memutils.h"
69 :
70 :
71 : /*
72 : * Initialization requires a lot of memory that's not needed
73 : * after the initialization is done. During initialization,
74 : * CurrentMemoryContext is the long-lived memory context associated
75 : * with the dictionary cache entry. We keep the short-lived stuff
76 : * in the Conf->buildCxt context.
77 : */
78 : #define tmpalloc(sz) MemoryContextAlloc(Conf->buildCxt, (sz))
79 : #define tmpalloc0(sz) MemoryContextAllocZero(Conf->buildCxt, (sz))
80 :
81 : /*
82 : * Prepare for constructing an ISpell dictionary.
83 : *
84 : * The IspellDict struct is assumed to be zeroed when allocated.
85 : */
86 : void
87 13 : NIStartBuild(IspellDict *Conf)
88 : {
89 : /*
90 : * The temp context is a child of CurTransactionContext, so that it will
91 : * go away automatically on error.
92 : */
93 13 : Conf->buildCxt = AllocSetContextCreate(CurTransactionContext,
94 : "Ispell dictionary init context",
95 : ALLOCSET_DEFAULT_SIZES);
96 13 : }
97 :
98 : /*
99 : * Clean up when dictionary construction is complete.
100 : */
101 : void
102 13 : NIFinishBuild(IspellDict *Conf)
103 : {
104 : /* Release no-longer-needed temp memory */
105 13 : MemoryContextDelete(Conf->buildCxt);
106 : /* Just for cleanliness, zero the now-dangling pointers */
107 13 : Conf->buildCxt = NULL;
108 13 : Conf->Spell = NULL;
109 13 : Conf->firstfree = NULL;
110 13 : Conf->CompoundAffixFlags = NULL;
111 13 : }
112 :
113 :
114 : /*
115 : * "Compact" palloc: allocate without extra palloc overhead.
116 : *
117 : * Since we have no need to free the ispell data items individually, there's
118 : * not much value in the per-chunk overhead normally consumed by palloc.
119 : * Getting rid of it is helpful since ispell can allocate a lot of small nodes.
120 : *
121 : * We currently pre-zero all data allocated this way, even though some of it
122 : * doesn't need that. The cpalloc and cpalloc0 macros are just documentation
123 : * to indicate which allocations actually require zeroing.
124 : */
125 : #define COMPACT_ALLOC_CHUNK 8192 /* amount to get from palloc at once */
126 : #define COMPACT_MAX_REQ 1024 /* must be < COMPACT_ALLOC_CHUNK */
127 :
128 : static void *
129 1163 : compact_palloc0(IspellDict *Conf, size_t size)
130 : {
131 : void *result;
132 :
133 : /* Should only be called during init */
134 1163 : Assert(Conf->buildCxt != NULL);
135 :
136 : /* No point in this for large chunks */
137 1163 : if (size > COMPACT_MAX_REQ)
138 0 : return palloc0(size);
139 :
140 : /* Keep everything maxaligned */
141 1163 : size = MAXALIGN(size);
142 :
143 : /* Need more space? */
144 1163 : if (size > Conf->avail)
145 : {
146 13 : Conf->firstfree = palloc0(COMPACT_ALLOC_CHUNK);
147 13 : Conf->avail = COMPACT_ALLOC_CHUNK;
148 : }
149 :
150 1163 : result = (void *) Conf->firstfree;
151 1163 : Conf->firstfree += size;
152 1163 : Conf->avail -= size;
153 :
154 1163 : return result;
155 : }
156 :
157 : #define cpalloc(size) compact_palloc0(Conf, size)
158 : #define cpalloc0(size) compact_palloc0(Conf, size)
159 :
160 : static char *
161 539 : cpstrdup(IspellDict *Conf, const char *str)
162 : {
163 539 : char *res = cpalloc(strlen(str) + 1);
164 :
165 539 : strcpy(res, str);
166 539 : return res;
167 : }
168 :
169 :
170 : /*
171 : * Apply lowerstr(), producing a temporary result (in the buildCxt).
172 : */
173 : static char *
174 467 : lowerstr_ctx(IspellDict *Conf, const char *src)
175 : {
176 : MemoryContext saveCtx;
177 : char *dst;
178 :
179 467 : saveCtx = MemoryContextSwitchTo(Conf->buildCxt);
180 467 : dst = lowerstr(src);
181 467 : MemoryContextSwitchTo(saveCtx);
182 :
183 467 : return dst;
184 : }
185 :
186 : #define MAX_NORM 1024
187 : #define MAXNORMLEN 256
188 :
189 : #define STRNCMP(s,p) strncmp( (s), (p), strlen(p) )
190 : #define GETWCHAR(W,L,N,T) ( ((const uint8*)(W))[ ((T)==FF_PREFIX) ? (N) : ( (L) - 1 - (N) ) ] )
191 : #define GETCHAR(A,N,T) GETWCHAR( (A)->repl, (A)->replen, N, T )
192 :
193 : static char *VoidString = "";
194 :
195 : static int
196 264 : cmpspell(const void *s1, const void *s2)
197 : {
198 264 : return (strcmp((*(SPELL *const *) s1)->word, (*(SPELL *const *) s2)->word));
199 : }
200 :
201 : static int
202 218 : cmpspellaffix(const void *s1, const void *s2)
203 : {
204 218 : return (strcmp((*(SPELL *const *) s1)->p.flag,
205 218 : (*(SPELL *const *) s2)->p.flag));
206 : }
207 :
208 : static int
209 245 : cmpcmdflag(const void *f1, const void *f2)
210 : {
211 245 : CompoundAffixFlag *fv1 = (CompoundAffixFlag *) f1,
212 245 : *fv2 = (CompoundAffixFlag *) f2;
213 :
214 245 : Assert(fv1->flagMode == fv2->flagMode);
215 :
216 245 : if (fv1->flagMode == FM_NUM)
217 : {
218 60 : if (fv1->flag.i == fv2->flag.i)
219 15 : return 0;
220 :
221 45 : return (fv1->flag.i > fv2->flag.i) ? 1 : -1;
222 : }
223 :
224 185 : return strcmp(fv1->flag.s, fv2->flag.s);
225 : }
226 :
227 : static char *
228 104 : findchar(char *str, int c)
229 : {
230 897 : while (*str)
231 : {
232 780 : if (t_iseq(str, c))
233 91 : return str;
234 689 : str += pg_mblen(str);
235 : }
236 :
237 13 : return NULL;
238 : }
239 :
240 : static char *
241 4 : findchar2(char *str, int c1, int c2)
242 : {
243 88 : while (*str)
244 : {
245 84 : if (t_iseq(str, c1) || t_iseq(str, c2))
246 4 : return str;
247 80 : str += pg_mblen(str);
248 : }
249 :
250 0 : return NULL;
251 : }
252 :
253 :
254 : /* backward string compare for suffix tree operations */
255 : static int
256 117 : strbcmp(const unsigned char *s1, const unsigned char *s2)
257 : {
258 117 : int l1 = strlen((const char *) s1) - 1,
259 117 : l2 = strlen((const char *) s2) - 1;
260 :
261 273 : while (l1 >= 0 && l2 >= 0)
262 : {
263 130 : if (s1[l1] < s2[l2])
264 13 : return -1;
265 117 : if (s1[l1] > s2[l2])
266 78 : return 1;
267 39 : l1--;
268 39 : l2--;
269 : }
270 26 : if (l1 < l2)
271 13 : return -1;
272 13 : if (l1 > l2)
273 13 : return 1;
274 :
275 0 : return 0;
276 : }
277 :
278 : static int
279 0 : strbncmp(const unsigned char *s1, const unsigned char *s2, size_t count)
280 : {
281 0 : int l1 = strlen((const char *) s1) - 1,
282 0 : l2 = strlen((const char *) s2) - 1,
283 0 : l = count;
284 :
285 0 : while (l1 >= 0 && l2 >= 0 && l > 0)
286 : {
287 0 : if (s1[l1] < s2[l2])
288 0 : return -1;
289 0 : if (s1[l1] > s2[l2])
290 0 : return 1;
291 0 : l1--;
292 0 : l2--;
293 0 : l--;
294 : }
295 0 : if (l == 0)
296 0 : return 0;
297 0 : if (l1 < l2)
298 0 : return -1;
299 0 : if (l1 > l2)
300 0 : return 1;
301 0 : return 0;
302 : }
303 :
304 : /*
305 : * Compares affixes.
306 : * First compares the type of an affix. Prefixes should go before affixes.
307 : * If types are equal then compares replaceable string.
308 : */
309 : static int
310 182 : cmpaffix(const void *s1, const void *s2)
311 : {
312 182 : const AFFIX *a1 = (const AFFIX *) s1;
313 182 : const AFFIX *a2 = (const AFFIX *) s2;
314 :
315 182 : if (a1->type < a2->type)
316 39 : return -1;
317 143 : if (a1->type > a2->type)
318 0 : return 1;
319 143 : if (a1->type == FF_PREFIX)
320 26 : return strcmp(a1->repl, a2->repl);
321 : else
322 117 : return strbcmp((const unsigned char *) a1->repl,
323 117 : (const unsigned char *) a2->repl);
324 : }
325 :
326 : /*
327 : * Gets an affix flag from the set of affix flags (sflagset).
328 : *
329 : * Several flags can be stored in a single string. Flags can be represented by:
330 : * - 1 character (FM_CHAR). A character may be Unicode.
331 : * - 2 characters (FM_LONG). A character may be Unicode.
332 : * - numbers from 1 to 65000 (FM_NUM).
333 : *
334 : * Depending on the flagMode an affix string can have the following format:
335 : * - FM_CHAR: ABCD
336 : * Here we have 4 flags: A, B, C and D
337 : * - FM_LONG: ABCDE*
338 : * Here we have 3 flags: AB, CD and E*
339 : * - FM_NUM: 200,205,50
340 : * Here we have 3 flags: 200, 205 and 50
341 : *
342 : * Conf: current dictionary.
343 : * sflagset: the set of affix flags. Returns a reference to the start of a next
344 : * affix flag.
345 : * sflag: returns an affix flag from sflagset.
346 : */
347 : static void
348 494 : getNextFlagFromString(IspellDict *Conf, char **sflagset, char *sflag)
349 : {
350 : int32 s;
351 : char *next,
352 494 : *sbuf = *sflagset;
353 : int maxstep;
354 494 : bool stop = false;
355 494 : bool met_comma = false;
356 :
357 494 : maxstep = (Conf->flagMode == FM_LONG) ? 2 : 1;
358 :
359 1111 : while (**sflagset)
360 : {
361 617 : switch (Conf->flagMode)
362 : {
363 : case FM_LONG:
364 : case FM_CHAR:
365 503 : COPYCHAR(sflag, *sflagset);
366 503 : sflag += pg_mblen(*sflagset);
367 :
368 : /* Go to start of the next flag */
369 503 : *sflagset += pg_mblen(*sflagset);
370 :
371 : /* Check if we get all characters of flag */
372 503 : maxstep--;
373 503 : stop = (maxstep == 0);
374 503 : break;
375 : case FM_NUM:
376 114 : s = strtol(*sflagset, &next, 10);
377 114 : if (*sflagset == next || errno == ERANGE)
378 0 : ereport(ERROR,
379 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
380 : errmsg("invalid affix flag \"%s\"", *sflagset)));
381 114 : if (s < 0 || s > FLAGNUM_MAXSIZE)
382 0 : ereport(ERROR,
383 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
384 : errmsg("affix flag \"%s\" is out of range",
385 : *sflagset)));
386 114 : sflag += sprintf(sflag, "%0d", s);
387 :
388 : /* Go to start of the next flag */
389 114 : *sflagset = next;
390 294 : while (**sflagset)
391 : {
392 132 : if (t_isdigit(*sflagset))
393 : {
394 66 : if (!met_comma)
395 0 : ereport(ERROR,
396 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
397 : errmsg("invalid affix flag \"%s\"",
398 : *sflagset)));
399 66 : break;
400 : }
401 66 : else if (t_iseq(*sflagset, ','))
402 : {
403 66 : if (met_comma)
404 0 : ereport(ERROR,
405 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
406 : errmsg("invalid affix flag \"%s\"",
407 : *sflagset)));
408 66 : met_comma = true;
409 : }
410 0 : else if (!t_isspace(*sflagset))
411 : {
412 0 : ereport(ERROR,
413 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
414 : errmsg("invalid character in affix flag \"%s\"",
415 : *sflagset)));
416 : }
417 :
418 66 : *sflagset += pg_mblen(*sflagset);
419 : }
420 114 : stop = true;
421 114 : break;
422 : default:
423 0 : elog(ERROR, "unrecognized type of Conf->flagMode: %d",
424 : Conf->flagMode);
425 : }
426 :
427 617 : if (stop)
428 494 : break;
429 : }
430 :
431 494 : if (Conf->flagMode == FM_LONG && maxstep > 0)
432 0 : ereport(ERROR,
433 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
434 : errmsg("invalid affix flag \"%s\" with \"long\" flag value",
435 : sbuf)));
436 :
437 494 : *sflag = '\0';
438 494 : }
439 :
440 : /*
441 : * Checks if the affix set Conf->AffixData[affix] contains affixflag.
442 : * Conf->AffixData[affix] does not contain affixflag if this flag is not used
443 : * actually by the .dict file.
444 : *
445 : * Conf: current dictionary.
446 : * affix: index of the Conf->AffixData array.
447 : * affixflag: the affix flag.
448 : *
449 : * Returns true if the string Conf->AffixData[affix] contains affixflag,
450 : * otherwise returns false.
451 : */
452 : static bool
453 255 : IsAffixFlagInUse(IspellDict *Conf, int affix, char *affixflag)
454 : {
455 : char *flagcur;
456 : char flag[BUFSIZ];
457 :
458 255 : if (*affixflag == 0)
459 104 : return true;
460 :
461 151 : flagcur = Conf->AffixData[affix];
462 :
463 534 : while (*flagcur)
464 : {
465 303 : getNextFlagFromString(Conf, &flagcur, flag);
466 : /* Compare first affix flag in flagcur with affixflag */
467 303 : if (strcmp(flag, affixflag) == 0)
468 71 : return true;
469 : }
470 :
471 : /* Could not find affixflag */
472 80 : return false;
473 : }
474 :
475 : /*
476 : * Adds the new word into the temporary array Spell.
477 : *
478 : * Conf: current dictionary.
479 : * word: new word.
480 : * flag: set of affix flags. Single flag can be get by getNextFlagFromString().
481 : */
482 : static void
483 104 : NIAddSpell(IspellDict *Conf, const char *word, const char *flag)
484 : {
485 104 : if (Conf->nspell >= Conf->mspell)
486 : {
487 13 : if (Conf->mspell)
488 : {
489 0 : Conf->mspell *= 2;
490 0 : Conf->Spell = (SPELL **) repalloc(Conf->Spell, Conf->mspell * sizeof(SPELL *));
491 : }
492 : else
493 : {
494 13 : Conf->mspell = 1024 * 20;
495 13 : Conf->Spell = (SPELL **) tmpalloc(Conf->mspell * sizeof(SPELL *));
496 : }
497 : }
498 104 : Conf->Spell[Conf->nspell] = (SPELL *) tmpalloc(SPELLHDRSZ + strlen(word) + 1);
499 104 : strcpy(Conf->Spell[Conf->nspell]->word, word);
500 208 : Conf->Spell[Conf->nspell]->p.flag = (*flag != '\0')
501 104 : ? cpstrdup(Conf, flag) : VoidString;
502 104 : Conf->nspell++;
503 104 : }
504 :
505 : /*
506 : * Imports dictionary into the temporary array Spell.
507 : *
508 : * Note caller must already have applied get_tsearch_config_filename.
509 : *
510 : * Conf: current dictionary.
511 : * filename: path to the .dict file.
512 : */
513 : void
514 13 : NIImportDictionary(IspellDict *Conf, const char *filename)
515 : {
516 : tsearch_readline_state trst;
517 : char *line;
518 :
519 13 : if (!tsearch_readline_begin(&trst, filename))
520 0 : ereport(ERROR,
521 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
522 : errmsg("could not open dictionary file \"%s\": %m",
523 : filename)));
524 :
525 130 : while ((line = tsearch_readline(&trst)) != NULL)
526 : {
527 : char *s,
528 : *pstr;
529 :
530 : /* Set of affix flags */
531 : const char *flag;
532 :
533 : /* Extract flag from the line */
534 104 : flag = NULL;
535 104 : if ((s = findchar(line, '/')))
536 : {
537 91 : *s++ = '\0';
538 91 : flag = s;
539 448 : while (*s)
540 : {
541 : /* we allow only single encoded flags for faster works */
542 357 : if (pg_mblen(s) == 1 && t_isprint(s) && !t_isspace(s))
543 266 : s++;
544 : else
545 : {
546 91 : *s = '\0';
547 91 : break;
548 : }
549 : }
550 : }
551 : else
552 13 : flag = "";
553 :
554 : /* Remove trailing spaces */
555 104 : s = line;
556 884 : while (*s)
557 : {
558 689 : if (t_isspace(s))
559 : {
560 13 : *s = '\0';
561 13 : break;
562 : }
563 676 : s += pg_mblen(s);
564 : }
565 104 : pstr = lowerstr_ctx(Conf, line);
566 :
567 104 : NIAddSpell(Conf, pstr, flag);
568 104 : pfree(pstr);
569 :
570 104 : pfree(line);
571 : }
572 13 : tsearch_readline_end(&trst);
573 13 : }
574 :
575 : /*
576 : * Searches a basic form of word in the prefix tree. This word was generated
577 : * using an affix rule. This rule may not be presented in an affix set of
578 : * a basic form of word.
579 : *
580 : * For example, we have the entry in the .dict file:
581 : * meter/GMD
582 : *
583 : * The affix rule with the flag S:
584 : * SFX S y ies [^aeiou]y
585 : * is not presented here.
586 : *
587 : * The affix rule with the flag M:
588 : * SFX M 0 's .
589 : * is presented here.
590 : *
591 : * Conf: current dictionary.
592 : * word: basic form of word.
593 : * affixflag: affix flag, by which a basic form of word was generated.
594 : * flag: compound flag used to compare with StopMiddle->compoundflag.
595 : *
596 : * Returns 1 if the word was found in the prefix tree, else returns 0.
597 : */
598 : static int
599 408 : FindWord(IspellDict *Conf, const char *word, char *affixflag, int flag)
600 : {
601 408 : SPNode *node = Conf->Dictionary;
602 : SPNodeData *StopLow,
603 : *StopHigh,
604 : *StopMiddle;
605 408 : const uint8 *ptr = (const uint8 *) word;
606 :
607 408 : flag &= FF_COMPOUNDFLAGMASK;
608 :
609 2322 : while (node && *ptr)
610 : {
611 1828 : StopLow = node->data;
612 1828 : StopHigh = node->data + node->length;
613 4398 : while (StopLow < StopHigh)
614 : {
615 2410 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
616 2410 : if (StopMiddle->val == *ptr)
617 : {
618 1668 : if (*(ptr + 1) == '\0' && StopMiddle->isword)
619 : {
620 184 : if (flag == 0)
621 : {
622 : /*
623 : * The word can be formed only with another word. And
624 : * in the flag parameter there is not a sign that we
625 : * search compound words.
626 : */
627 118 : if (StopMiddle->compoundflag & FF_COMPOUNDONLY)
628 0 : return 0;
629 : }
630 66 : else if ((flag & StopMiddle->compoundflag) == 0)
631 0 : return 0;
632 :
633 : /*
634 : * Check if this affix rule is presented in the affix set
635 : * with index StopMiddle->affix.
636 : */
637 184 : if (IsAffixFlagInUse(Conf, StopMiddle->affix, affixflag))
638 162 : return 1;
639 : }
640 1506 : node = StopMiddle->node;
641 1506 : ptr++;
642 1506 : break;
643 : }
644 742 : else if (StopMiddle->val < *ptr)
645 222 : StopLow = StopMiddle + 1;
646 : else
647 520 : StopHigh = StopMiddle;
648 : }
649 1666 : if (StopLow >= StopHigh)
650 160 : break;
651 : }
652 246 : return 0;
653 : }
654 :
655 : /*
656 : * Adds a new affix rule to the Affix field.
657 : *
658 : * Conf: current dictionary.
659 : * flag: affix flag ('\' in the below example).
660 : * flagflags: set of flags from the flagval field for this affix rule. This set
661 : * is listed after '/' character in the added string (repl).
662 : *
663 : * For example L flag in the hunspell_sample.affix:
664 : * SFX \ 0 Y/L [^Y]
665 : *
666 : * mask: condition for search ('[^Y]' in the above example).
667 : * find: stripping characters from beginning (at prefix) or end (at suffix)
668 : * of the word ('0' in the above example, 0 means that there is not
669 : * stripping character).
670 : * repl: adding string after stripping ('Y' in the above example).
671 : * type: FF_SUFFIX or FF_PREFIX.
672 : */
673 : static void
674 91 : NIAddAffix(IspellDict *Conf, const char *flag, char flagflags, const char *mask,
675 : const char *find, const char *repl, int type)
676 : {
677 : AFFIX *Affix;
678 :
679 91 : if (Conf->naffixes >= Conf->maffixes)
680 : {
681 13 : if (Conf->maffixes)
682 : {
683 0 : Conf->maffixes *= 2;
684 0 : Conf->Affix = (AFFIX *) repalloc((void *) Conf->Affix, Conf->maffixes * sizeof(AFFIX));
685 : }
686 : else
687 : {
688 13 : Conf->maffixes = 16;
689 13 : Conf->Affix = (AFFIX *) palloc(Conf->maffixes * sizeof(AFFIX));
690 : }
691 : }
692 :
693 91 : Affix = Conf->Affix + Conf->naffixes;
694 :
695 : /* This affix rule can be applied for words with any ending */
696 91 : if (strcmp(mask, ".") == 0 || *mask == '\0')
697 : {
698 26 : Affix->issimple = 1;
699 26 : Affix->isregis = 0;
700 : }
701 : /* This affix rule will use regis to search word ending */
702 65 : else if (RS_isRegis(mask))
703 : {
704 65 : Affix->issimple = 0;
705 65 : Affix->isregis = 1;
706 65 : RS_compile(&(Affix->reg.regis), (type == FF_SUFFIX),
707 65 : *mask ? mask : VoidString);
708 : }
709 : /* This affix rule will use regex_t to search word ending */
710 : else
711 : {
712 : int masklen;
713 : int wmasklen;
714 : int err;
715 : pg_wchar *wmask;
716 : char *tmask;
717 :
718 0 : Affix->issimple = 0;
719 0 : Affix->isregis = 0;
720 0 : tmask = (char *) tmpalloc(strlen(mask) + 3);
721 0 : if (type == FF_SUFFIX)
722 0 : sprintf(tmask, "%s$", mask);
723 : else
724 0 : sprintf(tmask, "^%s", mask);
725 :
726 0 : masklen = strlen(tmask);
727 0 : wmask = (pg_wchar *) tmpalloc((masklen + 1) * sizeof(pg_wchar));
728 0 : wmasklen = pg_mb2wchar_with_len(tmask, wmask, masklen);
729 :
730 0 : err = pg_regcomp(&(Affix->reg.regex), wmask, wmasklen,
731 : REG_ADVANCED | REG_NOSUB,
732 : DEFAULT_COLLATION_OID);
733 0 : if (err)
734 : {
735 : char errstr[100];
736 :
737 0 : pg_regerror(err, &(Affix->reg.regex), errstr, sizeof(errstr));
738 0 : ereport(ERROR,
739 : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
740 : errmsg("invalid regular expression: %s", errstr)));
741 : }
742 : }
743 :
744 91 : Affix->flagflags = flagflags;
745 91 : if ((Affix->flagflags & FF_COMPOUNDONLY) || (Affix->flagflags & FF_COMPOUNDPERMITFLAG))
746 : {
747 13 : if ((Affix->flagflags & FF_COMPOUNDFLAG) == 0)
748 13 : Affix->flagflags |= FF_COMPOUNDFLAG;
749 : }
750 91 : Affix->flag = cpstrdup(Conf, flag);
751 91 : Affix->type = type;
752 :
753 91 : Affix->find = (find && *find) ? cpstrdup(Conf, find) : VoidString;
754 91 : if ((Affix->replen = strlen(repl)) > 0)
755 91 : Affix->repl = cpstrdup(Conf, repl);
756 : else
757 0 : Affix->repl = VoidString;
758 91 : Conf->naffixes++;
759 91 : }
760 :
761 : /* Parsing states for parse_affentry() and friends */
762 : #define PAE_WAIT_MASK 0
763 : #define PAE_INMASK 1
764 : #define PAE_WAIT_FIND 2
765 : #define PAE_INFIND 3
766 : #define PAE_WAIT_REPL 4
767 : #define PAE_INREPL 5
768 : #define PAE_WAIT_TYPE 6
769 : #define PAE_WAIT_FLAG 7
770 :
771 : /*
772 : * Parse next space-separated field of an .affix file line.
773 : *
774 : * *str is the input pointer (will be advanced past field)
775 : * next is where to copy the field value to, with null termination
776 : *
777 : * The buffer at "next" must be of size BUFSIZ; we truncate the input to fit.
778 : *
779 : * Returns TRUE if we found a field, FALSE if not.
780 : */
781 : static bool
782 774 : get_nextfield(char **str, char *next)
783 : {
784 774 : int state = PAE_WAIT_MASK;
785 774 : int avail = BUFSIZ;
786 :
787 4014 : while (**str)
788 : {
789 3150 : if (state == PAE_WAIT_MASK)
790 : {
791 1434 : if (t_iseq(*str, '#'))
792 21 : return false;
793 1413 : else if (!t_isspace(*str))
794 : {
795 663 : int clen = pg_mblen(*str);
796 :
797 663 : if (clen < avail)
798 : {
799 663 : COPYCHAR(next, *str);
800 663 : next += clen;
801 663 : avail -= clen;
802 : }
803 663 : state = PAE_INMASK;
804 : }
805 : }
806 : else /* state == PAE_INMASK */
807 : {
808 1716 : if (t_isspace(*str))
809 : {
810 663 : *next = '\0';
811 663 : return true;
812 : }
813 : else
814 : {
815 1053 : int clen = pg_mblen(*str);
816 :
817 1053 : if (clen < avail)
818 : {
819 1053 : COPYCHAR(next, *str);
820 1053 : next += clen;
821 1053 : avail -= clen;
822 : }
823 : }
824 : }
825 2466 : *str += pg_mblen(*str);
826 : }
827 :
828 90 : *next = '\0';
829 :
830 90 : return (state == PAE_INMASK); /* OK if we got a nonempty field */
831 : }
832 :
833 : /*
834 : * Parses entry of an .affix file of MySpell or Hunspell format.
835 : *
836 : * An .affix file entry has the following format:
837 : * - header
838 : * <type> <flag> <cross_flag> <flag_count>
839 : * - fields after header:
840 : * <type> <flag> <find> <replace> <mask>
841 : *
842 : * str is the input line
843 : * field values are returned to type etc, which must be buffers of size BUFSIZ.
844 : *
845 : * Returns number of fields found; any omitted fields are set to empty strings.
846 : */
847 : static int
848 174 : parse_ooaffentry(char *str, char *type, char *flag, char *find,
849 : char *repl, char *mask)
850 : {
851 174 : int state = PAE_WAIT_TYPE;
852 174 : int fields_read = 0;
853 174 : bool valid = false;
854 :
855 174 : *type = *flag = *find = *repl = *mask = '\0';
856 :
857 948 : while (*str)
858 : {
859 774 : switch (state)
860 : {
861 : case PAE_WAIT_TYPE:
862 174 : valid = get_nextfield(&str, type);
863 174 : state = PAE_WAIT_FLAG;
864 174 : break;
865 : case PAE_WAIT_FLAG:
866 174 : valid = get_nextfield(&str, flag);
867 174 : state = PAE_WAIT_FIND;
868 174 : break;
869 : case PAE_WAIT_FIND:
870 174 : valid = get_nextfield(&str, find);
871 174 : state = PAE_WAIT_REPL;
872 174 : break;
873 : case PAE_WAIT_REPL:
874 126 : valid = get_nextfield(&str, repl);
875 126 : state = PAE_WAIT_MASK;
876 126 : break;
877 : case PAE_WAIT_MASK:
878 126 : valid = get_nextfield(&str, mask);
879 126 : state = -1; /* force loop exit */
880 126 : break;
881 : default:
882 0 : elog(ERROR, "unrecognized state in parse_ooaffentry: %d",
883 : state);
884 : break;
885 : }
886 774 : if (valid)
887 663 : fields_read++;
888 : else
889 111 : break; /* early EOL */
890 663 : if (state < 0)
891 63 : break; /* got all fields */
892 : }
893 :
894 174 : return fields_read;
895 : }
896 :
897 : /*
898 : * Parses entry of an .affix file of Ispell format
899 : *
900 : * An .affix file entry has the following format:
901 : * <mask> > [-<find>,]<replace>
902 : */
903 : static bool
904 28 : parse_affentry(char *str, char *mask, char *find, char *repl)
905 : {
906 28 : int state = PAE_WAIT_MASK;
907 28 : char *pmask = mask,
908 28 : *pfind = find,
909 28 : *prepl = repl;
910 :
911 28 : *mask = *find = *repl = '\0';
912 :
913 764 : while (*str)
914 : {
915 736 : if (state == PAE_WAIT_MASK)
916 : {
917 68 : if (t_iseq(str, '#'))
918 0 : return false;
919 68 : else if (!t_isspace(str))
920 : {
921 28 : COPYCHAR(pmask, str);
922 28 : pmask += pg_mblen(str);
923 28 : state = PAE_INMASK;
924 : }
925 : }
926 668 : else if (state == PAE_INMASK)
927 : {
928 272 : if (t_iseq(str, '>'))
929 : {
930 28 : *pmask = '\0';
931 28 : state = PAE_WAIT_FIND;
932 : }
933 244 : else if (!t_isspace(str))
934 : {
935 96 : COPYCHAR(pmask, str);
936 96 : pmask += pg_mblen(str);
937 : }
938 : }
939 396 : else if (state == PAE_WAIT_FIND)
940 : {
941 112 : if (t_iseq(str, '-'))
942 : {
943 4 : state = PAE_INFIND;
944 : }
945 108 : else if (t_isalpha(str) || t_iseq(str, '\'') /* english 's */ )
946 : {
947 24 : COPYCHAR(prepl, str);
948 24 : prepl += pg_mblen(str);
949 24 : state = PAE_INREPL;
950 : }
951 84 : else if (!t_isspace(str))
952 0 : ereport(ERROR,
953 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
954 : errmsg("syntax error")));
955 : }
956 284 : else if (state == PAE_INFIND)
957 : {
958 8 : if (t_iseq(str, ','))
959 : {
960 4 : *pfind = '\0';
961 4 : state = PAE_WAIT_REPL;
962 : }
963 4 : else if (t_isalpha(str))
964 : {
965 4 : COPYCHAR(pfind, str);
966 4 : pfind += pg_mblen(str);
967 : }
968 0 : else if (!t_isspace(str))
969 0 : ereport(ERROR,
970 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
971 : errmsg("syntax error")));
972 : }
973 276 : else if (state == PAE_WAIT_REPL)
974 : {
975 4 : if (t_iseq(str, '-'))
976 : {
977 0 : break; /* void repl */
978 : }
979 4 : else if (t_isalpha(str))
980 : {
981 4 : COPYCHAR(prepl, str);
982 4 : prepl += pg_mblen(str);
983 4 : state = PAE_INREPL;
984 : }
985 0 : else if (!t_isspace(str))
986 0 : ereport(ERROR,
987 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
988 : errmsg("syntax error")));
989 : }
990 272 : else if (state == PAE_INREPL)
991 : {
992 272 : if (t_iseq(str, '#'))
993 : {
994 28 : *prepl = '\0';
995 28 : break;
996 : }
997 244 : else if (t_isalpha(str))
998 : {
999 36 : COPYCHAR(prepl, str);
1000 36 : prepl += pg_mblen(str);
1001 : }
1002 208 : else if (!t_isspace(str))
1003 0 : ereport(ERROR,
1004 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1005 : errmsg("syntax error")));
1006 : }
1007 : else
1008 0 : elog(ERROR, "unrecognized state in parse_affentry: %d", state);
1009 :
1010 708 : str += pg_mblen(str);
1011 : }
1012 :
1013 28 : *pmask = *pfind = *prepl = '\0';
1014 :
1015 28 : return (*mask && (*find || *repl));
1016 : }
1017 :
1018 : /*
1019 : * Sets a Hunspell options depending on flag type.
1020 : */
1021 : static void
1022 213 : setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry,
1023 : char *s, uint32 val)
1024 : {
1025 213 : if (Conf->flagMode == FM_NUM)
1026 : {
1027 : char *next;
1028 : int i;
1029 :
1030 51 : i = strtol(s, &next, 10);
1031 51 : if (s == next || errno == ERANGE)
1032 0 : ereport(ERROR,
1033 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1034 : errmsg("invalid affix flag \"%s\"", s)));
1035 51 : if (i < 0 || i > FLAGNUM_MAXSIZE)
1036 0 : ereport(ERROR,
1037 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1038 : errmsg("affix flag \"%s\" is out of range", s)));
1039 :
1040 51 : entry->flag.i = i;
1041 : }
1042 : else
1043 162 : entry->flag.s = cpstrdup(Conf, s);
1044 :
1045 213 : entry->flagMode = Conf->flagMode;
1046 213 : entry->value = val;
1047 213 : }
1048 :
1049 : /*
1050 : * Sets up a correspondence for the affix parameter with the affix flag.
1051 : *
1052 : * Conf: current dictionary.
1053 : * s: affix flag in string.
1054 : * val: affix parameter.
1055 : */
1056 : static void
1057 22 : addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val)
1058 : {
1059 : CompoundAffixFlag *newValue;
1060 : char sbuf[BUFSIZ];
1061 : char *sflag;
1062 : int clen;
1063 :
1064 62 : while (*s && t_isspace(s))
1065 18 : s += pg_mblen(s);
1066 :
1067 22 : if (!*s)
1068 0 : ereport(ERROR,
1069 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1070 : errmsg("syntax error")));
1071 :
1072 : /* Get flag without \n */
1073 22 : sflag = sbuf;
1074 84 : while (*s && !t_isspace(s) && *s != '\n')
1075 : {
1076 40 : clen = pg_mblen(s);
1077 40 : COPYCHAR(sflag, s);
1078 40 : sflag += clen;
1079 40 : s += clen;
1080 : }
1081 22 : *sflag = '\0';
1082 :
1083 : /* Resize array or allocate memory for array CompoundAffixFlag */
1084 22 : if (Conf->nCompoundAffixFlag >= Conf->mCompoundAffixFlag)
1085 : {
1086 13 : if (Conf->mCompoundAffixFlag)
1087 : {
1088 0 : Conf->mCompoundAffixFlag *= 2;
1089 0 : Conf->CompoundAffixFlags = (CompoundAffixFlag *)
1090 0 : repalloc((void *) Conf->CompoundAffixFlags,
1091 0 : Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
1092 : }
1093 : else
1094 : {
1095 13 : Conf->mCompoundAffixFlag = 10;
1096 13 : Conf->CompoundAffixFlags = (CompoundAffixFlag *)
1097 13 : tmpalloc(Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
1098 : }
1099 : }
1100 :
1101 22 : newValue = Conf->CompoundAffixFlags + Conf->nCompoundAffixFlag;
1102 :
1103 22 : setCompoundAffixFlagValue(Conf, newValue, sbuf, val);
1104 :
1105 22 : Conf->usecompound = true;
1106 22 : Conf->nCompoundAffixFlag++;
1107 22 : }
1108 :
1109 : /*
1110 : * Returns a set of affix parameters which correspondence to the set of affix
1111 : * flags s.
1112 : */
1113 : static int
1114 113 : getCompoundAffixFlagValue(IspellDict *Conf, char *s)
1115 : {
1116 113 : uint32 flag = 0;
1117 : CompoundAffixFlag *found,
1118 : key;
1119 : char sflag[BUFSIZ];
1120 : char *flagcur;
1121 :
1122 113 : if (Conf->nCompoundAffixFlag == 0)
1123 0 : return 0;
1124 :
1125 113 : flagcur = s;
1126 417 : while (*flagcur)
1127 : {
1128 191 : getNextFlagFromString(Conf, &flagcur, sflag);
1129 191 : setCompoundAffixFlagValue(Conf, &key, sflag, 0);
1130 :
1131 191 : found = (CompoundAffixFlag *)
1132 191 : bsearch(&key, (void *) Conf->CompoundAffixFlags,
1133 191 : Conf->nCompoundAffixFlag, sizeof(CompoundAffixFlag),
1134 : cmpcmdflag);
1135 191 : if (found != NULL)
1136 61 : flag |= found->value;
1137 : }
1138 :
1139 113 : return flag;
1140 : }
1141 :
1142 : /*
1143 : * Returns a flag set using the s parameter.
1144 : *
1145 : * If Conf->useFlagAliases is true then the s parameter is index of the
1146 : * Conf->AffixData array and function returns its entry.
1147 : * Else function returns the s parameter.
1148 : */
1149 : static char *
1150 9 : getAffixFlagSet(IspellDict *Conf, char *s)
1151 : {
1152 9 : if (Conf->useFlagAliases && *s != '\0')
1153 : {
1154 : int curaffix;
1155 : char *end;
1156 :
1157 3 : curaffix = strtol(s, &end, 10);
1158 3 : if (s == end || errno == ERANGE)
1159 0 : ereport(ERROR,
1160 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1161 : errmsg("invalid affix alias \"%s\"", s)));
1162 :
1163 3 : if (curaffix > 0 && curaffix <= Conf->nAffixData)
1164 :
1165 : /*
1166 : * Do not subtract 1 from curaffix because empty string was added
1167 : * in NIImportOOAffixes
1168 : */
1169 3 : return Conf->AffixData[curaffix];
1170 : else
1171 0 : return VoidString;
1172 : }
1173 : else
1174 6 : return s;
1175 : }
1176 :
1177 : /*
1178 : * Import an affix file that follows MySpell or Hunspell format.
1179 : *
1180 : * Conf: current dictionary.
1181 : * filename: path to the .affix file.
1182 : */
1183 : static void
1184 9 : NIImportOOAffixes(IspellDict *Conf, const char *filename)
1185 : {
1186 : char type[BUFSIZ],
1187 9 : *ptype = NULL;
1188 : char sflag[BUFSIZ];
1189 : char mask[BUFSIZ],
1190 : *pmask;
1191 : char find[BUFSIZ],
1192 : *pfind;
1193 : char repl[BUFSIZ],
1194 : *prepl;
1195 9 : bool isSuffix = false;
1196 9 : int naffix = 0,
1197 9 : curaffix = 0;
1198 9 : int sflaglen = 0;
1199 9 : char flagflags = 0;
1200 : tsearch_readline_state trst;
1201 : char *recoded;
1202 :
1203 : /* read file to find any flag */
1204 9 : Conf->usecompound = false;
1205 9 : Conf->useFlagAliases = false;
1206 9 : Conf->flagMode = FM_CHAR;
1207 :
1208 9 : if (!tsearch_readline_begin(&trst, filename))
1209 0 : ereport(ERROR,
1210 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1211 : errmsg("could not open affix file \"%s\": %m",
1212 : filename)));
1213 :
1214 264 : while ((recoded = tsearch_readline(&trst)) != NULL)
1215 : {
1216 246 : if (*recoded == '\0' || t_isspace(recoded) || t_iseq(recoded, '#'))
1217 : {
1218 72 : pfree(recoded);
1219 72 : continue;
1220 : }
1221 :
1222 174 : if (STRNCMP(recoded, "COMPOUNDFLAG") == 0)
1223 9 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDFLAG"),
1224 : FF_COMPOUNDFLAG);
1225 165 : else if (STRNCMP(recoded, "COMPOUNDBEGIN") == 0)
1226 0 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDBEGIN"),
1227 : FF_COMPOUNDBEGIN);
1228 165 : else if (STRNCMP(recoded, "COMPOUNDLAST") == 0)
1229 0 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDLAST"),
1230 : FF_COMPOUNDLAST);
1231 : /* COMPOUNDLAST and COMPOUNDEND are synonyms */
1232 165 : else if (STRNCMP(recoded, "COMPOUNDEND") == 0)
1233 0 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDEND"),
1234 : FF_COMPOUNDLAST);
1235 165 : else if (STRNCMP(recoded, "COMPOUNDMIDDLE") == 0)
1236 0 : addCompoundAffixFlagValue(Conf, recoded + strlen("COMPOUNDMIDDLE"),
1237 : FF_COMPOUNDMIDDLE);
1238 165 : else if (STRNCMP(recoded, "ONLYINCOMPOUND") == 0)
1239 9 : addCompoundAffixFlagValue(Conf, recoded + strlen("ONLYINCOMPOUND"),
1240 : FF_COMPOUNDONLY);
1241 156 : else if (STRNCMP(recoded, "COMPOUNDPERMITFLAG") == 0)
1242 0 : addCompoundAffixFlagValue(Conf,
1243 : recoded + strlen("COMPOUNDPERMITFLAG"),
1244 : FF_COMPOUNDPERMITFLAG);
1245 156 : else if (STRNCMP(recoded, "COMPOUNDFORBIDFLAG") == 0)
1246 0 : addCompoundAffixFlagValue(Conf,
1247 : recoded + strlen("COMPOUNDFORBIDFLAG"),
1248 : FF_COMPOUNDFORBIDFLAG);
1249 156 : else if (STRNCMP(recoded, "FLAG") == 0)
1250 : {
1251 6 : char *s = recoded + strlen("FLAG");
1252 :
1253 18 : while (*s && t_isspace(s))
1254 6 : s += pg_mblen(s);
1255 :
1256 6 : if (*s)
1257 : {
1258 6 : if (STRNCMP(s, "long") == 0)
1259 3 : Conf->flagMode = FM_LONG;
1260 3 : else if (STRNCMP(s, "num") == 0)
1261 3 : Conf->flagMode = FM_NUM;
1262 0 : else if (STRNCMP(s, "default") != 0)
1263 0 : ereport(ERROR,
1264 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1265 : errmsg("Ispell dictionary supports only "
1266 : "\"default\", \"long\", "
1267 : "and \"num\" flag values")));
1268 : }
1269 : }
1270 :
1271 174 : pfree(recoded);
1272 : }
1273 9 : tsearch_readline_end(&trst);
1274 :
1275 9 : if (Conf->nCompoundAffixFlag > 1)
1276 9 : qsort((void *) Conf->CompoundAffixFlags, Conf->nCompoundAffixFlag,
1277 : sizeof(CompoundAffixFlag), cmpcmdflag);
1278 :
1279 9 : if (!tsearch_readline_begin(&trst, filename))
1280 0 : ereport(ERROR,
1281 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1282 : errmsg("could not open affix file \"%s\": %m",
1283 : filename)));
1284 :
1285 264 : while ((recoded = tsearch_readline(&trst)) != NULL)
1286 : {
1287 : int fields_read;
1288 :
1289 246 : if (*recoded == '\0' || t_isspace(recoded) || t_iseq(recoded, '#'))
1290 : goto nextline;
1291 :
1292 174 : fields_read = parse_ooaffentry(recoded, type, sflag, find, repl, mask);
1293 :
1294 174 : if (ptype)
1295 165 : pfree(ptype);
1296 174 : ptype = lowerstr_ctx(Conf, type);
1297 :
1298 : /* First try to parse AF parameter (alias compression) */
1299 174 : if (STRNCMP(ptype, "af") == 0)
1300 : {
1301 : /* First line is the number of aliases */
1302 24 : if (!Conf->useFlagAliases)
1303 : {
1304 3 : Conf->useFlagAliases = true;
1305 3 : naffix = atoi(sflag);
1306 3 : if (naffix == 0)
1307 0 : ereport(ERROR,
1308 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1309 : errmsg("invalid number of flag vector aliases")));
1310 :
1311 : /* Also reserve place for empty flag set */
1312 3 : naffix++;
1313 :
1314 3 : Conf->AffixData = (char **) palloc0(naffix * sizeof(char *));
1315 3 : Conf->lenAffixData = Conf->nAffixData = naffix;
1316 :
1317 : /* Add empty flag set into AffixData */
1318 3 : Conf->AffixData[curaffix] = VoidString;
1319 3 : curaffix++;
1320 : }
1321 : /* Other lines is aliases */
1322 : else
1323 : {
1324 21 : if (curaffix < naffix)
1325 : {
1326 21 : Conf->AffixData[curaffix] = cpstrdup(Conf, sflag);
1327 21 : curaffix++;
1328 : }
1329 : }
1330 24 : goto nextline;
1331 : }
1332 : /* Else try to parse prefixes and suffixes */
1333 276 : if (fields_read < 4 ||
1334 162 : (STRNCMP(ptype, "sfx") != 0 && STRNCMP(ptype, "pfx") != 0))
1335 : goto nextline;
1336 :
1337 126 : sflaglen = strlen(sflag);
1338 126 : if (sflaglen == 0
1339 126 : || (sflaglen > 1 && Conf->flagMode == FM_CHAR)
1340 126 : || (sflaglen > 2 && Conf->flagMode == FM_LONG))
1341 : goto nextline;
1342 :
1343 : /*--------
1344 : * Affix header. For example:
1345 : * SFX \ N 1
1346 : *--------
1347 : */
1348 126 : if (fields_read == 4)
1349 : {
1350 63 : isSuffix = (STRNCMP(ptype, "sfx") == 0);
1351 63 : if (t_iseq(find, 'y') || t_iseq(find, 'Y'))
1352 45 : flagflags = FF_CROSSPRODUCT;
1353 : else
1354 18 : flagflags = 0;
1355 : }
1356 : /*--------
1357 : * Affix fields. For example:
1358 : * SFX \ 0 Y/L [^Y]
1359 : *--------
1360 : */
1361 : else
1362 : {
1363 : char *ptr;
1364 63 : int aflg = 0;
1365 :
1366 : /* Get flags after '/' (flags are case sensitive) */
1367 63 : if ((ptr = strchr(repl, '/')) != NULL)
1368 9 : aflg |= getCompoundAffixFlagValue(Conf,
1369 : getAffixFlagSet(Conf,
1370 : ptr + 1));
1371 : /* Get lowercased version of string before '/' */
1372 63 : prepl = lowerstr_ctx(Conf, repl);
1373 63 : if ((ptr = strchr(prepl, '/')) != NULL)
1374 9 : *ptr = '\0';
1375 63 : pfind = lowerstr_ctx(Conf, find);
1376 63 : pmask = lowerstr_ctx(Conf, mask);
1377 63 : if (t_iseq(find, '0'))
1378 54 : *pfind = '\0';
1379 63 : if (t_iseq(repl, '0'))
1380 0 : *prepl = '\0';
1381 :
1382 63 : NIAddAffix(Conf, sflag, flagflags | aflg, pmask, pfind, prepl,
1383 : isSuffix ? FF_SUFFIX : FF_PREFIX);
1384 63 : pfree(prepl);
1385 63 : pfree(pfind);
1386 63 : pfree(pmask);
1387 : }
1388 :
1389 : nextline:
1390 246 : pfree(recoded);
1391 : }
1392 :
1393 9 : tsearch_readline_end(&trst);
1394 9 : if (ptype)
1395 9 : pfree(ptype);
1396 9 : }
1397 :
1398 : /*
1399 : * import affixes
1400 : *
1401 : * Note caller must already have applied get_tsearch_config_filename
1402 : *
1403 : * This function is responsible for parsing ispell ("old format") affix files.
1404 : * If we realize that the file contains new-format commands, we pass off the
1405 : * work to NIImportOOAffixes(), which will re-read the whole file.
1406 : */
1407 : void
1408 13 : NIImportAffixes(IspellDict *Conf, const char *filename)
1409 : {
1410 13 : char *pstr = NULL;
1411 : char flag[BUFSIZ];
1412 : char mask[BUFSIZ];
1413 : char find[BUFSIZ];
1414 : char repl[BUFSIZ];
1415 : char *s;
1416 13 : bool suffixes = false;
1417 13 : bool prefixes = false;
1418 13 : char flagflags = 0;
1419 : tsearch_readline_state trst;
1420 13 : bool oldformat = false;
1421 13 : char *recoded = NULL;
1422 :
1423 13 : if (!tsearch_readline_begin(&trst, filename))
1424 0 : ereport(ERROR,
1425 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1426 : errmsg("could not open affix file \"%s\": %m",
1427 : filename)));
1428 :
1429 13 : Conf->usecompound = false;
1430 13 : Conf->useFlagAliases = false;
1431 13 : Conf->flagMode = FM_CHAR;
1432 :
1433 130 : while ((recoded = tsearch_readline(&trst)) != NULL)
1434 : {
1435 113 : pstr = lowerstr(recoded);
1436 :
1437 : /* Skip comments and empty lines */
1438 113 : if (*pstr == '#' || *pstr == '\n')
1439 : goto nextline;
1440 :
1441 77 : if (STRNCMP(pstr, "compoundwords") == 0)
1442 : {
1443 : /* Find case-insensitive L flag in non-lowercased string */
1444 4 : s = findchar2(recoded, 'l', 'L');
1445 4 : if (s)
1446 : {
1447 24 : while (*s && !t_isspace(s))
1448 16 : s += pg_mblen(s);
1449 12 : while (*s && t_isspace(s))
1450 4 : s += pg_mblen(s);
1451 :
1452 4 : if (*s && pg_mblen(s) == 1)
1453 : {
1454 4 : addCompoundAffixFlagValue(Conf, s, FF_COMPOUNDFLAG);
1455 4 : Conf->usecompound = true;
1456 : }
1457 4 : oldformat = true;
1458 4 : goto nextline;
1459 : }
1460 : }
1461 73 : if (STRNCMP(pstr, "suffixes") == 0)
1462 : {
1463 4 : suffixes = true;
1464 4 : prefixes = false;
1465 4 : oldformat = true;
1466 4 : goto nextline;
1467 : }
1468 69 : if (STRNCMP(pstr, "prefixes") == 0)
1469 : {
1470 4 : suffixes = false;
1471 4 : prefixes = true;
1472 4 : oldformat = true;
1473 4 : goto nextline;
1474 : }
1475 65 : if (STRNCMP(pstr, "flag") == 0)
1476 : {
1477 34 : s = recoded + 4; /* we need non-lowercased string */
1478 34 : flagflags = 0;
1479 :
1480 102 : while (*s && t_isspace(s))
1481 34 : s += pg_mblen(s);
1482 :
1483 34 : if (*s == '*')
1484 : {
1485 20 : flagflags |= FF_CROSSPRODUCT;
1486 20 : s++;
1487 : }
1488 14 : else if (*s == '~')
1489 : {
1490 4 : flagflags |= FF_COMPOUNDONLY;
1491 4 : s++;
1492 : }
1493 :
1494 34 : if (*s == '\\')
1495 4 : s++;
1496 :
1497 : /*
1498 : * An old-format flag is a single ASCII character; we expect it to
1499 : * be followed by EOL, whitespace, or ':'. Otherwise this is a
1500 : * new-format flag command.
1501 : */
1502 34 : if (*s && pg_mblen(s) == 1)
1503 : {
1504 34 : COPYCHAR(flag, s);
1505 34 : flag[1] = '\0';
1506 :
1507 34 : s++;
1508 40 : if (*s == '\0' || *s == '#' || *s == '\n' || *s == ':' ||
1509 6 : t_isspace(s))
1510 : {
1511 28 : oldformat = true;
1512 28 : goto nextline;
1513 : }
1514 : }
1515 6 : goto isnewformat;
1516 : }
1517 59 : if (STRNCMP(recoded, "COMPOUNDFLAG") == 0 ||
1518 56 : STRNCMP(recoded, "COMPOUNDMIN") == 0 ||
1519 56 : STRNCMP(recoded, "PFX") == 0 ||
1520 28 : STRNCMP(recoded, "SFX") == 0)
1521 : goto isnewformat;
1522 :
1523 28 : if ((!suffixes) && (!prefixes))
1524 0 : goto nextline;
1525 :
1526 28 : if (!parse_affentry(pstr, mask, find, repl))
1527 0 : goto nextline;
1528 :
1529 28 : NIAddAffix(Conf, flag, flagflags, mask, find, repl, suffixes ? FF_SUFFIX : FF_PREFIX);
1530 :
1531 : nextline:
1532 104 : pfree(recoded);
1533 104 : pfree(pstr);
1534 : }
1535 4 : tsearch_readline_end(&trst);
1536 17 : return;
1537 :
1538 : isnewformat:
1539 9 : if (oldformat)
1540 0 : ereport(ERROR,
1541 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1542 : errmsg("affix file contains both old-style and new-style commands")));
1543 9 : tsearch_readline_end(&trst);
1544 :
1545 9 : NIImportOOAffixes(Conf, filename);
1546 : }
1547 :
1548 : /*
1549 : * Merges two affix flag sets and stores a new affix flag set into
1550 : * Conf->AffixData.
1551 : *
1552 : * Returns index of a new affix flag set.
1553 : */
1554 : static int
1555 0 : MergeAffix(IspellDict *Conf, int a1, int a2)
1556 : {
1557 : char **ptr;
1558 :
1559 : /* Do not merge affix flags if one of affix flags is empty */
1560 0 : if (*Conf->AffixData[a1] == '\0')
1561 0 : return a2;
1562 0 : else if (*Conf->AffixData[a2] == '\0')
1563 0 : return a1;
1564 :
1565 0 : while (Conf->nAffixData + 1 >= Conf->lenAffixData)
1566 : {
1567 0 : Conf->lenAffixData *= 2;
1568 0 : Conf->AffixData = (char **) repalloc(Conf->AffixData,
1569 0 : sizeof(char *) * Conf->lenAffixData);
1570 : }
1571 :
1572 0 : ptr = Conf->AffixData + Conf->nAffixData;
1573 0 : if (Conf->flagMode == FM_NUM)
1574 : {
1575 0 : *ptr = cpalloc(strlen(Conf->AffixData[a1]) +
1576 : strlen(Conf->AffixData[a2]) +
1577 : 1 /* comma */ + 1 /* \0 */ );
1578 0 : sprintf(*ptr, "%s,%s", Conf->AffixData[a1], Conf->AffixData[a2]);
1579 : }
1580 : else
1581 : {
1582 0 : *ptr = cpalloc(strlen(Conf->AffixData[a1]) +
1583 : strlen(Conf->AffixData[a2]) +
1584 : 1 /* \0 */ );
1585 0 : sprintf(*ptr, "%s%s", Conf->AffixData[a1], Conf->AffixData[a2]);
1586 : }
1587 0 : ptr++;
1588 0 : *ptr = NULL;
1589 0 : Conf->nAffixData++;
1590 :
1591 0 : return Conf->nAffixData - 1;
1592 : }
1593 :
1594 : /*
1595 : * Returns a set of affix parameters which correspondence to the set of affix
1596 : * flags with the given index.
1597 : */
1598 : static uint32
1599 104 : makeCompoundFlags(IspellDict *Conf, int affix)
1600 : {
1601 104 : char *str = Conf->AffixData[affix];
1602 :
1603 104 : return (getCompoundAffixFlagValue(Conf, str) & FF_COMPOUNDFLAGMASK);
1604 : }
1605 :
1606 : /*
1607 : * Makes a prefix tree for the given level.
1608 : *
1609 : * Conf: current dictionary.
1610 : * low: lower index of the Conf->Spell array.
1611 : * high: upper index of the Conf->Spell array.
1612 : * level: current prefix tree level.
1613 : */
1614 : static SPNode *
1615 468 : mkSPNode(IspellDict *Conf, int low, int high, int level)
1616 : {
1617 : int i;
1618 468 : int nchar = 0;
1619 468 : char lastchar = '\0';
1620 : SPNode *rs;
1621 : SPNodeData *data;
1622 468 : int lownew = low;
1623 :
1624 1521 : for (i = low; i < high; i++)
1625 1053 : if (Conf->Spell[i]->p.d.len > level && lastchar != Conf->Spell[i]->word[level])
1626 : {
1627 455 : nchar++;
1628 455 : lastchar = Conf->Spell[i]->word[level];
1629 : }
1630 :
1631 468 : if (!nchar)
1632 65 : return NULL;
1633 :
1634 403 : rs = (SPNode *) cpalloc0(SPNHDRSZ + nchar * sizeof(SPNodeData));
1635 403 : rs->length = nchar;
1636 403 : data = rs->data;
1637 :
1638 403 : lastchar = '\0';
1639 1352 : for (i = low; i < high; i++)
1640 949 : if (Conf->Spell[i]->p.d.len > level)
1641 : {
1642 676 : if (lastchar != Conf->Spell[i]->word[level])
1643 : {
1644 455 : if (lastchar)
1645 : {
1646 : /* Next level of the prefix tree */
1647 52 : data->node = mkSPNode(Conf, lownew, i, level + 1);
1648 52 : lownew = i;
1649 52 : data++;
1650 : }
1651 455 : lastchar = Conf->Spell[i]->word[level];
1652 : }
1653 676 : data->val = ((uint8 *) (Conf->Spell[i]->word))[level];
1654 676 : if (Conf->Spell[i]->p.d.len == level + 1)
1655 : {
1656 104 : bool clearCompoundOnly = false;
1657 :
1658 104 : if (data->isword && data->affix != Conf->Spell[i]->p.d.affix)
1659 : {
1660 : /*
1661 : * MergeAffix called a few times. If one of word is
1662 : * allowed to be in compound word and another isn't, then
1663 : * clear FF_COMPOUNDONLY flag.
1664 : */
1665 :
1666 0 : clearCompoundOnly = (FF_COMPOUNDONLY & data->compoundflag
1667 0 : & makeCompoundFlags(Conf, Conf->Spell[i]->p.d.affix))
1668 0 : ? false : true;
1669 0 : data->affix = MergeAffix(Conf, data->affix, Conf->Spell[i]->p.d.affix);
1670 : }
1671 : else
1672 104 : data->affix = Conf->Spell[i]->p.d.affix;
1673 104 : data->isword = 1;
1674 :
1675 104 : data->compoundflag = makeCompoundFlags(Conf, data->affix);
1676 :
1677 104 : if ((data->compoundflag & FF_COMPOUNDONLY) &&
1678 0 : (data->compoundflag & FF_COMPOUNDFLAG) == 0)
1679 0 : data->compoundflag |= FF_COMPOUNDFLAG;
1680 :
1681 104 : if (clearCompoundOnly)
1682 0 : data->compoundflag &= ~FF_COMPOUNDONLY;
1683 : }
1684 : }
1685 :
1686 : /* Next level of the prefix tree */
1687 403 : data->node = mkSPNode(Conf, lownew, high, level + 1);
1688 :
1689 403 : return rs;
1690 : }
1691 :
1692 : /*
1693 : * Builds the Conf->Dictionary tree and AffixData from the imported dictionary
1694 : * and affixes.
1695 : */
1696 : void
1697 13 : NISortDictionary(IspellDict *Conf)
1698 : {
1699 : int i;
1700 13 : int naffix = 0;
1701 : int curaffix;
1702 :
1703 : /* compress affixes */
1704 :
1705 : /*
1706 : * If we use flag aliases then we need to use Conf->AffixData filled in
1707 : * the NIImportOOAffixes().
1708 : */
1709 13 : if (Conf->useFlagAliases)
1710 : {
1711 27 : for (i = 0; i < Conf->nspell; i++)
1712 : {
1713 : char *end;
1714 :
1715 24 : if (*Conf->Spell[i]->p.flag != '\0')
1716 : {
1717 21 : curaffix = strtol(Conf->Spell[i]->p.flag, &end, 10);
1718 21 : if (Conf->Spell[i]->p.flag == end || errno == ERANGE)
1719 0 : ereport(ERROR,
1720 : (errcode(ERRCODE_CONFIG_FILE_ERROR),
1721 : errmsg("invalid affix alias \"%s\"",
1722 : Conf->Spell[i]->p.flag)));
1723 : }
1724 : else
1725 : {
1726 : /*
1727 : * If Conf->Spell[i]->p.flag is empty, then get empty value of
1728 : * Conf->AffixData (0 index).
1729 : */
1730 3 : curaffix = 0;
1731 : }
1732 :
1733 24 : Conf->Spell[i]->p.d.affix = curaffix;
1734 24 : Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
1735 : }
1736 : }
1737 : /* Otherwise fill Conf->AffixData here */
1738 : else
1739 : {
1740 : /* Count the number of different flags used in the dictionary */
1741 10 : qsort((void *) Conf->Spell, Conf->nspell, sizeof(SPELL *),
1742 : cmpspellaffix);
1743 :
1744 10 : naffix = 0;
1745 90 : for (i = 0; i < Conf->nspell; i++)
1746 : {
1747 80 : if (i == 0
1748 70 : || strcmp(Conf->Spell[i]->p.flag, Conf->Spell[i - 1]->p.flag))
1749 70 : naffix++;
1750 : }
1751 :
1752 : /*
1753 : * Fill in Conf->AffixData with the affixes that were used in the
1754 : * dictionary. Replace textual flag-field of Conf->Spell entries with
1755 : * indexes into Conf->AffixData array.
1756 : */
1757 10 : Conf->AffixData = (char **) palloc0(naffix * sizeof(char *));
1758 :
1759 10 : curaffix = -1;
1760 90 : for (i = 0; i < Conf->nspell; i++)
1761 : {
1762 80 : if (i == 0
1763 70 : || strcmp(Conf->Spell[i]->p.flag, Conf->AffixData[curaffix]))
1764 : {
1765 70 : curaffix++;
1766 70 : Assert(curaffix < naffix);
1767 140 : Conf->AffixData[curaffix] = cpstrdup(Conf,
1768 70 : Conf->Spell[i]->p.flag);
1769 : }
1770 :
1771 80 : Conf->Spell[i]->p.d.affix = curaffix;
1772 80 : Conf->Spell[i]->p.d.len = strlen(Conf->Spell[i]->word);
1773 : }
1774 :
1775 10 : Conf->lenAffixData = Conf->nAffixData = naffix;
1776 : }
1777 :
1778 : /* Start build a prefix tree */
1779 13 : qsort((void *) Conf->Spell, Conf->nspell, sizeof(SPELL *), cmpspell);
1780 13 : Conf->Dictionary = mkSPNode(Conf, 0, Conf->nspell, 0);
1781 13 : }
1782 :
1783 : /*
1784 : * Makes a prefix tree for the given level using the repl string of an affix
1785 : * rule. Affixes with empty replace string do not include in the prefix tree.
1786 : * This affixes are included by mkVoidAffix().
1787 : *
1788 : * Conf: current dictionary.
1789 : * low: lower index of the Conf->Affix array.
1790 : * high: upper index of the Conf->Affix array.
1791 : * level: current prefix tree level.
1792 : * type: FF_SUFFIX or FF_PREFIX.
1793 : */
1794 : static AffixNode *
1795 208 : mkANode(IspellDict *Conf, int low, int high, int level, int type)
1796 : {
1797 : int i;
1798 208 : int nchar = 0;
1799 208 : uint8 lastchar = '\0';
1800 : AffixNode *rs;
1801 : AffixNodeData *data;
1802 208 : int lownew = low;
1803 : int naff;
1804 : AFFIX **aff;
1805 :
1806 533 : for (i = low; i < high; i++)
1807 325 : if (Conf->Affix[i].replen > level && lastchar != GETCHAR(Conf->Affix + i, level, type))
1808 : {
1809 182 : nchar++;
1810 182 : lastchar = GETCHAR(Conf->Affix + i, level, type);
1811 : }
1812 :
1813 208 : if (!nchar)
1814 78 : return NULL;
1815 :
1816 130 : aff = (AFFIX **) tmpalloc(sizeof(AFFIX *) * (high - low + 1));
1817 130 : naff = 0;
1818 :
1819 130 : rs = (AffixNode *) cpalloc0(ANHRDSZ + nchar * sizeof(AffixNodeData));
1820 130 : rs->length = nchar;
1821 130 : data = rs->data;
1822 :
1823 130 : lastchar = '\0';
1824 364 : for (i = low; i < high; i++)
1825 234 : if (Conf->Affix[i].replen > level)
1826 : {
1827 208 : if (lastchar != GETCHAR(Conf->Affix + i, level, type))
1828 : {
1829 182 : if (lastchar)
1830 : {
1831 : /* Next level of the prefix tree */
1832 52 : data->node = mkANode(Conf, lownew, i, level + 1, type);
1833 52 : if (naff)
1834 : {
1835 13 : data->naff = naff;
1836 13 : data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
1837 13 : memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
1838 13 : naff = 0;
1839 : }
1840 52 : data++;
1841 52 : lownew = i;
1842 : }
1843 182 : lastchar = GETCHAR(Conf->Affix + i, level, type);
1844 : }
1845 208 : data->val = GETCHAR(Conf->Affix + i, level, type);
1846 208 : if (Conf->Affix[i].replen == level + 1)
1847 : { /* affix stopped */
1848 91 : aff[naff++] = Conf->Affix + i;
1849 : }
1850 : }
1851 :
1852 : /* Next level of the prefix tree */
1853 130 : data->node = mkANode(Conf, lownew, high, level + 1, type);
1854 130 : if (naff)
1855 : {
1856 78 : data->naff = naff;
1857 78 : data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * naff);
1858 78 : memcpy(data->aff, aff, sizeof(AFFIX *) * naff);
1859 78 : naff = 0;
1860 : }
1861 :
1862 130 : pfree(aff);
1863 :
1864 130 : return rs;
1865 : }
1866 :
1867 : /*
1868 : * Makes the root void node in the prefix tree. The root void node is created
1869 : * for affixes which have empty replace string ("repl" field).
1870 : */
1871 : static void
1872 26 : mkVoidAffix(IspellDict *Conf, bool issuffix, int startsuffix)
1873 : {
1874 : int i,
1875 26 : cnt = 0;
1876 26 : int start = (issuffix) ? startsuffix : 0;
1877 26 : int end = (issuffix) ? Conf->naffixes : startsuffix;
1878 26 : AffixNode *Affix = (AffixNode *) palloc0(ANHRDSZ + sizeof(AffixNodeData));
1879 :
1880 26 : Affix->length = 1;
1881 26 : Affix->isvoid = 1;
1882 :
1883 26 : if (issuffix)
1884 : {
1885 13 : Affix->data->node = Conf->Suffix;
1886 13 : Conf->Suffix = Affix;
1887 : }
1888 : else
1889 : {
1890 13 : Affix->data->node = Conf->Prefix;
1891 13 : Conf->Prefix = Affix;
1892 : }
1893 :
1894 : /* Count affixes with empty replace string */
1895 117 : for (i = start; i < end; i++)
1896 91 : if (Conf->Affix[i].replen == 0)
1897 0 : cnt++;
1898 :
1899 : /* There is not affixes with empty replace string */
1900 26 : if (cnt == 0)
1901 52 : return;
1902 :
1903 0 : Affix->data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * cnt);
1904 0 : Affix->data->naff = (uint32) cnt;
1905 :
1906 0 : cnt = 0;
1907 0 : for (i = start; i < end; i++)
1908 0 : if (Conf->Affix[i].replen == 0)
1909 : {
1910 0 : Affix->data->aff[cnt] = Conf->Affix + i;
1911 0 : cnt++;
1912 : }
1913 : }
1914 :
1915 : /*
1916 : * Checks if the affixflag is used by dictionary. Conf->AffixData does not
1917 : * contain affixflag if this flag is not used actually by the .dict file.
1918 : *
1919 : * Conf: current dictionary.
1920 : * affixflag: affix flag.
1921 : *
1922 : * Returns true if the Conf->AffixData array contains affixflag, otherwise
1923 : * returns false.
1924 : */
1925 : static bool
1926 13 : isAffixInUse(IspellDict *Conf, char *affixflag)
1927 : {
1928 : int i;
1929 :
1930 71 : for (i = 0; i < Conf->nAffixData; i++)
1931 71 : if (IsAffixFlagInUse(Conf, i, affixflag))
1932 13 : return true;
1933 :
1934 0 : return false;
1935 : }
1936 :
1937 : /*
1938 : * Builds Conf->Prefix and Conf->Suffix trees from the imported affixes.
1939 : */
1940 : void
1941 13 : NISortAffixes(IspellDict *Conf)
1942 : {
1943 : AFFIX *Affix;
1944 : size_t i;
1945 : CMPDAffix *ptr;
1946 13 : int firstsuffix = Conf->naffixes;
1947 :
1948 13 : if (Conf->naffixes == 0)
1949 13 : return;
1950 :
1951 : /* Store compound affixes in the Conf->CompoundAffix array */
1952 13 : if (Conf->naffixes > 1)
1953 13 : qsort((void *) Conf->Affix, Conf->naffixes, sizeof(AFFIX), cmpaffix);
1954 13 : Conf->CompoundAffix = ptr = (CMPDAffix *) palloc(sizeof(CMPDAffix) * Conf->naffixes);
1955 13 : ptr->affix = NULL;
1956 :
1957 104 : for (i = 0; i < Conf->naffixes; i++)
1958 : {
1959 91 : Affix = &(((AFFIX *) Conf->Affix)[i]);
1960 91 : if (Affix->type == FF_SUFFIX && i < firstsuffix)
1961 13 : firstsuffix = i;
1962 :
1963 104 : if ((Affix->flagflags & FF_COMPOUNDFLAG) && Affix->replen > 0 &&
1964 13 : isAffixInUse(Conf, Affix->flag))
1965 : {
1966 13 : if (ptr == Conf->CompoundAffix ||
1967 0 : ptr->issuffix != (ptr - 1)->issuffix ||
1968 0 : strbncmp((const unsigned char *) (ptr - 1)->affix,
1969 0 : (const unsigned char *) Affix->repl,
1970 0 : (ptr - 1)->len))
1971 : {
1972 : /* leave only unique and minimals suffixes */
1973 13 : ptr->affix = Affix->repl;
1974 13 : ptr->len = Affix->replen;
1975 13 : ptr->issuffix = (Affix->type == FF_SUFFIX);
1976 13 : ptr++;
1977 : }
1978 : }
1979 : }
1980 13 : ptr->affix = NULL;
1981 13 : Conf->CompoundAffix = (CMPDAffix *) repalloc(Conf->CompoundAffix, sizeof(CMPDAffix) * (ptr - Conf->CompoundAffix + 1));
1982 :
1983 : /* Start build a prefix tree */
1984 13 : Conf->Prefix = mkANode(Conf, 0, firstsuffix, 0, FF_PREFIX);
1985 13 : Conf->Suffix = mkANode(Conf, firstsuffix, Conf->naffixes, 0, FF_SUFFIX);
1986 13 : mkVoidAffix(Conf, true, firstsuffix);
1987 13 : mkVoidAffix(Conf, false, firstsuffix);
1988 : }
1989 :
1990 : static AffixNodeData *
1991 608 : FindAffixes(AffixNode *node, const char *word, int wrdlen, int *level, int type)
1992 : {
1993 : AffixNodeData *StopLow,
1994 : *StopHigh,
1995 : *StopMiddle;
1996 : uint8 symbol;
1997 :
1998 608 : if (node->isvoid)
1999 : { /* search void affixes */
2000 572 : if (node->data->naff)
2001 0 : return node->data;
2002 572 : node = node->data->node;
2003 : }
2004 :
2005 1368 : while (node && *level < wrdlen)
2006 : {
2007 756 : StopLow = node->data;
2008 756 : StopHigh = node->data + node->length;
2009 2430 : while (StopLow < StopHigh)
2010 : {
2011 1252 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
2012 1252 : symbol = GETWCHAR(word, wrdlen, *level, type);
2013 :
2014 1252 : if (StopMiddle->val == symbol)
2015 : {
2016 334 : (*level)++;
2017 334 : if (StopMiddle->naff)
2018 182 : return StopMiddle;
2019 152 : node = StopMiddle->node;
2020 152 : break;
2021 : }
2022 918 : else if (StopMiddle->val < symbol)
2023 242 : StopLow = StopMiddle + 1;
2024 : else
2025 676 : StopHigh = StopMiddle;
2026 : }
2027 574 : if (StopLow >= StopHigh)
2028 422 : break;
2029 : }
2030 426 : return NULL;
2031 : }
2032 :
2033 : static char *
2034 182 : CheckAffix(const char *word, size_t len, AFFIX *Affix, int flagflags, char *newword, int *baselen)
2035 : {
2036 : /*
2037 : * Check compound allow flags
2038 : */
2039 :
2040 182 : if (flagflags == 0)
2041 : {
2042 154 : if (Affix->flagflags & FF_COMPOUNDONLY)
2043 6 : return NULL;
2044 : }
2045 28 : else if (flagflags & FF_COMPOUNDBEGIN)
2046 : {
2047 0 : if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
2048 0 : return NULL;
2049 0 : if ((Affix->flagflags & FF_COMPOUNDBEGIN) == 0)
2050 0 : if (Affix->type == FF_SUFFIX)
2051 0 : return NULL;
2052 : }
2053 28 : else if (flagflags & FF_COMPOUNDMIDDLE)
2054 : {
2055 48 : if ((Affix->flagflags & FF_COMPOUNDMIDDLE) == 0 ||
2056 24 : (Affix->flagflags & FF_COMPOUNDFORBIDFLAG))
2057 0 : return NULL;
2058 : }
2059 4 : else if (flagflags & FF_COMPOUNDLAST)
2060 : {
2061 4 : if (Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
2062 0 : return NULL;
2063 4 : if ((Affix->flagflags & FF_COMPOUNDLAST) == 0)
2064 4 : if (Affix->type == FF_PREFIX)
2065 0 : return NULL;
2066 : }
2067 :
2068 : /*
2069 : * make replace pattern of affix
2070 : */
2071 176 : if (Affix->type == FF_SUFFIX)
2072 : {
2073 104 : strcpy(newword, word);
2074 104 : strcpy(newword + len - Affix->replen, Affix->find);
2075 104 : if (baselen) /* store length of non-changed part of word */
2076 104 : *baselen = len - Affix->replen;
2077 : }
2078 : else
2079 : {
2080 : /*
2081 : * if prefix is an all non-changed part's length then all word
2082 : * contains only prefix and suffix, so out
2083 : */
2084 72 : if (baselen && *baselen + strlen(Affix->find) <= Affix->replen)
2085 0 : return NULL;
2086 72 : strcpy(newword, Affix->find);
2087 72 : strcat(newword, word + Affix->replen);
2088 : }
2089 :
2090 : /*
2091 : * check resulting word
2092 : */
2093 176 : if (Affix->issimple)
2094 72 : return newword;
2095 104 : else if (Affix->isregis)
2096 : {
2097 104 : if (RS_execute(&(Affix->reg.regis), newword))
2098 100 : return newword;
2099 : }
2100 : else
2101 : {
2102 : int err;
2103 : pg_wchar *data;
2104 : size_t data_len;
2105 : int newword_len;
2106 :
2107 : /* Convert data string to wide characters */
2108 0 : newword_len = strlen(newword);
2109 0 : data = (pg_wchar *) palloc((newword_len + 1) * sizeof(pg_wchar));
2110 0 : data_len = pg_mb2wchar_with_len(newword, data, newword_len);
2111 :
2112 0 : if (!(err = pg_regexec(&(Affix->reg.regex), data, data_len, 0, NULL, 0, NULL, 0)))
2113 : {
2114 0 : pfree(data);
2115 0 : return newword;
2116 : }
2117 0 : pfree(data);
2118 : }
2119 :
2120 4 : return NULL;
2121 : }
2122 :
2123 : static int
2124 86 : addToResult(char **forms, char **cur, char *word)
2125 : {
2126 86 : if (cur - forms >= MAX_NORM - 1)
2127 0 : return 0;
2128 86 : if (forms == cur || strcmp(word, *(cur - 1)) != 0)
2129 : {
2130 86 : *cur = pstrdup(word);
2131 86 : *(cur + 1) = NULL;
2132 86 : return 1;
2133 : }
2134 :
2135 0 : return 0;
2136 : }
2137 :
2138 : static char **
2139 236 : NormalizeSubWord(IspellDict *Conf, char *word, int flag)
2140 : {
2141 236 : AffixNodeData *suffix = NULL,
2142 236 : *prefix = NULL;
2143 236 : int slevel = 0,
2144 236 : plevel = 0;
2145 236 : int wrdlen = strlen(word),
2146 : swrdlen;
2147 : char **forms;
2148 : char **cur;
2149 236 : char newword[2 * MAXNORMLEN] = "";
2150 236 : char pnewword[2 * MAXNORMLEN] = "";
2151 236 : AffixNode *snode = Conf->Suffix,
2152 : *pnode;
2153 : int i,
2154 : j;
2155 :
2156 236 : if (wrdlen > MAXNORMLEN)
2157 0 : return NULL;
2158 236 : cur = forms = (char **) palloc(MAX_NORM * sizeof(char *));
2159 236 : *cur = NULL;
2160 :
2161 :
2162 : /* Check that the word itself is normal form */
2163 236 : if (FindWord(Conf, word, VoidString, flag))
2164 : {
2165 76 : *cur = pstrdup(word);
2166 76 : cur++;
2167 76 : *cur = NULL;
2168 : }
2169 :
2170 : /* Find all other NORMAL forms of the 'word' (check only prefix) */
2171 236 : pnode = Conf->Prefix;
2172 236 : plevel = 0;
2173 504 : while (pnode)
2174 : {
2175 236 : prefix = FindAffixes(pnode, word, wrdlen, &plevel, FF_PREFIX);
2176 236 : if (!prefix)
2177 204 : break;
2178 64 : for (j = 0; j < prefix->naff; j++)
2179 : {
2180 32 : if (CheckAffix(word, wrdlen, prefix->aff[j], flag, newword, NULL))
2181 : {
2182 : /* prefix success */
2183 32 : if (FindWord(Conf, newword, prefix->aff[j]->flag, flag))
2184 8 : cur += addToResult(forms, cur, newword);
2185 : }
2186 : }
2187 32 : pnode = prefix->node;
2188 : }
2189 :
2190 : /*
2191 : * Find all other NORMAL forms of the 'word' (check suffix and then
2192 : * prefix)
2193 : */
2194 582 : while (snode)
2195 : {
2196 272 : int baselen = 0;
2197 :
2198 : /* find possible suffix */
2199 272 : suffix = FindAffixes(snode, word, wrdlen, &slevel, FF_SUFFIX);
2200 272 : if (!suffix)
2201 162 : break;
2202 : /* foreach suffix check affix */
2203 220 : for (i = 0; i < suffix->naff; i++)
2204 : {
2205 110 : if (CheckAffix(word, wrdlen, suffix->aff[i], flag, newword, &baselen))
2206 : {
2207 : /* suffix success */
2208 100 : if (FindWord(Conf, newword, suffix->aff[i]->flag, flag))
2209 42 : cur += addToResult(forms, cur, newword);
2210 :
2211 : /* now we will look changed word with prefixes */
2212 100 : pnode = Conf->Prefix;
2213 100 : plevel = 0;
2214 100 : swrdlen = strlen(newword);
2215 240 : while (pnode)
2216 : {
2217 100 : prefix = FindAffixes(pnode, newword, swrdlen, &plevel, FF_PREFIX);
2218 100 : if (!prefix)
2219 60 : break;
2220 80 : for (j = 0; j < prefix->naff; j++)
2221 : {
2222 40 : if (CheckAffix(newword, swrdlen, prefix->aff[j], flag, pnewword, &baselen))
2223 : {
2224 : /* prefix success */
2225 80 : char *ff = (prefix->aff[j]->flagflags & suffix->aff[i]->flagflags & FF_CROSSPRODUCT) ?
2226 40 : VoidString : prefix->aff[j]->flag;
2227 :
2228 40 : if (FindWord(Conf, pnewword, ff, flag))
2229 36 : cur += addToResult(forms, cur, pnewword);
2230 : }
2231 : }
2232 40 : pnode = prefix->node;
2233 : }
2234 : }
2235 : }
2236 :
2237 110 : snode = suffix->node;
2238 : }
2239 :
2240 236 : if (cur == forms)
2241 : {
2242 102 : pfree(forms);
2243 102 : return (NULL);
2244 : }
2245 134 : return (forms);
2246 : }
2247 :
2248 : typedef struct SplitVar
2249 : {
2250 : int nstem;
2251 : int lenstem;
2252 : char **stem;
2253 : struct SplitVar *next;
2254 : } SplitVar;
2255 :
2256 : static int
2257 968 : CheckCompoundAffixes(CMPDAffix **ptr, char *word, int len, bool CheckInPlace)
2258 : {
2259 : bool issuffix;
2260 :
2261 : /* in case CompoundAffix is null: */
2262 968 : if (*ptr == NULL)
2263 0 : return -1;
2264 :
2265 968 : if (CheckInPlace)
2266 : {
2267 2482 : while ((*ptr)->affix)
2268 : {
2269 824 : if (len > (*ptr)->len && strncmp((*ptr)->affix, word, (*ptr)->len) == 0)
2270 : {
2271 10 : len = (*ptr)->len;
2272 10 : issuffix = (*ptr)->issuffix;
2273 10 : (*ptr)++;
2274 10 : return (issuffix) ? len : 0;
2275 : }
2276 814 : (*ptr)++;
2277 : }
2278 : }
2279 : else
2280 : {
2281 : char *affbegin;
2282 :
2283 374 : while ((*ptr)->affix)
2284 : {
2285 120 : if (len > (*ptr)->len && (affbegin = strstr(word, (*ptr)->affix)) != NULL)
2286 : {
2287 14 : len = (*ptr)->len + (affbegin - word);
2288 14 : issuffix = (*ptr)->issuffix;
2289 14 : (*ptr)++;
2290 14 : return (issuffix) ? len : 0;
2291 : }
2292 106 : (*ptr)++;
2293 : }
2294 : }
2295 944 : return -1;
2296 : }
2297 :
2298 : static SplitVar *
2299 226 : CopyVar(SplitVar *s, int makedup)
2300 : {
2301 226 : SplitVar *v = (SplitVar *) palloc(sizeof(SplitVar));
2302 :
2303 226 : v->next = NULL;
2304 226 : if (s)
2305 : {
2306 : int i;
2307 :
2308 106 : v->lenstem = s->lenstem;
2309 106 : v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
2310 106 : v->nstem = s->nstem;
2311 162 : for (i = 0; i < s->nstem; i++)
2312 56 : v->stem[i] = (makedup) ? pstrdup(s->stem[i]) : s->stem[i];
2313 : }
2314 : else
2315 : {
2316 120 : v->lenstem = 16;
2317 120 : v->stem = (char **) palloc(sizeof(char *) * v->lenstem);
2318 120 : v->nstem = 0;
2319 : }
2320 226 : return v;
2321 : }
2322 :
2323 : static void
2324 304 : AddStem(SplitVar *v, char *word)
2325 : {
2326 304 : if (v->nstem >= v->lenstem)
2327 : {
2328 0 : v->lenstem *= 2;
2329 0 : v->stem = (char **) repalloc(v->stem, sizeof(char *) * v->lenstem);
2330 : }
2331 :
2332 304 : v->stem[v->nstem] = word;
2333 304 : v->nstem++;
2334 304 : }
2335 :
2336 : static SplitVar *
2337 212 : SplitToVariants(IspellDict *Conf, SPNode *snode, SplitVar *orig, char *word, int wordlen, int startpos, int minpos)
2338 : {
2339 212 : SplitVar *var = NULL;
2340 : SPNodeData *StopLow,
2341 : *StopHigh,
2342 212 : *StopMiddle = NULL;
2343 212 : SPNode *node = (snode) ? snode : Conf->Dictionary;
2344 212 : int level = (snode) ? minpos : startpos; /* recursive
2345 : * minpos==level */
2346 : int lenaff;
2347 : CMPDAffix *caff;
2348 : char *notprobed;
2349 212 : int compoundflag = 0;
2350 :
2351 212 : notprobed = (char *) palloc(wordlen);
2352 212 : memset(notprobed, 1, wordlen);
2353 212 : var = CopyVar(orig, 1);
2354 :
2355 1410 : while (level < wordlen)
2356 : {
2357 : /* find word with epenthetic or/and compound affix */
2358 1156 : caff = Conf->CompoundAffix;
2359 2336 : while (level > startpos && (lenaff = CheckCompoundAffixes(&caff, word + level, wordlen - level, (node) ? true : false)) >= 0)
2360 : {
2361 : /*
2362 : * there is one of compound affixes, so check word for existings
2363 : */
2364 : char buf[MAXNORMLEN];
2365 : char **subres;
2366 :
2367 24 : lenaff = level - startpos + lenaff;
2368 :
2369 24 : if (!notprobed[startpos + lenaff - 1])
2370 0 : continue;
2371 :
2372 24 : if (level + lenaff - 1 <= minpos)
2373 0 : continue;
2374 :
2375 24 : if (lenaff >= MAXNORMLEN)
2376 0 : continue; /* skip too big value */
2377 24 : if (lenaff > 0)
2378 24 : memcpy(buf, word + startpos, lenaff);
2379 24 : buf[lenaff] = '\0';
2380 :
2381 24 : if (level == 0)
2382 0 : compoundflag = FF_COMPOUNDBEGIN;
2383 24 : else if (level == wordlen - 1)
2384 0 : compoundflag = FF_COMPOUNDLAST;
2385 : else
2386 24 : compoundflag = FF_COMPOUNDMIDDLE;
2387 24 : subres = NormalizeSubWord(Conf, buf, compoundflag);
2388 24 : if (subres)
2389 : {
2390 : /* Yes, it was a word from dictionary */
2391 14 : SplitVar *new = CopyVar(var, 0);
2392 14 : SplitVar *ptr = var;
2393 14 : char **sptr = subres;
2394 :
2395 14 : notprobed[startpos + lenaff - 1] = 0;
2396 :
2397 42 : while (*sptr)
2398 : {
2399 14 : AddStem(new, *sptr);
2400 14 : sptr++;
2401 : }
2402 14 : pfree(subres);
2403 :
2404 28 : while (ptr->next)
2405 0 : ptr = ptr->next;
2406 14 : ptr->next = SplitToVariants(Conf, NULL, new, word, wordlen, startpos + lenaff, startpos + lenaff);
2407 :
2408 14 : pfree(new->stem);
2409 14 : pfree(new);
2410 : }
2411 : }
2412 :
2413 1156 : if (!node)
2414 120 : break;
2415 :
2416 1036 : StopLow = node->data;
2417 1036 : StopHigh = node->data + node->length;
2418 2416 : while (StopLow < StopHigh)
2419 : {
2420 1278 : StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
2421 1278 : if (StopMiddle->val == ((uint8 *) (word))[level])
2422 934 : break;
2423 344 : else if (StopMiddle->val < ((uint8 *) (word))[level])
2424 144 : StopLow = StopMiddle + 1;
2425 : else
2426 200 : StopHigh = StopMiddle;
2427 : }
2428 :
2429 1036 : if (StopLow < StopHigh)
2430 : {
2431 934 : if (startpos == 0)
2432 526 : compoundflag = FF_COMPOUNDBEGIN;
2433 408 : else if (level == wordlen - 1)
2434 46 : compoundflag = FF_COMPOUNDLAST;
2435 : else
2436 362 : compoundflag = FF_COMPOUNDMIDDLE;
2437 :
2438 : /* find infinitive */
2439 1182 : if (StopMiddle->isword &&
2440 454 : (StopMiddle->compoundflag & compoundflag) &&
2441 206 : notprobed[level])
2442 : {
2443 : /* ok, we found full compoundallowed word */
2444 206 : if (level > minpos)
2445 : {
2446 : /* and its length more than minimal */
2447 128 : if (wordlen == level + 1)
2448 : {
2449 : /* well, it was last word */
2450 50 : AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
2451 50 : pfree(notprobed);
2452 50 : return var;
2453 : }
2454 : else
2455 : {
2456 : /* then we will search more big word at the same point */
2457 78 : SplitVar *ptr = var;
2458 :
2459 200 : while (ptr->next)
2460 44 : ptr = ptr->next;
2461 78 : ptr->next = SplitToVariants(Conf, node, var, word, wordlen, startpos, level);
2462 : /* we can find next word */
2463 78 : level++;
2464 78 : AddStem(var, pnstrdup(word + startpos, level - startpos));
2465 78 : node = Conf->Dictionary;
2466 78 : startpos = level;
2467 78 : continue;
2468 : }
2469 : }
2470 : }
2471 806 : node = StopMiddle->node;
2472 : }
2473 : else
2474 102 : node = NULL;
2475 908 : level++;
2476 : }
2477 :
2478 162 : AddStem(var, pnstrdup(word + startpos, wordlen - startpos));
2479 162 : pfree(notprobed);
2480 162 : return var;
2481 : }
2482 :
2483 : static void
2484 212 : addNorm(TSLexeme **lres, TSLexeme **lcur, char *word, int flags, uint16 NVariant)
2485 : {
2486 212 : if (*lres == NULL)
2487 96 : *lcur = *lres = (TSLexeme *) palloc(MAX_NORM * sizeof(TSLexeme));
2488 :
2489 212 : if (*lcur - *lres < MAX_NORM - 1)
2490 : {
2491 212 : (*lcur)->lexeme = word;
2492 212 : (*lcur)->flags = flags;
2493 212 : (*lcur)->nvariant = NVariant;
2494 212 : (*lcur)++;
2495 212 : (*lcur)->lexeme = NULL;
2496 : }
2497 212 : }
2498 :
2499 : TSLexeme *
2500 120 : NINormalizeWord(IspellDict *Conf, char *word)
2501 : {
2502 : char **res;
2503 120 : TSLexeme *lcur = NULL,
2504 120 : *lres = NULL;
2505 120 : uint16 NVariant = 1;
2506 :
2507 120 : res = NormalizeSubWord(Conf, word, 0);
2508 :
2509 120 : if (res)
2510 : {
2511 78 : char **ptr = res;
2512 :
2513 262 : while (*ptr && (lcur - lres) < MAX_NORM)
2514 : {
2515 106 : addNorm(&lres, &lcur, *ptr, 0, NVariant++);
2516 106 : ptr++;
2517 : }
2518 78 : pfree(res);
2519 : }
2520 :
2521 120 : if (Conf->usecompound)
2522 : {
2523 120 : int wordlen = strlen(word);
2524 : SplitVar *ptr,
2525 120 : *var = SplitToVariants(Conf, NULL, NULL, word, wordlen, 0, -1);
2526 : int i;
2527 :
2528 452 : while (var)
2529 : {
2530 212 : if (var->nstem > 1)
2531 : {
2532 92 : char **subres = NormalizeSubWord(Conf, var->stem[var->nstem - 1], FF_COMPOUNDLAST);
2533 :
2534 92 : if (subres)
2535 : {
2536 42 : char **subptr = subres;
2537 :
2538 126 : while (*subptr)
2539 : {
2540 106 : for (i = 0; i < var->nstem - 1; i++)
2541 : {
2542 64 : addNorm(&lres, &lcur, (subptr == subres) ? var->stem[i] : pstrdup(var->stem[i]), 0, NVariant);
2543 : }
2544 :
2545 42 : addNorm(&lres, &lcur, *subptr, 0, NVariant);
2546 42 : subptr++;
2547 42 : NVariant++;
2548 : }
2549 :
2550 42 : pfree(subres);
2551 42 : var->stem[0] = NULL;
2552 42 : pfree(var->stem[var->nstem - 1]);
2553 : }
2554 : }
2555 :
2556 442 : for (i = 0; i < var->nstem && var->stem[i]; i++)
2557 230 : pfree(var->stem[i]);
2558 212 : ptr = var->next;
2559 212 : pfree(var->stem);
2560 212 : pfree(var);
2561 212 : var = ptr;
2562 : }
2563 : }
2564 :
2565 120 : return lres;
2566 : }
|