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