fixes pinyin_get_full_pinyin_candidates
[platform/upstream/libpinyin.git] / src / storage / pinyin_parser2.cpp
1 /* 
2  *  libpinyin
3  *  Library to deal with pinyin.
4  *  
5  *  Copyright (C) 2011 Peng Wu <alexepico@gmail.com>
6  *  
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  * 
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  *  GNU General Public License for more details.
16  *  
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21
22
23 #include "pinyin_parser2.h"
24 #include <ctype.h>
25 #include <assert.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include "stl_lite.h"
29 #include "pinyin_phrase2.h"
30 #include "pinyin_custom2.h"
31 #include "chewing_key.h"
32 #include "pinyin_parser_table.h"
33 #include "double_pinyin_table.h"
34 #include "chewing_table.h"
35
36
37 using namespace pinyin;
38
39 static bool check_pinyin_options(pinyin_option_t options, const pinyin_index_item_t * item) {
40     guint32 flags = item->m_flags;
41     assert (flags & IS_PINYIN);
42
43     /* handle incomplete pinyin. */
44     if (flags & PINYIN_INCOMPLETE) {
45         if (!(options & PINYIN_INCOMPLETE))
46             return false;
47     }
48
49     /* handle correct pinyin, currently only one flag per item. */
50     flags &= PINYIN_CORRECT_ALL;
51     options &= PINYIN_CORRECT_ALL;
52
53     if (flags) {
54         if ((flags & options) != flags)
55             return false;
56     }
57
58     return true;
59 }
60
61 static bool check_chewing_options(pinyin_option_t options, const chewing_index_item_t * item) {
62     guint32 flags = item->m_flags;
63     assert (flags & IS_CHEWING);
64
65     /* handle incomplete chewing. */
66     if (flags & CHEWING_INCOMPLETE) {
67         if (!(options & CHEWING_INCOMPLETE))
68             return false;
69     }
70
71     return true;
72 }
73
74
75 gint ChewingKey::get_table_index() {
76     assert(m_initial <  CHEWING_NUMBER_OF_INITIALS);
77     assert(m_middle < CHEWING_NUMBER_OF_MIDDLES);
78     assert(m_final < CHEWING_NUMBER_OF_FINALS);
79
80     gint index = chewing_key_table[(m_initial * CHEWING_NUMBER_OF_MIDDLES + m_middle) * CHEWING_NUMBER_OF_FINALS + m_final];
81     return index == -1 ? 0 : index;
82 }
83
84 gchar * ChewingKey::get_pinyin_string() {
85     assert(m_tone < CHEWING_NUMBER_OF_TONES);
86     gint index = get_table_index();
87     assert(index < G_N_ELEMENTS(content_table));
88     const content_table_item_t & item = content_table[index];
89
90     if (CHEWING_ZERO_TONE == m_tone) {
91         return g_strdup(item.m_pinyin_str);
92     } else {
93         return g_strdup_printf("%s%d", item.m_pinyin_str, m_tone);
94     }
95 }
96
97 gchar * ChewingKey::get_chewing_string() {
98     assert(m_tone < CHEWING_NUMBER_OF_TONES);
99     gint index = get_table_index();
100     assert(index < G_N_ELEMENTS(content_table));
101     const content_table_item_t & item = content_table[index];
102
103     if (CHEWING_ZERO_TONE == m_tone) {
104         return g_strdup(item.m_chewing_str);
105     } else {
106         return g_strdup_printf("%s%s", item.m_chewing_str,
107                                chewing_tone_table[m_tone]);
108     }
109 }
110
111
112 /* Pinyin Parsers */
113
114 /* internal information for pinyin parsers. */
115 struct parse_value_t{
116     ChewingKey m_key;
117     ChewingKeyRest m_key_rest;
118     gint16 m_num_keys;
119     gint16 m_parsed_len;
120     gint16 m_last_step;
121
122     /* constructor */
123 public:
124     parse_value_t(){
125         m_num_keys = 0;
126         m_parsed_len = 0;
127         m_last_step = -1;
128     }
129 };
130
131 const guint16 max_full_pinyin_length   = 7;  /* include tone. */
132
133 const guint16 max_double_pinyin_length = 3;  /* include tone. */
134
135 const guint16 max_chewing_length       = 4;  /* include tone. */
136
137 static bool compare_pinyin_less_than(const pinyin_index_item_t & lhs,
138                                      const pinyin_index_item_t & rhs){
139     return 0 > strcmp(lhs.m_pinyin_input, rhs.m_pinyin_input);
140 }
141
142 static inline bool search_pinyin_index(pinyin_option_t options,
143                                        const char * pinyin,
144                                        ChewingKey & key){
145     pinyin_index_item_t item;
146     memset(&item, 0, sizeof(item));
147     item.m_pinyin_input = pinyin;
148
149     std_lite::pair<const pinyin_index_item_t *,
150                    const pinyin_index_item_t *> range;
151     range = std_lite::equal_range
152         (pinyin_index, pinyin_index + G_N_ELEMENTS(pinyin_index),
153          item, compare_pinyin_less_than);
154
155     guint16 range_len = range.second - range.first;
156     assert(range_len <= 1);
157     if (range_len == 1) {
158         const pinyin_index_item_t * index = range.first;
159
160         if (!check_pinyin_options(options, index))
161             return false;
162
163         key = content_table[index->m_table_index].m_chewing_key;
164         assert(key.get_table_index() == index->m_table_index);
165         return true;
166     }
167
168     return false;
169 }
170
171 static bool compare_chewing_less_than(const chewing_index_item_t & lhs,
172                                       const chewing_index_item_t & rhs){
173     return 0 > strcmp(lhs.m_chewing_input, rhs.m_chewing_input);
174 }
175
176 static inline bool search_chewing_index(pinyin_option_t options,
177                                         const char * chewing,
178                                         ChewingKey & key){
179     chewing_index_item_t item;
180     memset(&item, 0, sizeof(item));
181     item.m_chewing_input = chewing;
182
183     std_lite::pair<const chewing_index_item_t *,
184                    const chewing_index_item_t *> range;
185     range = std_lite::equal_range
186         (chewing_index, chewing_index + G_N_ELEMENTS(chewing_index),
187          item, compare_chewing_less_than);
188
189     guint16 range_len = range.second - range.first;
190     assert (range_len <= 1);
191
192     if (range_len == 1) {
193         const chewing_index_item_t * index = range.first;
194
195         if (!check_chewing_options(options, index))
196             return false;
197
198         key = content_table[index->m_table_index].m_chewing_key;
199         assert(key.get_table_index() == index->m_table_index);
200         return true;
201     }
202
203     return false;
204 }
205
206 /* Full Pinyin Parser */
207 FullPinyinParser2::FullPinyinParser2 (){
208     m_parse_steps = g_array_new(TRUE, FALSE, sizeof(parse_value_t));
209 }
210
211
212 bool FullPinyinParser2::parse_one_key (pinyin_option_t options,
213                                        ChewingKey & key,
214                                        const char * pinyin, int len) const {
215     /* "'" are not accepted in parse_one_key. */
216     gchar * input = g_strndup(pinyin, len);
217     assert(NULL == strchr(input, '\''));
218
219     guint16 tone = CHEWING_ZERO_TONE; guint16 tone_pos = 0;
220     guint16 parsed_len = len;
221     key = ChewingKey();
222
223     if (options & USE_TONE) {
224         /* find the tone in the last character. */
225         char chr = input[parsed_len - 1];
226         if ( '0' < chr && chr <= '5' ) {
227             tone = chr - '0';
228             parsed_len --;
229             tone_pos = parsed_len;
230         }
231     }
232
233     /* parse pinyin core staff here. */
234
235     /* Note: optimize here? */
236     input[parsed_len] = '\0';
237     if (!search_pinyin_index(options, input, key)) {
238         g_free(input);
239         return false;
240     }
241
242     if (options & USE_TONE) {
243         /* post processing tone. */
244         if ( parsed_len == tone_pos ) {
245             if (tone != CHEWING_ZERO_TONE) {
246                 key.m_tone = tone;
247                 parsed_len ++;
248             }
249         }
250     }
251
252     g_free(input);
253     return parsed_len == len;
254 }
255
256
257 int FullPinyinParser2::parse (pinyin_option_t options, ChewingKeyVector & keys,
258                               ChewingKeyRestVector & key_rests,
259                               const char *str, int len) const {
260     int i;
261     /* clear arrays. */
262     g_array_set_size(keys, 0);
263     g_array_set_size(key_rests, 0);
264
265     /* init m_parse_steps, and prepare dynamic programming. */
266     int step_len = len + 1;
267     g_array_set_size(m_parse_steps, 0);
268     parse_value_t value;
269     for (i = 0; i < step_len; ++i) {
270         g_array_append_val(m_parse_steps, value);
271     }
272
273     size_t next_sep = 0;
274     gchar * input = g_strndup(str, len);
275     parse_value_t * curstep = NULL, * nextstep = NULL;
276
277     for (i = 0; i < len; ++i) {
278         if (input[i] == '\'') {
279             curstep = &g_array_index(m_parse_steps, parse_value_t, i);
280             nextstep = &g_array_index(m_parse_steps, parse_value_t, i + 1);
281
282             /* propagate current step into next step. */
283             nextstep->m_key = ChewingKey();
284             nextstep->m_key_rest = ChewingKeyRest();
285             nextstep->m_num_keys = curstep->m_num_keys;
286             nextstep->m_parsed_len = curstep->m_parsed_len + 1;
287             nextstep->m_last_step = i;
288             next_sep = 0;
289             continue;
290         }
291
292         /* forward to next "'" */
293         if ( 0 == next_sep ) {
294             int k;
295             for (k = i;  k < len; ++k) {
296                 if (input[k] == '\'')
297                     break;
298             }
299             next_sep = k;
300         }
301
302         /* dynamic programming here. */
303         /* for (size_t m = i; m < next_sep; ++m) */
304         {
305             size_t m = i;
306             curstep = &g_array_index(m_parse_steps, parse_value_t, m);
307             size_t try_len = std_lite::min
308                 (m + max_full_pinyin_length, next_sep);
309             for (size_t n = m + 1; n < try_len + 1; ++n) {
310                 nextstep = &g_array_index(m_parse_steps, parse_value_t, n);
311
312                 /* gen next step */
313                 const char * onepinyin = input + m;
314                 gint16 onepinyinlen = n - m;
315                 value = parse_value_t();
316
317                 ChewingKey key; ChewingKeyRest rest;
318                 bool parsed = parse_one_key
319                     (options, key, onepinyin, onepinyinlen);
320                 rest.m_raw_begin = m; rest.m_raw_end = n;
321                 if (!parsed)
322                     continue;
323
324                 //printf("onepinyin:%s len:%d\n", onepinyin, onepinyinlen);
325
326                 value.m_key = key; value.m_key_rest = rest;
327                 value.m_num_keys = curstep->m_num_keys + 1;
328                 value.m_parsed_len = curstep->m_parsed_len + onepinyinlen;
329                 value.m_last_step = m;
330
331                 /* save next step */
332                 /* no previous result */
333                 if (-1 == nextstep->m_last_step)
334                     *nextstep = value;
335                 /* prefer the longest pinyin */
336                 if (value.m_parsed_len > nextstep->m_parsed_len)
337                     *nextstep = value;
338                 /* prefer the shortest keys with the same pinyin length */
339                 if (value.m_parsed_len == nextstep->m_parsed_len &&
340                     value.m_num_keys < nextstep->m_num_keys)
341                     *nextstep = value;
342
343                 /* handle with the same pinyin length and the number of keys */
344                 if (value.m_parsed_len == nextstep->m_parsed_len &&
345                     value.m_num_keys == nextstep->m_num_keys) {
346
347 #if 0
348                     /* prefer the complete pinyin with shengmu
349                      * over without shengmu,
350                      * ex: "kaneiji" -> "ka'nei'ji".
351                      */
352                     if ((value.m_key.m_initial != CHEWING_ZERO_INITIAL &&
353                          !(value.m_key.m_middle == CHEWING_ZERO_MIDDLE &&
354                            value.m_key.m_final == CHEWING_ZERO_FINAL)) &&
355                         nextstep->m_key.m_initial == CHEWING_ZERO_INITIAL)
356                         *nextstep = value;
357
358                     /* prefer the complete pinyin 'er'
359                      * over the in-complete pinyin 'r',
360                      * ex: "xierqi" -> "xi'er'qi."
361                      */
362                     if ((value.m_key.m_initial == CHEWING_ZERO_INITIAL &&
363                         value.m_key.m_middle == CHEWING_ZERO_MIDDLE &&
364                         value.m_key.m_final == CHEWING_ER) &&
365                         (nextstep->m_key.m_initial == CHEWING_R &&
366                          nextstep->m_key.m_middle == CHEWING_ZERO_MIDDLE &&
367                          nextstep->m_key.m_final == CHEWING_ZERO_FINAL))
368                         *nextstep = value;
369 #endif
370
371                     /* prefer the 'a' at the end of clause,
372                      * ex: "zheyanga$" -> "zhe'yang'a$".
373                      */
374                     if (value.m_parsed_len == len &&
375                         (nextstep->m_key.m_initial != CHEWING_ZERO_INITIAL &&
376                          nextstep->m_key.m_final == CHEWING_A) &&
377                         (value.m_key.m_initial == CHEWING_ZERO_INITIAL &&
378                          value.m_key.m_middle == CHEWING_ZERO_MIDDLE &&
379                          value.m_key.m_final == CHEWING_A))
380                         *nextstep = value;
381                 }
382             }
383         }
384     }
385
386     /* final step for back tracing. */
387     gint16 parsed_len = final_step(step_len, keys, key_rests);
388
389     /* post processing for re-split table. */
390     if (options & USE_RESPLIT_TABLE) {
391         post_process2(options, keys, key_rests, str, len);
392     }
393
394     g_free(input);
395     return parsed_len;
396 }
397
398 int FullPinyinParser2::final_step(size_t step_len, ChewingKeyVector & keys,
399                                   ChewingKeyRestVector & key_rests) const{
400     int i;
401     gint16 parsed_len = 0;
402     parse_value_t * curstep = NULL;
403
404     /* find longest match, which starts from the beginning of input. */
405     for (i = step_len - 1; i >= 0; --i) {
406         curstep = &g_array_index(m_parse_steps, parse_value_t, i);
407         if (i == curstep->m_parsed_len)
408             break;
409     }
410     /* prepare saving. */
411     parsed_len = curstep->m_parsed_len;
412     gint16 num_keys = curstep->m_num_keys;
413     g_array_set_size(keys, num_keys);
414     g_array_set_size(key_rests, num_keys);
415
416     /* save the match. */
417     while (curstep->m_last_step != -1) {
418         gint16 pos = curstep->m_num_keys - 1;
419
420         /* skip "'" */
421         if (0 != curstep->m_key.get_table_index()) {
422             ChewingKey * key = &g_array_index(keys, ChewingKey, pos);
423             ChewingKeyRest * rest = &g_array_index
424                 (key_rests, ChewingKeyRest, pos);
425             *key = curstep->m_key; *rest = curstep->m_key_rest;
426         }
427
428         /* back ward */
429         curstep = &g_array_index(m_parse_steps, parse_value_t,
430                                  curstep->m_last_step);
431     }
432     return parsed_len;
433 }
434
435 bool FullPinyinParser2::post_process2(pinyin_option_t options,
436                                       ChewingKeyVector & keys,
437                                       ChewingKeyRestVector & key_rests,
438                                       const char * str,
439                                       int len) const {
440     int i;
441     assert(keys->len == key_rests->len);
442     gint num_keys = keys->len;
443
444     ChewingKey * cur_key = NULL, * next_key = NULL;
445     ChewingKeyRest * cur_rest = NULL, * next_rest = NULL;
446     guint16 next_tone = CHEWING_ZERO_TONE;
447
448     for (i = 0; i < num_keys - 1; ++i) {
449         cur_rest = &g_array_index(key_rests, ChewingKeyRest, i);
450         next_rest = &g_array_index(key_rests, ChewingKeyRest, i + 1);
451
452         /* some "'" here */
453         if (cur_rest->m_raw_end != next_rest->m_raw_begin)
454             continue;
455
456         cur_key = &g_array_index(keys, ChewingKey, i);
457         next_key = &g_array_index(keys, ChewingKey, i + 1);
458
459         /* some tone here */
460         if (CHEWING_ZERO_TONE != cur_key->m_tone)
461             continue;
462
463         /* back up tone */
464         if (options & USE_TONE) {
465             next_tone = next_key->m_tone;
466             if (CHEWING_ZERO_TONE != next_tone) {
467                 next_key->m_tone = CHEWING_ZERO_TONE;
468                 next_rest->m_raw_end --;
469             }
470         }
471
472         /* lookup re-split table */
473         const resplit_table_item_t * item = NULL;
474
475         item = retrieve_resplit_item_by_original_pinyins
476             (options, cur_key, cur_rest, next_key, next_rest, str, len);
477
478         if (item) {
479             /* no ops */
480             if (item->m_orig_freq >= item->m_new_freq)
481                 continue;
482
483             /* do re-split */
484             const char * onepinyin = str + cur_rest->m_raw_begin;
485             size_t len = strlen(item->m_new_keys[0]);
486
487             assert(parse_one_key(options, *cur_key, onepinyin, len));
488             cur_rest->m_raw_end = cur_rest->m_raw_begin + len;
489
490             next_rest->m_raw_begin = cur_rest->m_raw_end;
491             onepinyin = str + next_rest->m_raw_begin;
492             len = strlen(item->m_new_keys[1]);
493
494             assert(parse_one_key(options, *next_key, onepinyin, len));
495         }
496
497         /* restore tones */
498         if (options & USE_TONE) {
499             if (CHEWING_ZERO_TONE != next_tone) {
500                 next_key->m_tone = next_tone;
501                 next_rest->m_raw_end ++;
502             }
503         }
504     }
505
506     return true;
507 }
508
509 const divided_table_item_t * FullPinyinParser2::retrieve_divided_item
510 (pinyin_option_t options, ChewingKey * key, ChewingKeyRest * rest,
511  const char * str, int len) const {
512
513     /* lookup divided table */
514     size_t k;
515     const divided_table_item_t * item = NULL;
516     for (k = 0; k < G_N_ELEMENTS(divided_table); ++k) {
517         item = divided_table + k;
518
519         const char * onepinyin = str + rest->m_raw_begin;
520         size_t len = strlen(item->m_orig_key);
521
522         if (rest->length() != len)
523             continue;
524
525         if (0 == strncmp(onepinyin, item->m_orig_key, len))
526             break;
527     }
528
529     /* found the match */
530     if (k < G_N_ELEMENTS(divided_table)) {
531         /* do divided */
532         item = divided_table + k;
533         return item;
534     }
535
536     return NULL;
537 }
538
539
540 const resplit_table_item_t * FullPinyinParser2::retrieve_resplit_item_by_original_pinyins
541 (pinyin_option_t options,
542  ChewingKey * cur_key, ChewingKeyRest * cur_rest,
543  ChewingKey * next_key, ChewingKeyRest * next_rest,
544  const char * str, int len) const{
545     /* lookup re-split table */
546     size_t k;
547     const resplit_table_item_t * item = NULL;
548
549     for (k = 0; k < G_N_ELEMENTS(resplit_table); ++k) {
550         item = resplit_table + k;
551
552         const char * onepinyin = str + cur_rest->m_raw_begin;
553         size_t len = strlen(item->m_orig_keys[0]);
554
555         if (cur_rest->length() != len)
556             continue;
557
558         if (0 != strncmp(onepinyin, item->m_orig_keys[0], len))
559             continue;
560
561         onepinyin = str + next_rest->m_raw_begin;
562         len = strlen(item->m_orig_keys[1]);
563
564         if (next_rest->length() != len)
565             continue;
566
567         if (0 == strncmp(onepinyin, item->m_orig_keys[1], len))
568             break;
569     }
570
571     /* found the match */
572     if (k < G_N_ELEMENTS(resplit_table)) {
573         item = resplit_table + k;
574         return item;
575     }
576
577     return NULL;
578 }
579
580 const resplit_table_item_t * FullPinyinParser2::retrieve_resplit_item_by_resplit_pinyins
581 (pinyin_option_t options,
582  ChewingKey * cur_key, ChewingKeyRest * cur_rest,
583  ChewingKey * next_key, ChewingKeyRest * next_rest,
584  const char * str, int len) const {
585     /* lookup divide table */
586     size_t k;
587     const resplit_table_item_t * item = NULL;
588
589     for (k = 0; k < G_N_ELEMENTS(resplit_table); ++k) {
590         item = resplit_table + k;
591
592         const char * onepinyin = str + cur_rest->m_raw_begin;
593         size_t len = strlen(item->m_new_keys[0]);
594
595         if (cur_rest->length() != len)
596             continue;
597
598         if (0 != strncmp(onepinyin, item->m_new_keys[0], len))
599             continue;
600
601         onepinyin = str + next_rest->m_raw_begin;
602         len = strlen(item->m_new_keys[1]);
603
604         if (next_rest->length() != len)
605             continue;
606
607         if (0 == strncmp(onepinyin, item->m_new_keys[1], len))
608             break;
609     }
610
611     /* found the match */
612     if (k < G_N_ELEMENTS(resplit_table)) {
613         item = resplit_table + k;
614         return item;
615     }
616
617     return NULL;
618 }
619
620 #define IS_KEY(x)   (('a' <= x && x <= 'z') || x == ';')
621
622 bool DoublePinyinParser2::parse_one_key(pinyin_option_t options,
623                                         ChewingKey & key,
624                                         const char *str, int len) const {
625     options &= ~(PINYIN_CORRECT_ALL|PINYIN_AMB_ALL);
626
627     if (1 == len) {
628         if (!(options & PINYIN_INCOMPLETE))
629             return false;
630
631         char ch = str[0];
632         if (!IS_KEY(ch))
633             return false;
634
635         int charid = ch == ';' ? 26 : ch - 'a';
636         const char * sheng = m_shengmu_table[charid].m_shengmu;
637         if (NULL == sheng || strcmp(sheng, "'") == 0)
638             return false;
639
640         if (search_pinyin_index(options, sheng, key)) {
641             return true;
642         } else {
643             return false;
644         }
645     }
646
647     ChewingTone tone = CHEWING_ZERO_TONE;
648     options &= ~(PINYIN_INCOMPLETE|CHEWING_INCOMPLETE);
649
650     /* parse tone */
651     if (3 == len) {
652         if (!(options & USE_TONE))
653             return false;
654         char ch = str[2];
655         if (!('0' < ch && ch <= '5'))
656             return false;
657         tone = (ChewingTone) (ch - '0');
658     }
659
660     if (2 == len || 3 == len) {
661         /* parse shengmu here. */
662         char ch = str[0];
663         if (!IS_KEY(ch))
664             return false;
665
666         int charid = ch == ';' ? 26 : ch - 'a';
667         const char * sheng = m_shengmu_table[charid].m_shengmu;
668         if (NULL == sheng)
669             return false;
670         if (strcmp(sheng, "'") == 0)
671             sheng = "";
672
673         /* parse yunmu here. */
674         ch = str[1];
675         if (!IS_KEY(ch))
676             return false;
677
678         charid = ch == ';' ? 26 : ch - 'a';
679         /* first yunmu */
680         const char * yun = m_yunmu_table[charid].m_yunmus[0];
681         if (NULL == yun)
682             return false;
683
684         gchar * pinyin = g_strdup_printf("%s%s", sheng, yun);
685         if (search_pinyin_index(options, pinyin, key)) {
686             key.m_tone = tone;
687             g_free(pinyin);
688             return true;
689         }
690         g_free(pinyin);
691
692         /* second yunmu */
693         yun = m_yunmu_table[charid].m_yunmus[1];
694         if (NULL == yun)
695             return false;
696
697         pinyin = g_strdup_printf("%s%s", sheng, yun);
698         if (search_pinyin_index(options, pinyin, key)) {
699             key.m_tone = tone;
700             g_free(pinyin);
701             return true;
702         }
703         g_free(pinyin);
704
705     }
706
707     return false;
708 }
709
710
711 /* only 'a'-'z' and ';' are accepted here. */
712 int DoublePinyinParser2::parse(pinyin_option_t options, ChewingKeyVector & keys,
713                                ChewingKeyRestVector & key_rests,
714                                const char *str, int len) const {
715     g_array_set_size(keys, 0);
716     g_array_set_size(key_rests, 0);
717
718     int maximum_len = 0; int i;
719     /* probe the longest possible double pinyin string. */
720     for (i = 0; i < len; ++i) {
721         const char ch = str[i];
722         if (!(IS_KEY(ch) || ('0' < ch && ch <= '5')))
723             break;
724     }
725     maximum_len = i;
726
727     /* maximum forward match for double pinyin. */
728     int parsed_len = 0;
729     while (parsed_len < maximum_len) {
730         const char * cur_str = str + parsed_len;
731         i = std_lite::min(maximum_len - parsed_len,
732                           (int)max_double_pinyin_length);
733
734         ChewingKey key; ChewingKeyRest key_rest;
735         for (; i > 0; --i) {
736             bool success = parse_one_key(options, key, cur_str, i);
737             if (success)
738                 break;
739         }
740
741         if (0 == i)        /* no more possible double pinyins. */
742             break;
743
744         key_rest.m_raw_begin = parsed_len; key_rest.m_raw_end = parsed_len + i;
745         parsed_len += i;
746
747         /* save the pinyin */
748         g_array_append_val(keys, key);
749         g_array_append_val(key_rests, key_rest);
750     }
751
752     return parsed_len;
753 }
754
755 #undef IS_KEY
756
757 bool DoublePinyinParser2::set_scheme(DoublePinyinScheme scheme) {
758
759     switch (scheme) {
760     case DOUBLE_PINYIN_ZRM:
761         m_shengmu_table = double_pinyin_zrm_sheng;
762         m_yunmu_table   = double_pinyin_zrm_yun;
763         return true;
764     case DOUBLE_PINYIN_MS:
765         m_shengmu_table = double_pinyin_mspy_sheng;
766         m_yunmu_table   = double_pinyin_mspy_yun;
767         return true;
768     case DOUBLE_PINYIN_ZIGUANG:
769         m_shengmu_table = double_pinyin_zgpy_sheng;
770         m_yunmu_table   = double_pinyin_zgpy_yun;
771         return true;
772     case DOUBLE_PINYIN_ABC:
773         m_shengmu_table = double_pinyin_abc_sheng;
774         m_yunmu_table   = double_pinyin_abc_yun;
775         return true;
776     case DOUBLE_PINYIN_PYJJ:
777         m_shengmu_table = double_pinyin_pyjj_sheng;
778         m_yunmu_table   = double_pinyin_pyjj_yun;
779         return true;
780     case DOUBLE_PINYIN_XHE:
781         m_shengmu_table = double_pinyin_xhe_sheng;
782         m_yunmu_table   = double_pinyin_xhe_yun;
783         return true;
784     case DOUBLE_PINYIN_CUSTOMIZED:
785         assert(FALSE);
786     };
787
788     return false; /* no such scheme. */
789 }
790
791 /* the chewing string must be freed with g_free. */
792 static bool search_chewing_symbols(const chewing_symbol_item_t * symbol_table,
793                                    const char key, const char ** chewing) {
794     *chewing = NULL;
795     /* just iterate the table, as we only have < 50 items. */
796     while (symbol_table->m_input != '\0') {
797         if (symbol_table->m_input == key) {
798             *chewing = symbol_table->m_chewing;
799             return true;
800         }
801         symbol_table ++;
802     }
803     return false;
804 }
805
806 static bool search_chewing_tones(const chewing_tone_item_t * tone_table,
807                                  const char key, char * tone) {
808     *tone = CHEWING_ZERO_TONE;
809     /* just iterate the table, as we only have < 10 items. */
810     while (tone_table->m_input != '\0') {
811         if (tone_table->m_input == key) {
812             *tone = tone_table->m_tone;
813             return true;
814         }
815         tone_table ++;
816     }
817     return false;
818 }
819
820
821 bool ChewingParser2::parse_one_key(pinyin_option_t options,
822                                    ChewingKey & key,
823                                    const char *str, int len) const {
824     options &= ~(PINYIN_CORRECT_ALL|PINYIN_AMB_ALL);
825     char tone = CHEWING_ZERO_TONE;
826
827     int symbols_len = len;
828     /* probe whether the last key is tone key in str. */
829     if (options & USE_TONE) {
830         char ch = str[len - 1];
831         /* remove tone from input */
832         if (search_chewing_tones(m_tone_table, ch, &tone))
833             symbols_len --;
834     }
835
836     int i;
837     gchar * chewing = NULL; const char * onechar = NULL;
838
839     /* probe the possible chewing map in the rest of str. */
840     for (i = 0; i < symbols_len; ++i) {
841         if (!search_chewing_symbols(m_symbol_table, str[i], &onechar)) {
842             g_free(chewing);
843             return false;
844         }
845
846         if (!chewing) {
847             chewing = g_strdup(onechar);
848         } else {
849             gchar * tmp = chewing;
850             chewing = g_strconcat(chewing, onechar, NULL);
851             g_free(tmp);
852         }
853     }
854
855     /* search the chewing in the chewing index table. */
856     if (chewing && search_chewing_index(options, chewing, key)) {
857         /* save back tone if available. */
858         key.m_tone = tone;
859         g_free(chewing);
860         return true;
861     }
862
863     g_free(chewing);
864     return false;
865 }
866
867
868 /* only characters in chewing keyboard scheme are accepted here. */
869 int ChewingParser2::parse(pinyin_option_t options, ChewingKeyVector & keys,
870                           ChewingKeyRestVector & key_rests,
871                           const char *str, int len) const {
872     g_array_set_size(keys, 0);
873     g_array_set_size(key_rests, 0);
874
875     int maximum_len = 0; int i;
876     /* probe the longest possible chewing string. */
877     for (i = 0; i < len; ++i) {
878         if (!in_chewing_scheme(options, str[i], NULL))
879             break;
880     }
881     maximum_len = i;
882
883     /* maximum forward match for chewing. */
884     int parsed_len = 0;
885     while (parsed_len < maximum_len) {
886         const char * cur_str = str + parsed_len;
887         i = std_lite::min(maximum_len - parsed_len,
888                           (int)max_chewing_length);
889
890         ChewingKey key; ChewingKeyRest key_rest;
891         for (; i > 0; --i) {
892             bool success = parse_one_key(options, key, cur_str, i);
893             if (success)
894                 break;
895         }
896
897         if (0 == i)        /* no more possible chewings. */
898             break;
899
900         key_rest.m_raw_begin = parsed_len; key_rest.m_raw_end = parsed_len + i;
901         parsed_len += i;
902
903         /* save the pinyin. */
904         g_array_append_val(keys, key);
905         g_array_append_val(key_rests, key_rest);
906     }
907
908     return parsed_len;
909 }
910
911
912 bool ChewingParser2::set_scheme(ChewingScheme scheme) {
913     switch(scheme) {
914     case CHEWING_STANDARD:
915         m_symbol_table = chewing_standard_symbols;
916         m_tone_table   = chewing_standard_tones;
917         return true;
918     case CHEWING_IBM:
919         m_symbol_table = chewing_ibm_symbols;
920         m_tone_table   = chewing_ibm_tones;
921         return true;
922     case CHEWING_GINYIEH:
923         m_symbol_table = chewing_ginyieh_symbols;
924         m_tone_table   = chewing_ginyieh_tones;
925         return true;
926     case CHEWING_ETEN:
927         m_symbol_table = chewing_eten_symbols;
928         m_tone_table   = chewing_eten_tones;
929         return true;
930     }
931
932     return false;
933 }
934
935
936 bool ChewingParser2::in_chewing_scheme(pinyin_option_t options,
937                                        const char key, const char ** symbol)
938  const {
939     const gchar * chewing = NULL;
940     char tone = CHEWING_ZERO_TONE;
941
942     if (search_chewing_symbols(m_symbol_table, key, &chewing)) {
943         if (symbol)
944             *symbol = chewing;
945         return true;
946     }
947
948     if (!(options & USE_TONE))
949         return false;
950
951     if (search_chewing_tones(m_tone_table, key, &tone)) {
952         if (symbol)
953             *symbol = chewing_tone_table[tone];
954         return true;
955     }
956
957     return false;
958 }