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