1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright 1987, 1988, 1989, 1992, 1993, Geoff Kuenning, Granada Hills, CA
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All modifications to the source code must be clearly marked as
16 * such. Binary redistributions based on modified source code
17 * must be clearly marked as modified versions in the documentation
18 * and/or other materials provided with the distribution.
19 * 4. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgment:
21 * This product includes software developed by Geoff Kuenning and
22 * other unpaid contributors.
23 * 5. The name of Geoff Kuenning may not be used to endorse or promote
24 * products derived from this software without specific prior
25 * written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
38 */
39
40 /*
41 * Table-driven version of good.c.
42 *
43 * Geoff Kuenning, July 1987
44 */
45
46 /*
47 * $Log$
48 * Revision 1.4 2003/08/14 17:51:29 dom
49 * update license - exception clause should be Lesser GPL
50 *
51 * Revision 1.3 2003/07/28 20:40:28 dom
52 * fix up the license clause, further win32-registry proof some directory getting functions
53 *
54 * Revision 1.2 2003/07/16 22:52:56 dom
55 * LGPL + exception license
56 *
57 * Revision 1.1 2003/07/15 01:15:09 dom
58 * ispell enchant backend
59 *
60 * Revision 1.2 2003/01/29 05:50:12 hippietrail
61 *
62 * Fixed my mess in EncodingManager.
63 * Changed many C casts to C++ casts.
64 *
65 * Revision 1.1 2003/01/24 05:52:36 hippietrail
66 *
67 * Refactored ispell code. Old ispell global variables had been put into
68 * an allocated structure, a pointer to which was passed to many functions.
69 * I have now made all such functions and variables private members of the
70 * ISpellChecker class. It was C OO, now it's C++ OO.
71 *
72 * I've fixed the makefiles and tested compilation but am unable to test
73 * operation. Please back out my changes if they cause problems which
74 * are not obvious or easy to fix.
75 *
76 * Revision 1.6 2003/01/06 18:48:42 dom
77 * ispell cleanup, start of using new 'add' save features
78 *
79 * Revision 1.5 2002/09/19 05:31:20 hippietrail
80 *
81 * More Ispell cleanup. Conditional globals and DEREF macros are removed.
82 * K&R function declarations removed, converted to Doxygen style comments
83 * where possible. No code has been changed (I hope). Compiles for me but
84 * unable to test.
85 *
86 * Revision 1.4 2002/09/17 03:03:31 hippietrail
87 *
88 * After seeking permission on the developer list I've reformatted all the
89 * spelling source which seemed to have parts which used 2, 3, 4, and 8
90 * spaces for tabs. It should all look good with our standard 4-space
91 * tabs now.
92 * I've concentrated just on indentation in the actual code. More prettying
93 * could be done.
94 * * NO code changes were made *
95 *
96 * Revision 1.3 2002/09/13 17:20:14 mpritchett
97 * Fix more warnings for Linux build
98 *
99 * Revision 1.2 2001/05/12 16:05:42 thomasf
100 * Big pseudo changes to ispell to make it pass around a structure rather
101 * than rely on all sorts of gloabals willy nilly here and there. Also
102 * fixed our spelling class to work with accepting suggestions once more.
103 * This code is dirty, gross and ugly (not to mention still not supporting
104 * multiple hash sized just yet) but it works on my machine and will no
105 * doubt break other machines.
106 *
107 * Revision 1.1 2001/04/15 16:01:24 tomas_f
108 * moving to spell/xp
109 *
110 * Revision 1.7 1999/10/20 06:03:56 sterwill
111 * Changed C++-style comments to C-style comments in C code.
112 *
113 * Revision 1.6 1999/10/20 03:19:35 paul
114 * Hacked ispell code to ignore any characters that don't fit in the lookup tables loaded from the dictionary. It ain't pretty, but at least we don't crash there any more.
115 *
116 * Revision 1.5 1999/04/13 17:12:51 jeff
117 * Applied "Darren O. Benham" <gecko@benham.net> spell check changes.
118 * Fixed crash on Win32 with the new code.
119 *
120 * Revision 1.4 1998/12/29 14:55:33 eric
121 *
122 * I've doctored the ispell code pretty extensively here. It is now
123 * warning-free on Win32. It also *works* on Win32 now, since I
124 * replaced all the I/O calls with ANSI standard ones.
125 *
126 * Revision 1.4 1998/12/29 14:55:33 eric
127 *
128 * I've doctored the ispell code pretty extensively here. It is now
129 * warning-free on Win32. It also *works* on Win32 now, since I
130 * replaced all the I/O calls with ANSI standard ones.
131 *
132 * Revision 1.3 1998/12/28 23:11:30 eric
133 *
134 * modified spell code and integration to build on Windows.
135 * This is still a hack.
136 *
137 * Actually, it doesn't yet WORK on Windows. It just builds.
138 * SpellCheckInit is failing for some reason.
139 *
140 * Revision 1.2 1998/12/28 22:16:22 eric
141 *
142 * These changes begin to incorporate the spell checker into AbiWord. Most
143 * of this is a hack.
144 *
145 * 1. added other/spell to the -I list in config/abi_defs
146 * 2. replaced other/spell/Makefile with one which is more like
147 * our build system.
148 * 3. added other/spell to other/Makefile so that the build will now
149 * dive down and build the spell check library.
150 * 4. added the AbiSpell library to the Makefiles in wp/main
151 * 5. added a call to SpellCheckInit in wp/main/unix/UnixMain.cpp.
152 * This call is a HACK and should be replaced with something
153 * proper later.
154 * 6. added code to fv_View.cpp as follows:
155 * whenever you double-click on a word, the spell checker
156 * verifies that word and prints its status to stdout.
157 *
158 * Caveats:
159 * 1. This will break the Windows build. I'm going to work on fixing it
160 * now.
161 * 2. This only works if your dictionary is in /usr/lib/ispell/american.hash.
162 * The dictionary location is currently hard-coded. This will be
163 * fixed as well.
164 *
165 * Anyway, such as it is, it works.
166 *
167 * Revision 1.1 1998/12/28 18:04:43 davet
168 * Spell checker code stripped from ispell. At this point, there are
169 * two external routines... the Init routine, and a check-a-word routine
170 * which returns a boolean value, and takes a 16 bit char string.
171 * The code resembles the ispell code as much as possible still.
172 *
173 * Revision 1.32 1994/11/02 06:56:16 geoff
174 * Remove the anyword feature, which I've decided is a bad idea.
175 *
176 * Revision 1.31 1994/10/25 05:46:25 geoff
177 * Add support for the FF_ANYWORD (affix applies to all words, even if
178 * flag bit isn't set) flag option.
179 *
180 * Revision 1.30 1994/05/24 06:23:08 geoff
181 * Don't create a hit if "allhits" is clear and capitalization
182 * mismatches. This cures a bug where a word could be in the dictionary
183 * and yet not found.
184 *
185 * Revision 1.29 1994/05/17 06:44:21 geoff
186 * Add support for controlled compound formation and the COMPOUNDONLY
187 * option to affix flags.
188 *
189 * Revision 1.28 1994/01/25 07:12:13 geoff
190 * Get rid of all old RCS log lines in preparation for the 3.1 release.
191 *
192 */
193
194 #include <ctype.h>
195 #include <stdlib.h>
196 #include <string.h>
197
198 #include "ispell_checker.h"
199
200 /*!
201 * Check possible affixes
202 *
203 * \param word Word to be checked
204 * \param ucword Upper-case-only copy of word
205 * \param len The length of word/ucword
206 * \param ignoreflagbits Ignore whether affix is legal
207 * \param allhits Keep going after first hit
208 * \param pfxopts Options to apply to prefixes
209 * \param sfxopts Options to apply to suffixes
210 */
chk_aff(ichar_t * word,ichar_t * ucword,int len,int ignoreflagbits,int allhits,int pfxopts,int sfxopts)211 void ISpellChecker::chk_aff (ichar_t *word, ichar_t *ucword,
212 int len, int ignoreflagbits, int allhits, int pfxopts, int sfxopts)
213 {
214 register ichar_t * cp; /* Pointer to char to index on */
215 struct flagptr * ind; /* Flag index table to test */
216
217 pfx_list_chk (word, ucword, len, pfxopts, sfxopts, &m_pflagindex[0],
218 ignoreflagbits, allhits);
219 cp = ucword;
220 /* HACK: bail on unrecognized chars */
221 if (*cp >= (SET_SIZE + MAXSTRINGCHARS))
222 return;
223 ind = &m_pflagindex[*cp++];
224 while (ind->numents == 0 && ind->pu.fp != NULL)
225 {
226 if (*cp == 0)
227 return;
228 if (ind->pu.fp[0].numents)
229 {
230 pfx_list_chk (word, ucword, len, pfxopts, sfxopts, &ind->pu.fp[0],
231 ignoreflagbits, allhits);
232 if (m_numhits && !allhits && /* !cflag && */ !ignoreflagbits)
233 return;
234 }
235 /* HACK: bail on unrecognized chars */
236 if (*cp >= (SET_SIZE + MAXSTRINGCHARS))
237 return;
238 ind = &ind->pu.fp[*cp++];
239 }
240 pfx_list_chk (word, ucword, len, pfxopts, sfxopts, ind, ignoreflagbits,
241 allhits);
242 if (m_numhits && !allhits && /* !cflag &&*/ !ignoreflagbits)
243 return;
244 chk_suf (word, ucword, len, sfxopts, static_cast<struct flagent *>(NULL),
245 ignoreflagbits, allhits);
246 }
247
248 /*!
249 * Check some prefix flags
250 *
251 * \param word Word to be checked
252 * \param ucword Upper-case-only word
253 * \param len The length of ucword
254 * \param optflags Options to apply
255 * \param sfxopts Options to apply to suffixes
256 * \param ind Flag index table
257 * \param ignoreflagbits Ignore whether affix is legal
258 * \param allhits Keep going after first hit
259 * */
pfx_list_chk(ichar_t * word,ichar_t * ucword,int len,int optflags,int sfxopts,struct flagptr * ind,int ignoreflagbits,int allhits)260 void ISpellChecker::pfx_list_chk (ichar_t *word, ichar_t *ucword, int len, int optflags,
261 int sfxopts, struct flagptr * ind, int ignoreflagbits, int allhits)
262 {
263 int cond; /* Condition number */
264 register ichar_t * cp; /* Pointer into end of ucword */
265 struct dent * dent; /* Dictionary entry we found */
266 int entcount; /* Number of entries to process */
267 register struct flagent *
268 flent; /* Current table entry */
269 int preadd; /* Length added to tword2 as prefix */
270 register int tlen; /* Length of tword */
271 ichar_t tword[INPUTWORDLEN + 4 * MAXAFFIXLEN + 4]; /* Tmp cpy */
272 ichar_t tword2[sizeof tword]; /* 2nd copy for ins_root_cap */
273
274 for (flent = ind->pu.ent, entcount = ind->numents;
275 entcount > 0;
276 flent++, entcount--)
277 {
278 /*
279 * If this is a compound-only affix, ignore it unless we're
280 * looking for that specific thing.
281 */
282 if ((flent->flagflags & FF_COMPOUNDONLY) != 0
283 && (optflags & FF_COMPOUNDONLY) == 0)
284 continue;
285
286 /*
287 * See if the prefix matches.
288 */
289 tlen = len - flent->affl;
290 if (tlen > 0
291 && (flent->affl == 0
292 || icharncmp (flent->affix, ucword, flent->affl) == 0)
293 && tlen + flent->stripl >= flent->numconds)
294 {
295 /*
296 * The prefix matches. Remove it, replace it by the "strip"
297 * string (if any), and check the original conditions.
298 */
299 if (flent->stripl)
300 icharcpy (tword, flent->strip);
301 icharcpy (tword + flent->stripl, ucword + flent->affl);
302 cp = tword;
303 for (cond = 0; cond < flent->numconds; cond++)
304 {
305 if ((flent->conds[*cp++] & (1 << cond)) == 0)
306 break;
307 }
308 if (cond >= flent->numconds)
309 {
310 /*
311 * The conditions match. See if the word is in the
312 * dictionary.
313 */
314 tlen += flent->stripl;
315
316 if (ignoreflagbits)
317 {
318 if ((dent = ispell_lookup (tword, 1)) != NULL)
319 {
320 cp = tword2;
321 if (flent->affl)
322 {
323 icharcpy (cp, flent->affix);
324 cp += flent->affl;
325 *cp++ = '+';
326 }
327 preadd = cp - tword2;
328 icharcpy (cp, tword);
329 cp += tlen;
330 if (flent->stripl)
331 {
332 *cp++ = '-';
333 icharcpy (cp, flent->strip);
334 }
335 }
336 }
337 else if ((dent = ispell_lookup (tword, 1)) != NULL
338 && TSTMASKBIT (dent->mask, flent->flagbit))
339 {
340 if (m_numhits < MAX_HITS)
341 {
342 m_hits[m_numhits].dictent = dent;
343 m_hits[m_numhits].prefix = flent;
344 m_hits[m_numhits].suffix = NULL;
345 m_numhits++;
346 }
347 if (!allhits)
348 {
349 #ifndef NO_CAPITALIZATION_SUPPORT
350 if (cap_ok (word, &m_hits[0], len))
351 return;
352 m_numhits = 0;
353 #else /* NO_CAPITALIZATION_SUPPORT */
354 return;
355 #endif /* NO_CAPITALIZATION_SUPPORT */
356 }
357 }
358 /*
359 * Handle cross-products.
360 */
361 if (flent->flagflags & FF_CROSSPRODUCT)
362 chk_suf (word, tword, tlen, sfxopts | FF_CROSSPRODUCT,
363 flent, ignoreflagbits, allhits);
364 }
365 }
366 }
367 }
368
369 /*!
370 * Check possible suffixes
371 *
372 * \param word Word to be checked
373 * \param ucword Upper-case-only word
374 * \param len The length of ucword
375 * \param optflags Affix option flags
376 * \param pfxent Prefix flag entry if cross-prod
377 * \param ignoreflagbits Ignore whether affix is legal
378 * \param allhits Keep going after first hit
379 */
380 void
chk_suf(ichar_t * word,ichar_t * ucword,int len,int optflags,struct flagent * pfxent,int ignoreflagbits,int allhits)381 ISpellChecker::chk_suf (ichar_t *word, ichar_t *ucword,
382 int len, int optflags, struct flagent *pfxent,
383 int ignoreflagbits, int allhits)
384 {
385 register ichar_t * cp; /* Pointer to char to index on */
386 struct flagptr * ind; /* Flag index table to test */
387
388 suf_list_chk (word, ucword, len, &m_sflagindex[0], optflags, pfxent,
389 ignoreflagbits, allhits);
390 cp = ucword + len - 1;
391 /* HACK: bail on unrecognized chars */
392 if (*cp >= (SET_SIZE + MAXSTRINGCHARS))
393 return;
394 ind = &m_sflagindex[*cp];
395 while (ind->numents == 0 && ind->pu.fp != NULL)
396 {
397 if (cp == ucword)
398 return;
399 if (ind->pu.fp[0].numents)
400 {
401 suf_list_chk (word, ucword, len, &ind->pu.fp[0],
402 optflags, pfxent, ignoreflagbits, allhits);
403 if (m_numhits != 0 && !allhits && /* !cflag && */ !ignoreflagbits)
404 return;
405 }
406 /* HACK: bail on unrecognized chars */
407 if (*(cp-1) >= (SET_SIZE + MAXSTRINGCHARS))
408 return;
409 ind = &ind->pu.fp[*--cp];
410 }
411 suf_list_chk (word, ucword, len, ind, optflags, pfxent,
412 ignoreflagbits, allhits);
413 }
414
415 /*!
416 * \param word Word to be checked
417 * \param ucword Upper-case-only word
418 * \param len The length of ucword
419 * \param ind Flag index table
420 * \param optflags Affix option flags
421 * \param pfxent Prefix flag entry if crossonly
422 * \param ignoreflagbits Ignore whether affix is legal
423 * \pram allhits Keep going after first hit
424 */
suf_list_chk(ichar_t * word,ichar_t * ucword,int len,struct flagptr * ind,int optflags,struct flagent * pfxent,int ignoreflagbits,int allhits)425 void ISpellChecker::suf_list_chk (ichar_t *word, ichar_t *ucword,
426 int len, struct flagptr *ind, int optflags,
427 struct flagent *pfxent, int ignoreflagbits, int allhits)
428 {
429 register ichar_t * cp; /* Pointer into end of ucword */
430 int cond; /* Condition number */
431 struct dent * dent; /* Dictionary entry we found */
432 int entcount; /* Number of entries to process */
433 register struct flagent *
434 flent; /* Current table entry */
435 int preadd; /* Length added to tword2 as prefix */
436 register int tlen; /* Length of tword */
437 ichar_t tword[INPUTWORDLEN + 4 * MAXAFFIXLEN + 4]; /* Tmp cpy */
438 ichar_t tword2[sizeof tword]; /* 2nd copy for ins_root_cap */
439
440 icharcpy (tword, ucword);
441 for (flent = ind->pu.ent, entcount = ind->numents;
442 entcount > 0;
443 flent++, entcount--)
444 {
445 if ((optflags & FF_CROSSPRODUCT) != 0
446 && (flent->flagflags & FF_CROSSPRODUCT) == 0)
447 continue;
448 /*
449 * If this is a compound-only affix, ignore it unless we're
450 * looking for that specific thing.
451 */
452 if ((flent->flagflags & FF_COMPOUNDONLY) != 0
453 && (optflags & FF_COMPOUNDONLY) == 0)
454 continue;
455
456 /*
457 * See if the suffix matches.
458 */
459 tlen = len - flent->affl;
460 if (tlen > 0
461 && (flent->affl == 0
462 || icharcmp (flent->affix, ucword + tlen) == 0)
463 && tlen + flent->stripl >= flent->numconds)
464 {
465 /*
466 * The suffix matches. Remove it, replace it by the "strip"
467 * string (if any), and check the original conditions.
468 */
469 icharcpy (tword, ucword);
470 cp = tword + tlen;
471 if (flent->stripl)
472 {
473 icharcpy (cp, flent->strip);
474 tlen += flent->stripl;
475 cp = tword + tlen;
476 }
477 else
478 *cp = '\0';
479 for (cond = flent->numconds; --cond >= 0; )
480 {
481 if ((flent->conds[*--cp] & (1 << cond)) == 0)
482 break;
483 }
484 if (cond < 0)
485 {
486 /*
487 * The conditions match. See if the word is in the
488 * dictionary.
489 */
490 if (ignoreflagbits)
491 {
492 if ((dent = ispell_lookup (tword, 1)) != NULL)
493 {
494 cp = tword2;
495 if ((optflags & FF_CROSSPRODUCT)
496 && pfxent->affl != 0)
497 {
498 icharcpy (cp, pfxent->affix);
499 cp += pfxent->affl;
500 *cp++ = '+';
501 }
502 preadd = cp - tword2;
503 icharcpy (cp, tword);
504 cp += tlen;
505 if ((optflags & FF_CROSSPRODUCT)
506 && pfxent->stripl != 0)
507 {
508 *cp++ = '-';
509 icharcpy (cp, pfxent->strip);
510 cp += pfxent->stripl;
511 }
512 if (flent->stripl)
513 {
514 *cp++ = '-';
515 icharcpy (cp, flent->strip);
516 cp += flent->stripl;
517 }
518 if (flent->affl)
519 {
520 *cp++ = '+';
521 icharcpy (cp, flent->affix);
522 cp += flent->affl;
523 }
524 }
525 }
526 else if ((dent = ispell_lookup (tword, 1)) != NULL
527 && TSTMASKBIT (dent->mask, flent->flagbit)
528 && ((optflags & FF_CROSSPRODUCT) == 0
529 || TSTMASKBIT (dent->mask, pfxent->flagbit)))
530 {
531 if (m_numhits < MAX_HITS)
532 {
533 m_hits[m_numhits].dictent = dent;
534 m_hits[m_numhits].prefix = pfxent;
535 m_hits[m_numhits].suffix = flent;
536 m_numhits++;
537 }
538 if (!allhits)
539 {
540 #ifndef NO_CAPITALIZATION_SUPPORT
541 if (cap_ok (word, &m_hits[0], len))
542 return;
543 m_numhits = 0;
544 #else /* NO_CAPITALIZATION_SUPPORT */
545 return;
546 #endif /* NO_CAPITALIZATION_SUPPORT */
547 }
548 }
549 }
550 }
551 }
552 }
553
554 /*!
555 * Expand a dictionary prefix entry
556 *
557 * \param croot Char version of rootword
558 * \param rootword Root word to expand
559 * \param mask Mask bits to expand on
560 * \param option Option, see expandmode
561 * \param extra Extra info to add to line
562 *
563 * \return
564 */
expand_pre(char * croot,ichar_t * rootword,MASKTYPE mask[],int option,char * extra)565 int ISpellChecker::expand_pre (char *croot, ichar_t *rootword, MASKTYPE mask[],
566 int option, char *extra)
567 {
568 int entcount; /* No. of entries to process */
569 int explength; /* Length of expansions */
570 register struct flagent *
571 flent; /* Current table entry */
572
573 for (flent = m_pflaglist, entcount = m_numpflags, explength = 0;
574 entcount > 0;
575 flent++, entcount--)
576 {
577 if (TSTMASKBIT (mask, flent->flagbit))
578 explength +=
579 pr_pre_expansion (croot, rootword, flent, mask, option, extra);
580 }
581 return explength;
582 }
583
584 /*!
585 * Print a prefix expansion
586 *
587 * \param croot Char version of rootword
588 * \param rootword Root word to expand
589 * \param flent Current table entry
590 * \param mask Mask bits to expand on
591 * \param option Option, see expandmode
592 * \param extra Extra info to add to line
593 *
594 * \return
595 */
pr_pre_expansion(char * croot,ichar_t * rootword,struct flagent * flent,MASKTYPE mask[],int option,char * extra)596 int ISpellChecker::pr_pre_expansion ( char *croot, ichar_t *rootword,
597 struct flagent *flent, MASKTYPE mask[], int option,
598 char *extra)
599 {
600 int cond; /* Current condition number */
601 register ichar_t * nextc; /* Next case choice */
602 int tlen; /* Length of tword */
603 ichar_t tword[INPUTWORDLEN + MAXAFFIXLEN]; /* Temp */
604
605 tlen = icharlen (rootword);
606 if (flent->numconds > tlen)
607 return 0;
608 tlen -= flent->stripl;
609 if (tlen <= 0)
610 return 0;
611 tlen += flent->affl;
612 for (cond = 0, nextc = rootword; cond < flent->numconds; cond++)
613 {
614 if ((flent->conds[mytoupper (*nextc++)] & (1 << cond)) == 0)
615 return 0;
616 }
617 /*
618 * The conditions are satisfied. Copy the word, add the prefix,
619 * and make it the proper case. This code is carefully written
620 * to match that ins_cap and cap_ok. Note that the affix, as
621 * inserted, is uppercase.
622 *
623 * There is a tricky bit here: if the root is capitalized, we
624 * want a capitalized result. If the root is followcase, however,
625 * we want to duplicate the case of the first remaining letter
626 * of the root. In other words, "Loved/U" should generate "Unloved",
627 * but "LOved/U" should generate "UNLOved" and "lOved/U" should
628 * produce "unlOved".
629 */
630 if (flent->affl)
631 {
632 icharcpy (tword, flent->affix);
633 nextc = tword + flent->affl;
634 }
635 icharcpy (nextc, rootword + flent->stripl);
636 if (myupper (rootword[0]))
637 {
638 /* We must distinguish followcase from capitalized and all-upper */
639 for (nextc = rootword + 1; *nextc; nextc++)
640 {
641 if (!myupper (*nextc))
642 break;
643 }
644 if (*nextc)
645 {
646 /* It's a followcase or capitalized word. Figure out which. */
647 for ( ; *nextc; nextc++)
648 {
649 if (myupper (*nextc))
650 break;
651 }
652 if (*nextc)
653 {
654 /* It's followcase. */
655 if (!myupper (tword[flent->affl]))
656 forcelc (tword, flent->affl);
657 }
658 else
659 {
660 /* It's capitalized */
661 forcelc (tword + 1, tlen - 1);
662 }
663 }
664 }
665 else
666 {
667 /* Followcase or all-lower, we don't care which */
668 if (!myupper (*nextc))
669 forcelc (tword, flent->affl);
670 }
671 if (option == 3)
672 printf ("\n%s", croot);
673 if (option != 4)
674 printf (" %s%s", ichartosstr (tword, 1), extra);
675 if (flent->flagflags & FF_CROSSPRODUCT)
676 return tlen
677 + expand_suf (croot, tword, mask, FF_CROSSPRODUCT, option, extra);
678 else
679 return tlen;
680 }
681
682 /*!
683 * Expand a dictionary suffix entry
684 *
685 * \param croot Char version of rootword
686 * \param rootword Root word to expand
687 * \param mask Mask bits to expand on
688 * \param optflags Affix option flags
689 * \param option Option, see expandmode
690 * \param extra Extra info to add to line
691 *
692 * \return
693 */
expand_suf(char * croot,ichar_t * rootword,MASKTYPE mask[],int optflags,int option,char * extra)694 int ISpellChecker::expand_suf (char *croot, ichar_t *rootword, MASKTYPE mask[],
695 int optflags, int option, char *extra)
696 {
697 int entcount; /* No. of entries to process */
698 int explength; /* Length of expansions */
699 register struct flagent *
700 flent; /* Current table entry */
701
702 for (flent = m_sflaglist, entcount = m_numsflags, explength = 0;
703 entcount > 0;
704 flent++, entcount--)
705 {
706 if (TSTMASKBIT (mask, flent->flagbit))
707 {
708 if ((optflags & FF_CROSSPRODUCT) == 0
709 || (flent->flagflags & FF_CROSSPRODUCT))
710 explength +=
711 pr_suf_expansion (croot, rootword, flent, option, extra);
712 }
713 }
714 return explength;
715 }
716
717 /*!
718 * Print a suffix expansion
719 *
720 * \param croot Char version of rootword
721 * \param rootword Root word to expand
722 * \param flent Current table entry
723 * \param option Option, see expandmode
724 * \param extra Extra info to add to line
725 *
726 * \return
727 */
pr_suf_expansion(char * croot,ichar_t * rootword,struct flagent * flent,int option,char * extra)728 int ISpellChecker::pr_suf_expansion (char *croot, ichar_t *rootword,
729 struct flagent *flent, int option, char *extra)
730 {
731 int cond; /* Current condition number */
732 register ichar_t * nextc; /* Next case choice */
733 int tlen; /* Length of tword */
734 ichar_t tword[INPUTWORDLEN + MAXAFFIXLEN]; /* Temp */
735
736 tlen = icharlen (rootword);
737 cond = flent->numconds;
738 if (cond > tlen)
739 return 0;
740 if (tlen - flent->stripl <= 0)
741 return 0;
742 for (nextc = rootword + tlen; --cond >= 0; )
743 {
744 if ((flent->conds[mytoupper (*--nextc)] & (1 << cond)) == 0)
745 return 0;
746 }
747 /*
748 * The conditions are satisfied. Copy the word, add the suffix,
749 * and make it match the case of the last remaining character of the
750 * root. Again, this code carefully matches ins_cap and cap_ok.
751 */
752 icharcpy (tword, rootword);
753 nextc = tword + tlen - flent->stripl;
754 if (flent->affl)
755 {
756 icharcpy (nextc, flent->affix);
757 if (!myupper (nextc[-1]))
758 forcelc (nextc, flent->affl);
759 }
760 else
761 *nextc = 0;
762 if (option == 3)
763 printf ("\n%s", croot);
764 if (option != 4)
765 printf (" %s%s", ichartosstr (tword, 1), extra);
766 return tlen + flent->affl - flent->stripl;
767 }
768
769 /*!
770 * \param dst Destination to modify
771 * \param len Length to copy
772 */
forcelc(ichar_t * dst,int len)773 void ISpellChecker::forcelc (ichar_t *dst, int len) /* Force to lowercase */
774 {
775
776 for ( ; --len >= 0; dst++)
777 *dst = mytolower (*dst);
778 }
779