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


syntax highlighted by Code2HTML, v. 0.9.1