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 Wubi = function()  {}; 
 38 
 39 Wubi.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_WUBI,
 46 
 47     // array to keep all matched words 
 48     charArray: null,
 49 
 50     // current position of charArray 
 51     charIndex: 0, 
 52 
 53     // invalid input key 
 54     validInputKey: null,
 55 
 56     // the hash table for wubi key=> word
 57     keyWubiHash: null, 
 58 
 59     // the hash table for wubi word=>key for learning 
 60     wordWubiHash: null, 
 61 
 62     // the hash table for user frequency 
 63     userCodeHash: null, 
 64 
 65     // the wubi key map in format of z 
 66     keyMapTable: null, 
 67 
 68     // use code hash table event 
 69     userTableChanged: false, 
 70 
 71     // full/half letter converter 
 72     letterConverter: null, 
 73 
 74     // pinyin Schema 
 75     wubiSchema: null, 
 76 
 77     // encoding mode. Either simplified or big5. Simplified default. 
 78     encodingMode: ENCODING_ZH, 
 79 
 80     // engine enabled 
 81     engineDisabled: false, 
 82 
 83     // can auto insertion 
 84     autoInsertion: false, 
 85 
 86     // number of selection word/phrase that will be sent back to IME panel for display
 87     numSelection: 9,
 88 
 89     // the entrance function to load all related tables 
 90     loadTable: function()
 91     {
 92        letterConverter = new FullLetterConverter(); 
 93 
 94        // setTimeout to not block firefox start
 95        var self = this; 
 96        setTimeout(function() { return self.loadWubiTable(); }, 500); 
 97 
 98        // init encoding table 
 99        FireinputEncoding.init(); 
100     },
101 
102     insertKey: function(keyList, key)
103     {
104        if(typeof(keyList) == 'undefined')
105        {
106           keyList = new Array();
107           keyList.push(key);
108        }
109        else
110           keyList.push(key);
111        return keyList;
112     },
113 
114     getKeyMapList: function(length)
115     {
116        var str = ""; 
117        for(var i=0; i<length; i++)
118        {
119           str += "z"; 
120        }
121        return str; 
122     }, 
123 
124     insert: function(keyTable, key)
125     {
126        var itemStr = this.getKeyMapList(key.length); 
127        var keyList = keyTable[itemStr]; 
128        keyTable[itemStr] = this.insertKey(keyList, key); 
129        return; 
130     },
131 
132     getCodeLine: function(str)
133     {
134        var strArray = str.split(':');
135        if(strArray.length < 2)
136           return;
137 
138        // key=>word1, word2 
139        this.keyWubiHash.setItem(strArray[0],strArray[1]);
140 
141        // build key map table in a format as: 
142        // a => { "z" => array(), "zz" => array(), "zzz"=> array(), "zzzz"=> array()}
143        
144        var initialChar = strArray[0].substring(0,1);
145        if(this.keyMapTable.hasItem(initialChar))
146        {
147           var keyTable = this.keyMapTable.getItem(initialChar); 
148           this.insert(keyTable, strArray[0]); 
149        }
150        else
151        {
152           var keyTable = new Object(); 
153           this.insert(keyTable, strArray[0]); 
154           this.keyMapTable.setItem(initialChar, keyTable);
155        }
156        
157     },
158 
159     sortKeyMapTable: function()
160     {
161        this.keyMapTable.foreach(function(k, v) { for(var s in v) { v[s] = v[s].sort(); } }); 
162     }, 
163 
164     loadWubiTable: function()
165     {
166        var ios = FireinputXPC.getIOService(); 
167        var fileHandler = ios.getProtocolHandler("file")
168                          .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
169 
170        var path = this.getDataPath(); 
171        var datafile = ""; 
172        if(this.wubiSchema == WUBI_86)
173           datafile = fileHandler.getFileFromURLSpec(path + this.getWubi86File()); 
174        if(this.wubiSchema == WUBI_98)
175           datafile = fileHandler.getFileFromURLSpec(path + this.getWubi98File()); 
176        
177        if(!datafile.exists())
178        {
179            this.engineDisabled = true; 
180            return; 
181        }
182 
183        this.keyWubiHash = new FireinputHash();
184        this.keyMapTable = new FireinputHash(); 
185 
186        var options = {
187           caller: this, 
188           oncomplete: bind(function() { this.sortKeyMapTable(); this.loadUserTable(); }, this), 
189           onavailable: this.getCodeLine
190        }; 
191 
192        FireinputStream.loadDataAsync(datafile, options);
193     },
194 
195     updateUserCodeValue: function(key, word, freq)
196     {
197        if(this.keyWubiHash.hasItem(key))
198        {
199 
200           var phrase = this.keyWubiHash.getItem(key);
201           var regex = new RegExp(word + "\\d+", "g");
202           var oldWordFreq = phrase.match(regex);
203           if(oldWordFreq)
204           {
205              phrase = phrase.replace(oldWordFreq, "");
206           }
207 
208           phrase = word+freq + "," + phrase;
209           this.keyWubiHash.setItem(key, phrase);
210 
211           return;
212        }
213 
214        //the initKey is not in hash. Add it in  
215        this.keyWubiHash.setItem(key, word+freq);
216     },
217      
218     getUserCodeLine: function(str)
219     {
220        var strArray = str.split(':');
221        if(strArray.length < 4)
222           return;
223 
224        // old user data format: word: freq key initKey 
225        // new user data format: schema: word: freq key initKey 
226        if(isNaN(parseInt(strArray[0])))
227        {
228           this.userCodeHash.setItem(strArray[0]+":"+strArray[2], {freq: strArray[1], initKey: strArray[3], schema: this.wubiSchema});
229           this.updateUserCodeValue(strArray[2], strArray[0], strArray[1]);
230        }
231        else
232        {
233           this.userCodeHash.setItem(strArray[1]+":"+strArray[3], {freq: strArray[2], initKey: strArray[4], schema: strArray[0]});
234           if(strArray[0] == this.wubiSchema)
235              this.updateUserCodeValue(strArray[3], strArray[1], strArray[2]);
236        }      
237 
238     },
239 
240     loadUserTable: function()
241     {
242        var ios = FireinputXPC.getIOService(); 
243        var fileHandler = ios.getProtocolHandler("file")
244                          .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
245 
246        var path = FireinputUtils.getAppRootPath();
247        var datafile = fileHandler.getFileFromURLSpec(path + this.getUserDataFile());
248        if(!datafile.exists())
249           return; 
250        this.userCodeHash = new FireinputHash();
251 
252        var options = {
253           caller: this, 
254           onavailable: this.getUserCodeLine
255        }; 
256        FireinputStream.loadDataAsync(datafile, options); 
257     },
258 
259     isEnabled: function()
260     {
261        if(this.engineDisabled)
262           return false; 
263        var ios = FireinputXPC.getIOService(); 
264        var fileHandler = ios.getProtocolHandler("file")
265                          .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
266 
267        var path = this.getDataPath();
268        var datafile = fileHandler.getFileFromURLSpec(path + this.getWubi86File());
269        if(!datafile.exists())
270        {
271           datafile = fileHandler.getFileFromURLSpec(path + this.getWubi98File());
272           if(datafile.exists())
273              return true; 
274           return false; 
275        }
276 
277        return true; 
278     }, 
279 
280     isSchemaEnabled: function()
281     {
282        if(this.engineDisabled)
283           return false;
284        var ios = FireinputXPC.getIOService(); 
285        var fileHandler = ios.getProtocolHandler("file")
286                          .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
287 
288        var path = this.getDataPath();
289        if(this.wubiSchema == WUBI_86)
290           datafile = fileHandler.getFileFromURLSpec(path + this.getWubi86File()); 
291        if(this.wubiSchema == WUBI_98)
292           datafile = fileHandler.getFileFromURLSpec(path + this.getWubi98File()); 
293        if(!datafile.exists())
294        {
295           return false;
296        }
297 
298        return true;
299     },
300 
301     canComposeNew: function()
302     {
303        return false; 
304     },
305 
306     setNumWordSelection: function(num)
307     {
308        this.numSelection = num > 9 ? 9 : (num < 1 ? 1: num);
309     },
310 
311     getIMEType: function()
312     {
313        return  this.wubiSchema;
314     },
315 
316     setSchema: function(schema)
317     {
318        //FireinputLog.debug(this, "Set schema: " + schema);
319        this.wubiSchema = schema; 
320     }, 
321 
322     getAllowedInputKey: function()
323     {
324        return "abcdefghijklmnopqrstuvwxyz"; 
325     },
326 
327     setEncoding: function(encoding)
328     {
329        this.encodingMode = encoding; 
330     }, 
331 
332     convertLetter: function(code)
333     {
334        // Full: number + alpha character
335        // Punct: any printable character which is not a space or an alphanumeric character
336        if((!this.isHalfLetterMode() && 
337           ((code > 47 && code < 58) || 
338            (code > 64 && code < 91))) || 
339           (!this.isHalfPunctMode() && 
340            !((code > 47 && code < 58) ||
341             (code > 64 && code < 91))))
342           return letterConverter.toFullLetter(String.fromCharCode(code)); 
343 
344        return String.fromCharCode(code); 
345     }, 
346 
347     find: function(inputChar)
348     {
349        var s = inputChar; 
350        var retArray = null;
351 
352        this.autoInsertion = false; 
353        // here we will do searching on inputChar by length -1 every time if retArray is null 
354        while(s.length > 0 && (retArray=this.searchAll(s)) == null)
355        {
356           s = s.substr(0, s.length - 1); 
357           // remove last single quot if it presents.  
358           if(s.substr(s.length-1, 1) == "'")
359           { 
360             s = s.substr(0, s.length - 1); 
361           }
362        }
363 
364        this.validInputKey = s;
365        return {charArray:retArray, validInputKey: this.validInputKey};
366     },
367 
368     searchAll: function(inputChar)
369     {
370        this.charArray = null; 
371        this.charIndex = 0; 
372 
373        this.charArray = this.codeLookup(inputChar); 
374        if(this.charArray != null)
375           return this.charArray.slice(0, this.numSelection);
376        return null; 
377     },
378 
379     codeLookup: function(keys)
380     {
381        var charArray = null; 
382        if(keys == null || keys.length <= 0)
383           return null; 
384 
385        // a valid charArray consist of {key:key, word: word}
386        charArray = this.getValidWord(keys);
387        if(!charArray)
388           return null; 
389 
390        return charArray; 
391     },
392 
393     next: function (endFlag)
394     {
395        if(!this.charArray)
396           return null; 
397 
398        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
399        // if the next this.numSelection are already displayed, return null
400        if((this.charIndex+this.numSelection) >= this.charArray.length)
401           return null; 
402 
403        var i = this.charIndex; 
404        if(!endFlag)
405            this.charIndex += this.numSelection; 
406        else 
407        {
408            i = this.charArray.length-this.numSelection; 
409            i -= this.numSelection; 
410            this.charIndex = i>0 ? i:0; 
411        }
412        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
413        return {charArray:this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
414     }, 
415 
416     prev: function (homeFlag)
417     {
418        if(!this.charArray)
419           return null; 
420        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
421        // if the previous this.numSelection are already displayed, return null
422        if((this.charIndex-this.numSelection) < 0)
423           return null; 
424 
425        if(!homeFlag)
426           this.charIndex -= this.numSelection; 
427        else
428           this.charIndex = 0; 
429        
430        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
431        return {charArray: this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
432     }, 
433 
434     isBeginning: function()
435     {
436        return this.charIndex == 0; 
437     },
438 
439     isEnd: function()
440     {
441        return (this.charIndex+this.numSelection) >= this.charArray.length; 
442     }, 
443 
444     canAutoInsert: function()
445     {
446        return this.autoInsertion;       
447     }, 
448 
449 
450     getValidWord: function(key)
451     {
452        if(key.indexOf('z') < 0)
453        {
454           return this.getValidWordWithKey(key); 
455        }
456 
457        var initialChar = key.substr(0, 1); 
458        if(!this.keyMapTable.hasItem(initialChar))
459        {
460          // just return none if initial key is z or sth. else 
461          return null; 
462        }
463 
464        var keyTable = this.keyMapTable.getItem(initialChar); 
465        var itemStr = this.getKeyMapList(key.length);  
466        var keyList = keyTable[itemStr]; 
467 
468        var regexpStr = key.replace(/z/g, "\\S"); 
469        var validWord = new Array(); 
470 
471        // let check whether there are "z"s
472        for(var i=0; i<keyList.length; i++)
473        {
474           if(new RegExp(regexpStr).test(keyList[i]))
475           {
476               arrayInsert(validWord, validWord.length, this.getValidWordWithKey(keyList[i])); 
477           }
478        }
479 //       validWord.sort(this.sortCodeArray); 
480 
481        return validWord; 
482     }, 
483 
484     getValidWordWithKey: function(key)
485     {
486        var wordArray = null; 
487        var userArray = new Array(); 
488        var wordList = new Array();
489        if(!key)
490           return null; 
491 
492        if(!this.keyWubiHash.hasItem(key))
493           return null; 
494  
495        // only enable autoinsertion for 4 keys 
496        if(key.length >= 4)
497           this.autoInsertion = true; 
498 
499        var wubiWordList = this.keyWubiHash.getItem(key); 
500 
501        var wubiWordArray = wubiWordList.split(","); 
502 
503        wordArray = new Array(); 
504 
505        for(var i=0; i < wubiWordArray.length; i++)
506        {
507           var word = ""; 
508           try 
509           {
510              word = wubiWordArray[i].match(/[\D\.]+/g)[0];
511           }        
512           catch(e) {}
513 
514 
515           if(word.length <= 0) 
516              continue; 
517 
518           var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
519           if(typeof(wordList[encodedWord]) != 'undefined')
520              continue;
521 
522           wordList[encodedWord] = 1; 
523  
524           if(this.userCodeHash && this.userCodeHash.hasItem(word + ":" + key))
525           {
526              var ufreq = this.userCodeHash.getItem(word + ":" + key);
527              /* use this way other than push to have better performance 
528               * http://aymanh.com/9-javascript-tips-you-may-not-know
529               */
530              userArray[userArray.length] = {key: key, word:word+ufreq.freq, encodedWord:encodedWord+ufreq.freq}; 
531           }
532           else
533           {
534              var freq = wubiWordArray[i].match(/[\d\.]+/g)[0];
535              wordArray[wordArray.length] = {key:key, word:wubiWordArray[i], encodedWord:encodedWord+freq};
536           }
537        }
538 
539        // free it 
540        wordList = null;
541        //FireinputLog.debug(this,"wordArray: " + this.getKeyWord(wordArray));
542        if(userArray.length <= 0)         
543           return wordArray; 
544 
545        // sort the first (this.numSelection+1) items 
546        if(userArray.length < (this.numSelection+1))
547        {
548           arrayInsert(userArray, userArray.length, wordArray.slice(0, this.numSelection)); 
549           userArray.sort(this.sortCodeArray); 
550           arrayInsert(userArray, userArray.length, wordArray.slice((this.numSelection+1), wordArray.length)); 
551        }
552        else
553        {
554           userArray.sort(this.sortCodeArray); 
555           arrayInsert(userArray, userArray.length, wordArray.slice(0, wordArray.length)); 
556        }
557 
558        //FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
559        return userArray; 
560     },
561 
562     flushUserTable: function()
563     {
564        if(this.userCodeHash && this.userTableChanged)
565        { 
566           FireinputSaver.save(this.userCodeHash);
567        }
568     },
569 
570     updateFrequency: function(word, key, initKey)
571     {
572        var freq = word.match(/[\d\.]+/g)[0];
573        var chars = word.match(/[\D\.]+/g)[0];
574        if(!this.userCodeHash)
575           this.userCodeHash = new FireinputHash();
576 
577        var newfreq = 0; 
578        if(this.userCodeHash.hasItem(chars + ":" + key))
579        {
580           var charopt = this.userCodeHash.getItem(chars + ":" + key); 
581           var freq1 = charopt.freq; 
582           newfreq = parseInt("0xFFFFFFFF", 16) - freq1; 
583 
584           if(typeof(initKey) == "undefined")
585               initKey = charopt.initKey; 
586        }
587        else
588        {
589           newfreq = parseInt("0xFFFFFFFF", 16) - freq; 
590 
591           if(typeof(initKey) == "undefined")
592           {
593               initKey = key.substring(0, 1);
594               if(key.length >= 3)
595                  initKey = key.substring(0, 3);
596           }
597        }
598 
599        if(newfreq)
600        {
601           newfreq /= Math.pow(2, 16); 
602           if(newfreq < 1) newfreq = 1; 
603        }
604        freq = Math.round(newfreq) + parseInt(freq); 
605 
606        this.userCodeHash.setItem(chars+":"+key , {freq: freq, initKey: initKey, schema: this.wubiSchema});
607 
608        this.userTableChanged = true; 
609 
610        // FireinputLog.debug(this,"word: " + word);
611        //FireinputLog.debug(this,"chars: " + chars + ", freq: " + freq);
612        //FireinputLog.debug(this,"chars: " + chars + ", key: " + key + ", initKey: " + initKey);
613        return freq; 
614     },
615 
616     storeUserPhrases: function(userPhrases)
617     {
618        return; 
619     },
620  
621     storeOneUpdatePhrase: function(updatePhrase)
622     {
623        return; 
624     }   
625 });


syntax highlighted by Code2HTML, v. 0.9.1