1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Initial Developer of the Original Code is Fireinput Inc.
15 *
16 * Portions created by the Initial Developer are Copyright (C) 2007
17 * the Initial Developer. All Rights Reserved.
18 *
19 * Contributor(s):
20 * Olly Ja <ollyja@gmail.com>
21 *
22 * Alternatively, the contents of this file may be used under the terms of
23 * either the GNU General Public License Version 2 or later (the "GPL"), or
24 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
25 * in which case the provisions of the GPL or the LGPL are applicable instead
26 * of those above. If you wish to allow use of your version of this file only
27 * under the terms of either the GPL or the LGPL, and not to allow others to
28 * use your version of this file under the terms of the MPL, indicate your
29 * decision by deleting the provisions above and replace them with the notice
30 * and other provisions required by the GPL or the LGPL. If you do not delete
31 * the provisions above, a recipient may use your version of this file under
32 * the terms of any one of the MPL, the GPL or the LGPL.
33 *
34 * ***** END LICENSE BLOCK *****
35 */
36
37 var SmartPinyin = function() {};
38
39 SmartPinyin.prototype = extend(new FireinputIME(),
40 {
41 // 0 to disable debug or non zero to enable debug
42 debug: 0,
43
44 // the name of IME
45 name: IME_SMART_PINYIN,
46
47 // pinyin Initial list
48 pinyinInitials: [],
49
50 // pinyin Fials list
51 pinyinFinals: [],
52
53 // array to keep all matched words
54 charArray: null,
55
56 // invalid input key
57 validInputKey: null,
58
59 // current position of charArray
60 charIndex: 0,
61
62 // the hash table for single word-pinyin
63 codePinyinHash: null,
64
65 // the hash table for phrase
66 phraseCodeHash: null,
67
68 // the hash table for user frequency
69 userCodeHash: null,
70
71 // use code hash table event
72 userTableChanged: false,
73
74 // full/half letter converter
75 letterConverter: null,
76
77 // pinyin Schema
78 pinyinSchema: null,
79
80 // encoding mode. Either simplified or big5. Simplified default.
81 encodingMode: ENCODING_ZH,
82
83 // engine enabled
84 engineDisabled: false,
85
86 // auto insertion, only enable for 4 keys
87 autoInsertion: false,
88
89 // the entrance function to load all related tables
90 loadTable: function()
91 {
92 letterConverter = new FullLetterConverter();
93
94 for(var i=0; i<PinyinInitials.length; i++)
95 this.pinyinInitials[PinyinInitials[i]] = PinyinInitials[i];
96
97 for(var i=0; i<PinyinFinals.length; i++)
98 this.pinyinFinals[PinyinFinals[i]] = PinyinFinals[i];
99
100 // setTimeout to not block firefox start
101 var self = this;
102 setTimeout(function() { return self.loadPinyinTable(); }, 500);
103
104 // init encoding table
105 FireinputEncoding.init();
106 },
107
108 getCodeLine: function(str)
109 {
110 var strArray = str.split(':');
111 if(strArray.length < 2)
112 return;
113
114 // initKey:key=>word
115 this.codePinyinHash.setItem(strArray[0],strArray[1]);
116 },
117
118 loadPinyinTable: function()
119 {
120 var ios = IOService.getService(Components.interfaces.nsIIOService);
121 var fileHandler = ios.getProtocolHandler("file")
122 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
123
124 var path = this.getDataPath();
125 var datafile = fileHandler.getFileFromURLSpec(path + this.getPinyinDataFile());
126 if(!datafile.exists())
127 {
128 this.engineDisabled = true;
129 return;
130 }
131
132 this.codePinyinHash = new FireinputHash();
133
134 var options = {
135 caller: this,
136 oncomplete: this.loadPinyinPhrase,
137 onavailable: this.getCodeLine
138 };
139
140 FireinputStream.loadDataAsync(datafile, options);
141 },
142
143
144 getPhraseLine: function(str)
145 {
146 var strArray = str.split(':');
147 if(strArray.length < 1)
148 return;
149
150 // initKey:key=>phrase
151 this.phraseCodeHash.setItem(strArray[0], strArray[1]);
152 },
153
154 loadPinyinPhrase: function()
155 {
156 var ios = IOService.getService(Components.interfaces.nsIIOService);
157 var fileHandler = ios.getProtocolHandler("file")
158 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
159
160 var path = this.getDataPath();
161
162 var datafile = fileHandler.getFileFromURLSpec(path + this.getPinyinPhraseFile());
163
164 this.phraseCodeHash = new FireinputHash();
165
166 if(!datafile.exists())
167 return;
168
169 var options = {
170 caller: this,
171 onavailable: this.getPhraseLine,
172 oncomplete: this.loadUserTable
173 };
174
175 FireinputStream.loadDataAsync(datafile, options);
176 },
177
178 getUserCodeLine: function(str)
179 {
180 var strArray = str.split(':');
181 if(strArray.length < 4)
182 return;
183
184 // user data format: word: freq key initKey
185
186 var word = strArray[0];
187 var freq = strArray[1];
188 var key = strArray[2].replace(/^\s+|\s+$/g, '');
189 var initKey = strArray[3].replace(/^\s+|\s+$/g, '');
190 var newPhrase = false;
191 if(strArray.length > 4 && strArray[4] == "1")
192 newPhrase = true;
193
194 //FIXME: We want to update the codeHash and phraseHash instead of keep it in user hash
195 if(newPhrase)
196 {
197 this.userCodeHash.setItem(word, {freq: freq, key: key, initKey: initKey, newPhrase: newPhrase});
198 }
199 else
200 this.userCodeHash.setItem(word, {freq: freq, key: key, initKey: initKey});
201
202
203 if(this.codePinyinHash.hasItem(initKey))
204 {
205 return;
206 }
207
208 // update phraseCodeHash
209 if(this.phraseCodeHash.hasItem(initKey))
210 {
211
212 var phrase = this.phraseCodeHash.getItem(initKey);
213 var regex = new RegExp(word + "\\d+", "g");
214 var oldWordFreq = phrase.match(regex);
215 if(oldWordFreq)
216 {
217 //phrase = phrase.replace(oldWordFreq, word+freq);
218 //this.phraseCodeHash.setItem(initKey, phrase);
219 }
220 else
221 {
222 phrase += "," + key+"=>"+word+freq;
223 this.phraseCodeHash.setItem(initKey, phrase);
224 }
225
226 return;
227 }
228
229 //the initKey is not in hash. Add it in
230 this.phraseCodeHash.setItem(initKey, key+"=>"+word+freq);
231 },
232
233 loadUserTable: function()
234 {
235 var ios = IOService.getService(Components.interfaces.nsIIOService);
236 var fileHandler = ios.getProtocolHandler("file")
237 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
238
239 var path = FireinputUtils.getAppRootPath();
240 var datafile = fileHandler.getFileFromURLSpec(path + this.getUserDataFile());
241 if(!datafile.exists())
242 return;
243 this.userCodeHash = new FireinputHash();
244
245 var options = {
246 caller: this,
247 onavailable: this.getUserCodeLine
248 };
249 FireinputStream.loadDataAsync(datafile, options);
250 },
251
252 isEnabled: function()
253 {
254 if(this.engineDisabled)
255 return false;
256 var ios = IOService.getService(Components.interfaces.nsIIOService);
257 var fileHandler = ios.getProtocolHandler("file")
258 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
259
260 var path = this.getDataPath();
261 var datafile = fileHandler.getFileFromURLSpec(path + this.getPinyinDataFile());
262 if(!datafile.exists())
263 return false;
264
265 return true;
266 },
267
268 isSchemaEnabled: function()
269 {
270 if(this.engineDisabled)
271 return false;
272
273 return this.isEnabled();
274 },
275
276 canComposeNew: function()
277 {
278 return true;
279 },
280
281 setSchema: function(schema)
282 {
283 if(!this.pinyinSchema)
284 this.pinyinSchema = new PinyinSchema();
285
286 if(!this.pinyinSchema)
287 return;
288
289 //FireinputLog.debug(this, "Set schema: " + schema);
290 this.pinyinSchema.setSchema(schema);
291 },
292
293 getAllowedInputKey: function()
294 {
295 if(this.pinyinSchema)
296 return this.pinyinSchema.getAllowedKeys();
297
298 return "abcdefghijklmnopqrstuvwxyz";
299 },
300
301 setEncoding: function(encoding)
302 {
303 this.encodingMode = encoding;
304 },
305
306 convertLetter: function(code)
307 {
308 // Full: number + alpha character
309 // Punct: any printable character which is not a space or an alphanumeric character
310 if((!this.isHalfLetterMode() &&
311 ((code > 47 && code < 58) ||
312 (code > 64 && code < 91))) ||
313 (!this.isHalfPunctMode() &&
314 !((code > 47 && code < 58) ||
315 (code > 64 && code < 91))))
316 return letterConverter.toFullLetter(String.fromCharCode(code));
317
318 return String.fromCharCode(code);
319 },
320
321 find: function(inputChar)
322 {
323 var result = null;
324
325 // use current schema
326 result = this.findBySchema(inputChar);
327 if(!result)
328 {
329 // use default schema: pinyin
330 result = this.findBySchema(inputChar, true);
331 }
332
333 return result;
334 },
335
336 findBySchema: function(inputChar, useDefaultSchema)
337 {
338 var s = inputChar;
339 var retArray = null;
340
341 this.autoInsertion = false;
342 var keyMatch = false;
343 // here we will do searching on inputChar by length -1 every time if retArray is null
344 FireinputLog.debug(this, "Send original key=" + inputChar);
345 while(s.length > 0)
346 {
347 var result=this.searchAll(s, useDefaultSchema, keyMatch);
348 if(!result)
349 break;
350 if(result.charArray)
351 {
352 retArray = result.charArray;
353 break;
354 }
355
356 /*
357 * The exact match wasn't found, so the second loop needs to make the exact match
358 */
359 keyMatch = true;
360 result.keySize = result.keySize >0 ? result.keySize : 1;
361 s = s.substr(0, s.length - result.keySize);
362 FireinputLog.debug(this, "Send key after reduced=" + s);
363
364 // remove last single quot if it presents.
365 if(s.substr(s.length-1, 1) == "'")
366 {
367 s = s.substr(0, s.length - 1);
368 }
369 }
370 this.validInputKey = s;
371 return {charArray:retArray, validInputKey: this.validInputKey};
372 },
373
374 searchAll: function(inputChar, useDefaultSchema, keyMatch)
375 {
376 this.charArray = null;
377 var keySet = null;
378 var keyArray = this.pinyinSchema.getComposeKey(inputChar, useDefaultSchema);
379
380 FireinputLog.debug(this, "keyArray=" + keyArray);
381 if(typeof(keyArray) == "string")
382 {
383 keySet = this.parseKeys(inputChar);
384 this.charArray = this.codeLookup(keySet, keyMatch);
385 /* for single key, if no result was found, it should be a valid hash key for
386 * phrase table. We will just return key w/o further looping
387 */
388 if(!this.charArray && keySet.length <= 1)
389 {
390 keySet = this.parseKeys(inputChar, true);
391 this.charArray = this.codeLookup(keySet, keyMatch);
392 }
393
394 /*
395 * if the chararray is not found, we need to adjust the inputchar by removing last inputkey in keySet
396 * array. Just make sure the FULL type will not be messed up
397 */
398 if(this.charArray != null)
399 return {charArray: this.charArray.slice(0, 9), keySize: 0};
400 else if(keySet && keySet.length > 0)
401 {
402 /* If the last input is FINAL, there might be more input coming, return null to wait
403 * if the keymatch is set, take the input as completed pinyin
404 */
405 if(keySet[keySet.length-1].type == KEY_FINAL && !keyMatch)
406 return null;
407 else
408 return {charArray: null, keySize: keySet[keySet.length-1].key.length};
409 }
410 else
411 return {charArray: null, keySize: 1};
412 }
413 else if(keyArray != null)
414 {
415 /*
416 for(var i=0; i<keyArray.length; i++)
417 {
418 for (var j=0; j<keyArray[i].length; j++)
419 {
420 FireinputLog.debug(this, "keyArray[i][j].type=" + keyArray[i][j].type);
421 FireinputLog.debug(this, "keyArray[i][j].key=" + keyArray[i][j].key);
422 }
423 }
424 */
425 // loop through all possible combinations
426 for(var i=0; i<keyArray.length; i++)
427 {
428 var charArray = this.codeLookup(keyArray[i], keyMatch);
429 if(this.charArray == null)
430 {
431 this.charArray = charArray;
432 }
433 else if(this.charArray != null && charArray != null)
434 {
435 arrayInsert(this.charArray, this.charArray.length, charArray);
436 this.charArray.sort(this.sortCodeArray);
437 }
438
439 }
440
441 if(!this.charArray)
442 return {charArray: null, keySize: 1};
443
444 return {charArray: this.charArray.slice(0, 9), keySize: 1};
445 }
446
447 return {charArray: null, keySize: 1};
448 },
449
450 codeLookup: function(keys, keyMatch)
451 {
452 var charArray = null;
453 this.charIndex = 0;
454
455 var originalKeys = keys;
456
457 if(keys == null || keys.length <= 0)
458 return null;
459
460 FireinputLog.debug(this, "keys.length=" + keys.length);
461 // a valid charArray consist of {key:key, word: word}
462 if(keys.length <= 1)
463 {
464 charArray = this.getValidWord(keys);
465 }
466 else
467 {
468 // any keys (here is group keys) is more than 4 pinyins, autoInsertion will be enabled
469 if(keys.length >= 4)
470 this.autoInsertion = true;
471
472 // look through all possible keyset
473 while(1)
474 {
475 var result = this.getKey(keys);
476 if(result == null)
477 break;
478
479 keys = result.keys;
480 var keySet = result.keyset;
481 var phraseKey = keySet == null ? keys : keySet;
482
483 // add the charArray to global list
484 var tmpCharArray = this.getValidPhrase(phraseKey, keyMatch);
485 if(!tmpCharArray || tmpCharArray.length <= 0)
486 {
487 if(!keySet)
488 break;
489 else
490 continue;
491 }
492 if(charArray == null)
493 charArray = tmpCharArray;
494 else if(charArray != null && tmpCharArray != null)
495 {
496 arrayInsert(charArray, charArray.length, tmpCharArray);
497 charArray.sort(this.sortCodeArray);
498 }
499
500 // there is no additional keyset, stop
501 if(keySet == null)
502 break;
503 }
504 }
505
506 return charArray;
507 },
508
509 next: function (endFlag)
510 {
511 if(!this.charArray)
512 return null;
513
514 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
515 // if the next 9 are already displayed, return null
516 if((this.charIndex+10) >= this.charArray.length)
517 return null;
518
519 var i = this.charIndex;
520 if(!endFlag)
521 this.charIndex += 9;
522 else
523 {
524 i = this.charArray.length-9;
525 i -= 9;
526 this.charIndex = i>0 ? i:0;
527 }
528 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
529 return {charArray:this.charArray.slice(this.charIndex, this.charIndex+9), validInputKey: this.validInputKey};
530 },
531
532 prev: function (homeFlag)
533 {
534 if(!this.charArray)
535 return null;
536 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
537 // if the previous 9 are already displayed, return null
538 if((this.charIndex-9) < 0)
539 return null;
540
541 if(!homeFlag)
542 this.charIndex -= 9;
543 else
544 this.charIndex = 0;
545
546 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
547 return {charArray: this.charArray.slice(this.charIndex, this.charIndex+9), validInputKey: this.validInputKey};
548 },
549
550 isBeginning: function()
551 {
552 return this.charIndex == 0;
553 },
554
555 isEnd: function()
556 {
557 return (this.charIndex+10) >= this.charArray.length;
558 },
559
560 canAutoInsert: function()
561 {
562 return this.autoInsertion;
563 },
564
565 getKeyType: function(key)
566 {
567 if(typeof(this.pinyinFinals[key]) != 'undefined')
568 return KEY_FINAL;
569 else if (typeof(this.pinyinInitials[key]) != 'undefined')
570 return KEY_INITIAL;
571 else
572 return KEY_FULL;
573 },
574
575 parseOneKey: function (keyInitial, keyFinal, sFlag)
576 {
577 var keyInitialLen = keyInitial.length;
578
579 if(typeof(this.pinyinFinals[keyFinal]) != 'undefined')
580 {
581 if(typeof(sFlag) != 'undefined' && sFlag)
582 {
583 // if sFlag is true, these two keys will be recognized as separated keys
584 if(keyInitialLen >0)
585 return ({key: keyInitial, type: KEY_INITIAL, pos: keyInitialLen});
586 else
587 return ({key: keyFinal, type: KEY_FINAL, pos: keyFinal.length});
588 }
589 else
590 {
591 var pinyinKey = {};
592 pinyinKey.key = keyInitial + keyFinal;
593 pinyinKey.type = keyInitialLen>0 ? KEY_FULL : KEY_FINAL;
594 pinyinKey.pos = keyInitialLen + keyFinal.length;
595 return pinyinKey;
596 }
597 }
598 else
599 {
600 for(var i=keyFinal.length-1; i>0; i--)
601 {
602 var subFinal = keyFinal.substring(0, i);
603 if(typeof(this.pinyinFinals[subFinal]) != 'undefined')
604 {
605 var pinyinKey = {};
606 pinyinKey.key = keyInitial + subFinal;
607 pinyinKey.type = keyInitialLen>0 ? KEY_FULL : KEY_FINAL;
608 pinyinKey.pos = keyInitialLen + i;
609 return pinyinKey;
610 }
611 }
612 }
613
614 return ({key: keyInitial, type: KEY_INITIAL, pos: keyInitialLen});
615 },
616
617
618 parseKeys: function(keyList, sFlag)
619 {
620 var keys = new Array();
621 // the space is from updateFrequency. Treat them as single quot
622 keyList = keyList.replace(/\s+/g, "'");
623 // if there are single quot delimiters, process them first
624 if(keyList.search(/\'/) != null)
625 {
626 var keyListArray = keyList.split("'");
627 for(var i=0; i<keyListArray.length; i++)
628 {
629 var retArray = this.parseKeySteps(keyListArray[i], sFlag);
630 if(retArray != null)
631 arrayInsert(keys, keys.length, retArray.slice(0, retArray.length));
632 }
633
634 return keys;
635 }
636
637 return this.parseKeySteps(keyList, sFlag);
638 },
639
640 parseKeySteps: function(keyList, sFlag)
641 {
642 var keys = new Array();
643
644 for(var i=0; i< keyList.length;)
645 {
646 var key1 = keyList.substr(i, 1);
647 var key2 = keyList.substr(i, 2);
648 if(typeof(this.pinyinInitials[key2]) != 'undefined')
649 {
650 var finals = keyList.substr(i+2, 4);
651 if(finals.length <= 0)
652 {
653 keys.push({key: key2, type: KEY_INITIAL});
654
655 break;
656 }
657 var pinyinKey = this.parseOneKey(key2, finals, sFlag);
658 if(pinyinKey.type == KEY_INITIAL)
659 {
660 // No finals. Store them each one as Initial
661 keys.push({key: key2, type: KEY_INITIAL});
662 }
663 else
664 {
665 // for char as g and n, if the followings are finals,
666 // then g/n should not be part of this key
667 var lastChar = pinyinKey.key.substr(pinyinKey.key.length-1, 1);
668 if(pinyinKey.type == KEY_FULL && (lastChar == "n" || lastChar == "g"))
669 {
670 var followingKey = this.parseOneKey("", keyList.substr(i+pinyinKey.pos, 4), sFlag);
671 if(followingKey.type == KEY_FINAL)
672 {
673 pinyinKey.type = KEY_SWING;
674 }
675 }
676
677 keys.push({key: pinyinKey.key, type: pinyinKey.type});
678 }
679
680 i += pinyinKey.pos;
681 }
682 else if(typeof(this.pinyinInitials[key1]) != 'undefined')
683 {
684 var finals = keyList.substr(i+1, 4);
685 if(finals.length <= 0)
686 {
687 keys.push({key: key1, type: KEY_INITIAL});
688 break;
689 }
690 var pinyinKey = this.parseOneKey(key1, finals, sFlag);
691 // for char as g and n, if the followings are finals,
692 // then g/n should not be part of this key
693 var lastChar = pinyinKey.key.substr(pinyinKey.key.length-1, 1);
694 if(pinyinKey.type == KEY_FULL && (lastChar == "n" || lastChar == "g"))
695 {
696 var followingKey = this.parseOneKey("", keyList.substr(i+pinyinKey.pos, 4), sFlag);
697 if(followingKey.type == KEY_FINAL)
698 {
699 pinyinKey.type = KEY_SWING;
700 }
701 }
702
703 keys.push({key: pinyinKey.key, type: pinyinKey.type});
704 i += pinyinKey.pos;
705 }
706 else
707 {
708 var finals = keyList.substr(i, 4);
709 if(finals.length <= 0)
710 {
711 // we don't know what kind of key it's
712 break;
713 }
714 var pinyinKey = this.parseOneKey("", finals, sFlag);
715 keys.push({key: pinyinKey.key, type: KEY_FINAL});
716 i += pinyinKey.pos;
717 }
718 }
719 return keys;
720 },
721
722 getKey: function(keys)
723 {
724 if(!keys)
725 return null;
726
727 var keySet = new Array();
728
729 var foundone = -1;
730 for (var i =0; i<keys.length; i++)
731 {
732 if(foundone !=-1 || keys[i].type != KEY_SWING)
733 keySet.push({key: keys[i].key, type: keys[i].type});
734 else
735 {
736 foundone = i;
737 keySet.push({key: keys[i].key, type: KEY_FULL});
738 }
739 }
740
741 if(foundone > -1)
742 {
743 // found one, move the swing key to next chars before return
744 keys[foundone+1].key = keys[foundone].key.substr(keys[foundone].key.length-1, 1) +
745 keys[foundone+1].key;
746 keys[foundone+1].type = KEY_FULL;
747 keys[foundone].type = KEY_FULL;
748 keys[foundone].key = keys[foundone].key.substr(0, keys[foundone].key.length-1);
749
750 return {keys: keys, keyset: keySet};
751 }
752
753 return {keys: keys, keyset: null};
754 },
755
756 getValidWord: function(keys)
757 {
758 var wordArray = null;
759 var userArray = new Array();
760 var wordList = new Array();
761
762 // this is phrase, not single char
763 if(!keys || keys.length > 1)
764 return null;
765
766 var key = keys[0].key;
767 var keyType = keys[0].type;
768
769 //FireinputLog.debug(this, "key: " + key + ", keyType: " + keyType);
770
771 var keyInitial = key.substring(0, 1);
772 if(key.length >=3)
773 keyInitial = key.substring(0, 3);
774
775 if(!this.codePinyinHash.hasItem(keyInitial))
776 return null;
777
778 var pinyinWordList = this.codePinyinHash.getItem(keyInitial);
779
780 var pinyinWordArray = pinyinWordList.split(",");
781
782 wordArray = new Array();
783
784 for(var i=0; i < pinyinWordArray.length; i++)
785 {
786 var pinyinWord = pinyinWordArray[i].split("=>");
787 if(keyType != KEY_INITIAL)
788 {
789 if(!this.pinyinSchema.compareAMB(key, pinyinWord[0]) && pinyinWord[0] != key)
790 continue;
791 }
792
793 // FireinputLog.debug(this,"pinyinWordArray[i]: " + FireinputUnicode.getUnicodeString(pinyinWordArray[i]));
794
795 var word = "";
796 try
797 {
798 word = pinyinWord[1].match(/[\D\.]+/g)[0];
799 }
800 catch(e) {}
801
802
803 if(word.length <= 0)
804 continue;
805
806 if(typeof(wordList[word]) != 'undefined')
807 continue;
808
809 var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
810
811 wordList[word] = 1;
812 if(this.userCodeHash && this.userCodeHash.hasItem(word))
813 {
814 var ufreq = this.userCodeHash.getItem(word);
815 /* use this way other than push to have better performance
816 * http://aymanh.com/9-javascript-tips-you-may-not-know
817 */
818 userArray[userArray.length] = {key: pinyinWord[0], word:word+ufreq.freq, encodedWord:encodedWord+ufreq.freq};
819 }
820 else
821 {
822 var freq = pinyinWord[1].match(/[\d\.]+/g)[0];
823 wordArray[wordArray.length] = {key:pinyinWord[0], word:pinyinWord[1], encodedWord:encodedWord+freq};
824 }
825
826 // if it's just initial key, don't page too deep
827 if(keyType == KEY_INITIAL)
828 {
829 if(wordArray.length >= 30)
830 break;
831 }
832 }
833
834 // free it
835 wordList = null;
836
837 //FireinputLog.debug(this,"wordArray: " + this.getKeyWord(wordArray));
838 if(userArray.length <= 0)
839 return wordArray;
840
841 // sort the first 10 items
842 if(userArray.length < 10)
843 {
844 arrayInsert(userArray, userArray.length, wordArray.slice(0, 9));
845 userArray.sort(this.sortCodeArray);
846 arrayInsert(userArray, userArray.length, wordArray.slice(10, wordArray.length));
847 }
848 else
849 {
850 userArray.sort(this.sortCodeArray);
851 arrayInsert(userArray, userArray.length, wordArray.slice(0, wordArray.length));
852 }
853
854 //FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
855 return userArray;
856 },
857
858 getInitialKeys: function(keys)
859 {
860 if(!keys)
861 return null;
862
863
864 var initialKeys = "";
865
866 for (var i =0; i<keys.length; i++)
867 {
868 if(keys[i].type == KEY_INITIAL)
869 initialKeys += keys[i].key.substring(0,1);
870 else if(keys[i].type == KEY_FINAL)
871 initialKeys += keys[i].key;
872 else
873 initialKeys += keys[i].key.substring(0, 1);
874 }
875
876 //FireinputLog.debug(this,"initialKeys: " + initialKeys);
877
878 return initialKeys;
879 },
880
881 getValidPhrase: function(keys, keyMatch)
882 {
883 if(!keys)
884 return null;
885
886 var initialKeys = this.getInitialKeys(keys);
887 FireinputLog.debug(this, "initialKeys=" + initialKeys);
888 return this.getValidPhraseWithInitialKey(keys, initialKeys, keyMatch);
889 },
890
891 getValidPhraseWithInitialKey: function(keys, initialKeys, keyMatch)
892 {
893 var phraseArray = [];
894 var phraseList = [];
895 var userArray = new Array();
896
897 // fast lookup for longer chars
898 if(initialKeys.length >=4)
899 {
900 initialKeys = initialKeys.substring(0, 4);
901 // some phrases have been re-hashed by 3 init key
902 if(!this.phraseCodeHash.hasItem(initialKeys))
903 {
904 initialKeys = initialKeys.substring(0,3);
905 }
906 }
907
908 FireinputLog.debug(this,"checking: " + initialKeys + ", keyMatch: " + keyMatch);
909 // make final check before going forward
910 if(!this.phraseCodeHash.hasItem(initialKeys))
911 return null;
912
913 var stringList = this.phraseCodeHash.getItem(initialKeys);
914
915 var stringArray = stringList.split(",");
916
917 for(var i=0; i<stringArray.length; i++)
918 {
919 var phraseKeyValue = stringArray[i].split("=>");
920
921 var keyList = phraseKeyValue[0].split(" ");
922 var shouldAdd = 1;
923 // only match first few chars, should ignore it
924 if(keyList.length < keys.length || (keyMatch && keyList.length != keys.length))
925 continue;
926
927 for (var j =0; j<keys.length; j++)
928 {
929 if(keys[j].type == KEY_INITIAL)
930 {
931 if(keyList[j].indexOf(keys[j].key) !=0)
932 {
933 shouldAdd = 0;
934 break;
935 }
936 }
937 /*
938 * if keyMatch is false, the keyList value should be longer
939 * otherwise it should be exactly matched for both FULL and FINAL
940 * key types
941 */
942 else if(!this.pinyinSchema.compareAMB(keys[j].key, keyList[j]) &&
943 ((keyMatch && keyList[j] != keys[j].key) ||
944 keyList[j].indexOf(keys[j].key) < 0))
945 {
946 shouldAdd = 0;
947 break;
948 }
949 }
950
951 if(shouldAdd == 1)
952 {
953 if(typeof(phraseList[phraseKeyValue[1]]) == 'undefined')
954 {
955 var word = phraseKeyValue[1].match(/[\D\.]+/g)[0];
956 var freq = phraseKeyValue[1].match(/[\d\.]+/g)[0];
957
958 phraseList[phraseKeyValue[1]] = "";
959
960 var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
961 if(this.userCodeHash && this.userCodeHash.hasItem(word))
962 {
963 var ufreq = this.userCodeHash.getItem(word);
964 userArray[userArray.length] = {key: phraseKeyValue[0], word:word+ufreq.freq, encodedWord:encodedWord+ufreq.freq};
965 }
966 else
967 phraseArray[phraseArray.length] = {key: phraseKeyValue[0], word:phraseKeyValue[1], encodedWord:encodedWord+freq};
968 }
969 }
970 }
971
972 if(userArray.length <= 0)
973 return phraseArray.length > 0 ? phraseArray:null;
974
975 // sort the first 10 items
976 if(userArray.length < 10)
977 {
978 arrayInsert(userArray, userArray.length, phraseArray.slice(0, 9));
979 userArray.sort(this.sortCodeArray);
980 arrayInsert(userArray, userArray.length, phraseArray.slice(10, phraseArray.length));
981 }
982 else
983 {
984 userArray.sort(this.sortCodeArray);
985 arrayInsert(userArray, userArray.length, phraseArray.slice(0, phraseArray.length));
986 }
987
988 return userArray;
989 },
990
991 flushUserTable: function()
992 {
993 if(this.userCodeHash && this.userTableChanged)
994 {
995 FireinputSaver.save(this.userCodeHash);
996 }
997 },
998
999 updateFrequency: function(word, key, initKey, newPhrase)
1000 {
1001 var freq = word.match(/[\d\.]+/g)[0];
1002 var chars = word.match(/[\D\.]+/g)[0];
1003 if(!this.userCodeHash)
1004 this.userCodeHash = new FireinputHash();
1005
1006 if(typeof(newPhrase) == "undefined")
1007 newPhrase = false;
1008
1009 var newfreq = 0;
1010 if(this.userCodeHash.hasItem(chars))
1011 {
1012 var charopt = this.userCodeHash.getItem(chars);
1013 var freq1 = charopt.freq;
1014 newfreq = parseInt("0xFFFFFFFF", 16) - freq1;
1015
1016 if(typeof(charopt.newPhrase) != 'undefined')
1017 newPhrase = charopt.newPhrase;
1018
1019 if(typeof(initKey) == "undefined")
1020 initKey = charopt.initKey;
1021 }
1022 else
1023 {
1024 newfreq = parseInt("0xFFFFFFFF", 16) - freq;
1025
1026 if(typeof(initKey) == "undefined")
1027 {
1028 initKey = "";
1029 var keys = this.parseKeys(key);
1030 for(var i=0; i<keys.length; i++)
1031 {
1032 if(keys[i].type == KEY_FINAL)
1033 initKey += keys[i].key;
1034 else
1035 initKey += keys[i].key.substring(0,1);
1036 }
1037
1038 // a single char or phrase
1039 if(/" "/.test(key))
1040 {
1041 if(initKey.length >= 4)
1042 initKey = initKey.substring(0, 4);
1043 }
1044 else
1045 {
1046 if(initKey.length >= 3)
1047 initKey = initKey.substring(0, 3);
1048 }
1049 }
1050 }
1051
1052 if(newfreq)
1053 {
1054 newfreq /= Math.pow(2, 16);
1055 if(newfreq < 1) newfreq = 1;
1056 }
1057 freq = Math.round(newfreq) + parseInt(freq);
1058
1059 if(newPhrase)
1060 this.userCodeHash.setItem(chars, {freq: freq, key: key, initKey: initKey, newPhrase: newPhrase});
1061 else
1062 this.userCodeHash.setItem(chars, {freq: freq, key: key, initKey: initKey});
1063
1064 //FireinputLog.debug(this,"word: " + word);
1065 //FireinputLog.debug(this,"chars: " + chars + ", freq: " + freq);
1066 FireinputLog.debug(this,"chars: " + chars + ", key: " + key + ", initKey: " + initKey);
1067 this.userTableChanged = true;
1068 return freq;
1069 },
1070
1071 storeUserPhrase: function(userPhrase)
1072 {
1073 if(!userPhrase || userPhrase.length <= 0)
1074 return;
1075
1076 FireinputLog.debug(this,"userPhrase: " + this.getKeyWord(userPhrase));
1077 var validInitialKey = "";
1078 var phrase = "";
1079 var keys = "";
1080 for(var i=0; i<userPhrase.length; i++)
1081 {
1082 var word = userPhrase[i].word.match(/[\D\.]+/g)[0];
1083 phrase += word;
1084 keys += userPhrase[i].key;
1085
1086 if(i < (userPhrase.length - 1))
1087 keys += " ";
1088
1089 var keyArray = userPhrase[i].key.split(" ");
1090 for(j=0; j<keyArray.length; j++)
1091 {
1092 if(typeof(this.pinyinFinals[keyArray[j]]) != 'undefined')
1093 {
1094 validInitialKey += keyArray[j];
1095 }
1096 else
1097 {
1098 validInitialKey += keyArray[j].substring(0,1);
1099 }
1100 }
1101
1102 if(validInitialKey.length >= 4)
1103 {
1104 var subValidInitialKey = validInitialKey.substring(0, 3);
1105 if(!this.phraseCodeHash.hasItem(subValidInitialKey))
1106 validInitialKey = subValidInitialKey;
1107 else
1108 validInitialKey = validInitialKey.substring(0, 4);
1109 }
1110 }
1111
1112 FireinputLog.debug(this,"keys: " + keys + ", phrase: " + phrase);
1113 FireinputLog.debug(this,"validInitialKey: " + validInitialKey);
1114 if(!this.userCodeHash)
1115 this.userCodeHash = new FireinputHash();
1116
1117 if(this.userCodeHash.hasItem(phrase))
1118 return;
1119
1120 var freq = this.updateFrequency(phrase+0, keys, validInitialKey, true);
1121 FireinputLog.debug(this,"freq: " + freq);
1122
1123 if(this.phraseCodeHash.hasItem(validInitialKey))
1124 {
1125 // the new phrase is already in phrase table, don't add it in
1126 var nowPhrase = this.phraseCodeHash.getItem(validInitialKey);
1127 var regex = new RegExp(phrase + "\\d+", "g");
1128 var matched = nowPhrase.match(regex);
1129 if(!matched)
1130 {
1131 this.phraseCodeHash.setItem(validInitialKey, this.phraseCodeHash.getItem(validInitialKey) + "," + keys + "=>" + phrase+freq);
1132 }
1133 }
1134 else
1135 this.phraseCodeHash.setItem(validInitialKey, keys + "=>" + phrase+freq);
1136
1137 }
1138
1139 });
syntax highlighted by Code2HTML, v. 0.9.1