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: 1,
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 // async timer
87 asyncFindTimer: null,
88
89 // current lookup env variables, used for asynchronous finding
90 currLookupEnv: null,
91
92 // number of selection word/phrase that will be sent back to IME panel for display
93 numSelection: 9,
94
95 // the entrance function to load all related tables
96 loadTable: function()
97 {
98 letterConverter = new FullLetterConverter();
99
100 for(var i=0; i<PinyinInitials.length; i++)
101 this.pinyinInitials[PinyinInitials[i]] = PinyinInitials[i];
102
103 for(var i=0; i<PinyinFinals.length; i++)
104 this.pinyinFinals[PinyinFinals[i]] = PinyinFinals[i];
105
106 // setTimeout to not block firefox start
107 var self = this;
108 setTimeout(function() { return self.loadPinyinTable(); }, 500);
109
110 // init encoding table
111 FireinputEncoding.init();
112 },
113
114 getCodeLine: function(str)
115 {
116 var strArray = str.split(':');
117 if(strArray.length < 2)
118 return;
119
120 // initKey:key=>word
121 this.codePinyinHash.setItem(strArray[0],strArray[1]);
122 },
123
124 loadPinyinTable: function()
125 {
126 var ios = FireinputXPC.getIOService();
127 var fileHandler = ios.getProtocolHandler("file")
128 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
129
130 var path = this.getDataPath();
131 var datafile = fileHandler.getFileFromURLSpec(path + this.getPinyinDataFile());
132 if(!datafile.exists())
133 {
134 this.engineDisabled = true;
135 return;
136 }
137
138 this.codePinyinHash = new FireinputHash();
139
140 var options = {
141 caller: this,
142 oncomplete: this.loadPinyinPhrase,
143 onavailable: this.getCodeLine
144 };
145
146 FireinputStream.loadDataAsync(datafile, options);
147 },
148
149
150 getPhraseLine: function(str)
151 {
152 var strArray = str.split(':');
153 if(strArray.length < 1)
154 return;
155
156 // initKey:key=>phrase
157 this.phraseCodeHash.setItem(strArray[0], strArray[1]);
158 },
159
160 loadPinyinPhrase: function()
161 {
162 var ios = FireinputXPC.getIOService();
163 var fileHandler = ios.getProtocolHandler("file")
164 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
165
166 var path = this.getDataPath();
167
168 var datafile = fileHandler.getFileFromURLSpec(path + this.getPinyinPhraseFile());
169
170 this.phraseCodeHash = new FireinputHash();
171
172 if(!datafile.exists())
173 return;
174
175 var options = {
176 caller: this,
177 onavailable: this.getPhraseLine,
178 oncomplete: this.loadUserTable
179 };
180
181 FireinputStream.loadDataAsync(datafile, options);
182 },
183
184 updateUserCodeValue: function(key, initKey, word, freq)
185 {
186 // check if it's single char
187 if (!/ /.test(key) && (this.codePinyinHash.hasItem(initKey) || this.codePinyinHash.hasItem(key)))
188 {
189 // check the list based on key first
190 var hashKey = key;
191 var words = this.codePinyinHash.getItem(key);
192
193 if(!words)
194 {
195 words = this.codePinyinHash.getItem(initKey);
196 if(!words)
197 return;
198
199 hashKey = initKey;
200 }
201
202 var regex = new RegExp(word + "\\d+", "g");
203 var oldWordFreq = words.match(regex);
204 if(oldWordFreq)
205 {
206 words = words.replace(key + "=>" + oldWordFreq, "");
207 }
208
209 // append to beginning
210 words = key + "=>" + word + freq + "," + words;
211 this.codePinyinHash.setItem(hashKey, words);
212
213 return;
214 }
215
216 // update phraseCodeHash
217
218 if(this.phraseCodeHash.hasItem(initKey))
219 {
220
221 var phrase = this.phraseCodeHash.getItem(initKey);
222 var regex = new RegExp("\\w" + word + "\\d+", "g");
223
224 // replace the old word. We replace key and word just in case
225 // someone has manually edited the local userinput table
226 phrase = phrase.replace(regex, "");
227 phrase = key+"=>"+word+freq + "," + phrase;
228 this.phraseCodeHash.setItem(initKey, phrase);
229
230 return;
231 }
232
233 //the initKey is not in hash. Add it in
234 this.phraseCodeHash.setItem(initKey, key+"=>"+word+freq);
235 },
236
237 getUserCodeLine: function(str)
238 {
239 var strArray = str.split(':');
240 if(strArray.length < 4)
241 return;
242
243 // user data format: word: freq key initKey
244 // new user data format: schema: word: freq key initKey
245 var word = "";
246 var freq = "";
247 var key = "";
248 var initKey = "";
249 var newPhrase = false;
250
251 var schema = parseInt(strArray[0]);
252 if(isNaN(schema))
253 {
254 word = strArray[0];
255 freq = strArray[1];
256 key = strArray[2].replace(/^\s+|\s+$/g, '');
257 initKey = strArray[3].replace(/^\s+|\s+$/g, '');
258 if(strArray.length > 4 && strArray[4] == "1")
259 newPhrase = true;
260 }
261 else if(schema <= 5) // everything for pinyin, no matter full or shuang
262 {
263 word = strArray[1];
264 freq = strArray[2];
265 key = strArray[3].replace(/^\s+|\s+$/g, '');
266 initKey = strArray[4].replace(/^\s+|\s+$/g, '');
267 if(strArray.length > 5 && strArray[5] == "1")
268 newPhrase = true;
269 }
270 else
271 {
272 word = strArray[1];
273 freq = strArray[2];
274 key = strArray[3].replace(/^\s+|\s+$/g, '');
275 initKey = strArray[4].replace(/^\s+|\s+$/g, '');
276 // just leave it into userCodeHash for later save
277 this.userCodeHash.setItem(word + ":" + key, {freq: freq, initKey: initKey, schema: schema});
278 return;
279 }
280
281 //FIXME: We want to update the codeHash and phraseHash instead of keep it in user hash
282 if(newPhrase)
283 {
284 // to avoid incorrect hashing for words with different key, we hash work + " " + key which should be unique
285 this.userCodeHash.setItem(word + ":" + key, {freq: freq, initKey: initKey, schema: this.pinyinSchema.getSchema(), newPhrase: newPhrase});
286 }
287 else
288 this.userCodeHash.setItem(word + ":" + key, {freq: freq, initKey: initKey, schema: this.pinyinSchema.getSchema()});
289
290
291 this.updateUserCodeValue(key, initKey, word, freq);
292 },
293
294 loadUserTable: function()
295 {
296 var ios = FireinputXPC.getIOService();
297 var fileHandler = ios.getProtocolHandler("file")
298 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
299
300 var path = FireinputUtils.getAppRootPath();
301 var datafile = fileHandler.getFileFromURLSpec(path + this.getUserDataFile());
302 if(!datafile.exists())
303 return;
304 this.userCodeHash = new FireinputHash();
305
306 var options = {
307 caller: this,
308 onavailable: this.getUserCodeLine
309 };
310 FireinputStream.loadDataAsync(datafile, options);
311 },
312
313 isEnabled: function()
314 {
315 if(this.engineDisabled)
316 return false;
317 var ios = FireinputXPC.getIOService();
318 var fileHandler = ios.getProtocolHandler("file")
319 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
320
321 var path = this.getDataPath();
322 var datafile = fileHandler.getFileFromURLSpec(path + this.getPinyinDataFile());
323 if(!datafile.exists())
324 return false;
325
326 return true;
327 },
328
329 isSchemaEnabled: function()
330 {
331 if(this.engineDisabled)
332 return false;
333
334 return this.isEnabled();
335 },
336
337 canComposeNew: function()
338 {
339 return true;
340 },
341
342 setNumWordSelection: function(num)
343 {
344 this.numSelection = num > 9 ? 9 : (num < 1 ? 1: num);
345 },
346
347 getIMEType: function()
348 {
349 return SMART_PINYIN;
350 },
351
352 setSchema: function(schema)
353 {
354 if(!this.pinyinSchema)
355 this.pinyinSchema = new PinyinSchema();
356
357 if(!this.pinyinSchema)
358 return;
359
360 //FireinputLog.debug(this, "Set schema: " + schema);
361 this.pinyinSchema.setSchema(schema);
362 },
363
364 getAllowedInputKey: function()
365 {
366 if(this.pinyinSchema)
367 return this.pinyinSchema.getAllowedKeys();
368
369 return "abcdefghijklmnopqrstuvwxyz";
370 },
371
372 setEncoding: function(encoding)
373 {
374 this.encodingMode = encoding;
375 },
376
377 convertLetter: function(code)
378 {
379 // Full: number + alpha character
380 // Punct: any printable character which is not a space or an alphanumeric character
381 if((!this.isHalfLetterMode() &&
382 ((code > 47 && code < 58) ||
383 (code > 64 && code < 91))) ||
384 (!this.isHalfPunctMode() &&
385 !((code > 47 && code < 58) ||
386 (code > 64 && code < 91))))
387 return letterConverter.toFullLetter(String.fromCharCode(code));
388
389 return String.fromCharCode(code);
390 },
391
392 find: function(inputChar, singleWord, keyMatch)
393 {
394 var result = null;
395 FireinputLog.debug(this, "find, inputChar: " + inputChar);
396 // use current schema
397 result = this.findBySchema(inputChar, false, singleWord, keyMatch);
398 if(!result)
399 {
400 // use default schema: pinyin
401 result = this.findBySchema(inputChar, true, singleWord, keyMatch);
402 }
403
404 return result;
405 },
406
407 findBySchema: function(inputChar, useDefaultSchema, singleWord, keyMatch)
408 {
409 var s = inputChar;
410 var retArray = null;
411 var _keymatch = keyMatch;
412
413 // here we will do searching on inputChar by length -1 every time if retArray is null
414 // FireinputLog.debug(this, "Send original key=" + inputChar);
415 while(s.length > 0)
416 {
417 // FireinputLog.debug(this, "findBySchema: searchAll\n");
418 var result=this.searchAll(s, useDefaultSchema, singleWord, keyMatch);
419 if(!result)
420 break;
421 if(result.charArray)
422 {
423 retArray = result.charArray;
424 break;
425 }
426
427 /*
428 * If the keyMatch from upper level is true, it's not necessary for loop
429 */
430 if(_keymatch)
431 break;
432 /*
433 * The loose match wasn't found, so the second loop needs to make the exact match
434 */
435 keyMatch = true;
436 result.keySize = result.keySize >0 ? result.keySize : 1;
437 s = s.substr(0, s.length - result.keySize);
438 // FireinputLog.debug(this, "Send key after reduced=" + s);
439
440 // remove last single quot if it presents.
441 if(s.substr(s.length-1, 1) == "'")
442 {
443 s = s.substr(0, s.length - 1);
444 }
445 }
446 this.validInputKey = s;
447
448 //FIXME: how to return the a valid English word
449 /*
450 if(!retArray || retArray.length <= 0)
451 {
452 retArray = new Array({key: inputChar, word: inputChar, ufreq: 'false'});
453 }
454 */
455
456 return {charArray:retArray, validInputKey: this.validInputKey};
457 },
458
459 searchAll: function(inputChar, useDefaultSchema, singleWord, keyMatch)
460 {
461 this.charArray = null;
462 this.currLookupEnv = null;
463 this.charIndex = 0;
464
465 var keySet = null;
466 var keyArray = this.pinyinSchema.getComposeKey(inputChar, useDefaultSchema);
467
468 FireinputLog.debug(this, "keyArray=" + keyArray);
469 if(typeof(keyArray) == "string")
470 {
471 keySet = this.parseKeys(inputChar);
472 /*
473 * if singleWord is forced(e.g. the composer is enabled), only single word will be returned.
474 * So we only need to take first onefrom keySet to search the word.
475 * Please note: we only do this if the input key is multiple keys since a single key is going
476 * to be handled correctly
477 */
478 if(singleWord && keySet.length>1)
479 {
480 return {charArray: null, keySize: keySet[keySet.length-1].key.length};
481 }
482 else
483 {
484 this.charArray = this.codeLookup(keySet, keyMatch);
485 }
486
487 /* for single key, if no result was found, it should be a valid hash key for
488 * phrase table. We will just return key w/o further looping
489 */
490 if(!this.charArray && keySet.length <= 1)
491 {
492 keySet = this.parseKeys(inputChar, true);
493 this.charArray = this.codeLookup(keySet, keyMatch);
494 }
495
496 /*
497 * if the chararray is not found, we need to adjust the inputchar by removing last inputkey in keySet
498 * array. Just make sure the FULL type will not be messed up
499 */
500 if(this.charArray != null)
501 {
502 // the first set of word value is ready. We issue an asynchronous to get next list of values
503 this.currLookupEnv = {table: PINYIN_WORD_LOOKUP, keyArray: keySet, keyMatch: keyMatch};
504 // FireinputLog.debug(this, "findNext before PINYIN_WORD_LOOKUP\n");
505 this.findNext();
506 return {charArray: this.charArray.slice(0,this.numSelection), keySize: 0};
507
508 }
509 else if(keySet && keySet.length > 0)
510 {
511 /* If the last input is FINAL, there might be more input coming, return null to wait
512 * if the keymatch is set, take the input as completed pinyin
513 */
514 if(keySet[keySet.length-1].type == KEY_FINAL && !keyMatch)
515 return null;
516 else
517 return {charArray: null, keySize: keySet[keySet.length-1].key.length};
518 }
519 else
520 return {charArray: null, keySize: 1};
521 }
522 else if(keyArray != null)
523 {
524 // This is for Shuangping
525 /*
526 for(var i=0; i<keyArray.length; i++)
527 {
528 for (var j=0; j<keyArray[i].length; j++)
529 {
530 // FireinputLog.debug(this, "keyArray[" + i + "][" + j + "].type=" + keyArray[i][j].type);
531 // FireinputLog.debug(this, "keyArray[" + i + "][" + j + "].key=" + keyArray[i][j].key);
532 }
533 }
534 */
535 // loop through all possible combinations
536 for(var i=0; i<keyArray.length; i++)
537 {
538 var charArray = this.codeLookup(keyArray[i], keyMatch);
539 if(this.charArray == null)
540 {
541 this.charArray = charArray;
542 }
543 else if(this.charArray != null && charArray != null)
544 {
545 arrayInsert(this.charArray, this.charArray.length, charArray);
546 this.charArray.sort(this.sortCodeArray);
547 }
548
549 }
550
551
552 if(!this.charArray)
553 return {charArray: null, keySize: 1};
554
555 // the first set of word phrase is ready. We issue an asynchronous to get next list of values
556 // FireinputLog.debug(this, "findNext before PINYIN_PHRASE_LOOKUP\n");
557 this.currLookupEnv = {table: PINYIN_PHRASE_LOOKUP, keyArray: keyArray, keyMatch: keyMatch};
558 this.findNext();
559
560 return {charArray: this.charArray.slice(0,this.numSelection), keySize: 1};
561 }
562
563 return {charArray: null, keySize: 1};
564 },
565
566 codeLookup: function(keys, keyMatch, currIndex)
567 {
568 var charArray = null;
569
570 var originalKeys = keys;
571 if(typeof(currIndex) == 'undefined')
572 currIndex = this.charIndex;
573
574 if(keys == null || keys.length <= 0)
575 return null;
576
577 // FireinputLog.debug(this, "coodLookup keys.length=" + keys.length);
578 // a valid charArray consist of {key:key, word: word}
579 if(keys.length <= 1)
580 {
581 charArray = this.getValidWord(keys, currIndex);
582 }
583 // check different mode
584 else if(keys[0].key == 'i')
585 {
586 // we are in special char imode
587 var ikey = this.getInitialKeys(keys);
588 if(ikey)
589 {
590 ikey = ikey.ikey;
591 charArray = FireinputSpecialChar.getIMode(ikey);
592 }
593 }
594 else
595 {
596 // look through all possible keyset
597 while(1)
598 {
599 var result = this.getKey(keys);
600 if(result == null)
601 break;
602
603 keys = result.keys;
604 var keySet = result.keyset;
605 var phraseKey = keySet == null ? keys : keySet;
606
607 // add the charArray to global list
608 var tmpCharArray = this.getValidPhrase(phraseKey, keyMatch, currIndex);
609 if(!tmpCharArray || tmpCharArray.length <= 0)
610 {
611 if(!keySet)
612 break;
613 else
614 continue;
615 }
616 if(charArray == null)
617 charArray = tmpCharArray;
618 else if(charArray != null && tmpCharArray != null)
619 {
620 arrayInsert(charArray, charArray.length, tmpCharArray);
621 charArray.sort(this.sortCodeArray);
622 }
623
624 // there is no additional keyset, stop
625 if(keySet == null)
626 break;
627 }
628 }
629
630 return charArray;
631 },
632
633 next: function (endFlag)
634 {
635 if(!this.charArray)
636 return null;
637
638 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
639 // FireinputLog.debug(this,"this.charArray: " + this.charArray.length);
640 // if the next this.numSelection are already displayed, return null
641 if((this.charIndex+this.numSelection) >= this.charArray.length)
642 return null;
643
644 var i = this.charIndex;
645 if(!endFlag)
646 this.charIndex += this.numSelection;
647 else
648 {
649 i = this.charArray.length-this.numSelection;
650 i -= this.numSelection;
651 this.charIndex = i>0 ? i:0;
652 }
653 // preloading the next set of value, we only need one more (this.numSelection+1) items by default
654 // The first page this.charIndex is 0, second page: this.charIndex: 1
655 // The reason we need to put the check between k & k+2 because we want to make sure the isEnd won't be hit
656 // when charArray.length is larger than k+1 but shorter than k+2. Please note the charArray.length may not be exactly
657 // number of times of (numSelection+1) as userArray is included
658 var k = this.charIndex / this.numSelection;
659 // FireinputLog.debug(this,"next, this.charIndex: " + this.charIndex + ", this.charArray.length: "+ this.charArray.length);
660 if(k*(this.numSelection+1) <= this.charArray.length && (k+2)*(this.numSelection+1) >= this.charArray.length)
661 {
662 this.findNext();
663 }
664 else if(k*(this.numSelection+1) >= this.charArray.length)
665 {
666 // it might be too late to schedule a search in asynchronous way. Do it immediately
667 this.findNextAsync(this.charArray);
668 }
669
670 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
671 return {charArray:this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
672 },
673
674 prev: function (homeFlag)
675 {
676 if(!this.charArray)
677 return null;
678 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
679 // if the previous this.numSelection are already displayed, return null
680 if((this.charIndex-this.numSelection) < 0)
681 return null;
682
683 if(!homeFlag)
684 this.charIndex -= this.numSelection;
685 else
686 this.charIndex = 0;
687
688 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
689 return {charArray: this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
690 },
691
692 findNext: function()
693 {
694 // FireinputLog.debug(this,"find next, this.charArray.length: "+ this.charArray.length);
695 if(this.isEnd())
696 {
697 // there are not enough left to be searched
698 // FireinputLog.debug(this,"find next, reach end");
699 return;
700 }
701
702 if(this.asyncFindTimer)
703 clearTimeout(this.asyncFindTimer);
704
705 var self = this;
706 // use a reference to original charArray to make sure the original array is available
707 // for insertion. It's okay to discard it later.
708 this.asyncFindTimer = setTimeout(function(){ self.findNextAsync(self.charArray); }, 100);
709
710 },
711
712 findNextAsync: function(charArray)
713 {
714 // if a new finding is in the way, don't find next
715 if(!this.currLookupEnv)
716 return;
717
718 var nextCharArray = null;
719 if(this.currLookupEnv.table == PINYIN_WORD_LOOKUP)
720 {
721 nextCharArray = this.codeLookup(this.currLookupEnv.keyArray, this.currLookupEnv.keyMatch, charArray.length);
722 }
723 else
724 {
725 for(var i=0; i<this.currLookupEnv.keyArray.length; i++)
726 {
727 var tmpCharArray = this.codeLookup(this.currLookupEnv.keyArray[i], this.currLookupEnv.keyMatch, charArray.length);
728 if(nextCharArray == null)
729 {
730 nextCharArray = tmpCharArray;
731 }
732 else if(nextCharArray != null && tmpCharArray != null)
733 {
734 arrayInsert(nextCharArray, nextCharArray.length, tmpCharArray);
735 nextCharArray.sort(this.sortCodeArray);
736 }
737 }
738 }
739
740 if(nextCharArray && charArray)
741 {
742 arrayInsert(charArray, charArray.length, nextCharArray);
743 }
744
745 // FireinputLog.debug(this,"charArray: " + this.getKeyWord(charArray));
746
747 },
748
749 isBeginning: function()
750 {
751 return this.charIndex == 0;
752 },
753
754 isEnd: function()
755 {
756 return (this.charIndex+this.numSelection) >= this.charArray.length;
757 },
758
759 canAutoInsert: function()
760 {
761 return false;
762 },
763
764 getKeyType: function(key)
765 {
766 if(typeof(this.pinyinFinals[key]) != 'undefined')
767 return KEY_FINAL;
768 else if (typeof(this.pinyinInitials[key]) != 'undefined')
769 return KEY_INITIAL;
770 else
771 return KEY_FULL;
772 },
773
774 parseOneKey: function (keyInitial, keyFinal, sFlag)
775 {
776 var keyInitialLen = keyInitial.length;
777
778 if(typeof(this.pinyinFinals[keyFinal]) != 'undefined')
779 {
780 if(typeof(sFlag) != 'undefined' && sFlag)
781 {
782 // if sFlag is true, these two keys will be recognized as separated keys
783 if(keyInitialLen >0)
784 return ({key: keyInitial, type: KEY_INITIAL, pos: keyInitialLen});
785 else
786 return ({key: keyFinal, type: KEY_FINAL, pos: keyFinal.length});
787 }
788 else
789 {
790 var pinyinKey = {};
791 pinyinKey.key = keyInitial + keyFinal;
792 pinyinKey.type = keyInitialLen>0 ? KEY_FULL : KEY_FINAL;
793 pinyinKey.pos = keyInitialLen + keyFinal.length;
794 return pinyinKey;
795 }
796 }
797 else
798 {
799 for(var i=keyFinal.length-1; i>0; i--)
800 {
801 var subFinal = keyFinal.substring(0, i);
802 if(typeof(this.pinyinFinals[subFinal]) != 'undefined')
803 {
804 var pinyinKey = {};
805 pinyinKey.key = keyInitial + subFinal;
806 pinyinKey.type = keyInitialLen>0 ? KEY_FULL : KEY_FINAL;
807 pinyinKey.pos = keyInitialLen + i;
808 return pinyinKey;
809 }
810 }
811 }
812
813 // When we reach here, it means the engine might encounter the unsupported input chars.
814 // let move to next but ignore this keyFinal chars
815 keyInitialLen = keyInitialLen>0 ? keyInitialLen : keyFinal.length;
816 return ({key: keyInitial, type: KEY_INITIAL, pos: keyInitialLen});
817 },
818
819
820 parseKeys: function(keyList, sFlag)
821 {
822 var keys = new Array();
823 // the space is from updateFrequency. Treat them as single quot
824 keyList = keyList.replace(/\s+/g, "'");
825 // if there are single quot delimiters, process them first
826 if(keyList.search(/\'/) != null)
827 {
828 var keyListArray = keyList.split("'");
829 for(var i=0; i<keyListArray.length; i++)
830 {
831 var retArray = this.parseKeySteps(keyListArray[i], sFlag);
832 if(retArray != null)
833 arrayInsert(keys, keys.length, retArray.slice(0, retArray.length));
834 }
835
836 return keys;
837 }
838
839 return this.parseKeySteps(keyList, sFlag);
840 },
841
842 parseKeySteps: function(keyList, sFlag)
843 {
844 var keys = new Array();
845
846 for(var i=0; i< keyList.length;)
847 {
848 var key1 = keyList.substr(i, 1);
849 var key2 = keyList.substr(i, 2);
850
851 if(typeof(this.pinyinInitials[key2]) != 'undefined')
852 {
853 var finals = keyList.substr(i+2, 4);
854 if(finals.length <= 0)
855 {
856 keys.push({key: key2, type: KEY_INITIAL});
857 break;
858 }
859 var pinyinKey = this.parseOneKey(key2, finals, sFlag);
860 if(pinyinKey.type == KEY_INITIAL)
861 {
862 // No finals. Store them each one as Initial
863 keys.push({key: key2, type: KEY_INITIAL});
864 }
865 else
866 {
867 // for char as g, n and r, if the followings are finals,
868 // then g/n should not be part of this key
869 var lastChar = pinyinKey.key.substr(pinyinKey.key.length-1, 1);
870 if(pinyinKey.type == KEY_FULL && (lastChar == "n" || lastChar == "g" || lastChar == "r"))
871 {
872 var followingKey = this.parseOneKey("", keyList.substr(i+pinyinKey.pos, 4), sFlag);
873 if(followingKey.type == KEY_FINAL)
874 {
875 pinyinKey.type = KEY_SWING;
876 }
877 }
878
879 keys.push({key: pinyinKey.key, type: pinyinKey.type});
880 }
881
882 i += pinyinKey.pos;
883 }
884 else if(typeof(this.pinyinInitials[key1]) != 'undefined')
885 {
886 var finals = keyList.substr(i+1, 4);
887 if(finals.length <= 0)
888 {
889 keys.push({key: key1, type: KEY_INITIAL});
890 break;
891 }
892 var pinyinKey = this.parseOneKey(key1, finals, sFlag);
893 // for char as g/n/r, if the followings are finals,
894 // then g/n/r might not be part of this key
895 var lastChar = pinyinKey.key.substr(pinyinKey.key.length-1, 1);
896 if(pinyinKey.type == KEY_FULL && (lastChar == "n" || lastChar == "g" || lastChar == "r"))
897 {
898 var followingKey = this.parseOneKey("", keyList.substr(i+pinyinKey.pos, 4), sFlag);
899 if(followingKey.type == KEY_FINAL)
900 {
901 pinyinKey.type = KEY_SWING;
902 }
903 }
904
905 keys.push({key: pinyinKey.key, type: pinyinKey.type});
906 i += pinyinKey.pos;
907 }
908 else
909 {
910 var finals = keyList.substr(i, 4);
911 if(finals.length <= 0)
912 {
913 // we don't know what kind of key it's
914 break;
915 }
916 var pinyinKey = this.parseOneKey("", finals, sFlag);
917 keys.push({key: pinyinKey.key, type: KEY_FINAL});
918 i += pinyinKey.pos;
919 }
920 }
921 return keys;
922 },
923
924 getKey: function(keys)
925 {
926 if(!keys)
927 return null;
928
929 var keySet = new Array();
930
931 var foundone = -1;
932 for (var i =0; i<keys.length; i++)
933 {
934 if(foundone !=-1 || keys[i].type != KEY_SWING)
935 keySet.push({key: keys[i].key, type: keys[i].type});
936 else
937 {
938 foundone = i;
939 keySet.push({key: keys[i].key, type: KEY_FULL});
940 }
941 }
942
943 if(foundone > -1)
944 {
945 // found one, move the swing key to next chars before return
946 keys[foundone+1].key = keys[foundone].key.substr(keys[foundone].key.length-1, 1) +
947 keys[foundone+1].key;
948 keys[foundone+1].type = KEY_FULL;
949 keys[foundone].type = KEY_FULL;
950 keys[foundone].key = keys[foundone].key.substr(0, keys[foundone].key.length-1);
951
952 return {keys: keys, keyset: keySet};
953 }
954
955 return {keys: keys, keyset: null};
956 },
957
958 getValidWord: function(keys, currentIndex)
959 {
960 var wordArray = null;
961 var userArray = new Array();
962 var wordList = new Array();
963
964 // this is phrase, not single char
965 if(!keys || keys.length > 1)
966 return null;
967
968 var key = keys[0].key;
969 var keyType = keys[0].type;
970
971 // FireinputLog.debug(this, "key: " + key + ", keyType: " + keyType);
972
973 var keyInitial = key.substring(0, 1);
974 var keyInitial2 = key.substring(0, 2);
975 if((keyInitial2 == "sh" || keyInitial2 == "zh" || keyInitial2 == "ch") &&
976 (key.length <=3))
977 {
978 keyInitial = keyInitial2;
979 }
980 else if(key.length >=3)
981 keyInitial = key.substring(0, 3);
982
983 if(!this.codePinyinHash.hasItem(keyInitial))
984 return null;
985
986 var pinyinWordList = this.codePinyinHash.getItem(keyInitial);
987 // FireinputLog.debug(this,"pinyinWordList: " + FireinputUnicode.getUnicodeString(pinyinWordList));
988
989 var pinyinWordArray = pinyinWordList.split(",");
990
991 wordArray = new Array();
992 var oldIndex = 0;
993 for(var i=0; i < pinyinWordArray.length; i++)
994 {
995 var pinyinWord = null;
996 try {
997 pinyinWord = pinyinWordArray[i].split("=>");
998 } catch(e) { }
999
1000 if(!pinyinWord || pinyinWord.length < 2)
1001 continue;
1002
1003 // FireinputLog.debug(this,"pinyinWord: " + FireinputUnicode.getUnicodeString(pinyinWord));
1004 if(keyType != KEY_INITIAL)
1005 {
1006 if(!this.pinyinSchema.compareAMB(key, pinyinWord[0]) && pinyinWord[0] != key)
1007 continue;
1008 }
1009
1010 var word = "";
1011 try
1012 {
1013 word = pinyinWord[1].match(/[\D\.]+/g)[0];
1014 }
1015 catch(e) {}
1016
1017 // FireinputLog.debug(this,"word: " + FireinputUnicode.getUnicodeString(word));
1018
1019 if(word.length <= 0)
1020 continue;
1021
1022 var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
1023
1024 // make sure the same word won't show up twice
1025 if(typeof(wordList[encodedWord]) != 'undefined')
1026 continue;
1027
1028 wordList[encodedWord] = 1;
1029
1030 // FireinputLog.debug(this,"word: " + FireinputUnicode.getUnicodeString(word));
1031 // FireinputLog.debug(this,"oldIndex: " + oldIndex +", currentIndex: " + currentIndex);
1032 oldIndex++;
1033 if(oldIndex <= currentIndex)
1034 continue;
1035
1036 if(this.userCodeHash && this.userCodeHash.hasItem(word + ":" + pinyinWord[0]))
1037 {
1038 // valid selection
1039 var ufreq = this.userCodeHash.getItem(word + ":" + pinyinWord[0]);
1040 userArray[userArray.length] = {key: pinyinWord[0], word:word+ufreq.freq, encodedWord:encodedWord+ufreq.freq};
1041 continue;
1042 }
1043
1044 // add into wordArray for return
1045 var freq = pinyinWord[1].match(/[\d\.]+/g)[0];
1046 wordArray[wordArray.length] = {key:pinyinWord[0], word:pinyinWord[1], encodedWord:encodedWord+freq};
1047
1048 // controls how many we will list the available words from big hash
1049 if(wordArray.length>= (this.numSelection+1))
1050 break;
1051 }
1052
1053 // free it
1054 wordList = null;
1055
1056 // FireinputLog.debug(this,"wordArray: " + this.getKeyWord(wordArray));
1057 //FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
1058 if(userArray.length <= 0)
1059 {
1060 wordArray.sort(this.sortCodeArray);
1061 return wordArray;
1062 }
1063 else
1064 {
1065 arrayInsert(userArray, userArray.length, wordArray.slice(0, wordArray.length));
1066 userArray.sort(this.sortCodeArray);
1067
1068 // FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
1069 return userArray;
1070 }
1071 },
1072
1073 getInitialKeys: function(keys)
1074 {
1075 if(!keys)
1076 return null;
1077
1078
1079 var initialKeys = "";
1080 var hasInitialKey = false;
1081
1082 for (var i =0; i<keys.length; i++)
1083 {
1084 FireinputLog.debug(this,"keys[" + i + "]=" + keys[i].key);
1085 if(keys[i].type == KEY_INITIAL)
1086 {
1087 hasInitialKey = true;
1088 initialKeys += keys[i].key.substring(0,1);
1089 }
1090 else if(keys[i].type == KEY_FINAL)
1091 initialKeys += keys[i].key;
1092 else
1093 initialKeys += keys[i].key.substring(0, 1);
1094 }
1095
1096 FireinputLog.debug(this,"initialKeys: " + initialKeys);
1097
1098 return {ikey: initialKeys, hasInitial: hasInitialKey};
1099 },
1100
1101 getValidPhrase: function(keys, keyMatch, currentIndex)
1102 {
1103 if(!keys)
1104 return null;
1105
1106 var initialKeys = this.getInitialKeys(keys);
1107 // FireinputLog.debug(this, "initialKeys=" + initialKeys);
1108 return this.getValidPhraseWithInitialKey(keys, initialKeys, keyMatch, currentIndex);
1109 },
1110
1111 getValidPhraseWithInitialKey: function(keys, initialKeys, keyMatch, currentIndex)
1112 {
1113 var phraseList = [];
1114
1115 // anything other than not exactly match input chars
1116 var phraseArray = [];
1117 // match exactly input chars
1118 var exactPhraseArray = [];
1119 // user history
1120 var userArray = new Array();
1121
1122 // store the hasInitial flag
1123 var hasInitialKey = initialKeys.hasInitial;
1124
1125 initialKeys = initialKeys.ikey;
1126
1127 // fast lookup for longer chars
1128 if(initialKeys.length >=4)
1129 {
1130 initialKeys = initialKeys.substring(0, 4);
1131 // some user phrases have been re-hashed by 3 init key
1132 if(!this.phraseCodeHash.hasItem(initialKeys))
1133 {
1134 initialKeys = initialKeys.substring(0,3);
1135 }
1136 }
1137
1138 FireinputLog.debug(this,"checking: " + initialKeys + ", keyMatch: " + keyMatch);
1139 // make final check before going forward
1140 if(!this.phraseCodeHash.hasItem(initialKeys))
1141 return null;
1142
1143 var stringList = this.phraseCodeHash.getItem(initialKeys);
1144 //FireinputLog.debug(this, "currentInex: " + currentIndex);
1145 FireinputLog.debug(this,"stringList: " + FireinputUnicode.getUnicodeString(stringList));
1146
1147 var stringArray = stringList.split(",");
1148 //FireinputLog.debug(this,"stringArray.length: " + stringArray.length);
1149
1150 var oldIndex = 0;
1151 for(var i=0; i<stringArray.length; i++)
1152 {
1153 var phraseKeyValue = null;
1154 try {
1155 phraseKeyValue = stringArray[i].split("=>");
1156 } catch(e) {}
1157
1158 if(!phraseKeyValue)
1159 continue;
1160
1161 var keyList = phraseKeyValue[0].split(" ");
1162 var phrase = "";
1163 try
1164 {
1165 phrase = phraseKeyValue[1].match(/[\D\.]+/g)[0];
1166 }
1167 catch(e) {}
1168
1169 if(phrase.length <= 0)
1170 continue;
1171
1172
1173 // The key length should be checked to make sure small keys will be skipped
1174 // if keyMatch is true, the key length must be same
1175 // In case of keyList has one entry only, we should skip the check as it might be
1176 // from userword table
1177 if(keyList.length != 1 && (keyList.length < keys.length || (keyMatch && keyList.length != keys.length)))
1178 continue;
1179 FireinputLog.debug(this,"word: " + FireinputUnicode.getUnicodeString(phrase) + ", keyList.length: " + keyList.length);
1180
1181 // assume it's a exact Match with input char. Don't be confused with keyMatch here.
1182 // the keyMatch is to make sure a selection has to be matched for inputkey length and key string.
1183 // It's a enabler thing. The exact matched word will be on front
1184 //
1185 var exactMatch = 1;
1186
1187 // assume it should not be selected by default.
1188 var shouldAdd = 1;
1189
1190 // do strict filtering. If keyList has only one entry, skip it as it must be from userword table
1191 for (var j =0; j<keys.length && keyList.length>1; j++)
1192 {
1193 if(keys[j].type == KEY_INITIAL)
1194 {
1195 if(keyList[j].indexOf(keys[j].key) !=0)
1196 {
1197 shouldAdd = 0;
1198 break;
1199 }
1200 else if(keyList[j] != keys[j].key)
1201 {
1202 exactMatch = 0;
1203 }
1204 }
1205 /*
1206 * if keyMatch is false, the keyList value should be longer
1207 * otherwise it should be exactly matched for both FULL and FINAL
1208 * key types
1209 */
1210 else if(!this.pinyinSchema.compareAMB(keys[j].key, keyList[j]) &&
1211 ((keyMatch && keyList[j] != keys[j].key) ||
1212 keyList[j].indexOf(keys[j].key) < 0))
1213 {
1214 shouldAdd = 0;
1215 break;
1216 }
1217 else if(keyList[j] != keys[j].key)
1218 {
1219 exactMatch = 0;
1220 }
1221 }
1222
1223 if(shouldAdd == 1)
1224 {
1225 // FireinputLog.debug(this,"word: " + FireinputUnicode.getUnicodeString(phrase) + "exactMatch: " + exactMatch);
1226 var encodedWord = FireinputEncoding.getEncodedString(phrase, this.encodingMode);
1227 if(typeof(phraseList[encodedWord]) == 'undefined')
1228 {
1229
1230 phraseList[encodedWord] = "";
1231
1232 oldIndex++;
1233 if(oldIndex <= currentIndex)
1234 continue;
1235
1236 var freq = phraseKeyValue[1].match(/[\d\.]+/g)[0];
1237
1238 var inUserHash = this.userCodeHash && this.userCodeHash.hasItem(phrase + ":" + phraseKeyValue[0]);
1239 if(inUserHash)
1240 {
1241 var ufreq = this.userCodeHash.getItem(phrase + ":" + phraseKeyValue[0]);
1242 // for exact match words, we need to put in a separate array, so later we can add pre-append them
1243 if(exactMatch)
1244 exactPhraseArray[exactPhraseArray.length] = {key: phraseKeyValue[0], word:phrase+ufreq.freq, encodedWord:encodedWord+ufreq.freq};
1245 else
1246 userArray[userArray.length] = {key: phraseKeyValue[0], word:phrase+ufreq.freq, encodedWord:encodedWord+ufreq.freq};
1247
1248
1249 }
1250 else
1251 {
1252 // for exact match words, we need to put in a separate array, so later we can add pre-append them
1253 if(exactMatch)
1254 exactPhraseArray[exactPhraseArray.length] = {key: phraseKeyValue[0], word:phraseKeyValue[1], encodedWord:encodedWord+freq};
1255 else
1256 phraseArray[phraseArray.length] = {key: phraseKeyValue[0], word:phraseKeyValue[1], encodedWord:encodedWord+freq};
1257
1258 }
1259
1260 // if hasInitialKey is true, mostly like there will no exactMatch at all, so we need to check non-exact phraseArray and userArray
1261 // whether there are enough items to return
1262 if(hasInitialKey && (phraseArray.length + userArray.length) >= (this.numSelection+1))
1263 break;
1264 else if(exactPhraseArray.length >= (this.numSelection+1))
1265 break;
1266 }
1267 }
1268 }
1269
1270 FireinputLog.debug(this,"exactPhraseArray.length: " + exactPhraseArray.length);
1271 FireinputLog.debug(this,"phraseArray.length: " + phraseArray.length);
1272 FireinputLog.debug(this,"userArray.length: " + userArray.length);
1273 //FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
1274 //FireinputLog.debug(this,"exactPhraseArray: " + this.getKeyWord(exactPhraseArray));
1275 if(userArray.length <= 0)
1276 {
1277 if(exactPhraseArray.length > 0)
1278 arrayInsert(exactPhraseArray, exactPhraseArray.length, phraseArray.slice(0, phraseArray.length));
1279 }
1280 else
1281 {
1282 arrayInsert(userArray, userArray.length, phraseArray.slice(0, phraseArray.length));
1283 userArray.sort(this.sortCodeArray);
1284 if(exactPhraseArray.length > 0)
1285 arrayInsert(exactPhraseArray, exactPhraseArray.length, userArray.slice(0, userArray.length));
1286 }
1287
1288 return exactPhraseArray.length >0 ? exactPhraseArray :
1289 (userArray.length >0 ? userArray : phraseArray);
1290 },
1291
1292 flushUserTable: function()
1293 {
1294 if(this.userCodeHash && this.userTableChanged)
1295 {
1296 FireinputSaver.save(this.userCodeHash);
1297 }
1298 },
1299
1300 getPhraseInitKey: function(keys)
1301 {
1302 var validInitialKey = "";
1303
1304 // remove any num tone from word adding
1305 var keyArray = this.parseKeys(keys.replace(/\d+/g, ''));
1306 for(var i=0; i<keyArray.length; i++)
1307 {
1308 if(keyArray[i].type == KEY_FINAL)
1309 validInitialKey += keyArray[i].key;
1310 else
1311 validInitialKey += keyArray[i].key.substring(0,1);
1312 }
1313
1314 // limit the short key less than 4 keys if it's not from user(user input key shouldn't have spaces
1315 if(/ /.test(keys) && validInitialKey.length >= 4)
1316 {
1317 // put user phrase into 4 keys if 3 keys is already defined
1318 var subValidInitialKey = validInitialKey.substring(0, 3);
1319 if(!this.phraseCodeHash.hasItem(subValidInitialKey))
1320 validInitialKey = subValidInitialKey;
1321 else
1322 validInitialKey = validInitialKey.substring(0, 4);
1323 }
1324
1325 return validInitialKey;
1326 },
1327
1328 updateFrequency: function(word, key, initKey, newPhrase, keepFreq)
1329 {
1330 var freq = word.match(/[\d\.]+/g)[0];
1331 var chars = word.match(/[\D\.]+/g)[0];
1332 if(!this.userCodeHash)
1333 this.userCodeHash = new FireinputHash();
1334
1335 if(typeof(newPhrase) == "undefined")
1336 newPhrase = false;
1337
1338 var newfreq = 0;
1339 if(this.userCodeHash.hasItem(chars + ":" + key))
1340 {
1341 var charopt = this.userCodeHash.getItem(chars + ":" + key);
1342 var freq1 = charopt.freq;
1343 newfreq = parseInt("0xFFFFFFFF", 16) - freq1;
1344
1345 if(typeof(charopt.newPhrase) != 'undefined')
1346 newPhrase = charopt.newPhrase;
1347
1348 if(typeof(initKey) == "undefined")
1349 initKey = charopt.initKey;
1350 }
1351 else
1352 {
1353 newfreq = parseInt("0xFFFFFFFF", 16) - freq;
1354 if(typeof(initKey) == "undefined")
1355 {
1356 initKey = "";
1357 var keys = this.parseKeys(key);
1358 for(var i=0; i<keys.length; i++)
1359 {
1360 if(keys[i].type == KEY_FINAL)
1361 initKey += keys[i].key;
1362 else
1363 initKey += keys[i].key.substring(0,1);
1364 }
1365
1366 // a single char or phrase
1367 if(/ /.test(key))
1368 {
1369 if(initKey.length >= 4)
1370 initKey = initKey.substring(0, 4);
1371 }
1372 else
1373 {
1374 // a single char
1375 var keyInitial2 = key.substring(0, 2);
1376 if((keyInitial2 == "sh" || keyInitial2 == "zh" || keyInitial2 == "ch") &&
1377 (key.length <=3))
1378 {
1379 initKey = keyInitial2;
1380 }
1381 else if(initKey.length >= 3)
1382 initKey = initKey.substring(0, 3);
1383 }
1384 }
1385 }
1386
1387 // updateTable don't need to change freq
1388 if(!keepFreq)
1389 {
1390 if(newfreq)
1391 newfreq /= Math.pow(2, 16);
1392 if(newfreq < 1) newfreq = 1;
1393
1394 freq = Math.round(newfreq) + parseInt(freq);
1395 }
1396
1397 if(newPhrase)
1398 this.userCodeHash.setItem(chars+":"+key, {freq: freq, initKey: initKey, schema: this.pinyinSchema.getSchema(), newPhrase: newPhrase});
1399 else
1400 this.userCodeHash.setItem(chars+":"+key, {freq: freq, initKey: initKey, schema: this.pinyinSchema.getSchema()});
1401
1402 // update phrase hash or code hash. Always to add it at the beginning
1403 // ignore if it's new Phrase since it's always handled by storeUserPhrase
1404 if(!newPhrase)
1405 {
1406 this.updateUserCodeValue(key, initKey, chars, freq);
1407 }
1408
1409 //FireinputLog.debug(this,"word: " + word);
1410 //FireinputLog.debug(this,"chars: " + chars + ", freq: " + freq);
1411 //FireinputLog.debug(this,"chars: " + chars + ", key: " + key + ", initKey: " + initKey);
1412 this.userTableChanged = true;
1413 return freq;
1414 },
1415
1416 updatePhraseTable: function(phrase, keys, freq, validInitialKey)
1417 {
1418 FireinputLog.debug(this, "updatePhraseTable: " + phrase + ", freq: " + freq);
1419 if(this.phraseCodeHash.hasItem(validInitialKey))
1420 {
1421 // the new phrase is already in phrase table, don't add it in
1422 var nowPhrase = this.phraseCodeHash.getItem(validInitialKey);
1423 var regex = new RegExp(phrase + "\\d+", "g");
1424 var matched = nowPhrase.match(regex);
1425 if(matched)
1426 {
1427 nowPhrase = nowPhrase.replace(keys + "=>" + matched, "");
1428 }
1429 this.phraseCodeHash.setItem(validInitialKey, keys + "=>" + phrase+freq + "," + nowPhrase);
1430 }
1431 else
1432 this.phraseCodeHash.setItem(validInitialKey, keys + "=>" + phrase+freq);
1433
1434 },
1435
1436 storeUserPhrase: function(userPhrase)
1437 {
1438 if(!userPhrase || userPhrase.length <= 0)
1439 return;
1440
1441 // FireinputLog.debug(this,"userPhrase: " + this.getKeyWord(userPhrase));
1442 var validInitialKey = "";
1443 var phrase = "";
1444 var keys = "";
1445 for(var i=0; i<userPhrase.length; i++)
1446 {
1447 var word = userPhrase[i].word.match(/[\D\.]+/g)[0];
1448 phrase += word;
1449 keys += userPhrase[i].key;
1450
1451 if(i < (userPhrase.length - 1))
1452 keys += " ";
1453 }
1454
1455 validInitialKey = this.getPhraseInitKey(keys);
1456
1457 // FireinputLog.debug(this,"keys: " + keys + ", phrase: " + phrase);
1458 FireinputLog.debug(this,"storeUserPhrase, validInitialKey: " + validInitialKey);
1459 if(!this.userCodeHash)
1460 this.userCodeHash = new FireinputHash();
1461
1462 if(this.userCodeHash.hasItem(phrase + ":" + keys))
1463 return;
1464
1465 var freq = this.updateFrequency(phrase+0, keys, validInitialKey, true);
1466 // FireinputLog.debug(this,"freq: " + freq);
1467 FireinputLog.debug(this, "phrase: " + phrase + ", freq: " + freq);
1468
1469 this.updatePhraseTable(phrase, keys, freq, validInitialKey);
1470 },
1471
1472 storeUpdatePhrases: function(updatePhrases)
1473 {
1474 if(!updatePhrases || updatePhrases.length <= 0)
1475 return;
1476
1477 FireinputLog.debug(this, "updatePhrase.length: " + updatePhrases.length);
1478 //FireinputLog.debug(this,"userPhrase: " + this.getKeyWord(userPhrase));
1479 for(var i=0; i<updatePhrases.length; i++)
1480 {
1481 this.storeOneUpdatePhrase(updatePhrases[i]);
1482 }
1483 },
1484
1485 storeOneUpdatePhrase: function(updatePhrase)
1486 {
1487 FireinputLog.debug(this, "phrase: " + updatePhrase);
1488 if(!updatePhrase || updatePhrase.length <= 0)
1489 return;
1490
1491 if(/:/.test(updatePhrase))
1492 {
1493 var phraseKey = updatePhrase.split(':');
1494 var phrase = phraseKey[0].match(/[\D\.]+/g)[0];
1495
1496 // There are tones from auto update, but we don't support it yet. skip it
1497 var keys = phraseKey[1]; //.replace(/\d+/g, '');
1498 var freq = phraseKey[0].match(/[\d\.]+/g)[0];
1499
1500 var validInitialKey = this.getPhraseInitKey(keys);
1501 FireinputLog.debug(this, "phrase: " + phrase + ", validInitialKey: " + validInitialKey);
1502
1503 if(!this.userCodeHash)
1504 this.userCodeHash = new FireinputHash();
1505
1506 if(this.userCodeHash.hasItem(phrase + ":" + keys))
1507 return;
1508
1509 this.updateFrequency(phrase+freq, keys, validInitialKey, true, true);
1510 FireinputLog.debug(this, "phrase: " + phrase + ", freq: " + freq);
1511 this.updatePhraseTable(phrase, keys, freq, validInitialKey);
1512 }
1513 }
1514 });
syntax highlighted by Code2HTML, v. 0.9.1