refactor pinyin_phrase_files
[platform/upstream/libpinyin.git] / src / storage / phrase_index.cpp
1 /* 
2  *  libpinyin
3  *  Library to deal with pinyin.
4  *  
5  *  Copyright (C) 2006-2007 Peng Wu
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 #include "phrase_index.h"
23 #include "pinyin_custom2.h"
24
25 using namespace pinyin;
26
27 bool PhraseItem::set_n_pronunciation(guint8 n_prouns){
28     m_chunk.set_content(sizeof(guint8), &n_prouns, sizeof(guint8));
29     return true;
30 }
31
32 bool PhraseItem::get_nth_pronunciation(size_t index, ChewingKey * keys,
33                                        guint32 & freq){
34     guint8 phrase_length = get_phrase_length();
35     table_offset_t offset = phrase_item_header + phrase_length * sizeof( ucs4_t) + index * ( phrase_length * sizeof (ChewingKey) + sizeof(guint32));
36
37     bool retval = m_chunk.get_content
38         (offset, keys, phrase_length * sizeof(ChewingKey));
39     if ( !retval )
40         return retval;
41     return m_chunk.get_content
42         (offset + phrase_length * sizeof(ChewingKey), &freq , sizeof(guint32));
43 }
44
45 void PhraseItem::append_pronunciation(ChewingKey * keys, guint32 freq){
46     guint8 phrase_length = get_phrase_length();
47     set_n_pronunciation(get_n_pronunciation() + 1);
48     m_chunk.set_content(m_chunk.size(), keys,
49                         phrase_length * sizeof(ChewingKey));
50     m_chunk.set_content(m_chunk.size(), &freq, sizeof(guint32));
51 }
52
53 void PhraseItem::remove_nth_pronunciation(size_t index){
54     guint8 phrase_length = get_phrase_length();
55     set_n_pronunciation(get_n_pronunciation() - 1);
56     size_t offset = phrase_item_header + phrase_length * sizeof ( ucs4_t ) +
57         index * (phrase_length * sizeof (ChewingKey) + sizeof(guint32));
58     m_chunk.remove_content(offset, phrase_length * sizeof(ChewingKey) + sizeof(guint32));
59 }
60
61 bool PhraseItem::get_phrase_string(ucs4_t * phrase){
62     guint8 phrase_length = get_phrase_length();
63     return m_chunk.get_content(phrase_item_header, phrase, phrase_length * sizeof(ucs4_t));
64 }
65
66 bool PhraseItem::set_phrase_string(guint8 phrase_length, ucs4_t * phrase){
67     m_chunk.set_content(0, &phrase_length, sizeof(guint8));
68     m_chunk.set_content(phrase_item_header, phrase, phrase_length * sizeof(ucs4_t));
69     return true;
70 }
71
72 void PhraseItem::increase_pronunciation_possibility(pinyin_option_t options,
73                                              ChewingKey * keys,
74                                              gint32 delta){
75     guint8 phrase_length = get_phrase_length();
76     guint8 npron = get_n_pronunciation();
77     size_t offset = phrase_item_header + phrase_length * sizeof ( ucs4_t );
78     char * buf_begin = (char *) m_chunk.begin();
79     guint32 total_freq = 0;
80     for ( int i = 0 ; i < npron ; ++i){
81         char * chewing_begin = buf_begin + offset +
82             i * ( phrase_length * sizeof(ChewingKey) + sizeof(guint32) );
83         guint32 * freq = (guint32 *)(chewing_begin +
84                                      phrase_length * sizeof(ChewingKey));
85         total_freq += *freq;
86         if ( 0 == pinyin_compare_with_ambiguities2
87              (options, keys,
88               (ChewingKey *)chewing_begin, phrase_length) ){
89             //protect against total_freq overflow.
90             if ( delta > 0 && total_freq > total_freq + delta )
91                 return;
92             *freq += delta;
93             total_freq += delta;
94         }
95     }
96 }
97
98
99 guint32 SubPhraseIndex::get_phrase_index_total_freq(){
100     return m_total_freq;
101 }
102
103 int SubPhraseIndex::add_unigram_frequency(phrase_token_t token, guint32 delta){
104     table_offset_t offset;
105     guint32 freq;
106     bool result = m_phrase_index.get_content
107         ((token & PHRASE_MASK) 
108          * sizeof(table_offset_t), &offset, sizeof(table_offset_t));
109
110     if ( !result )
111         return ERROR_OUT_OF_RANGE;
112
113     if ( 0 == offset )
114     return ERROR_NO_ITEM;
115
116     result = m_phrase_content.get_content
117         (offset + sizeof(guint8) + sizeof(guint8), &freq, sizeof(guint32));
118
119     if ( !result )
120     return ERROR_FILE_CORRUPTION;
121
122     //protect total_freq overflow
123     if ( delta > 0 && m_total_freq > m_total_freq + delta )
124         return ERROR_INTEGER_OVERFLOW;
125
126     freq += delta;
127     m_total_freq += delta;
128     m_phrase_content.set_content(offset + sizeof(guint8) + sizeof(guint8), &freq, sizeof(guint32));
129
130     return ERROR_OK;
131 }
132
133 int SubPhraseIndex::get_phrase_item(phrase_token_t token, PhraseItem & item){
134     table_offset_t offset;
135     guint8 phrase_length;
136     guint8 n_prons;
137     
138     bool result = m_phrase_index.get_content
139         ((token & PHRASE_MASK) 
140          * sizeof(table_offset_t), &offset, sizeof(table_offset_t));
141
142     if ( !result )
143         return ERROR_OUT_OF_RANGE;
144
145     if ( 0 == offset )
146     return ERROR_NO_ITEM;
147
148     result = m_phrase_content.get_content(offset, &phrase_length, sizeof(guint8));
149     if ( !result ) 
150     return ERROR_FILE_CORRUPTION;
151     
152     result = m_phrase_content.get_content(offset+sizeof(guint8), &n_prons, sizeof(guint8));
153     if ( !result ) 
154         return ERROR_FILE_CORRUPTION;
155
156     size_t length = phrase_item_header + phrase_length * sizeof ( ucs4_t ) + n_prons * ( phrase_length * sizeof (ChewingKey) + sizeof(guint32) );
157     item.m_chunk.set_chunk((char *)m_phrase_content.begin() + offset, length, NULL);
158     return ERROR_OK;
159 }
160
161 int SubPhraseIndex::add_phrase_item(phrase_token_t token, PhraseItem * item){
162     table_offset_t offset = m_phrase_content.size();
163     if ( 0 == offset )
164         offset = 8;
165     m_phrase_content.set_content(offset, item->m_chunk.begin(), item->m_chunk.size());
166     m_phrase_index.set_content((token & PHRASE_MASK) 
167                                * sizeof(table_offset_t), &offset, sizeof(table_offset_t));
168     m_total_freq += item->get_unigram_frequency();
169     return ERROR_OK;
170 }
171
172 int SubPhraseIndex::remove_phrase_item(phrase_token_t token, PhraseItem * & item){
173     PhraseItem old_item;
174
175     int result = get_phrase_item(token, old_item);
176     if (result != ERROR_OK)
177     return result;
178
179     item = new PhraseItem;
180     //implictly copy data from m_chunk_content.
181     item->m_chunk.set_content(0, (char *) old_item.m_chunk.begin() , old_item.m_chunk.size());
182
183     const table_offset_t zero_const = 0;
184     m_phrase_index.set_content((token & PHRASE_MASK)
185                                * sizeof(table_offset_t), &zero_const, sizeof(table_offset_t));
186     m_total_freq -= item->get_unigram_frequency();
187     return ERROR_OK;
188 }
189
190 bool FacadePhraseIndex::load(guint8 phrase_index, MemoryChunk * chunk){
191     SubPhraseIndex * & sub_phrases = m_sub_phrase_indices[phrase_index];
192     if ( !sub_phrases ){
193         sub_phrases = new SubPhraseIndex;
194     }
195
196     m_total_freq -= sub_phrases->get_phrase_index_total_freq();
197     bool retval = sub_phrases->load(chunk, 0, chunk->size());
198     if ( !retval )
199         return retval;
200     m_total_freq += sub_phrases->get_phrase_index_total_freq();
201     return retval;
202 }
203
204 bool FacadePhraseIndex::store(guint8 phrase_index, MemoryChunk * new_chunk){
205     table_offset_t end;
206     SubPhraseIndex * & sub_phrases = m_sub_phrase_indices[phrase_index];
207     if ( !sub_phrases )
208         return false;
209     
210     sub_phrases->store(new_chunk, 0, end);
211     return true;
212 }
213
214 bool FacadePhraseIndex::unload(guint8 phrase_index){
215     SubPhraseIndex * & sub_phrases = m_sub_phrase_indices[phrase_index];
216     if ( !sub_phrases )
217         return false;
218     m_total_freq -= sub_phrases->get_phrase_index_total_freq();
219     delete sub_phrases;
220     sub_phrases = NULL;
221     return true;
222 }
223
224 bool FacadePhraseIndex::diff(guint8 phrase_index, MemoryChunk * oldchunk,
225                              MemoryChunk * newlog){
226     SubPhraseIndex * & sub_phrases = m_sub_phrase_indices[phrase_index];
227     if ( !sub_phrases )
228         return false;
229
230     SubPhraseIndex old_sub_phrases;
231     old_sub_phrases.load(oldchunk, 0, oldchunk->size());
232     PhraseIndexLogger logger;
233
234     bool retval = sub_phrases->diff(&old_sub_phrases, &logger);
235     logger.store(newlog);
236     return retval;
237 }
238
239 bool FacadePhraseIndex::merge(guint8 phrase_index, MemoryChunk * log){
240     SubPhraseIndex * & sub_phrases = m_sub_phrase_indices[phrase_index];
241     if ( !sub_phrases )
242         return false;
243
244     m_total_freq -= sub_phrases->get_phrase_index_total_freq();
245     PhraseIndexLogger logger;
246     logger.load(log);
247
248     bool retval = sub_phrases->merge(&logger);
249     m_total_freq += sub_phrases->get_phrase_index_total_freq();
250
251     return retval;
252 }
253
254 bool SubPhraseIndex::load(MemoryChunk * chunk, 
255                           table_offset_t offset, table_offset_t end){
256     //save the memory chunk
257     if ( m_chunk ){
258         delete m_chunk;
259         m_chunk = NULL;
260     }
261     m_chunk = chunk;
262     
263     char * buf_begin = (char *)chunk->begin();
264     chunk->get_content(offset, &m_total_freq, sizeof(guint32));
265     offset += sizeof(guint32);
266     table_offset_t index_one, index_two, index_three;
267     chunk->get_content(offset, &index_one, sizeof(table_offset_t));
268     offset += sizeof(table_offset_t);
269     chunk->get_content(offset, &index_two, sizeof(table_offset_t));
270     offset += sizeof(table_offset_t);
271     chunk->get_content(offset, &index_three, sizeof(table_offset_t));
272     offset += sizeof(table_offset_t);
273     g_return_val_if_fail(*(buf_begin + offset) == c_separate, FALSE);
274     g_return_val_if_fail(*(buf_begin + index_two - 1) == c_separate, FALSE);
275     g_return_val_if_fail(*(buf_begin + index_three - 1) == c_separate, FALSE);
276     m_phrase_index.set_chunk(buf_begin + index_one, 
277                              index_two - 1 - index_one, NULL);
278     m_phrase_content.set_chunk(buf_begin + index_two, 
279                                  index_three - 1 - index_two, NULL);
280     g_return_val_if_fail( index_three <= end, FALSE);
281     return true;
282 }
283
284 bool SubPhraseIndex::store(MemoryChunk * new_chunk, 
285                            table_offset_t offset, table_offset_t& end){
286     new_chunk->set_content(offset, &m_total_freq, sizeof(guint32));
287     table_offset_t index = offset + sizeof(guint32);
288         
289     offset = index + sizeof(table_offset_t) * 3 ;
290     new_chunk->set_content(offset, &c_separate, sizeof(char));
291     offset += sizeof(char);
292     
293     new_chunk->set_content(index, &offset, sizeof(table_offset_t));
294     index += sizeof(table_offset_t);
295     new_chunk->set_content(offset, m_phrase_index.begin(), m_phrase_index.size());
296     offset += m_phrase_index.size();
297     new_chunk->set_content(offset, &c_separate, sizeof(char));
298     offset += sizeof(char);
299
300     new_chunk->set_content(index, &offset, sizeof(table_offset_t));
301     index += sizeof(table_offset_t);
302     
303     new_chunk->set_content(offset, m_phrase_content.begin(), m_phrase_content.size());
304     offset += m_phrase_content.size();
305     new_chunk->set_content(offset, &c_separate, sizeof(char));
306     offset += sizeof(char);
307     new_chunk->set_content(index, &offset, sizeof(table_offset_t));
308     return true;
309 }
310
311 bool SubPhraseIndex::diff(SubPhraseIndex * oldone, PhraseIndexLogger * logger){
312     /* diff the header */
313     MemoryChunk oldheader, newheader;
314     guint32 total_freq = oldone->get_phrase_index_total_freq();
315     oldheader.set_content(0, &total_freq, sizeof(guint32));
316     total_freq = get_phrase_index_total_freq();
317     newheader.set_content(0, &total_freq, sizeof(guint32));
318     logger->append_record(LOG_MODIFY_HEADER, null_token,
319                           &oldheader, &newheader);
320
321     /* diff phrase items */
322     PhraseIndexRange oldrange, currange, range;
323     oldone->get_range(oldrange); get_range(currange);
324     range.m_range_begin = std_lite::min(oldrange.m_range_begin,
325                                         currange.m_range_begin);
326     range.m_range_end = std_lite::max(oldrange.m_range_end,
327                                      currange.m_range_end);
328     PhraseItem olditem, newitem;
329
330     for (phrase_token_t token = range.m_range_begin;
331          token < range.m_range_end; ++token ){
332         bool oldretval = ERROR_OK == oldone->get_phrase_item(token, olditem);
333         bool newretval = ERROR_OK == get_phrase_item(token, newitem);
334
335         if ( oldretval ){
336             if ( newretval ) { /* compare phrase item. */
337                 if ( olditem == newitem )
338                     continue;
339                 logger->append_record(LOG_MODIFY_RECORD, token,
340                                       &(olditem.m_chunk), &(newitem.m_chunk));
341             } else { /* remove phrase item. */
342                 logger->append_record(LOG_REMOVE_RECORD, token,
343                                       &(olditem.m_chunk), NULL);
344             }
345         } else {
346             if ( newretval ){ /* add phrase item. */
347                 logger->append_record(LOG_ADD_RECORD, token,
348                                       NULL, &(newitem.m_chunk));
349             } else { /* both empty. */
350                     /* do nothing. */
351             }
352         }
353     }
354
355     return true;
356 }
357
358 bool SubPhraseIndex::merge(PhraseIndexLogger * logger){
359     LOG_TYPE log_type; phrase_token_t token;
360     MemoryChunk oldchunk, newchunk;
361     PhraseItem olditem, newitem, item, * tmpitem;
362
363     while(logger->has_next_record()){
364         logger->next_record(log_type, token, &oldchunk, &newchunk);
365
366         switch(log_type){
367         case LOG_ADD_RECORD:{
368             assert( 0 == oldchunk.size() );
369             newitem.m_chunk.set_chunk(newchunk.begin(), newchunk.size(),
370                                       NULL);
371             add_phrase_item(token, &newitem);
372             break;
373         }
374         case LOG_REMOVE_RECORD:{
375             assert( 0 == newchunk.size() );
376             tmpitem = NULL;
377             remove_phrase_item(token, tmpitem);
378
379             olditem.m_chunk.set_chunk(oldchunk.begin(), oldchunk.size(),
380                                    NULL);
381             if (olditem != *tmpitem)
382                 return false;
383             delete tmpitem;
384
385             break;
386         }
387         case LOG_MODIFY_RECORD:{
388             get_phrase_item(token, item);
389             olditem.m_chunk.set_chunk(oldchunk.begin(), oldchunk.size(),
390                                       NULL);
391             newitem.m_chunk.set_chunk(newchunk.begin(), newchunk.size(),
392                                       NULL);
393             if (item != olditem)
394                 return false;
395
396             if (newchunk.size() > item.m_chunk.size() ){ /* increase size. */
397                 tmpitem = NULL;
398                 remove_phrase_item(token, tmpitem);
399                 assert(olditem == *tmpitem);
400                 add_phrase_item(token, &newitem);
401                 delete tmpitem;
402             } else { /* in place editing. */
403                 /* newchunk.size() <= item.m_chunk.size() */
404                 /* Hack here: we assume the behaviour of get_phrase_item
405                  * point to the actual data positon, so changes to item
406                  * will be saved in SubPhraseIndex immediately.
407                  */
408                 memmove(item.m_chunk.begin(), newchunk.begin(),
409                         newchunk.size());
410             }
411             break;
412         }
413         case LOG_MODIFY_HEADER:{
414             guint32 total_freq = get_phrase_index_total_freq();
415             guint32 tmp_freq = 0;
416             assert(null_token == token);
417             assert(oldchunk.size() == newchunk.size());
418             oldchunk.get_content(0, &tmp_freq, sizeof(guint32));
419             if (total_freq != tmp_freq)
420                 return false;
421             newchunk.get_content(0, &tmp_freq, sizeof(guint32));
422             m_total_freq = tmp_freq;
423             break;
424         }
425         default:
426             assert(false);
427         }
428     }
429     return true;
430 }
431
432 bool FacadePhraseIndex::load_text(guint8 phrase_index, FILE * infile){
433     SubPhraseIndex * & sub_phrases = m_sub_phrase_indices[phrase_index];
434     if ( !sub_phrases ){
435         sub_phrases = new SubPhraseIndex;
436     }
437
438     char pinyin[256];
439     char phrase[256];
440     phrase_token_t token;
441     size_t freq;
442     PhraseItem * item_ptr = new PhraseItem;
443     phrase_token_t cur_token = 0;
444     while ( !feof(infile)){
445         fscanf(infile, "%s", pinyin);
446         fscanf(infile, "%s", phrase);
447         fscanf(infile, "%u", &token);
448         fscanf(infile, "%ld", &freq);
449         if ( feof(infile) )
450             break;
451
452         assert(PHRASE_INDEX_LIBRARY_INDEX(token) == phrase_index );
453
454         glong written;
455         ucs4_t * phrase_ucs4 = g_utf8_to_ucs4(phrase, -1, NULL, 
456                                               &written, NULL);
457         
458         if ( 0 == cur_token ){
459             cur_token = token;
460             item_ptr->set_phrase_string(written, phrase_ucs4);
461         }
462
463         if ( cur_token != token ){
464             add_phrase_item( cur_token, item_ptr);
465             delete item_ptr;
466             item_ptr = new PhraseItem;
467             cur_token = token;
468             item_ptr->set_phrase_string(written, phrase_ucs4);
469         }
470
471         pinyin_option_t options = USE_TONE;
472         FullPinyinParser2 parser;
473         ChewingKeyVector keys = g_array_new(FALSE, FALSE, sizeof(ChewingKey));
474         ChewingKeyRestVector key_rests =
475             g_array_new(FALSE, FALSE, sizeof(ChewingKeyRest));
476
477         parser.parse(options, keys, key_rests, pinyin, strlen(pinyin));
478         
479         if (item_ptr->get_phrase_length() == keys->len) {
480             item_ptr->append_pronunciation((ChewingKey *)keys->data, freq);
481         } else {
482             fprintf(stderr, "FacadePhraseIndex::load_text:%s\t%s\n",
483                     pinyin, phrase);
484         }
485
486         g_array_free(keys, TRUE);
487         g_array_free(key_rests, TRUE);
488         g_free(phrase_ucs4);
489     }
490
491     add_phrase_item( cur_token, item_ptr);
492     delete item_ptr;
493     m_total_freq += m_sub_phrase_indices[phrase_index]->get_phrase_index_total_freq();
494     return true;
495 }
496
497 int FacadePhraseIndex::get_sub_phrase_range(guint8 & min_index,
498                                             guint8 & max_index){
499     min_index = PHRASE_INDEX_LIBRARY_COUNT; max_index = 0;
500     for ( guint8 i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i ){
501         if ( m_sub_phrase_indices[i] ) {
502             min_index = std_lite::min(min_index, i);
503             max_index = std_lite::max(max_index, i);
504         }
505     }
506     return ERROR_OK;
507 }
508
509 int FacadePhraseIndex::get_range(guint8 phrase_index, /* out */ PhraseIndexRange & range){
510     SubPhraseIndex * sub_phrase = m_sub_phrase_indices[phrase_index];
511     if ( !sub_phrase )
512         return ERROR_NO_SUB_PHRASE_INDEX;
513
514     int result = sub_phrase->get_range(range);
515     if ( result )
516         return result;
517
518     range.m_range_begin = PHRASE_INDEX_MAKE_TOKEN(phrase_index, range.m_range_begin);
519     range.m_range_end = PHRASE_INDEX_MAKE_TOKEN(phrase_index, range.m_range_end);
520     return ERROR_OK;
521 }
522
523 int SubPhraseIndex::get_range(/* out */ PhraseIndexRange & range){
524     const table_offset_t * begin = (const table_offset_t *)m_phrase_index.begin();
525     const table_offset_t * end = (const table_offset_t *)m_phrase_index.end();
526
527     range.m_range_begin = 1; /* token starts with 1 in gen_pinyin_table. */
528     range.m_range_end = end - begin;
529
530     return ERROR_OK;
531 }
532
533 bool FacadePhraseIndex::compact(){
534     for ( size_t index = 0; index < PHRASE_INDEX_LIBRARY_COUNT; ++index) {
535         SubPhraseIndex * sub_phrase = m_sub_phrase_indices[index];
536         if ( !sub_phrase )
537             continue;
538
539         SubPhraseIndex * new_sub_phrase =  new SubPhraseIndex;
540         PhraseIndexRange range;
541         int result = sub_phrase->get_range(range);
542         if ( result != ERROR_OK ) {
543             delete new_sub_phrase;
544             continue;
545         }
546
547         PhraseItem item;
548         for ( phrase_token_t token = range.m_range_begin;
549               token < range.m_range_end;
550               ++token ) {
551             result = sub_phrase->get_phrase_item(token, item);
552             if ( result != ERROR_OK )
553                 continue;
554             new_sub_phrase->add_phrase_item(token, &item);
555         }
556
557         delete sub_phrase;
558         m_sub_phrase_indices[index] = new_sub_phrase;
559     }
560     return true;
561 }
562
563 namespace pinyin{
564 const pinyin_table_info_t pinyin_phrase_files[PHRASE_INDEX_LIBRARY_COUNT] =
565     {
566         {NULL, NULL, NULL, NOT_USED},
567         {"gb_char.table", "gb_char.bin", "gb_char.dbin", SYSTEM_FILE},
568         {"gbk_char.table", "gbk_char.bin", "gbk_char.dbin", SYSTEM_FILE},
569         {NULL, NULL, NULL, NOT_USED},
570         {NULL, NULL, NULL, NOT_USED},
571
572         {NULL, NULL, NULL, NOT_USED},
573         {NULL, NULL, NULL, NOT_USED},
574         {NULL, NULL, NULL, NOT_USED},
575         {NULL, NULL, NULL, NOT_USED},
576         {NULL, NULL, NULL, NOT_USED},
577
578         {NULL, NULL, NULL, NOT_USED},
579         {NULL, NULL, NULL, NOT_USED},
580         {NULL, NULL, NULL, NOT_USED},
581         {NULL, NULL, NULL, NOT_USED},
582         {NULL, NULL, NULL, NOT_USED},
583
584         {NULL, NULL, "user.bin", USER_FILE}
585     };
586 };