refine parse one key
[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     options |= PINYIN_CORRECT_UE_VE;
650
651     /* parse tone */
652     if (3 == len) {
653         if (!(options & USE_TONE))
654             return false;
655         char ch = str[2];
656         if (!('0' < ch && ch <= '5'))
657             return false;
658         tone = (ChewingTone) (ch - '0');
659     }
660
661     if (2 == len || 3 == len) {
662         /* parse shengmu here. */
663         char ch = str[0];
664         if (!IS_KEY(ch))
665             return false;
666
667         int charid = ch == ';' ? 26 : ch - 'a';
668         const char * sheng = m_shengmu_table[charid].m_shengmu;
669         if (NULL == sheng)
670             return false;
671         if (strcmp(sheng, "'") == 0)
672             sheng = "";
673
674         /* parse yunmu here. */
675         ch = str[1];
676         if (!IS_KEY(ch))
677             return false;
678
679         charid = ch == ';' ? 26 : ch - 'a';
680         /* first yunmu */
681         const char * yun = m_yunmu_table[charid].m_yunmus[0];
682         if (NULL == yun)
683             return false;
684
685         gchar * pinyin = g_strdup_printf("%s%s", sheng, yun);
686         if (search_pinyin_index(options, pinyin, key)) {
687             key.m_tone = tone;
688             g_free(pinyin);
689             return true;
690         }
691         g_free(pinyin);
692
693         /* second yunmu */
694         yun = m_yunmu_table[charid].m_yunmus[1];
695         if (NULL == yun)
696             return false;
697
698         pinyin = g_strdup_printf("%s%s", sheng, yun);
699         if (search_pinyin_index(options, pinyin, key)) {
700             key.m_tone = tone;
701             g_free(pinyin);
702             return true;
703         }
704         g_free(pinyin);
705
706     }
707
708     return false;
709 }
710
711
712 /* only 'a'-'z' and ';' are accepted here. */
713 int DoublePinyinParser2::parse(pinyin_option_t options, ChewingKeyVector & keys,
714                                ChewingKeyRestVector & key_rests,
715                                const char *str, int len) const {
716     g_array_set_size(keys, 0);
717     g_array_set_size(key_rests, 0);
718
719     int maximum_len = 0; int i;
720     /* probe the longest possible double pinyin string. */
721     for (i = 0; i < len; ++i) {
722         const char ch = str[i];
723         if (!(IS_KEY(ch) || ('0' < ch && ch <= '5')))
724             break;
725     }
726     maximum_len = i;
727
728     /* maximum forward match for double pinyin. */
729     int parsed_len = 0;
730     while (parsed_len < maximum_len) {
731         const char * cur_str = str + parsed_len;
732         i = std_lite::min(maximum_len - parsed_len,
733                           (int)max_double_pinyin_length);
734
735         ChewingKey key; ChewingKeyRest key_rest;
736         for (; i > 0; --i) {
737             bool success = parse_one_key(options, key, cur_str, i);
738             if (success)
739                 break;
740         }
741
742         if (0 == i)        /* no more possible double pinyins. */
743             break;
744
745         key_rest.m_raw_begin = parsed_len; key_rest.m_raw_end = parsed_len + i;
746         parsed_len += i;
747
748         /* save the pinyin */
749         g_array_append_val(keys, key);
750         g_array_append_val(key_rests, key_rest);
751     }
752
753     return parsed_len;
754 }
755
756 #undef IS_KEY
757
758 bool DoublePinyinParser2::set_scheme(DoublePinyinScheme scheme) {
759
760     switch (scheme) {
761     case DOUBLE_PINYIN_ZRM:
762         m_shengmu_table = double_pinyin_zrm_sheng;
763         m_yunmu_table   = double_pinyin_zrm_yun;
764         return true;
765     case DOUBLE_PINYIN_MS:
766         m_shengmu_table = double_pinyin_mspy_sheng;
767         m_yunmu_table   = double_pinyin_mspy_yun;
768         return true;
769     case DOUBLE_PINYIN_ZIGUANG:
770         m_shengmu_table = double_pinyin_zgpy_sheng;
771         m_yunmu_table   = double_pinyin_zgpy_yun;
772         return true;
773     case DOUBLE_PINYIN_ABC:
774         m_shengmu_table = double_pinyin_abc_sheng;
775         m_yunmu_table   = double_pinyin_abc_yun;
776         return true;
777     case DOUBLE_PINYIN_PYJJ:
778         m_shengmu_table = double_pinyin_pyjj_sheng;
779         m_yunmu_table   = double_pinyin_pyjj_yun;
780         return true;
781     case DOUBLE_PINYIN_XHE:
782         m_shengmu_table = double_pinyin_xhe_sheng;
783         m_yunmu_table   = double_pinyin_xhe_yun;
784         return true;
785     case DOUBLE_PINYIN_CUSTOMIZED:
786         assert(FALSE);
787     };
788
789     return false; /* no such scheme. */
790 }
791
792 /* the chewing string must be freed with g_free. */
793 static bool search_chewing_symbols(const chewing_symbol_item_t * symbol_table,
794                                    const char key, const char ** chewing) {
795     *chewing = NULL;
796     /* just iterate the table, as we only have < 50 items. */
797     while (symbol_table->m_input != '\0') {
798         if (symbol_table->m_input == key) {
799             *chewing = symbol_table->m_chewing;
800             return true;
801         }
802         symbol_table ++;
803     }
804     return false;
805 }
806
807 static bool search_chewing_tones(const chewing_tone_item_t * tone_table,
808                                  const char key, char * tone) {
809     *tone = CHEWING_ZERO_TONE;
810     /* just iterate the table, as we only have < 10 items. */
811     while (tone_table->m_input != '\0') {
812         if (tone_table->m_input == key) {
813             *tone = tone_table->m_tone;
814             return true;
815         }
816         tone_table ++;
817     }
818     return false;
819 }
820
821
822 bool ChewingParser2::parse_one_key(pinyin_option_t options,
823                                    ChewingKey & key,
824                                    const char *str, int len) const {
825     options &= ~(PINYIN_CORRECT_ALL|PINYIN_AMB_ALL);
826     char tone = CHEWING_ZERO_TONE;
827
828     int symbols_len = len;
829     /* probe whether the last key is tone key in str. */
830     if (options & USE_TONE) {
831         char ch = str[len - 1];
832         /* remove tone from input */
833         if (search_chewing_tones(m_tone_table, ch, &tone))
834             symbols_len --;
835     }
836
837     int i;
838     gchar * chewing = NULL; const char * onechar = NULL;
839
840     /* probe the possible chewing map in the rest of str. */
841     for (i = 0; i < symbols_len; ++i) {
842         if (!search_chewing_symbols(m_symbol_table, str[i], &onechar)) {
843             g_free(chewing);
844             return false;
845         }
846
847         if (!chewing) {
848             chewing = g_strdup(onechar);
849         } else {
850             gchar * tmp = chewing;
851             chewing = g_strconcat(chewing, onechar, NULL);
852             g_free(tmp);
853         }
854     }
855
856     /* search the chewing in the chewing index table. */
857     if (chewing && search_chewing_index(options, chewing, key)) {
858         /* save back tone if available. */
859         key.m_tone = tone;
860         g_free(chewing);
861         return true;
862     }
863
864     g_free(chewing);
865     return false;
866 }
867
868
869 /* only characters in chewing keyboard scheme are accepted here. */
870 int ChewingParser2::parse(pinyin_option_t options, ChewingKeyVector & keys,
871                           ChewingKeyRestVector & key_rests,
872                           const char *str, int len) const {
873     g_array_set_size(keys, 0);
874     g_array_set_size(key_rests, 0);
875
876     int maximum_len = 0; int i;
877     /* probe the longest possible chewing string. */
878     for (i = 0; i < len; ++i) {
879         if (!in_chewing_scheme(options, str[i], NULL))
880             break;
881     }
882     maximum_len = i;
883
884     /* maximum forward match for chewing. */
885     int parsed_len = 0;
886     while (parsed_len < maximum_len) {
887         const char * cur_str = str + parsed_len;
888         i = std_lite::min(maximum_len - parsed_len,
889                           (int)max_chewing_length);
890
891         ChewingKey key; ChewingKeyRest key_rest;
892         for (; i > 0; --i) {
893             bool success = parse_one_key(options, key, cur_str, i);
894             if (success)
895                 break;
896         }
897
898         if (0 == i)        /* no more possible chewings. */
899             break;
900
901         key_rest.m_raw_begin = parsed_len; key_rest.m_raw_end = parsed_len + i;
902         parsed_len += i;
903
904         /* save the pinyin. */
905         g_array_append_val(keys, key);
906         g_array_append_val(key_rests, key_rest);
907     }
908
909     return parsed_len;
910 }
911
912
913 bool ChewingParser2::set_scheme(ChewingScheme scheme) {
914     switch(scheme) {
915     case CHEWING_STANDARD:
916         m_symbol_table = chewing_standard_symbols;
917         m_tone_table   = chewing_standard_tones;
918         return true;
919     case CHEWING_IBM:
920         m_symbol_table = chewing_ibm_symbols;
921         m_tone_table   = chewing_ibm_tones;
922         return true;
923     case CHEWING_GINYIEH:
924         m_symbol_table = chewing_ginyieh_symbols;
925         m_tone_table   = chewing_ginyieh_tones;
926         return true;
927     case CHEWING_ETEN:
928         m_symbol_table = chewing_eten_symbols;
929         m_tone_table   = chewing_eten_tones;
930         return true;
931     }
932
933     return false;
934 }
935
936
937 bool ChewingParser2::in_chewing_scheme(pinyin_option_t options,
938                                        const char key, const char ** symbol)
939  const {
940     const gchar * chewing = NULL;
941     char tone = CHEWING_ZERO_TONE;
942
943     if (search_chewing_symbols(m_symbol_table, key, &chewing)) {
944         if (symbol)
945             *symbol = chewing;
946         return true;
947     }
948
949     if (!(options & USE_TONE))
950         return false;
951
952     if (search_chewing_tones(m_tone_table, key, &tone)) {
953         if (symbol)
954             *symbol = chewing_tone_table[tone];
955         return true;
956     }
957
958     return false;
959 }