1 // This file is part of The New Aspell Copyright (C)
2 // 2002,2003,2004,2011,2019 by Kevin Atkinson under the GNU LGPL license
3 // version 2.0 or 2.1. You should have received a copy of the LGPL
4 // license along with this library if you did not you can find it at
5 // http://www.gnu.org/.
8 // NOTE: This program currently uses a very ugly mix of the internal
9 // API and the external C interface. The eventual goal is to
10 // use only the external C++ interface, however, the external
11 // C++ interface is currently incomplete. The C interface is
12 // used in some places because without the strings will not get
13 // converted properly when the encoding is not the same as the
14 // internal encoding used by Aspell.
24 #ifdef HAVE_LANGINFO_CODESET
25 # include <langinfo.h>
30 #include <sys/types.h>
38 #include "asc_ctype.hpp"
39 #include "check_funs.hpp"
41 #include "convert.hpp"
42 #include "document_checker.hpp"
43 #include "enumeration.hpp"
45 #include "file_util.hpp"
46 #include "fstream.hpp"
48 #include "iostream.hpp"
49 #include "posib_err.hpp"
50 #include "speller.hpp"
51 #include "stack_ptr.hpp"
52 #include "string_enumeration.hpp"
53 #include "string_map.hpp"
54 #include "word_list.hpp"
56 #include "string_list.hpp"
57 #include "speller_impl.hpp"
61 #include "hash_fun.hpp"
65 using namespace acommon;
69 // action functions declarations
72 void print_help(bool verbose = false);
96 void print_error(ParmString msg)
98 CERR.printf(_("Error: %s\n"), msg.str());
101 void print_error(ParmString msg, ParmString str)
103 CERR.put(_("Error: "));
104 CERR.printf(msg.str(), str.str());
108 #define EXIT_ON_ERR(command) \
109 do{PosibErrBase pe(command);\
110 if(pe.has_err()){print_error(pe.get_err()->mesg); exit(1);}\
112 #define EXIT_ON_ERR_SET(command, type, var)\
114 do{PosibErr< type > pe(command);\
115 if(pe.has_err()){print_error(pe.get_err()->mesg); exit(1);}\
118 #define BREAK_ON_ERR(command) \
119 do{PosibErrBase pe(command);\
120 if(pe.has_err()){print_error(pe.get_err()->mesg); break;}\
122 #define BREAK_ON_ERR_SET(command, type, var)\
124 do{PosibErr< type > pe(command);\
125 if(pe.has_err()){print_error(pe.get_err()->mesg); break;}\
130 /////////////////////////////////////////////////////////
132 // Command line options functions and classes
136 typedef Vector<String> Args;
137 typedef Config Options;
138 enum Action {do_create, do_merge, do_dump, do_test, do_other};
141 StackPtr<Options> options;
142 Action action = do_other;
144 struct PossibleOption {
151 #define OPTION(name,abrv,num) {name,abrv,num,false}
152 #define COMMAND(name,abrv,num) {name,abrv,num,true}
153 #define ISPELL_COMP(abrv,num) {"",abrv,num,false}
155 const PossibleOption possible_options[] = {
156 OPTION("master", 'd', 1),
157 OPTION("personal", 'p', 1),
158 OPTION("ignore", 'W', 1),
159 OPTION("lang", 'l', 1),
160 OPTION("backup", 'b', 0),
161 OPTION("dont-backup", 'x', 0),
162 OPTION("run-together", 'C', 0),
163 OPTION("dont-run-together",'B', 0),
164 OPTION("guess", 'm', 0),
165 OPTION("dont-guess", 'P', 0),
167 COMMAND("usage", '?', 0),
168 COMMAND("help", '\0', 0),
169 COMMAND("version", 'v', 0),
170 COMMAND("config", '\0', 0),
171 COMMAND("dicts", '\0', 0),
172 COMMAND("check", 'c', 0),
173 COMMAND("pipe", 'a', 0),
174 COMMAND("list", '\0', 0),
175 COMMAND("conv", '\0', 2),
176 COMMAND("norm", '\0', 1),
177 COMMAND("filter", '\0', 0),
178 COMMAND("soundslike",'\0', 0),
179 COMMAND("munch", '\0', 0),
180 COMMAND("expand", '\0', 0),
181 COMMAND("combine", '\0', 0),
182 COMMAND("munch-list",'\0', 0),
183 COMMAND("clean", '\0', 0),
184 COMMAND("filters", '\0', 0),
185 COMMAND("modes", '\0', 0),
187 COMMAND("dump", '\0', 1),
188 COMMAND("create", '\0', 1),
189 COMMAND("merge", '\0', 1),
191 ISPELL_COMP('S',0), ISPELL_COMP('w',1), ISPELL_COMP('T',1),
196 const PossibleOption * possible_options_end = possible_options + sizeof(possible_options)/sizeof(PossibleOption) - 2;
203 static const ModeAbrv mode_abrvs[] = {
204 {'e', "mode=email", N_("enter Email mode.")},
205 {'H', "mode=html", N_("enter HTML mode.")},
206 {'t', "mode=tex", N_("enter TeX mode.")},
207 {'n', "mode=nroff", N_("enter Nroff mode.")}
210 static const ModeAbrv * mode_abrvs_end = mode_abrvs + 4;
212 const PossibleOption * find_option(char c) {
213 const PossibleOption * i = possible_options;
214 while (i != possible_options_end && i->abrv != c)
219 static inline bool str_equal(const char * begin, const char * end,
222 while(begin != end && *begin == *other)
224 return (begin == end && *other == '\0');
227 static const PossibleOption * find_option(const char * begin, const char * end) {
228 const PossibleOption * i = possible_options;
229 while (i != possible_options_end
230 && !str_equal(begin, end, i->name))
235 static const PossibleOption * find_option(const char * str) {
236 const PossibleOption * i = possible_options;
237 while (i != possible_options_end
238 && strcmp(str, i->name) != 0)
243 static void line_buffer() {
245 // set up stdin and stdout to be line buffered
246 assert(setvbuf(stdin, 0, _IOLBF, 0) == 0);
247 assert(setvbuf(stdout, 0, _IOLBF, 0) == 0);
254 int main (int argc, const char *argv[])
256 options = new_config(); // this needs to be here because of a bug
257 // with static initlizers on Darwin.
259 setlocale (LC_ALL, "");
261 aspell_gettext_init();
263 options->set_committed_state(false);
265 if (argc == 1) {print_help(); return 0;}
268 const PossibleOption * o;
272 // process command line options by setting the appropriate options
273 // in "options" and/or pushing non-options onto "argv"
275 PossibleOption other_opt = OPTION("",'\0',0);
278 if (argv[i][0] == '-') {
279 bool have_parm = false;
280 if (argv[i][1] == '-') {
282 const char * c = argv[i] + 2;
283 while(*c != '=' && *c != '\0') ++c;
284 o = find_option(argv[i] + 2, c);
285 if (o == possible_options_end) {
286 option_name.assign(argv[i] + 2, c - argv[i] - 2);
287 other_opt.name = option_name.c_str();
288 other_opt.num_arg = -1;
291 if (*c == '=') {have_parm = true; ++c;}
295 const ModeAbrv * j = mode_abrvs;
296 while (j != mode_abrvs_end && j->abrv != argv[i][1]) ++j;
297 if (j == mode_abrvs_end) {
298 o = find_option(argv[i][1]);
299 if (argv[i][1] == 'v' && argv[i][2] == 'v')
304 } else { // mode option
305 other_opt.name = "mode";
306 other_opt.num_arg = 1;
310 if (*parm) have_parm = true;
312 if (o == possible_options_end) {
313 print_error(_("Invalid Option: %s"), argv[i]);
317 if (o->num_arg == 0) {
319 if (parm[0] != '\0') {
320 print_error(_(" does not take any parameters."),
321 String(argv[i], parm - argv[i]));
325 } else if (have_parm) {
328 } else if (i + 1 == argc || argv[i+1][0] == '-') {
329 if (o->num_arg == -1) {
333 print_error(_("You must specify a parameter for \"%s\"."), argv[i]);
337 num_parms = o->num_arg;
342 args.push_back(o->name);
344 args.push_back(parm);
345 } else if (o->name[0] != '\0') {
346 Config::Entry * entry = new Config::Entry;
347 entry->key = o->name;
349 entry->need_conv = true;
350 if (num_parms == -1) {
351 entry->place_holder = args.size();
352 args.push_back(parm);
357 args.push_back(argv[i]);
362 options->read_in_settings();
364 const char * codeset = 0;
365 #ifdef HAVE_LANGINFO_CODESET
366 codeset = nl_langinfo(CODESET);
367 if (ascii_encoding(*options, codeset)) codeset = 0;
371 // if (!options->have("encoding") && codeset)
372 // EXIT_ON_ERR(options->replace("encoding", codeset));
375 Vector<int> to_remove;
376 EXIT_ON_ERR(options->commit_all(&to_remove, codeset));
377 for (int i = to_remove.size() - 1; i >= 0; --i) {
378 args.erase(args.begin() + to_remove[i]);
382 print_error(_("You must specify an action"));
386 String action_str = args.front();
388 const PossibleOption * action_opt = find_option(action_str.str());
389 if (!action_opt->is_command) {
390 print_error(_("Unknown Action: %s"), action_str);
392 } else if (action_opt->num_arg == 1 && args.empty()) {
393 print_error(_("You must specify a parameter for \"%s\"."), action_str);
395 } else if (action_opt->num_arg > (int)args.size()) {
396 CERR.printf(_("Error: You must specify at least %d parameters for \"%s\".\n"),
397 action_opt->num_arg, action_str.str());
402 // perform the requested action
404 if (action_str == "usage")
406 else if (action_str == "help")
408 else if (action_str == "version")
410 else if (action_str == "config")
412 else if (action_str == "dicts")
414 else if (action_str == "check")
416 else if (action_str == "pipe")
418 else if (action_str == "list")
420 else if (action_str == "conv")
422 else if (action_str == "norm")
424 else if (action_str == "filter")
426 else if (action_str == "soundslike")
428 else if (action_str == "munch")
430 else if (action_str == "expand")
432 else if (action_str == "combine")
434 else if (action_str == "munch-list")
436 else if (action_str == "clean")
438 else if (action_str == "filters")
440 else if (action_str == "modes")
442 else if (action_str == "dump")
444 else if (action_str == "create")
446 else if (action_str == "merge")
449 abort(); // this should not happen
451 if (action != do_other) {
453 print_error(_("Unknown Action: %s"), action_str);
456 String what_str = args.front();
458 if (what_str == "config")
460 else if (what_str == "dicts")
462 else if (what_str == "filters")
464 else if (what_str == "modes")
466 else if (what_str == "master")
468 else if (what_str == "personal")
470 else if (what_str == "repl")
472 else if (what_str == "affix")
475 print_error(_("Unknown Action: %s"),
476 String(action_str + " " + what_str));
486 /////////////////////////////////////////////////////////
493 static Convert * setup_conv(const aspeller::Language * lang,
496 if (config->retrieve("encoding") != "none") {
497 PosibErr<Convert *> pe = new_convert_if_needed(*config,
499 config->retrieve("encoding"),
501 if (pe.has_err()) {print_error(pe.get_err()->mesg); exit(1);}
508 static Convert * setup_conv(Config * config,
509 const aspeller::Language * lang)
511 if (config->retrieve("encoding") != "none") {
512 PosibErr<Convert *> pe = new_convert_if_needed(*config,
513 config->retrieve("encoding"),
516 if (pe.has_err()) {print_error(pe.get_err()->mesg); exit(1);}
523 void setup_display_conv()
525 const char * gettext_enc = 0;
526 const char * env_enc = 0;
527 String doc_enc = options->retrieve("encoding");
530 gettext_enc = bind_textdomain_codeset("aspell", 0);
531 if (ascii_encoding(*options,gettext_enc)) gettext_enc = 0;
533 #ifdef HAVE_LANGINFO_CODESET
534 env_enc = nl_langinfo(CODESET);
535 if (ascii_encoding(*options, env_enc)) env_enc = 0;
537 if (gettext_enc && env_enc && strcmp(gettext_enc,env_enc) != 0)
539 fputs(("Error: bind_textdomain_codeset != nl_langinfo(CODESET)\n"), stderr);
549 EXIT_ON_ERR(dconv.setup(*options, doc_enc, enc, NormNone));
550 EXIT_ON_ERR(uiconv.setup(*options, enc, doc_enc, NormNone));
554 ///////////////////////////
561 if (args.size() == 0) {
562 load_all_filters(options);
563 options->write_to_stream(COUT);
565 EXIT_ON_ERR_SET(options->retrieve_any(args[0]), String, value);
566 COUT << value << "\n";
570 ///////////////////////////
577 const DictInfoList * dlist = get_dict_info_list(options);
579 StackPtr<DictInfoEnumeration> dels(dlist->elements());
581 const DictInfo * entry;
583 while ( (entry = dels->next()) != 0)
587 ///////////////////////////
589 // list available (filters/filter modes)
592 void list_available(PosibErr<StringPairEnumeration *> (*fun)(Config *))
594 EXIT_ON_ERR_SET(fun(options), StringPairEnumeration *, els);
596 while (!els->at_end()) {
598 printf("%-14s %s\n", sp.first, gt_(sp.second));
605 load_all_filters(options);
606 list_available(available_filters);
611 list_available(available_filter_modes);
614 ///////////////////////////
619 // precond: strlen(str) > 0
620 char * trim_wspace (char * str)
622 int last = strlen(str) - 1;
623 while (asc_isspace(str[0])) {
627 while (last > 0 && asc_isspace(str[last])) {
630 str[last + 1] = '\0';
634 bool get_word_pair(char * line, char * & w1, char * & w2)
636 w2 = strchr(line, ',');
638 print_error(_("Invalid Input"));
643 w1 = trim_wspace(line);
644 w2 = trim_wspace(w2);
648 void print_elements(const AspellWordList * wl) {
649 AspellStringEnumeration * els = aspell_word_list_elements(wl);
653 while ( (w = aspell_string_enumeration_next(els)) != 0 ) {
658 line.resize(line.size() - 2);
659 COUT.printf("%u: %s\n", count, line.c_str());
664 aspeller::SpellerImpl * real_speller;
667 StatusFunInf(Convert * c) : oconv(c) {}
670 void status_fun(void * d, Token, int correct)
672 StatusFunInf * p = static_cast<StatusFunInf *>(d);
673 if (p->verbose && correct) {
674 const CheckInfo * ci = p->real_speller->check_info();
677 else if (ci->pre_flag || ci->suf_flag)
678 COUT.printf("+ %s\n", p->oconv(ci->word.str()));
684 DocumentChecker * new_checker(AspellSpeller * speller,
685 StatusFunInf & status_fun_inf)
687 EXIT_ON_ERR_SET(new_document_checker(reinterpret_cast<Speller *>(speller)),
688 StackPtr<DocumentChecker>, checker);
689 checker->set_status_fun(status_fun, &status_fun_inf);
690 return checker.release();
693 #define BREAK_ON_SPELLER_ERR\
694 do {if (aspell_speller_error(speller)) {\
695 print_error(aspell_speller_error_message(speller)); break;\
702 bool terse_mode = true;
703 bool do_time = options->retrieve_bool("time");
704 bool suggest = options->retrieve_bool("suggest");
705 bool include_guesses = options->retrieve_bool("guess");
706 clock_t start,finish;
708 if (!options->have("mode") && !options->have("filter")) {
709 PosibErrBase err(options->replace("mode", "nroff"));
711 CERR.printf(_("WARNING: Unable to enter Nroff mode: %s\n"),
712 err.get_err()->mesg);
717 AspellCanHaveError * ret
718 = new_aspell_speller(reinterpret_cast<AspellConfig *>(options.get()));
719 if (aspell_error(ret)) {
720 print_error(aspell_error_message(ret));
723 AspellSpeller * speller = to_aspell_speller(ret);
724 aspeller::SpellerImpl * real_speller = reinterpret_cast<aspeller::SpellerImpl *>(speller);
725 Config * config = real_speller->config();
726 Conv iconv(setup_conv(config, &real_speller->lang()));
727 Conv oconv(setup_conv(&real_speller->lang(), config));
729 if (!config->retrieve_bool("byte-offsets"))
730 mb_len.setup(*config, config->retrieve("encoding"));
732 COUT << _("Time to load word list: ")
733 << (clock() - start)/(double)CLOCKS_PER_SEC << "\n";
734 StatusFunInf status_fun_inf(setup_conv(&real_speller->lang(), config));
735 status_fun_inf.real_speller = real_speller;
736 bool & print_star = status_fun_inf.verbose;
738 StackPtr<DocumentChecker> checker(new_checker(speller, status_fun_inf));
754 while (c = getchar(), c != '\n' && c != EOF)
755 buf.push_back(static_cast<char>(c));
756 buf.push_back('\n'); // always add new line so strlen > 0
762 if (c != EOF) continue;
765 word = trim_wspace(line + 1);
766 aspell_speller_add_to_personal(speller, word, -1);
767 BREAK_ON_SPELLER_ERR;
770 word = trim_wspace(line + 1);
771 aspell_speller_add_to_personal
773 real_speller->to_lower(word), -1);
774 BREAK_ON_SPELLER_ERR;
777 word = trim_wspace(line + 1);
778 aspell_speller_add_to_session(speller, word, -1);
779 BREAK_ON_SPELLER_ERR;
782 aspell_speller_save_all_word_lists(speller);
783 BREAK_ON_SPELLER_ERR;
786 word = trim_wspace(line + 1);
787 err = config->replace("mode", word);
789 config->replace("mode", "tex");
790 reload_filters(real_speller);
792 checker = new_checker(speller, status_fun_inf);
795 config->remove("filter");
796 reload_filters(real_speller);
798 checker = new_checker(speller, status_fun_inf);
811 if (line[1] == '$') {
816 if (get_word_pair(line + 4, word, word2))
817 aspell_speller_store_replacement(speller, word, -1, word2, -1);
824 if (get_word_pair(line + 4, word, word2))
825 BREAK_ON_ERR(err = config->replace(word, word2));
826 if (strcmp(word,"suggest") == 0)
827 suggest = config->retrieve_bool("suggest");
828 else if (strcmp(word,"time") == 0)
829 do_time = config->retrieve_bool("time");
830 else if (strcmp(word,"guess") == 0)
831 include_guesses = config->retrieve_bool("guess");
834 word = trim_wspace(line + 4);
835 BREAK_ON_ERR_SET(config->retrieve(word), String, ret);
843 print_elements(aspell_speller_personal_word_list(speller));
846 print_elements(aspell_speller_session_word_list(speller));
851 COUT.printl(config->retrieve("lang"));
856 // continue on (no break)
863 checker->process(line, strlen(line));
864 while (Token token = checker->next_misspelling()) {
865 word = line + token.offset;
866 word[token.len] = '\0';
867 const char * cword = iconv(word);
868 String guesses, guess;
869 const CheckInfo * ci = real_speller->check_info();
870 aspeller::CasePattern casep
871 = real_speller->lang().case_pattern(cword);
874 if (ci->pre_add && ci->pre_add[0])
875 guess.append(ci->pre_add, ci->pre_add_len).append('+');
876 guess.append(ci->word);
877 if (ci->pre_strip_len > 0)
878 guess.append('-').append(ci->word.str(), ci->pre_strip_len);
879 if (ci->suf_strip_len > 0)
880 guess.append('-').append(ci->word.str() + ci->word.size() - ci->suf_strip_len,
882 if (ci->suf_add && ci->suf_add[0])
883 guess.append('+').append(ci->suf_add, ci->suf_add_len);
884 real_speller->lang().fix_case(casep, guess.data(), guess.data());
885 guesses << ", " << oconv(guess.str());
889 const AspellWordList * suggestions = 0;
891 suggestions = aspell_speller_suggest(speller, word, -1);
893 unsigned offset = mb_len(line0, token.offset + ignore);
894 if (suggestions && !aspell_word_list_empty(suggestions))
896 COUT.printf("& %s %u %u:", word,
897 aspell_word_list_size(suggestions), offset);
898 AspellStringEnumeration * els
899 = aspell_word_list_elements(suggestions);
900 if (options->retrieve_bool("reverse")) {
902 sugs.reserve(aspell_word_list_size(suggestions));
903 while ( ( w = aspell_string_enumeration_next(els)) != 0)
905 Vector<String>::reverse_iterator i = sugs.rbegin();
907 COUT.printf(" %s", i->c_str());
909 if (i == sugs.rend()) break;
913 while ( ( w = aspell_string_enumeration_next(els)) != 0) {
914 COUT.printf(" %s%s", w,
915 aspell_string_enumeration_at_end(els) ? "" : ",");
918 delete_aspell_string_enumeration(els);
924 COUT.printf("# %s %u\n", word, offset);
926 COUT.printf("? %s 0 %u: %s\n", word, offset,
927 guesses.c_str() + 2);
930 COUT.printf(_("Suggestion Time: %f\n"),
931 (finish-start)/(double)CLOCKS_PER_SEC);
938 delete_aspell_speller(speller);
941 ///////////////////////////
946 enum UserChoice {None, Ignore, IgnoreAll, Replace, ReplaceAll,
947 Add, AddLower, Exit, Abort};
951 UserChoice reverse[256];
954 char & operator[] (UserChoice c) {return primary[c];}
955 UserChoice & operator[] (char c)
956 {return reverse[static_cast<unsigned char>(c)];}
961 void setup_display_conv();
970 bool changed = false;
972 if (args.size() == 0) {
973 print_error(_("You must specify a file name."));
975 } else if (args.size() > 1) {
976 print_error(_("Only one file name may be specified."));
981 new_name = file_name;
984 in = fopen(file_name.c_str(), "r");
986 print_error(_("Could not open the file \"%s\" for reading"), file_name);
990 if (!options->have("mode"))
991 EXIT_ON_ERR(set_mode_from_extension(options, file_name));
993 String m = options->retrieve("keymapping");
996 else if (m == "ispell")
999 print_error(_("Invalid keymapping: %s"), m);
1003 AspellCanHaveError * ret
1004 = new_aspell_speller(reinterpret_cast<AspellConfig *>(options.get()));
1005 if (aspell_error(ret)) {
1006 print_error(aspell_error_message(ret));
1012 fstat(fileno(in), &st);
1013 if (!S_ISREG(st.st_mode)) {
1014 print_error(_("\"%s\" is not a regular file"), file_name);
1018 int fd = open(new_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, st.st_mode);
1019 if (fd >= 0) out = fdopen(fd, "w");
1021 out = fopen(new_name.c_str(), "w");
1025 print_error(_("Could not open the file \"%s\" for writing. File not saved."), file_name);
1029 setup_display_conv();
1031 AspellSpeller * speller = to_aspell_speller(ret);
1033 state = new CheckerString(speller,in,out,64);
1035 word_choices = new Choices;
1037 menu_choices = new Choices;
1038 menu_choices->push_back(Choice(mapping[Ignore], _("Ignore")));
1039 menu_choices->push_back(Choice(mapping[IgnoreAll], _("Ignore all")));
1040 menu_choices->push_back(Choice(mapping[Replace], _("Replace")));
1041 menu_choices->push_back(Choice(mapping[ReplaceAll], _("Replace all")));
1042 menu_choices->push_back(Choice(mapping[Add], _("Add")));
1043 menu_choices->push_back(Choice(mapping[AddLower], _("Add Lower")));
1044 menu_choices->push_back(Choice(mapping[Abort], _("Abort")));
1045 menu_choices->push_back(Choice(mapping[Exit], _("Exit")));
1047 String word0, new_word;
1048 Vector<String> sug_con;
1049 StackPtr<StringMap> replace_list(new_string_map());
1054 while (state->next_misspelling()) {
1056 char * word = state->get_real_word(word0);
1059 // check if it is in the replace list
1062 if ((w = replace_list->lookup(word)) != 0) {
1068 // print the line with the misspelled word highlighted;
1071 display_misspelled_word();
1074 // print the suggestions and menu choices
1077 const AspellWordList * suggestions = aspell_speller_suggest(speller, word, -1);
1078 AspellStringEnumeration * els = aspell_word_list_elements(suggestions);
1080 while (sug_con.size() != 10
1081 && (w = aspell_string_enumeration_next(els)) != 0)
1082 sug_con.push_back(w);
1083 delete_aspell_string_enumeration(els);
1086 unsigned int suggestions_size = sug_con.size();
1087 unsigned int suggestions_mid = suggestions_size / 2;
1088 if (suggestions_size % 2) suggestions_mid++; // if odd
1089 word_choices->resize(0);
1090 for (unsigned int j = 0; j != suggestions_mid; ++j) {
1091 word_choices->push_back(Choice('0' + j+1, sug_con[j]));
1092 if (j + suggestions_mid != suggestions_size)
1094 ->push_back(Choice(j+suggestions_mid+1 == 10
1096 : '0' + j+suggestions_mid+1,
1097 sug_con[j+suggestions_mid]));
1109 // Handle the users choice
1115 if (choice == '0') choice = '9' + 1;
1117 switch (mapping[choice]) {
1121 prompt(_("Are you sure you want to abort (y/n)? "));
1123 /* TRANSLATORS: The user may input any of these characters to say "yes".
1124 MUST ONLY CONSIST OF ASCII CHARACTERS. */
1125 const char * yes_characters = _("Yy");
1126 if (strchr(yes_characters, choice) != 0)
1133 aspell_speller_add_to_session(speller, word, -1);
1136 aspell_speller_add_to_personal(speller, word, -1);
1140 // Emulate the c function add_to_personal, but add extra step to
1141 // convert word to lowercase. Yeah its a bit of a hack.
1142 Speller * sp = reinterpret_cast<Speller *>(speller);
1143 sp->temp_str_0.clear();
1144 sp->to_internal_->convert(word, -1, sp->temp_str_0);
1145 char * lower = sp->to_lower(sp->temp_str_0.mstr());
1146 PosibErr<void> ret = sp->add_to_personal(MutableString(lower));
1147 sp->err_.reset(ret.release_err());
1152 // the string new_word is in the encoding of the document
1153 prompt(_("With: "));
1155 if (new_word.size() == 0)
1157 if (new_word[0] >= '1' && new_word[0] < (char)suggestions_size + '1')
1158 new_word = sug_con[new_word[0]-'1'];
1159 state->replace(new_word);
1161 if (mapping[choice] == ReplaceAll && (strcmp(word,new_word.str()) != 0))
1162 replace_list->replace(word, new_word);
1165 // the replasments are in the encoding of the document
1166 if (choice >= '1' && choice < (char)suggestions_size + '1') {
1167 state->replace(sug_con[choice-'1']);
1170 error(_("Sorry that is an invalid choice!"));
1177 aspell_speller_save_all_word_lists(speller);
1178 state.del(); // to close the file handles
1179 delete_aspell_speller(speller);
1183 bool keep_backup = options->retrieve_bool("backup");
1185 String backup_name = file_name;
1186 backup_name += ".bak";
1187 rename_file(file_name, backup_name);
1189 rename_file(new_name, file_name);
1193 remove_file(new_name);
1203 state->abort(); // to close the file handles
1204 delete_aspell_speller(speller);
1206 remove_file(new_name);
1212 #define U (unsigned char)
1214 void Mapping::to_aspell()
1216 memset(this, 0, sizeof(Mapping));
1217 primary[Ignore ] = 'i';
1218 reverse[U'i'] = Ignore;
1219 reverse[U' '] = Ignore;
1220 reverse[U'\n'] = Ignore;
1222 primary[IgnoreAll ] = 'I';
1223 reverse[U'I'] = IgnoreAll;
1225 primary[Replace ] = 'r';
1226 reverse[U'r'] = Replace;
1228 primary[ReplaceAll] = 'R';
1229 reverse[U'R'] = ReplaceAll;
1231 primary[Add ] = 'a';
1232 reverse[U'A'] = Add;
1233 reverse[U'a'] = Add;
1235 primary[AddLower ] = 'l';
1236 reverse[U'L'] = AddLower;
1237 reverse[U'l'] = AddLower;
1239 primary[Abort ] = 'b';
1240 reverse[U'b'] = Abort;
1241 reverse[U'B'] = Abort;
1242 reverse[control('c')] = Abort;
1244 primary[Exit ] = 'x';
1245 reverse[U'x'] = Exit;
1246 reverse[U'X'] = Exit;
1249 void Mapping::to_ispell()
1251 memset(this, 0, sizeof(Mapping));
1252 primary[Ignore ] = ' ';
1253 reverse[U' '] = Ignore;
1254 reverse[U'\n'] = Ignore;
1256 primary[IgnoreAll ] = 'A';
1257 reverse[U'A'] = IgnoreAll;
1258 reverse[U'a'] = IgnoreAll;
1260 primary[Replace ] = 'R';
1261 reverse[U'R'] = ReplaceAll;
1262 reverse[U'r'] = Replace;
1264 primary[ReplaceAll] = 'E';
1265 reverse[U'E'] = ReplaceAll;
1266 reverse[U'e'] = Replace;
1268 primary[Add ] = 'I';
1269 reverse[U'I'] = Add;
1270 reverse[U'i'] = Add;
1272 primary[AddLower ] = 'U';
1273 reverse[U'U'] = AddLower;
1274 reverse[U'u'] = AddLower;
1276 primary[Abort ] = 'Q';
1277 reverse[U'Q'] = Abort;
1278 reverse[U'q'] = Abort;
1279 reverse[control('c')] = Abort;
1281 primary[Exit ] = 'X';
1282 reverse[U'X'] = Exit;
1283 reverse[U'x'] = Exit;
1287 ///////////////////////////
1294 AspellCanHaveError * ret
1295 = new_aspell_speller(reinterpret_cast<AspellConfig *>(options.get()));
1296 if (aspell_error(ret)) {
1297 print_error(aspell_error_message(ret));
1300 AspellSpeller * speller = to_aspell_speller(ret);
1302 state = new CheckerString(speller,stdin,0,64);
1306 while (state->next_misspelling()) {
1308 state->get_real_word(word);
1313 state.del(); // to close the file handles
1314 delete_aspell_speller(speller);
1317 ///////////////////////////
1326 const char * from = fix_encoding_str(args[0], buf1);
1327 const char * to = fix_encoding_str(args[1], buf2);
1328 Normalize norm = NormNone;
1329 if (strcmp(from, "utf-8") == 0 && strcmp(to, "utf-8") != 0)
1331 else if (strcmp(from, "utf-8") != 0 && strcmp(to, "utf-8") == 0)
1333 if (args.size() > 2) {
1334 for (String::iterator i = args[2].begin(); i != args[2].end(); ++i)
1335 *i = asc_tolower(*i);
1336 options->replace("normalize", "true");
1337 if (args[2] == "none")
1338 options->replace("normalize", "false");
1339 else if (args[2] == "internal")
1340 options->replace("norm-strict", "false");
1341 else if (args[2] == "strict")
1342 options->replace("norm-strict", "true");
1344 EXIT_ON_ERR(options->replace("norm-form", args[2]));
1346 EXIT_ON_ERR(conv.setup(*options, args[0], args[1], norm));
1348 while (CIN.getline(line))
1349 COUT.printl(conv(line));
1354 options->replace("normalize", "true");
1355 const char * from = args.size() < 3 ? "utf-8" : args[0].str();
1356 const char * to = args.size() < 3 ? "utf-8" : args[2].str();
1357 const char * intr = args.size() < 3 ? args[0].str() : args[1].str();
1358 String * form = (args.size() == 2 ? &args[1]
1359 : args.size() == 4 ? &args[3]
1361 Normalize decode_norm = NormTo;
1363 for (String::iterator i = form->begin(); i != form->end(); ++i)
1364 *i = asc_tolower(*i);
1365 if (*form == "internal") {
1366 options->replace("norm-strict", "false");
1367 decode_norm = NormNone;
1368 } else if (*form == "strict") {
1369 options->replace("norm-strict", "true");
1370 decode_norm = NormNone;
1372 if (decode_norm == NormTo) EXIT_ON_ERR(options->replace("norm-form", *form));
1375 EXIT_ON_ERR(encode.setup(*options, from, intr, NormFrom));
1376 EXIT_ON_ERR(decode.setup(*options, intr, to, decode_norm));
1378 while (CIN.getline(line))
1379 COUT.printl(decode(encode(line)));
1382 ///////////////////////////
1389 //assert(setvbuf(stdin, 0, _IOLBF, 0) == 0);
1390 //assert(setvbuf(stdout, 0, _IOLBF, 0) == 0);
1391 CERR << _("Sorry \"filter\" is currently unimplemented.\n");
1396 ///////////////////////////
1402 printf("@(#) International Ispell Version 3.1.20 "
1403 "(but really Aspell %s)\n", aspell_version_string());
1406 ///////////////////////////////////////////////////////////////////////
1408 // These functions use implementation details of the default speller
1412 class IstreamEnumeration : public StringEnumeration {
1416 IstreamEnumeration(FStream & i) : in(&i) {}
1417 IstreamEnumeration * clone() const {
1418 return new IstreamEnumeration(*this);
1420 void assign (const StringEnumeration * other) {
1421 *this = *static_cast<const IstreamEnumeration *>(other);
1424 if (!in->getline(data)) return 0;
1425 else return data.c_str();
1427 bool at_end() const {return *in;}
1430 ///////////////////////////
1437 using namespace aspeller;
1439 bool strict = args.size() != 0 && args[0] == "strict";
1441 Config * config = options;
1443 CachePtr<Language> lang;
1444 find_language(*config);
1445 PosibErr<Language *> res = new_language(*config);
1446 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1447 lang.reset(res.data);
1448 IstreamEnumeration in(CIN);
1449 WordListIterator wl_itr(&in, lang, &CERR);
1450 config->replace("validate-words", "true");
1451 config->replace("validate-affixes", "true");
1453 config->replace("clean-words", "true");
1454 config->replace("clean-affixes", "true");
1455 config->replace("skip-invalid-words", "true");
1456 wl_itr.init(*config);
1458 if (config->have("encoding")) {
1459 EXIT_ON_ERR(oconv.setup(*config, lang->charmap(), config->retrieve("encoding"), NormTo));
1460 oconv2.setup(*config, lang->charmap(), config->retrieve("encoding"), NormTo);
1462 EXIT_ON_ERR(oconv.setup(*config, lang->charmap(), lang->data_encoding(), NormTo));
1463 oconv2.setup(*config, lang->charmap(), lang->data_encoding(), NormTo);
1465 while (wl_itr.adv()) {
1466 if (*wl_itr->aff.str)
1467 COUT.printf("%s/%s\n", oconv(wl_itr->word), oconv2(wl_itr->aff));
1469 COUT.printl(oconv(wl_itr->word));
1473 ///////////////////////////
1478 void dump (aspeller::Dict * lws, Convert * conv)
1480 using namespace aspeller;
1482 switch (lws->basic_type) {
1483 case Dict::basic_dict:
1485 Dictionary * ws = static_cast<Dictionary *>(lws);
1486 StackPtr<WordEntryEnumeration> els(ws->detailed_elements());
1488 while (wi = els->next(), wi) {
1489 wi->write(COUT,*ws->lang(), conv);
1494 case Dict::multi_dict:
1496 StackPtr<DictsEnumeration> els(lws->dictionaries());
1498 while (ws = els->next(), ws)
1508 using namespace aspeller;
1510 if (args.size() != 0) {
1511 options->replace("master", args[0].c_str());
1514 Config * config = options;
1516 if (action == do_create) {
1518 find_language(*config);
1519 EXIT_ON_ERR(create_default_readonly_dict
1520 (new IstreamEnumeration(CIN),
1523 } else if (action == do_merge) {
1525 print_error(_("Can't merge a master word list yet. Sorry."));
1528 } else if (action == do_dump) {
1530 EXIT_ON_ERR_SET(add_data_set(config->retrieve("master-path"), *config), Dict *, d);
1531 StackPtr<Convert> conv(setup_conv(d->lang(), config));
1536 ///////////////////////////
1542 using namespace aspeller;
1544 if (args.size() != 0) {
1545 EXIT_ON_ERR(options->replace("personal", args[0]));
1547 options->replace("module", "aspeller");
1548 if (action == do_create || action == do_merge) {
1549 CERR << _("Sorry \"create/merge personal\" is currently unimplemented.\n");
1554 StackPtr<Speller> speller(new_speller(options));
1556 if (action == do_create) {
1557 if (file_exists(speller->config()->retrieve("personal-path"))) {
1558 print_error(_("Sorry I won't overwrite \"%s\""),
1559 speller->config()->retrieve("personal-path"));
1562 speller->personal_word_list().data->clear();
1567 speller->add_to_personal(word);
1569 speller->save_all_word_lists();
1572 } else { // action == do_dump
1574 // FIXME: This is currently broken
1576 Config * config = options;
1577 Dictionary * per = new_default_writable_dict();
1578 EXIT_ON_ERR(per->load(config->retrieve("personal-path"), *config));
1579 StackPtr<WordEntryEnumeration> els(per->detailed_elements());
1580 StackPtr<Convert> conv(setup_conv(per->lang(), config));
1583 while (wi = els->next(), wi) {
1584 wi->write(COUT,*(per->lang()), conv);
1591 ///////////////////////////
1597 using namespace aspeller;
1599 if (args.size() != 0) {
1600 options->replace("repl", args[0].c_str());
1603 if (action == do_create || action == do_merge) {
1605 CERR << _("Sorry \"create/merge repl\" is currently unimplemented.\n");
1610 SpellerImpl speller(options);
1612 if (action == do_create) {
1613 if (file_exists(speller->config()->retrieve("repl-path"))) {
1614 print_error(_("Sorry I won't overwrite \"%s\""),
1615 speller->config()->retrieve("repl-path"));
1618 speller->personal_repl().clear();
1625 get_word_pair(word,repl,':');
1626 EXIT_ON_ERR(speller->store_repl(word,repl,false));
1629 } catch (bad_cin) {}
1631 EXIT_ON_ERR(speller->personal_repl().synchronize());
1635 } else if (action == do_dump) {
1637 // FIXME: This is currently broken
1639 ReplacementDict * repl = new_default_replacement_dict();
1640 repl->load(options->retrieve("repl-path"), *options);
1641 StackPtr<WordEntryEnumeration> els(repl->detailed_elements());
1645 Conv conv(setup_conv(repl->lang(), options));
1646 while ((rl = els->next())) {
1647 repl->repl_lookup(*rl, words);
1649 COUT << conv(rl->word) << ": " << conv(words.word) << "\n";
1650 } while (words.adv());
1656 //////////////////////////
1662 using namespace aspeller;
1663 CachePtr<Language> lang;
1664 find_language(*options);
1665 PosibErr<Language *> res = new_language(*options);
1666 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1667 lang.reset(res.data);
1668 Conv iconv(setup_conv(options, lang));
1669 Conv oconv(setup_conv(lang, options));
1673 while (CIN.getline(word)) {
1674 const char * w = iconv(word);
1675 lang->LangImpl::to_soundslike(sl, w);
1676 printf("%s\t%s\n", word.str(), oconv(sl));
1680 //////////////////////////
1687 using namespace aspeller;
1688 CachePtr<Language> lang;
1689 find_language(*options);
1690 PosibErr<Language *> res = new_language(*options);
1691 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1692 lang.reset(res.data);
1693 Conv iconv(setup_conv(options, lang));
1694 Conv oconv(setup_conv(lang, options));
1698 while (CIN.getline(word)) {
1699 lang->munch(iconv(word), &gi);
1701 for (const aspeller::CheckInfo * ci = gi.head; ci; ci = ci->next)
1703 COUT << ' ' << oconv(ci->word) << '/';
1704 if (ci->pre_flag != 0) COUT << oconv(static_cast<char>(ci->pre_flag));
1705 if (ci->suf_flag != 0) COUT << oconv(static_cast<char>(ci->suf_flag));
1711 //////////////////////////
1719 if (args.size() > 0)
1720 level = atoi(args[0].c_str()); //FIXME: More verbose
1721 int limit = INT_MAX;
1722 if (args.size() > 1)
1723 limit = atoi(args[1].c_str());
1725 using namespace aspeller;
1726 CachePtr<Language> lang;
1727 find_language(*options);
1728 PosibErr<Language *> res = new_language(*options);
1729 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1730 lang.reset(res.data);
1731 Conv iconv(setup_conv(options, lang));
1732 Conv oconv(setup_conv(lang, options));
1737 while (CIN.getline(word)) {
1739 char * w = iconv(buf.mstr(), buf.size());
1740 char * af = strchr(w, '/');
1750 exp_list = lang->expand(w, af, exp_buf, limit);
1753 COUT << word << ' ';
1754 WordAff * p = exp_list;
1756 COUT << oconv(p->word);
1757 if (limit < INT_MAX && p->aff[0]) COUT << '/' << oconv((const char *)p->aff);
1762 } else if (level >= 3) {
1765 for (WordAff * p = exp_list; p; p = p->next)
1766 ratio += p->word.size;
1767 ratio /= exp_list->word.size; // it is assumed the first
1768 // expansion is just the root
1770 for (WordAff * p = exp_list; p; p = p->next) {
1771 COUT << word << ' ' << oconv(p->word);
1772 if (limit < INT_MAX && p->aff[0]) COUT << '/' << oconv((const char *)p->aff);
1773 if (level >= 4) COUT.printf(" %f\n", ratio);
1780 //////////////////////////
1785 static void combine_aff(String & aff, const char * app)
1787 for (; *app; ++app) {
1788 if (!memchr(aff.c_str(),*app,aff.size()))
1789 aff.push_back(*app);
1793 static void print_wordaff(const String & base, const String & affs, Conv & oconv)
1795 if (base.empty()) return;
1796 COUT << oconv(base);
1800 COUT.printf("/%s\n", oconv(affs));
1803 static bool lower_equal(aspeller::Language * l, ParmString a, ParmString b)
1805 if (a.size() != b.size()) return false;
1806 if (l->to_lower(a[0]) != l->to_lower(b[0])) return false;
1807 return memcmp(a + 1, b + 1, a.size() - 1) == 0;
1812 using namespace aspeller;
1813 CachePtr<Language> lang;
1814 find_language(*options);
1815 PosibErr<Language *> res = new_language(*options);
1816 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1817 lang.reset(res.data);
1818 Conv iconv(setup_conv(options, lang));
1819 Conv oconv(setup_conv(lang, options));
1824 while (CIN.getline(word)) {
1827 CharVector buf; buf.append(word.c_str(), word.size() + 1);
1828 char * w = buf.data();
1829 char * af = strchr(w, '/');
1839 if (lower_equal(lang, base, w)) {
1840 if (lang->is_lower(base.str())) {
1841 combine_aff(affs, af);
1844 combine_aff(affs, af);
1847 print_wordaff(base, affs, oconv);
1853 print_wordaff(base, affs, oconv);
1856 //////////////////////////
1861 void munch_list_simple();
1862 void munch_list_complete(bool, bool);
1866 bool simple = false;
1868 bool simplify = true;
1870 for (unsigned i = 0; i < args.size(); ++i) {
1871 if (args[i] == "simple") simple = true;
1872 else if (args[i] == "single") multi = false;
1873 else if (args[i] == "multi") multi = true;
1874 else if (args[i] == "keep") simplify = false;
1877 print_error(_("\"%s\" is not a valid flag for the \"munch-list\" command."),
1883 munch_list_simple();
1885 munch_list_complete(multi, simplify);
1889 // munch list (simple version)
1892 // This version works the same way as the myspell "munch" program.
1893 // However, because the results depends on the hash table used and the
1894 // order of the word list it wonn't produce identical results.
1896 struct SML_WordEntry {
1899 bool keep; // boolean
1900 SML_WordEntry(const char * w = 0) : word(w), aff(0), keep(false) {}
1904 typedef SML_WordEntry Value;
1905 typedef const char * Key;
1906 static const bool is_multi = false;
1907 acommon::hash<const char *> hash;
1908 bool equal(Key x, Key y) {return strcmp(x,y) == 0;}
1909 Key key(const Value & v) {return v.word;}
1912 typedef HashTable<SML_Parms> SML_Table;
1914 static inline void add_affix(SML_Table::iterator b, char aff)
1917 if (p) {while (*p) {if (*p == aff) return; ++p;}}
1919 b->aff = (char *)realloc(b->aff, s + 2);
1920 b->aff[s + 0] = aff;
1921 b->aff[s + 1] = '\0';
1924 void munch_list_simple()
1926 using namespace aspeller;
1927 CachePtr<Language> lang;
1928 find_language(*options);
1929 PosibErr<Language *> res = new_language(*options);
1930 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
1931 lang.reset(res.data);
1932 Conv iconv(setup_conv(options, lang));
1933 Conv oconv(setup_conv(lang, options));
1941 // add words to dictionary
1942 while (CIN.getline(word)) {
1944 char * w = iconv(buf.mstr(), buf.size());
1945 char * af = strchr(w, '/');
1955 exp_list = lang->expand(w, af, exp_buf);
1956 for (WordAff * q = exp_list; q; q = q->next) {
1957 table.insert(SML_WordEntry(table_buf.dup(q->word)));
1961 // now try to munch each word in the dictionary
1962 SML_Table::iterator p = table.begin();
1963 SML_Table::iterator end = table.end();
1965 for (; p != end; ++p)
1967 const aspeller::CheckInfo * best = 0;
1968 unsigned min_base_size = INT_MAX;
1969 lang->munch(p->word, &gi);
1970 const aspeller::CheckInfo * ci = gi.head;
1973 // check if the base word is in the dictionary
1974 SML_Table::iterator b = table.find(ci->word);
1975 if (b == table.end()) goto cont;
1977 // check if all the words once expanded are in the dictionary
1978 // this included the exiting flags due to pre-suf cross products
1979 if (b->aff) flags = b->aff;
1981 if (ci->pre_flag != 0) flags += ci->pre_flag;
1982 if (ci->suf_flag != 0) flags += ci->suf_flag;
1984 exp_list = lang->expand(ci->word, flags, exp_buf);
1985 for (WordAff * q = exp_list; q; q = q->next) {
1986 if (!table.have(q->word)) goto cont;
1989 // the base word and flags are valid, now keep the one with the
1990 // smallest base word
1991 if (ci->word.size() < min_base_size) {
1992 min_base_size = ci->word.size();
1999 // now add the base to the keep list if one exists
2000 // otherwise just keep the original word
2002 SML_Table::iterator b = table.find(best->word);
2003 assert(b != table.end());
2004 if (best->pre_flag) add_affix(b, best->pre_flag);
2005 if (best->suf_flag) add_affix(b, best->suf_flag);
2012 // Print the entries in the table marked as "to keep"
2014 for (; p != end; ++p)
2017 COUT << oconv(p->word);
2019 COUT << '/' << oconv(p->aff);
2026 for (; p != end; ++p)
2028 if (p->aff) free(p->aff);
2034 // munch list (complete version)
2037 // This version will produce a smaller list than the simple version.
2038 // It is very close to the optimum result.
2042 // Hash table to store the words
2051 CML_Entry(const char * w = 0) : word(w), aff(0), parent(0), next(0), rank(0) {}
2055 typedef CML_Entry Value;
2056 typedef const char * Key;
2057 static const bool is_multi = true;
2058 acommon::hash<const char *> hash;
2059 bool equal(Key x, Key y) {return strcmp(x,y) == 0;}
2060 Key key(const Value & v) {return v.word;}
2063 typedef HashTable<CML_Parms> CML_Table;
2066 // add an affix to a word but keep the prefixes and suffixes separate
2069 static void add_affix(CML_Table::iterator b, char aff, bool prefix)
2075 if (*p == aff) return;
2078 s = (p - b->aff) + 2;
2080 char * tmp = (char *)malloc(s);
2083 if (p) {while (*p != '/') *q++ = *p++;}
2084 if (prefix) *q++ = aff;
2086 if (p) {p++; while (*p != '\0') *q++ = *p++;}
2087 if (!prefix) *q++ = aff;
2089 assert(q - tmp == s);
2090 if (b->aff) free(b->aff);
2095 // Standard disjoint set algo with union by rank and path compression
2098 static void link(CML_Entry * x, CML_Entry * y)
2101 if (x->rank > y->rank) {
2105 if (x->rank == y->rank) y->rank++;
2109 static CML_Entry * find_set (CML_Entry * x)
2112 return x->parent = find_set(x->parent);
2118 // Stuff to manage prefix-suffix combinations
2124 String & get(int i) {return i == 0 ? pre : suf;}
2125 const String & get(int i) const {return i == 0 ? pre : suf;}
2126 PreSuf() : next(0) {}
2133 PreSufList() : head(0) {}
2134 void add(PreSuf * to_add) {
2135 to_add->next = head;
2140 PreSuf * tmp = head;
2145 void transfer(PreSufList & other) {
2156 // Example of usage:
2157 // combine(in, res, 0)
2158 // Pre: in = [(ab, c) (ab, d) (c, de) (c, ef)]
2159 // Post: res = [(ab, cd), (c, def)]
2160 static void combine(const PreSufList & in, PreSufList & res, int which)
2162 const PreSuf * i = in.head;
2164 const String & s = i->get(which);
2165 for (const PreSuf * j = in.head; j != i; j = j->next) {
2166 if (j->get(which) == s) goto cont;
2168 PreSuf * tmp = new PreSuf;
2171 String & b = tmp->get(!which);
2172 for (const PreSuf * j = i->next; j; j = j->next) {
2173 if (j->get(which) != s) continue;
2174 const String & a = j->get(!which);
2175 for (String::const_iterator x = a.begin(); x != a.end(); ++x) {
2176 if (memchr(b.data(), *x, b.size())) continue;
2187 // Stuff used when pruning the list of base words
2192 char * aff; // modifying this will modify the affix entry in the hash table
2193 std::vector<bool> exp;
2194 std::vector<bool> orig_exp;
2197 // static void dump(const Vector<Expansion *> & working,
2198 // const Vector<CML_Table::iterator> & entries)
2200 // for (unsigned i = 0; i != working.size(); ++i) {
2201 // if (!working[i]) continue;
2202 // CERR.printf("%s/%s ", working[i]->word, working[i]->aff);
2203 // for (unsigned j = 0; j != working[i]->exp.size(); ++j) {
2204 // if (working[i]->exp[j])
2205 // CERR.printf("%s ", entries[j]->word);
2212 // standard set algorithms on a bit vector
2214 static bool subset(const std::vector<bool> & smaller,
2215 const std::vector<bool> & larger)
2217 assert(smaller.size() == larger.size());
2218 unsigned s = larger.size();
2219 for (unsigned i = 0; i != s; ++i) {
2220 if (smaller[i] && !larger[i]) return false;
2225 static void merge(std::vector<bool> & x, const std::vector<bool> & y)
2227 assert(x.size() == y.size());
2228 unsigned s = x.size();
2229 for (unsigned i = 0; i != s; ++i) {
2230 if (y[i]) x[i] = true;
2234 static void purge(std::vector<bool> & x, const std::vector<bool> & y)
2236 assert(x.size() == y.size());
2237 unsigned s = x.size();
2238 for (unsigned i = 0; i != s; ++i) {
2239 if (y[i]) x[i] = false;
2243 static inline unsigned count(const std::vector<bool> & x) {
2245 for (unsigned i = 0; i != x.size(); ++i) {
2254 bool operator() (Expansion * x, Expansion * y) {
2256 // LARGEST number of expansions
2257 unsigned x_s = count(x->exp);
2258 unsigned y_s = count(y->exp);
2259 if (x_s != y_s) return x_s > y_s;
2261 // SMALLEST base word
2262 x_s = strlen(x->word);
2263 y_s = strlen(y->word);
2264 if (x_s != y_s) return x_s < y_s;
2266 // LARGEST affix string
2267 x_s = strlen(x->aff);
2268 y_s = strlen(y->aff);
2269 if (x_s != y_s) return x_s > y_s;
2272 int cmp = strcmp(x->word, y->word);
2273 if (cmp != 0) return cmp < 0;
2276 cmp = strcmp(x->aff, y->aff);
2282 // Finally the function that does the real work
2285 void munch_list_complete(bool multi, bool simplify)
2287 using namespace aspeller;
2288 CachePtr<Language> lang;
2289 find_language(*options);
2290 PosibErr<Language *> res = new_language(*options);
2291 if (res.has_err()) {print_error(res.get_err()->mesg); exit(1);}
2292 lang.reset(res.data);
2293 Conv iconv(setup_conv(options, lang));
2294 Conv oconv(setup_conv(lang, options));
2302 // add words to dictionary
2303 while (CIN.getline(word)) {
2305 char * w = iconv(buf.mstr(), buf.size());
2306 char * af = strchr(w, '/');
2316 exp_list = lang->expand(w, af, exp_buf);
2317 for (WordAff * q = exp_list; q; q = q->next) {
2318 if (!table.have(q->word)) // since it is a multi hash table
2319 table.insert(CML_Entry(table_buf.dup(q->word))).first;
2323 // Now try to munch each word in the dictionary. This will also
2324 // group the base words into disjoint sets based on there expansion.
2325 CML_Table::iterator p = table.begin();
2326 CML_Table::iterator end = table.end();
2328 for (; p != end; ++p)
2330 lang->munch(p->word, &gi, false);
2331 const aspeller::CheckInfo * ci = gi.head;
2334 // check if the base word is in the dictionary
2335 CML_Table::iterator b = table.find(ci->word);
2336 if (b == table.end()) goto cont;
2338 // check if all the words once expanded are in the dictionary
2340 assert(!(ci->pre_flag && ci->suf_flag));
2341 if (ci->pre_flag != 0) flags[0] = ci->pre_flag;
2342 else if (ci->suf_flag != 0) flags[0] = ci->suf_flag;
2345 exp_list = lang->expand(ci->word, flags, exp_buf);
2346 for (WordAff * q = exp_list; q; q = q->next) {
2347 if (!table.have(q->word)) goto cont;
2350 // all the expansions are in the dictionary now add the affix to
2351 // the base word and figure out which disjoint set it belongs to
2352 add_affix(b, flags[0], ci->pre_flag != 0);
2353 CML_Entry * bs = find_set(&*b);
2354 for (WordAff * q = exp_list; q; q = q->next) {
2355 CML_Table::iterator w = table.find(q->word);
2356 assert(b != table.end());
2357 CML_Entry * ws = find_set(&*w);
2366 // If a base word has both prefixes and suffixes try to combine them.
2367 // This can lead to multiple entries for the same base word. If "multi"
2368 // is true than include all the entries. Otherwise, only include the
2369 // one with the largest number of expansions. This is a greedy choice
2370 // that may not be optimal, but is close to it.
2373 CML_Entry * extras = 0;
2374 for (; p != end; ++p)
2376 pre.clear(); suf.clear();
2377 if (!p->aff) continue;
2379 while (*s != '/') pre += *s++;
2381 while (*s != '\0') suf += *s++;
2384 strcpy(p->aff, suf.str());
2386 } else if (suf.empty()) {
2388 strcpy(p->aff, pre.str());
2392 // Try all possible combinations and keep the ones which expand
2395 PreSufList cross,tmp1,tmp2;
2398 for (String::iterator pi = pre.begin(); pi != pre.end(); ++pi) {
2399 String::iterator si = suf.begin();
2400 while (si != suf.end()) { {
2401 char flags[3] = {*pi, *si, '\0'};
2403 exp_list = lang->expand(p->word, flags, exp_buf);
2404 for (WordAff * q = exp_list; q; q = q->next) {
2405 if (!table.have(q->word)) goto cont2;
2416 // Now combine the legal cross pairs with other ones when
2419 // final res = [ (pre, []) ([],suf),
2420 // (cross | combine first | combine second)
2421 // (cross | combine second | combine first)
2425 // combine first [(ab, c) (ab, d) (c, de) (c, ef)]
2426 // = [(ab, cd), (c, def)]
2428 combine(cross, tmp1, 0);
2429 combine(tmp1, tmp2, 1);
2432 combine(cross, tmp1, 1);
2433 combine(tmp1, tmp2, 0);
2445 combine(tmp2, tmp1, 0);
2446 combine(tmp1, cross, 1);
2450 // It is OK to have multiple entries with the same base word
2455 memcpy(p->aff, ps->pre.data(), ps->pre.size());
2456 memcpy(p->aff + ps->pre.size(), ps->suf.str(), ps->suf.size() + 1);
2459 CML_Entry * bs = find_set(&*p);
2460 for (; ps; ps = ps->next) {
2462 CML_Entry * tmp = new CML_Entry;
2463 tmp->word = p->word;
2464 tmp->aff = (char *)malloc(ps->pre.size() + ps->suf.size() + 1);
2465 memcpy(tmp->aff, ps->pre.data(), ps->pre.size());
2466 memcpy(tmp->aff + ps->pre.size(), ps->suf.str(), ps->suf.size() + 1);
2476 // chose the one which has the largest number of expansions
2482 for (ps = cross.head; ps; ps = ps->next) {
2486 exp_list = lang->expand(p->word, flags, exp_buf);
2488 for (WordAff * q = exp_list; q; q = q->next) ++c;
2489 if (c > max_exp) {max_exp = c; best = ps;}
2492 memcpy(p->aff, best->pre.data(), best->pre.size());
2493 memcpy(p->aff + best->pre.size(), best->suf.str(), best->suf.size() + 1);
2499 CML_Entry * tmp = extras;
2500 extras = extras->next;
2506 // Create a linked list for each disjoint set
2508 for (; p != end; ++p)
2511 CML_Entry * bs = find_set(&*p);
2518 // Now process each disjoint set independently
2520 for (; p != end; ++p)
2522 if (p->parent) continue;
2524 Vector<CML_Table::iterator> entries;
2525 Vector<Expansion> expansions;
2526 Vector<Expansion *> to_keep;
2527 std::vector<bool> to_keep_exp;
2528 Vector<Expansion *> working;
2529 Vector<unsigned> to_remove;
2531 // First assign numbers to each unique word. The rank field is
2532 // no longer used so use it to store the number.
2533 for (CML_Entry * q = &*p; q; q = q->next) {
2534 CML_Table::iterator e = table.find(q->word);
2535 if (e->rank == -1) {
2536 e->rank = entries.size();
2537 q->rank = entries.size();
2538 entries.push_back(e);
2546 expansions.push_back(tmp);
2550 to_keep_exp.resize(entries.size());
2551 //for (int i = 0; i != to_keep_exp.size(); ++i) {
2552 // printf(">>> %d %d\n", i, (int)to_keep_exp[i]);
2555 // Store the expansion of each base word in a bit vector and
2556 // add it to the working set
2557 for (Vector<Expansion>::iterator q = expansions.begin();
2558 q != expansions.end();
2561 q->exp.resize(entries.size());
2563 exp_list = lang->expand(q->word, q->aff, exp_buf);
2564 for (WordAff * i = exp_list; i; i = i->next) {
2565 CML_Table::iterator e = table.find(i->word);
2566 assert(0 <= e->rank && e->rank < (int)entries.size());
2567 q->exp[e->rank] = true;
2569 q->orig_exp = q->exp;
2570 working.push_back(&*q);
2573 unsigned prev_working_size = INT_MAX;
2575 // This loop will repeat until the working set is empty. This
2576 // will produce optimum results in most cases. Non optimum
2577 // results may be possible if step (4) is necessary, but in
2578 // practice this step is rarly necessary.
2580 prev_working_size = working.size();
2582 // Sort the list based on WorkingLt. This is necessary every
2583 // time since the expansion list can change.
2584 std::sort(working.begin(), working.end(), WorkingLt());
2586 // (1) Eliminate any elements which are a subset of others
2587 for (unsigned i = 0; i != working.size(); ++i) {
2588 if (!working[i]) continue;
2589 for (unsigned j = i + 1; j != working.size(); ++j) {
2590 if (!working[j]) continue;
2591 if (subset(working[j]->exp, working[i]->exp)) {
2597 // (2) Move any elements which expand to unique entree
2598 // into the to_keep list
2600 for (unsigned i = 0; i != entries.size(); ++i) {
2602 for (unsigned j = 0; j != working.size(); ++j) {
2603 if (working[j] && working[j]->exp[i]) {
2608 if (n >= 0) to_remove.push_back(n);
2610 for (unsigned i = 0; i != to_remove.size(); ++i) {
2611 unsigned n = to_remove[i];
2612 if (!working[n]) continue;
2613 to_keep.push_back(working[n]);
2614 merge(to_keep_exp, working[n]->exp);
2618 // (3) Eliminate any elements which are a subset of all the
2619 // elements in the to_keep list
2620 for (unsigned i = 0; i != working.size(); ++i) {
2621 if (working[i] && subset(working[i]->exp, to_keep_exp)) {
2626 // Compact the working list
2629 while (j != (int)working.size()) {
2631 working[i] = working[j];
2639 // (4) If none of the entries in working have been removed via
2640 // the above methods then make a greedy choice and move the
2641 // first element into the to_keep list.
2642 if (working.size() > 0 && working.size() == prev_working_size)
2644 to_keep.push_back(working[0]);
2645 //CERR.printf("Making greedy choice! Choosing %s/%s.\n",
2646 // working[0]->word, working[0]->aff);
2647 merge(to_keep_exp, working[0]->exp);
2648 working.erase(working.begin(), working.begin() + 1);
2651 // (5) Trim the expansion list for any elements left in the
2652 // working set by removing the expansions that already exist in
2654 for (unsigned i = 0; i != working.size(); ++i) {
2655 purge(working[i]->exp, to_keep_exp);
2658 } while (working.size() > 0);
2662 // Remove unnecessary flags. A flag is unnecessary if it does
2663 // does not expand to any new words, that is words that are not
2664 // already covered by an earlier entries in the list.
2666 for (unsigned i = 0; i != to_keep.size(); ++i) {
2667 to_keep[i]->exp = to_keep[i]->orig_exp;
2670 std::sort(to_keep.begin(), to_keep.end(), WorkingLt());
2672 std::vector<bool> tally(entries.size());
2673 std::vector<bool> backup(entries.size());
2674 std::vector<bool> working(entries.size());
2677 for (unsigned i = 0; i != to_keep.size(); ++i) {
2681 merge(tally, to_keep[i]->exp);
2683 String flags_to_keep = to_keep[i]->aff;
2684 bool something_changed;
2686 something_changed = false;
2687 for (unsigned j = 0; j != flags_to_keep.size(); ++j) {
2688 flags.assign(flags_to_keep.data(), j);
2689 flags.append(flags_to_keep.data(j+1),
2690 flags_to_keep.size() - (j+1));
2693 exp_list = lang->expand(to_keep[i]->word, flags, exp_buf);
2694 for (WordAff * q = exp_list; q; q = q->next) {
2695 CML_Table::iterator e = table.find(q->word);
2696 working[e->rank] = true;
2698 if (working == tally) {
2699 flags_to_keep = flags;
2700 something_changed = true;
2704 } while (something_changed);
2706 if (flags_to_keep != to_keep[i]->aff) {
2707 memcpy(to_keep[i]->aff, flags_to_keep.str(), flags_to_keep.size() + 1);
2713 // Finally print the resulting list
2715 //printf("XXX %d %d\n", to_keep.size(), to_keep_exp.size());
2716 //for (int i = 0; i != to_keep_exp.size(); ++i) {
2717 // printf(">>> %d %d\n", i, (int)to_keep_exp[i]);
2720 for (unsigned i = 0; i != to_keep.size(); ++i) {
2721 COUT << oconv(to_keep[i]->word);
2722 if (to_keep[i]->aff[0]) {
2724 COUT << oconv(to_keep[i]->aff);
2728 for (unsigned i = 0; i != to_keep_exp.size(); ++i) {
2729 if (!to_keep_exp[i]) {
2730 assert(!entries[i]->aff);
2731 COUT.printf("%s\n", oconv(entries[i]->word));
2737 for (; p != end; ++p)
2739 if (p->aff) free(p->aff);
2745 //////////////////////////
2753 EXIT_ON_ERR(aspeller::open_affix_file(*options, in));
2756 while (in.getline(line))
2757 COUT << line << '\n';
2762 ///////////////////////////////////////////////////////////////////////
2765 ///////////////////////////
2770 void print_help_line(char abrv, char dont_abrv, const char * name,
2771 KeyInfoType type, const char * desc, bool no_dont = false)
2777 if (dont_abrv != '\0') {
2780 command += dont_abrv;
2785 if (type == KeyInfoBool && !no_dont) command += "[dont-]";
2786 if (type == KeyInfoList) command += "add|rem-";
2788 if (type == KeyInfoString || type == KeyInfoList)
2789 command += "=<str>";
2790 if (type == KeyInfoInt)
2791 command += "=<int>";
2792 const char * tdesc = _(desc);
2793 printf(" %-27s %s\n", command.c_str(), tdesc); // FIXME: consider word wrapping
2797 PosibErr<ConfigModule *> get_dynamic_filter(Config * config, ParmStr value);
2800 static const char * usage_text[] =
2802 /* TRANSLATORS: These should all be formated to fit in 80 column or
2804 N_("Usage: aspell [options] <command>"),
2805 N_("<command> is one of:"),
2806 N_(" -?|usage display a brief usage message"),
2807 N_(" help display a detailed help message"),
2808 N_(" -c|check <file> to check a file"),
2809 N_(" -a|pipe \"ispell -a\" compatibility mode"),
2810 N_(" [dump] config dumps the current configuration to stdout"),
2811 N_(" config <key> prints the current value of an option"),
2812 N_(" [dump] dicts | filters | modes"),
2813 N_(" lists available dictionaries / filters / filter modes"),
2814 N_("[options] is any of the following:")
2816 static const unsigned usage_text_size = sizeof(usage_text)/sizeof(const char *);
2818 static const char * help_text[] =
2827 N_(" list produce a list of misspelled words from standard input"),
2830 N_(" soundslike returns the sounds like equivalent for each word entered"),
2831 N_(" munch generate possible root words and affixes"),
2832 N_(" expand [1-4] expands affix flags"),
2833 N_(" clean [strict] cleans a word list so that every line is a valid word"),
2834 //N_(" filter passes standard input through filters"),
2835 N_(" -v|version prints a version line"),
2836 N_(" munch-list [simple] [single|multi] [keep]"),
2837 N_(" reduce the size of a word list via affix compression"),
2838 N_(" conv <from> <to> [<norm-form>]"),
2839 N_(" converts from one encoding to another"),
2840 N_(" norm (<norm-map> | <from> <norm-map> <to>) [<norm-form>]"),
2841 N_(" perform Unicode normalization"),
2844 N_(" dump|create|merge master|personal|repl [<name>]"),
2845 N_(" dumps, creates or merges a master, personal, or replacement dictionary."),
2847 /* TRANSLATORS: "none", "internal" and "strict" are literal values
2848 and should not be translated. */
2849 N_(" <norm-form> normalization form to use, either none, internal, or strict"),
2854 static const unsigned help_text_size = sizeof(help_text)/sizeof(const char *);
2856 void print_help (bool verbose) {
2857 load_all_filters(options);
2860 "Aspell %s. Copyright 2000-2011 by Kevin Atkinson.\n"
2861 "\n"), aspell_version_string());
2862 for (unsigned i = 0; i < help_text_size; ++i)
2863 puts(gt_(help_text[i]));
2865 for (unsigned i = 0; i < usage_text_size; ++i)
2866 puts(gt_(usage_text[i]));
2868 StackPtr<KeyInfoEnumeration> els(options->possible_elements(true,false));
2870 while (k = els->next(), k) {
2871 if (k->desc == 0 || k->flags & KEYINFO_HIDDEN) continue;
2872 if (!verbose && !(k->flags & KEYINFO_COMMON)) continue;
2873 const PossibleOption * o = find_option(k->name);
2874 const char * name = k->name;
2875 print_help_line(o->abrv,
2876 strncmp((o+1)->name, "dont-", 5) == 0 ? (o+1)->abrv : '\0',
2877 name, k->type, k->desc);
2878 if (verbose && strcmp(name, "mode") == 0) {
2879 for (const ModeAbrv * j = mode_abrvs;
2880 j != mode_abrvs_end;
2883 print_help_line(j->abrv, '\0', j->mode, KeyInfoBool, j->desc, true);
2893 _("Available Dictionaries:\n"
2894 " Dictionaries can be selected directly via the \"-d\" or \"master\"\n"
2895 " option. They can also be selected indirectly via the \"lang\",\n"
2896 " \"variety\", and \"size\" options.\n"));
2898 const DictInfoList * dlist = get_dict_info_list(options);
2900 StackPtr<DictInfoEnumeration> dels(dlist->elements());
2902 const DictInfo * entry;
2904 while ( (entry = dels->next()) != 0)
2906 printf(" %s\n", entry->name);
2914 _("Available Filters (and associated options):\n"
2915 " Filters can be added or removed via the \"filter\" option.\n"),
2917 for (Vector<ConfigModule>::const_iterator m = options->filter_modules.begin();
2918 m != options->filter_modules.end();
2921 printf(_("\n %s filter: %s\n"), m->name, gt_(m->desc));
2922 for (k = m->begin; k != m->end; ++k) {
2923 const PossibleOption * o = find_option(k->name);
2924 const char * name = k->name;
2925 const KeyInfo * ok = options->keyinfo(name + 2);
2926 if (k == ok) name += 2;
2927 print_help_line(o->abrv,
2928 strncmp((o+1)->name, "dont-", 5) == 0 ? (o+1)->abrv : '\0',
2929 name, k->type, k->desc);
2937 /* TRANSLATORS: This should be formated to fit in 80 column or less */
2938 _("Available Filter Modes:\n"
2939 " Filter Modes are reconfigured combinations of filters optimized for\n"
2940 " files of a specific type. A mode is selected via the \"mode\" option.\n"
2941 " This will happen implicitly if Aspell is able to identify the file\n"
2942 " type from the extension, and possibility the contents, of the file.\n"));
2944 EXIT_ON_ERR_SET(available_filter_modes(options), StringPairEnumeration *, els);
2946 while (!els->at_end()) {
2948 printf(" %-14s %s\n", sp.first, gt_(sp.second));