1 /*
2  * 文節に対して候補のリストを生成する。
3  * make_candidates()がcontext管理部から呼ばれる。
4  *
5  * 候補の生成は次の方法で行う
6  * (1)splitterが割り当てた品詞に対してproc_splitter_info()
7  *    から候補を生成する
8  * (2)ひらがなのみとカタカナのみの候補を生成する
9  * (3)最後の文字を助詞と解釈して無理矢理候補を生成する
10  */
11 /*
12  * Funded by IPA未踏ソフトウェア創造事業 2001 9/30
13  * Copyright (C) 2000-2005 TABATA Yusuke
14  * Copyright (C) 2004-2005 YOSHIDA Yuichi
15  * Copyright (C) 2002 UGAWA Tomoharu
16  *
17  */
18 /*
19   This library is free software; you can redistribute it and/or
20   modify it under the terms of the GNU Lesser General Public
21   License as published by the Free Software Foundation; either
22   version 2 of the License, or (at your option) any later version.
23 
24   This library is distributed in the hope that it will be useful,
25   but WITHOUT ANY WARRANTY; without even the implied warranty of
26   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
27   Lesser General Public License for more details.
28 
29   You should have received a copy of the GNU Lesser General Public
30   License along with this library; if not, write to the Free Software
31   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
32  */
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include <anthy/dic.h>
38 #include <anthy/splitter.h>
39 #include <anthy/segment.h>
40 #include "wordborder.h"
41 
42 
43 static struct cand_ent *
alloc_cand_ent(void)44 alloc_cand_ent(void)
45 {
46   struct cand_ent *ce;
47   ce = (struct cand_ent *)malloc(sizeof(struct cand_ent));
48   ce->nr_words = 0;
49   ce->elm = NULL;
50   ce->mw = NULL;
51   ce->core_elm_index = -1;
52   ce->dep_word_hash = 0;
53   return ce;
54 }
55 
56 /*
57  * 候補を複製する
58  */
59 static struct cand_ent *
dup_candidate(struct cand_ent * ce)60 dup_candidate(struct cand_ent *ce)
61 {
62   struct cand_ent *ce_new;
63   int i;
64   ce_new = alloc_cand_ent();
65   ce_new->nr_words = ce->nr_words;
66   ce_new->str.len = ce->str.len;
67   ce_new->str.str = anthy_xstr_dup_str(&ce->str);
68   ce_new->elm = malloc(sizeof(struct cand_elm)*ce->nr_words);
69   ce_new->flag = ce->flag;
70   ce_new->core_elm_index = ce->core_elm_index;
71   ce_new->mw = ce->mw;
72   ce_new->score = ce->score;
73   ce_new->dep_word_hash = ce->dep_word_hash;
74 
75   for (i = 0 ; i < ce->nr_words ; i++) {
76     ce_new->elm[i] = ce->elm[i];
77   }
78   return ce_new;
79 }
80 
81 /** 文節に候補を追加する */
82 static void
push_back_candidate(struct seg_ent * seg,struct cand_ent * ce)83 push_back_candidate(struct seg_ent *seg, struct cand_ent *ce)
84 {
85   /* seg_entに候補ceを追加 */
86   seg->nr_cands++;
87   seg->cands = (struct cand_ent **)
88     realloc(seg->cands, sizeof(struct cand_ent *) * seg->nr_cands);
89   seg->cands[seg->nr_cands - 1] = ce;
90   /**/
91   if (anthy_splitter_debug_flags() & SPLITTER_DEBUG_CAND) {
92     anthy_print_candidate(ce);
93     printf("\n");
94   }
95 }
96 
97 static void
push_back_guessed_candidate(struct seg_ent * seg)98 push_back_guessed_candidate(struct seg_ent *seg)
99 {
100   xchar xc;
101   xstr *xs;
102   struct cand_ent *ce;
103   if (seg->str.len < 2) {
104     return ;
105   }
106   /* 最後の文字は助詞か? */
107   xc = seg->str.str[seg->str.len - 1];
108   if (!(anthy_get_xchar_type(xc) & XCT_DEP)) {
109     return ;
110   }
111   /* 最後の文字以外をカタカナにしてみる */
112   ce = alloc_cand_ent();
113   xs = anthy_xstr_hira_to_kata(&seg->str);
114   xs->str[xs->len-1] = xc;
115   ce->str.str = anthy_xstr_dup_str(xs);
116   ce->str.len = xs->len;
117   ce->flag = CEF_GUESS;
118   anthy_free_xstr(xs);
119   push_back_candidate(seg, ce);
120 }
121 
122 /** 再帰で1単語ずつ候補を割当てていく */
123 static int
enum_candidates(struct seg_ent * seg,struct cand_ent * ce,int from,int n)124 enum_candidates(struct seg_ent *seg,
125 		struct cand_ent *ce,
126 		int from, int n)
127 {
128   int i, p;
129   struct cand_ent *cand;
130   int nr_cands = 0;
131   int pos;
132 
133   if (n == ce->mw->nr_parts) {
134     /* 完成形 */
135     /* 文節後部の解析しなかった部分を候補文字列に追加 */
136     xstr tail;
137     tail.len = seg->len - from;
138     tail.str = &seg->str.str[from];
139     anthy_xstrcat(&ce->str, &tail);
140     if (ce->str.str && (0 < ce->str.len)) { /* 辞書もしくは学習データが壊れていた時の対策 */
141       push_back_candidate(seg, dup_candidate(ce));
142     }
143     return 1;
144   }
145 
146   p = anthy_get_nr_dic_ents(ce->elm[n].se, &ce->elm[n].str);
147 
148   /* 品詞が割当てられているので、その品詞にマッチするものを割当てる */
149   for (i = 0; i < p; i++) {
150     wtype_t wt;
151     if (anthy_get_nth_dic_ent_is_compound(ce->elm[n].se, i)) {
152       continue;
153     }
154     anthy_get_nth_dic_ent_wtype(ce->elm[n].se, &ce->elm[n].str, i, &wt);
155 
156     if (anthy_wtype_equal (ce->elm[n].wt, wt)) {
157       xstr word, yomi;
158 
159       yomi.len = ce->elm[n].str.len;
160       yomi.str = &seg->str.str[from];
161       cand = dup_candidate(ce);
162       anthy_get_nth_dic_ent_str(cand->elm[n].se,
163 				&yomi, i, &word);
164       cand->elm[n].nth = i;
165       cand->elm[n].id = anthy_xstr_hash(&word);
166 
167       /* 単語の本体 */
168       anthy_xstrcat(&cand->str, &word);
169       free(word.str);
170       /* 自分を再帰呼び出しして続きを割り当てる */
171       nr_cands += enum_candidates(seg, cand,
172 				  from + yomi.len,
173 				  n+1);
174       anthy_release_cand_ent(cand);
175     }
176   }
177 
178   /* 品詞不定の場合には未変換で次の単語へ行く */
179   pos = anthy_wtype_get_pos(ce->elm[n].wt);
180   if (nr_cands == 0 || pos == POS_INVAL || pos == POS_NONE) {
181     xstr xs;
182     xs.len = ce->elm[n].str.len;
183     xs.str = &seg->str.str[from];
184     cand = dup_candidate(ce);
185     cand->elm[n].nth = -1;
186     cand->elm[n].id = -1;
187     anthy_xstrcat(&cand->str, &xs);
188     nr_cands = enum_candidates(seg,cand,
189 			       from + xs.len,
190 			       n + 1);
191     anthy_release_cand_ent(cand);
192     return nr_cands;
193   }
194 
195   return nr_cands;
196 }
197 
198 /**
199  * 文節全体を含む一単語(単漢字を含む)の候補を生成する
200  */
201 static void
push_back_singleword_candidate(struct seg_ent * seg,int is_reverse)202 push_back_singleword_candidate(struct seg_ent *seg,
203 			       int is_reverse)
204 {
205   seq_ent_t se;
206   struct cand_ent *ce;
207   wtype_t wt;
208   int i, n;
209   xstr xs;
210 
211   se = anthy_get_seq_ent_from_xstr(&seg->str, is_reverse);
212   n = anthy_get_nr_dic_ents(se, &seg->str);
213   /* 辞書の各エントリに対して */
214   for (i = 0; i < n; i++) {
215     int ct;
216     if (anthy_get_nth_dic_ent_is_compound(se, i)) {
217       continue;
218     }
219     /* 品詞を取り出して */
220     anthy_get_nth_dic_ent_wtype(se, &seg->str, i, &wt);
221     ct = anthy_wtype_get_ct(wt);
222     /* 終止形か活用しないものの原形なら */
223     if (ct == CT_SYUSI || ct == CT_NONE) {
224       ce = alloc_cand_ent();
225       anthy_get_nth_dic_ent_str(se,&seg->str, i, &xs);
226       ce->str.str = xs.str;
227       ce->str.len = xs.len;
228       ce->flag = CEF_SINGLEWORD;
229       push_back_candidate(seg, ce);
230     }
231   }
232 }
233 
234 static void
push_back_noconv_candidate(struct seg_ent * seg)235 push_back_noconv_candidate(struct seg_ent *seg)
236 {
237   /* 無変換で片仮名になる候補と平仮名のみになる候補を追加 */
238   struct cand_ent *ce;
239   xstr *xs;
240 
241   /* ひらがなのみ */
242   ce = alloc_cand_ent();
243   ce->str.str = anthy_xstr_dup_str(&seg->str);
244   ce->str.len = seg->str.len;
245   ce->flag = CEF_HIRAGANA;
246   push_back_candidate(seg, ce);
247 
248   /* 次にカタカナ */
249   ce = alloc_cand_ent();
250   xs = anthy_xstr_hira_to_kata(&seg->str);
251   ce->str.str = anthy_xstr_dup_str(xs);
252   ce->str.len = xs->len;
253   ce->flag = CEF_KATAKANA;
254   anthy_free_xstr(xs);
255   push_back_candidate(seg, ce);
256 
257   /* 記号のみの文節 */
258   xs = anthy_conv_half_wide(&seg->str);
259   if (xs) {
260     ce = alloc_cand_ent();
261     ce->str.str = anthy_xstr_dup_str(xs);
262     ce->str.len = xs->len;
263     ce->flag = CEF_NONE;
264     anthy_free_xstr(xs);
265     push_back_candidate(seg, ce);
266   }
267 }
268 
269 /* word_listの要素part_infoの配列からcand_elmの配列を作る */
270 static void
make_cand_elem_from_word_list(struct seg_ent * se,struct cand_ent * ce,struct word_list * wl,int index,int is_reverse)271 make_cand_elem_from_word_list(struct seg_ent *se,
272 			      struct cand_ent *ce,
273 			      struct word_list *wl,
274 			      int index,
275 			      int is_reverse)
276 {
277   int i;
278   int from = wl->from - se->from;
279 
280   for (i = 0; i < NR_PARTS; ++i) {
281     struct part_info *part = &wl->part[i];
282     xstr core_xs;
283     if (part->len == 0) {
284       /* 長さの無いpartは無視する */
285       continue;
286     }
287     if (i == PART_CORE) {
288       ce->core_elm_index = i + index;
289     }
290     core_xs.str = &se->str.str[from];
291     core_xs.len = part->len;
292     if (i == PART_DEPWORD) {
293       ce->dep_word_hash = anthy_dep_word_hash(&core_xs);
294     }
295     ce->elm[i + index].se = anthy_get_seq_ent_from_xstr(&core_xs, is_reverse);
296     ce->elm[i + index].str.str = core_xs.str;
297     ce->elm[i + index].str.len = core_xs.len;
298     ce->elm[i + index].wt = part->wt;
299     ce->elm[i + index].ratio = RATIO_BASE * wl->len;
300     from += part->len;
301   }
302 }
303 
304 
305 /** まずwordlistを持つmetawordからmeta_wordを取り出す */
306 static void
make_candidate_from_simple_metaword(struct seg_ent * se,struct meta_word * mw,struct meta_word * top_mw,int is_reverse)307 make_candidate_from_simple_metaword(struct seg_ent *se,
308 				    struct meta_word *mw,
309 				    struct meta_word *top_mw,
310 				    int is_reverse)
311 {
312   /*
313    * 各単語の品詞が決定された状態でコミットされる。
314    */
315   struct cand_ent *ce;
316 
317   /* 複数(1も含む)の単語で構成される文節に単語を割当てていく */
318   ce = alloc_cand_ent();
319   ce->nr_words = mw->nr_parts;
320   ce->str.str = NULL;
321   ce->str.len = 0;
322   ce->elm = calloc(sizeof(struct cand_elm),ce->nr_words);
323   ce->mw = mw;
324   ce->score = 0;
325 
326   /* 接頭辞, 自立語部, 接尾辞, 付属語 */
327   make_cand_elem_from_word_list(se, ce, mw->wl, 0, is_reverse);
328 
329   /* WRAPされていたらGUESSと同じ扱いにして点数を下げる */
330   if (anthy_metaword_type_tab[top_mw->type].status != MW_STATUS_WRAPPED) {
331     ce->flag = (se->best_mw == mw) ? CEF_BEST : CEF_NONE;
332   } else {
333     ce->flag = CEF_GUESS;
334   }
335 
336   enum_candidates(se, ce, 0, 0);
337   anthy_release_cand_ent(ce);
338 }
339 
340 /** combinedなmetawordは二つの語を合体して一つの語として出す */
341 static void
make_candidate_from_combined_metaword(struct seg_ent * se,struct meta_word * mw,struct meta_word * top_mw,int is_reverse)342 make_candidate_from_combined_metaword(struct seg_ent *se,
343 				      struct meta_word *mw,
344 				      struct meta_word *top_mw,
345 				      int is_reverse)
346 {
347   /*
348    * 各単語の品詞が決定された状態でコミットされる。
349    */
350   struct cand_ent *ce;
351 
352   /* 複数(1も含む)の単語で構成される文節に単語を割当てていく */
353   ce = alloc_cand_ent();
354   ce->nr_words = mw->nr_parts;
355   ce->score = 0;
356   ce->str.str = NULL;
357   ce->str.len = 0;
358   ce->elm = calloc(sizeof(struct cand_elm),ce->nr_words);
359   ce->mw = top_mw;
360 
361   /* 接頭辞, 自立語部, 接尾辞, 付属語 */
362   make_cand_elem_from_word_list(se, ce, mw->mw1->wl, 0, is_reverse);
363   if (mw->mw2) {
364     make_cand_elem_from_word_list(se, ce, mw->mw2->mw1->wl, NR_PARTS, is_reverse);
365   }
366 
367   /* WRAPされていたらGUESSと同じ扱いにして点数を下げる */
368   if (anthy_metaword_type_tab[top_mw->type].status != MW_STATUS_WRAPPED) {
369     ce->flag = (se->best_mw == mw) ? CEF_BEST : CEF_NONE;
370   } else {
371     ce->flag = CEF_GUESS;
372   }
373 
374   enum_candidates(se, ce, 0, 0);
375   anthy_release_cand_ent(ce);
376 }
377 
378 
379 /** splitterの情報を利用して候補を生成する
380  */
381 static void
proc_splitter_info(struct seg_ent * se,struct meta_word * mw,struct meta_word * top_mw,int is_reverse)382 proc_splitter_info(struct seg_ent *se,
383 		   struct meta_word *mw,
384 		   /* topとはtreeのトップ */
385 		   struct meta_word *top_mw,
386 		   int is_reverse)
387 {
388   enum mw_status st;
389   if (!mw) return;
390 
391   /* まずwordlistを持つmetawordの場合 */
392   if (mw->wl && mw->wl->len) {
393     make_candidate_from_simple_metaword(se, mw, top_mw, is_reverse);
394     return;
395   }
396 
397   st = anthy_metaword_type_tab[mw->type].status;
398   switch (st) {
399   case MW_STATUS_WRAPPED:
400     /* wrapされたものの情報を取り出す */
401     proc_splitter_info(se, mw->mw1, top_mw, is_reverse);
402     break;
403   case MW_STATUS_COMBINED:
404     make_candidate_from_combined_metaword(se, mw, top_mw, is_reverse);
405     break;
406   case MW_STATUS_COMPOUND:
407     /* 連文節の葉 */
408     {
409       struct cand_ent *ce;
410       ce = alloc_cand_ent();
411       ce->str.str = anthy_xstr_dup_str(&mw->cand_hint);
412       ce->str.len = mw->cand_hint.len;
413       ce->flag = CEF_COMPOUND;
414       ce->mw = top_mw;
415       push_back_candidate(se, ce);
416     }
417     break;
418   case MW_STATUS_COMPOUND_PART:
419     /* 連文節の個々の文節を結合して一つの文節としてみたもの */
420     /* BREAK THROUGH */
421   case MW_STATUS_OCHAIRE:
422     {
423     /* metawordを持たない候補文字列が
424        直接に指定された */
425       struct cand_ent *ce;
426       ce = alloc_cand_ent();
427       ce->str.str = anthy_xstr_dup_str(&mw->cand_hint);
428       ce->str.len = mw->cand_hint.len;
429       ce->mw = top_mw;
430       ce->flag = (st == MW_STATUS_OCHAIRE) ? CEF_OCHAIRE : CEF_COMPOUND_PART;
431 
432       if (mw->len < se->len) {
433 	/* metawordでカバーされてない領域の文字列を付ける */
434 	xstr xs;
435 	xs.str = &se->str.str[mw->len];
436 	xs.len = se->len - mw->len;
437 	anthy_xstrcat(&ce->str ,&xs);
438       }
439       push_back_candidate(se, ce);
440     }
441     break;
442   case MW_STATUS_NONE:
443     break;
444   default:
445     break;
446   }
447 }
448 
449 /** context.cから呼出されるもっとも大物
450  * 一つ以上の候補を必ず生成する
451  */
452 void
anthy_do_make_candidates(struct splitter_context * sc,struct seg_ent * se,int is_reverse)453 anthy_do_make_candidates(struct splitter_context *sc,
454 			 struct seg_ent *se, int is_reverse)
455 {
456   int i;
457 
458   /* metawordから候補を生成する */
459   for (i = 0; i < se->nr_metaword; i++) {
460     struct meta_word *mw = se->mw_array[i];
461     if (anthy_splitter_debug_flags() & SPLITTER_DEBUG_CAND) {
462       anthy_print_metaword(sc, mw);
463     }
464     proc_splitter_info(se, mw, mw, is_reverse);
465   }
466   if (anthy_splitter_debug_flags() & SPLITTER_DEBUG_CAND) {
467     printf("#done\n");
468   }
469   /* 単漢字などの候補 */
470   push_back_singleword_candidate(se, is_reverse);
471 
472   /* ひらがな、カタカナの無変換エントリを作る */
473   push_back_noconv_candidate(se);
474 
475   /* 候補が二つしか無いときは最後が助詞で残りが平仮名の候補を作れるか試す */
476   push_back_guessed_candidate(se);
477 }
478