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  *     OllyJa <ollyja@gmail.com>
 21  *     Chun-Kwong Wong <chunkwong.wong@gmail.com>
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK *****
 36  */
 37 var Cangjie = function(){};
 38 
 39 Cangjie.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_CANGJIE,
 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 cangjie key=> word
 57     keyCangjieHash: null,
 58 
 59     // the hash table for cangjie word=>key for learning
 60     wordCangjieHash: null,
 61 
 62     // the hash table for user frequency
 63     userCodeHash: null,
 64 
 65     // use code hash table event
 66     userTableChanged: false,
 67 
 68     // full/half letter converter
 69     letterConverter: null,
 70 
 71     // pinyin Schema
 72     cangjieSchema: null,
 73 
 74     // encoding mode
 75     encodingMode: ENCODING_ZH,
 76 
 77     // engine enabled
 78     engineDisabled: false,
 79 
 80     // can auto insertion
 81     autoInsertion: false,
 82 
 83     // number of selection word/phrase that will be sent back to IME panel for display
 84     numSelection: 9,
 85 
 86     // the entrance function to load all related tables
 87     loadTable: function()
 88     {
 89        letterConverter = new FullLetterConverter();
 90 
 91        // setTimeout to not block firefox start
 92        var self = this;
 93        setTimeout(function(){return self.loadCangjieTable();}, 500);
 94 
 95        // init encoding table
 96        FireinputEncoding.init();
 97     },
 98 
 99     getCodeLine: function(str)
100     {
101        var strArray = str.split(':');
102        if (strArray.length < 2)
103            return;
104 
105        // initKey:key=>word
106        this.keyCangjieHash.setItem(strArray[0],strArray[1]);
107     },
108 
109     loadCangjieTable: function()
110     {
111        var ios = FireinputXPC.getIOService(); 
112        var fileHandler = ios.getProtocolHandler("file")
113            .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
114 
115        var path = this.getDataPath();
116        var datafile = "";
117        if (this.cangjieSchema == CANGJIE_5)
118        {
119 	  datafile = fileHandler.getFileFromURLSpec(path + this.getCangjie5File());
120        }
121 
122        if (!datafile.exists())
123        {
124        	  this.engineDisabled = true;
125        	  return;
126        }
127 
128        this.keyCangjieHash = new FireinputHash();
129 
130        var options =
131        {
132        	  caller: this,
133        	  oncomplete: this.loadUserTable,
134        	  onavailable: this.getCodeLine
135        };
136 
137        FireinputStream.loadDataAsync(datafile, options);
138     },
139 
140     updateUserCodeValue: function(key, word, freq)
141     {
142        if(this.keyCangjieHash.hasItem(key))
143        {
144 
145           var phrase = this.keyCangjieHash.getItem(key);
146           var regex = new RegExp(word + "\\d+", "g");
147           var oldWordFreq = phrase.match(regex);
148           if(oldWordFreq)
149           {
150              phrase = phrase.replace(key + "=>" + oldWordFreq, "");
151           }
152 
153           phrase = key+"=>"+word + freq + "," + phrase;
154           this.keyCangjieHash.setItem(key, phrase);
155 
156           return;
157        }
158 
159        //the initKey is not in hash. Add it in  
160        this.keyCangjieHash.setItem(key, key+"=>"+word+freq);
161     }, 
162 
163     getUserCodeLine: function(str)
164     {
165        var strArray = str.split(':');
166 
167        if (strArray.length < 4) return;
168 
169        // user data format: word: freq key initKey
170        // new user data format: schema: word: freq initKey  
171        if(isNaN(parseInt(strArray[0])))
172        {
173           // hash as word:key 
174           this.userCodeHash.setItem(strArray[0]+":"+strArray[2], {freq: strArray[1], initKey: strArray[3], schema: this.cangjieSchema});
175           this.updateUserCodeValue(strArray[2], strArray[0], strArray[1]);
176        }
177        else
178        {
179           this.userCodeHash.setItem(strArray[1]+":"+strArray[3], {freq: strArray[2], initKey: strArray[4], schema: strArray[0]}); 
180           if(strArray[0] == this.cangjieSchema)
181              this.updateUserCodeValue(strArray[3], strArray[1], strArray[2]); 
182        }
183     },
184 
185     loadUserTable: function()
186     {
187        var ios = FireinputXPC.getIOService(); 
188        var fileHandler = ios.getProtocolHandler("file")
189        	.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
190        var path = FireinputUtils.getAppRootPath();
191        var datafile = fileHandler.getFileFromURLSpec(path + this.getUserDataFile());
192 
193        if (!datafile.exists()) return;
194 
195        this.userCodeHash = new FireinputHash();
196 
197        var options =
198        {
199        	  caller: this,
200        	  onavailable: this.getUserCodeLine
201        };
202 
203        FireinputStream.loadDataAsync(datafile, options);
204     },
205 
206     isEnabled: function()
207     {
208        if (this.engineDisabled) return false;
209 
210        var ios = FireinputXPC.getIOService(); 
211        var fileHandler = ios.getProtocolHandler("file")
212        	.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
213        var path = this.getDataPath();
214        var datafile = fileHandler.getFileFromURLSpec(path + this.getCangjie5File());
215 
216        if (datafile.exists())
217        	  return true;
218        else
219        	  return false;
220     },
221 
222     isSchemaEnabled: function()
223     {
224        if (this.engineDisabled) return false;
225 
226        var ios = FireinputXPC.getIOService(); 
227        var fileHandler = ios.getProtocolHandler("file")
228        	.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
229        var path = this.getDataPath();
230 
231        if (this.cangjieSchema == CANGJIE_5)
232        {
233        	  datafile = fileHandler.getFileFromURLSpec(path + this.getCangjie5File());
234        }
235 
236        if (datafile.exists())
237        	 return true;
238        else
239        	 return false;
240     },
241 
242     canComposeNew: function()
243     {
244        return false;
245     },
246 
247     setNumWordSelection: function(num)
248     {
249        this.numSelection = num > 9 ? 9 : (num < 1 ? 1: num);
250     },
251 
252     getIMEType: function()
253     {
254        return  this.cangjieSchema; 
255     },
256 
257     setSchema: function(schema)
258     {
259        //FireinputLog.debug(this, "Set schema: " + schema);
260        this.cangjieSchema = schema;
261     },
262 
263     getAllowedInputKey: function()
264     {
265        return "abcdefghijklmnopqrstuvwxyz";
266     },
267 
268     setEncoding: function(encoding)
269     {
270        this.encodingMode = encoding;
271     },
272 
273     convertLetter: function(code)
274     {
275        // Full: number + alpha character
276        // Punct: any printable character which is not a space or an alphanumeric character
277        if((!this.isHalfLetterMode() &&
278           ((code > 47 && code < 58) ||
279            (code > 64 && code < 91))) ||
280           (!this.isHalfPunctMode() &&
281            !((code > 47 && code < 58) ||
282             (code > 64 && code < 91))))
283           return letterConverter.toFullLetter(String.fromCharCode(code));
284 
285        return String.fromCharCode(code);
286     },
287 
288     find: function(inputChar)
289     {
290        var s = inputChar;
291        var retArray = null;
292 
293        this.autoInsertion = false;
294        // here we will do searching on inputChar by length -1 every time if retArray is null
295        while(s.length > 0 && (retArray=this.searchAll(s)) == null)
296        {
297        	s = s.substr(0, s.length - 1);
298        	// remove last single quot if it presents.
299        	if (s.substr(s.length - 1, 1) == "'")
300        	{
301        		s = s.substr(0, s.length - 1);
302        	}
303        }
304 
305        this.validInputKey = s;
306        return {charArray:retArray, validInputKey: this.validInputKey};
307     },
308 
309     searchAll: function(inputChar)
310     {
311        this.charArray = null;
312        this.charIndex = 0; 
313 
314        this.charArray = this.codeLookup(inputChar);
315        if (this.charArray != null)
316        	return this.charArray.slice(0, this.numSelection);
317 
318        return null;
319     },
320 
321     codeLookup: function(keys)
322     {
323        var charArray = null;
324 
325        if (keys == null || keys.length <= 0)
326        	return null;
327 
328        // a valid charArray consist of {key:key, word: word}
329        charArray = this.getValidWord(keys);
330        if (! charArray)
331        	return null;
332 
333        return charArray;
334     },
335 
336     next: function (endFlag)
337     {
338        if (! this.charArray)
339        	return null;
340 
341        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
342        // if the next this.numSelection are already displayed, return null
343        if ((this.charIndex+this.numSelection) >= this.charArray.length)
344        	return null;
345 
346        var i = this.charIndex;
347        if (! endFlag)
348        {
349        	  this.charIndex += this.numSelection;
350        }
351        else
352        {
353        	i = this.charArray.length-this.numSelection;
354        	i -= this.numSelection;
355        	this.charIndex = (i > 0) ? i : 0;
356        }
357        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
358        return {charArray:this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
359     },
360 
361     prev: function (homeFlag)
362     {
363        if (! this.charArray)
364        	  return null;
365        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
366        // if the previous this.numSelection are already displayed, return null
367        if ((this.charIndex - this.numSelection) < 0)
368        	  return null;
369 
370        if (! homeFlag)
371        	  this.charIndex -= this.numSelection;
372        else
373        	  this.charIndex = 0;
374 
375        // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
376        return {charArray: this.charArray.slice(this.charIndex, this.charIndex+this.numSelection), validInputKey: this.validInputKey};
377     },
378 
379     isBeginning: function()
380     {
381        return this.charIndex == 0;
382     },
383 
384     isEnd: function()
385     {
386        return (this.charIndex + this.numSelection) >= this.charArray.length;
387     },
388 
389     canAutoInsert: function()
390     {
391        return this.autoInsertion;
392     },
393 
394     getValidWord: function(key)
395     {
396        var wordArray = null;
397        var userArray = new Array();
398        var wordList = new Array();
399 
400        if (! key) return null;
401 
402        var keyInitial = key; 
403 
404        if (! this.keyCangjieHash.hasItem(keyInitial))
405        	  return null;
406 
407        // only enable autoinsertion for 4 keys
408        if (key.length >= 5)
409        	this.autoInsertion = true;
410 
411        var cangjieWordList = this.keyCangjieHash.getItem(keyInitial);
412 
413        var cangjieWordArray = cangjieWordList.split(",");
414 
415        wordArray = new Array();
416 
417        for (var i = 0; i < cangjieWordArray.length; i ++)
418        {
419        	var cangjieWord = cangjieWordArray[i].split("=>");
420 
421        	if (cangjieWord[0].search(new RegExp("^" + key)))
422        	   continue;
423 
424        	var word = "";
425        	try
426        	{
427        	   word = cangjieWord[1].match(/[\D\.]+/g)[0];
428        	}
429        	catch(e) { }
430 
431        	if (word.length <= 0)
432        	  continue;
433 
434 
435        	var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
436        	if (typeof(wordList[encodedWord]) != 'undefined')
437        	  continue;
438 
439        	wordList[encodedWord] = 1;
440 
441        	if (this.userCodeHash && this.userCodeHash.hasItem(word+":"+cangjieWord[0]))
442        	{
443        	   var ufreq = this.userCodeHash.getItem(word+":"+cangjieWord[0]);
444        	   /* use this way other than push to have better performance
445        	    * http://aymanh.com/9-javascript-tips-you-may-not-know
446        	    */
447        	   userArray[userArray.length] ={key: cangjieWord[0], word: word + ufreq.freq, encodedWord: encodedWord + ufreq.freq};
448        	}
449        	else
450        	{
451       	   var freq = cangjieWord[1].match(/[\d\.]+/g); 
452            if(freq) freq = freq[0];
453            else freq = 0; 
454 
455        	   wordArray[wordArray.length] = {key: cangjieWord[0], word: word+freq, encodedWord: encodedWord + freq};
456        	}
457        }
458 
459        // free it
460        wordList = null;
461 
462        // FireinputLog.debug(this,"wordArray: " + this.getKeyWord(wordArray));
463        // FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
464        if (userArray.length <= 0)
465        	  return wordArray;
466 
467        // sort the first this.numSelection items
468        if (userArray.length < (this.numSelection+1))
469        {
470        	  arrayInsert(userArray, userArray.length, wordArray.slice(0, this.numSelection));
471        	  userArray.sort(this.sortCodeArray);
472        	  arrayInsert(userArray, userArray.length, wordArray.slice(this.numSelection+1, wordArray.length));
473        }
474        else
475        {
476        	  userArray.sort(this.sortCodeArray);
477        	  arrayInsert(userArray, userArray.length, wordArray.slice(0, wordArray.length));
478        }
479 
480        return userArray;
481     },
482 
483     flushUserTable: function()
484     {
485        if (this.userCodeHash && this.userTableChanged)
486        {
487        	  FireinputSaver.save(this.userCodeHash);
488        }
489     },
490 
491     updateFrequency: function(word, key, initKey)
492     {
493        var freq = word.match(/[\d\.]+/g)[0];
494        var chars = word.match(/[\D\.]+/g)[0];
495 
496        if (! this.userCodeHash)
497        	  this.userCodeHash = new FireinputHash();
498 
499        var newfreq = 0;
500        if (this.userCodeHash.hasItem(chars + ":" + key))
501        {
502        	  var charopt = this.userCodeHash.getItem(chars + ":" + key);
503        	  var freq1 = charopt.freq;
504        	  newfreq = parseInt("0xFFFFFFFF", 16) - freq1;
505 
506        	  if (typeof(initKey) == "undefined")
507        	     initKey = charopt.initKey;
508        }
509        else
510        {
511        	  newfreq = parseInt("0xFFFFFFFF", 16) - freq;
512 
513        	  if (typeof(initKey) == "undefined")
514        	  {
515        	     initKey = key.substring(0, 1);
516        	     if (key.length >= 3)
517        	        initKey = key.substring(0, 3);
518        	  }
519        }
520 
521        if (newfreq)
522        {
523        	  newfreq /= Math.pow(2, 16);
524        	  if (newfreq < 1) newfreq = 1;
525        }
526 
527        freq = Math.round(newfreq) + parseInt(freq);
528 
529        this.userCodeHash.setItem(chars+":"+key, {freq: freq, initKey: initKey, schema: this.cangjieSchema});
530 
531        this.userTableChanged = true;
532 
533        // FireinputLog.debug(this,"word: " + word);
534        // FireinputLog.debug(this,"chars: " + chars + ", freq: " + freq);
535        // FireinputLog.debug(this,"chars: " + chars + ", key: " + key + ", initKey: " + initKey);
536        return freq;
537     },
538 
539     storeUserPhrases: function(userPhrases)
540     {
541        return;
542     },
543 
544     storeOneUserPhrase: function(userPhrase)
545     {
546        return;
547     }
548 });


syntax highlighted by Code2HTML, v. 0.9.1