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: 0,
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 // use code hash table event
66 userTableChanged: false,
67
68 // full/half letter converter
69 letterConverter: null,
70
71 // pinyin Schema
72 wubiSchema: null,
73
74 // encoding mode. Either simplified or big5. Simplified default.
75 encodingMode: ENCODING_ZH,
76
77 // engine enabled
78 engineDisabled: false,
79
80 // can auto insertion
81 autoInsertion: false,
82
83 // the entrance function to load all related tables
84 loadTable: function()
85 {
86 letterConverter = new FullLetterConverter();
87
88 // setTimeout to not block firefox start
89 var self = this;
90 setTimeout(function() { return self.loadWubiTable(); }, 500);
91
92 // init encoding table
93 FireinputEncoding.init();
94 },
95
96 getCodeLine: function(str)
97 {
98 var strArray = str.split(':');
99 if(strArray.length < 2)
100 return;
101
102 // initKey:key=>word
103 this.keyWubiHash.setItem(strArray[0],strArray[1]);
104 },
105
106 loadWubiTable: function()
107 {
108 var ios = IOService.getService(Components.interfaces.nsIIOService);
109 var fileHandler = ios.getProtocolHandler("file")
110 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
111
112 var path = this.getDataPath();
113 var datafile = "";
114 if(this.wubiSchema == WUBI_86)
115 datafile = fileHandler.getFileFromURLSpec(path + this.getWubi86File());
116 if(this.wubiSchema == WUBI_98)
117 datafile = fileHandler.getFileFromURLSpec(path + this.getWubi98File());
118
119 if(!datafile.exists())
120 {
121 this.engineDisabled = true;
122 return;
123 }
124
125 this.keyWubiHash = new FireinputHash();
126
127 var options = {
128 caller: this,
129 oncomplete: this.loadUserTable,
130 onavailable: this.getCodeLine
131 };
132
133 FireinputStream.loadDataAsync(datafile, options);
134 },
135
136
137 getUserCodeLine: function(str)
138 {
139 var strArray = str.split(':');
140 if(strArray.length < 4)
141 return;
142
143 // user data format: word: freq key initKey
144
145 this.userCodeHash.setItem(strArray[0], {freq: strArray[1], key: strArray[2], initKey: strArray[3]});
146
147 },
148
149 loadUserTable: function()
150 {
151 var ios = IOService.getService(Components.interfaces.nsIIOService);
152 var fileHandler = ios.getProtocolHandler("file")
153 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
154
155 var path = FireinputUtils.getAppRootPath();
156 var datafile = fileHandler.getFileFromURLSpec(path + this.getUserDataFile());
157 if(!datafile.exists())
158 return;
159 this.userCodeHash = new FireinputHash();
160
161 var options = {
162 caller: this,
163 onavailable: this.getUserCodeLine
164 };
165 FireinputStream.loadDataAsync(datafile, options);
166 },
167
168 isEnabled: function()
169 {
170 if(this.engineDisabled)
171 return false;
172 var ios = IOService.getService(Components.interfaces.nsIIOService);
173 var fileHandler = ios.getProtocolHandler("file")
174 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
175
176 var path = this.getDataPath();
177 var datafile = fileHandler.getFileFromURLSpec(path + this.getWubi86File());
178 if(!datafile.exists())
179 {
180 datafile = fileHandler.getFileFromURLSpec(path + this.getWubi98File());
181 if(datafile.exists())
182 return true;
183 return false;
184 }
185
186 return true;
187 },
188
189 isSchemaEnabled: function()
190 {
191 if(this.engineDisabled)
192 return false;
193 var ios = IOService.getService(Components.interfaces.nsIIOService);
194 var fileHandler = ios.getProtocolHandler("file")
195 .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
196
197 var path = this.getDataPath();
198 if(this.wubiSchema == WUBI_86)
199 datafile = fileHandler.getFileFromURLSpec(path + this.getWubi86File());
200 if(this.wubiSchema == WUBI_98)
201 datafile = fileHandler.getFileFromURLSpec(path + this.getWubi98File());
202 if(!datafile.exists())
203 {
204 return false;
205 }
206
207 return true;
208 },
209
210 canComposeNew: function()
211 {
212 return false;
213 },
214
215 setSchema: function(schema)
216 {
217 //FireinputLog.debug(this, "Set schema: " + schema);
218 this.wubiSchema = schema;
219 },
220
221 getAllowedInputKey: function()
222 {
223 return "abcdefghijklmnopqrstuvwxy";
224 },
225
226 setEncoding: function(encoding)
227 {
228 this.encodingMode = encoding;
229 },
230
231 convertLetter: function(code)
232 {
233 // Full: number + alpha character
234 // Punct: any printable character which is not a space or an alphanumeric character
235 if((!this.isHalfLetterMode() &&
236 ((code > 47 && code < 58) ||
237 (code > 64 && code < 91))) ||
238 (!this.isHalfPunctMode() &&
239 !((code > 47 && code < 58) ||
240 (code > 64 && code < 91))))
241 return letterConverter.toFullLetter(String.fromCharCode(code));
242
243 return String.fromCharCode(code);
244 },
245
246 find: function(inputChar)
247 {
248 var s = inputChar;
249 var retArray = null;
250
251 this.autoInsertion = false;
252 // here we will do searching on inputChar by length -1 every time if retArray is null
253 while(s.length > 0 && (retArray=this.searchAll(s)) == null)
254 {
255 s = s.substr(0, s.length - 1);
256 // remove last single quot if it presents.
257 if(s.substr(s.length-1, 1) == "'")
258 {
259 s = s.substr(0, s.length - 1);
260 }
261 }
262
263 this.validInputKey = s;
264 return {charArray:retArray, validInputKey: this.validInputKey};
265 },
266
267 searchAll: function(inputChar)
268 {
269 this.charArray = null;
270
271 this.charArray = this.codeLookup(inputChar);
272 if(this.charArray != null)
273 return this.charArray.slice(0, 9);
274 return null;
275 },
276
277 codeLookup: function(keys)
278 {
279 var charArray = null;
280 if(keys == null || keys.length <= 0)
281 return null;
282
283 // a valid charArray consist of {key:key, word: word}
284 charArray = this.getValidWord(keys);
285 if(!charArray)
286 return null;
287
288 return charArray;
289 },
290
291 next: function (endFlag)
292 {
293 if(!this.charArray)
294 return null;
295
296 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
297 // if the next 9 are already displayed, return null
298 if((this.charIndex+10) >= this.charArray.length)
299 return null;
300
301 var i = this.charIndex;
302 if(!endFlag)
303 this.charIndex += 9;
304 else
305 {
306 i = this.charArray.length-9;
307 i -= 9;
308 this.charIndex = i>0 ? i:0;
309 }
310 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
311 return {charArray:this.charArray.slice(this.charIndex, this.charIndex+9), validInputKey: this.validInputKey};
312 },
313
314 prev: function (homeFlag)
315 {
316 if(!this.charArray)
317 return null;
318 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
319 // if the previous 9 are already displayed, return null
320 if((this.charIndex-9) < 0)
321 return null;
322
323 if(!homeFlag)
324 this.charIndex -= 9;
325 else
326 this.charIndex = 0;
327
328 // FireinputLog.debug(this,"this.charIndex: " + this.charIndex);
329 return {charArray: this.charArray.slice(this.charIndex, this.charIndex+9), validInputKey: this.validInputKey};
330 },
331
332 isBeginning: function()
333 {
334 return this.charIndex == 0;
335 },
336
337 isEnd: function()
338 {
339 return (this.charIndex+10) >= this.charArray.length;
340 },
341
342 canAutoInsert: function()
343 {
344 return this.autoInsertion;
345 },
346
347 getValidWord: function(key)
348 {
349 var wordArray = null;
350 var userArray = new Array();
351 var wordList = new Array();
352
353 if(!key)
354 return null;
355
356 var keyInitial = key.substring(0, 1);
357 if(key.length >= 3)
358 keyInitial = key.substring(0, 3);
359 if(!this.keyWubiHash.hasItem(keyInitial))
360 return null;
361
362 // only enable autoinsertion for 4 keys
363 if(key.length >= 4)
364 this.autoInsertion = true;
365
366 var wubiWordList = this.keyWubiHash.getItem(keyInitial);
367
368 var wubiWordArray = wubiWordList.split(",");
369
370 wordArray = new Array();
371
372 for(var i=0; i < wubiWordArray.length; i++)
373 {
374 var wubiWord = wubiWordArray[i].split("=>");
375
376 if(wubiWord[0].search(new RegExp("^" + key)))
377 continue;
378
379 // FireinputLog.debug(this,"wubiWordArray[i]: " + FireinputUnicode.getUnicodeString(wubiWordArray[i]));
380
381 var word = "";
382 try
383 {
384 word = wubiWord[1].match(/[\D\.]+/g)[0];
385 }
386 catch(e) {}
387
388
389 if(word.length <= 0)
390 continue;
391
392
393 if(typeof(wordList[word]) != 'undefined')
394 continue;
395
396 var encodedWord = FireinputEncoding.getEncodedString(word, this.encodingMode);
397
398 wordList[word] = 1;
399 if(this.userCodeHash && this.userCodeHash.hasItem(word))
400 {
401 var ufreq = this.userCodeHash.getItem(word);
402 /* use this way other than push to have better performance
403 * http://aymanh.com/9-javascript-tips-you-may-not-know
404 */
405 userArray[userArray.length] = {key: wubiWord[0], word:word+ufreq.freq, encodedWord:encodedWord+ufreq.freq};
406 }
407 else
408 {
409 var freq = wubiWord[1].match(/[\d\.]+/g)[0];
410 wordArray[wordArray.length] = {key:wubiWord[0], word:wubiWord[1], encodedWord:encodedWord+freq};
411 }
412 }
413
414 // free it
415 wordList = null;
416
417 //FireinputLog.debug(this,"wordArray: " + this.getKeyWord(wordArray));
418 if(userArray.length <= 0)
419 return wordArray;
420
421 // sort the first 10 items
422 if(userArray.length < 10)
423 {
424 arrayInsert(userArray, userArray.length, wordArray.slice(0, 9));
425 userArray.sort(this.sortCodeArray);
426 arrayInsert(userArray, userArray.length, wordArray.slice(10, wordArray.length));
427 }
428 else
429 {
430 userArray.sort(this.sortCodeArray);
431 arrayInsert(userArray, userArray.length, wordArray.slice(0, wordArray.length));
432 }
433
434 //FireinputLog.debug(this,"userArray: " + this.getKeyWord(userArray));
435 return userArray;
436 },
437
438 flushUserTable: function()
439 {
440 if(this.userCodeHash && this.userTableChanged)
441 {
442 FireinputSaver.save(this.userCodeHash);
443 }
444 },
445
446 updateFrequency: function(word, key, initKey)
447 {
448 var freq = word.match(/[\d\.]+/g)[0];
449 var chars = word.match(/[\D\.]+/g)[0];
450 if(!this.userCodeHash)
451 this.userCodeHash = new FireinputHash();
452
453 var newfreq = 0;
454 if(this.userCodeHash.hasItem(chars))
455 {
456 var charopt = this.userCodeHash.getItem(chars);
457 var freq1 = charopt.freq;
458 newfreq = parseInt("0xFFFFFFFF", 16) - freq1;
459
460 if(typeof(initKey) == "undefined")
461 initKey = charopt.initKey;
462 }
463 else
464 {
465 newfreq = parseInt("0xFFFFFFFF", 16) - freq;
466
467 if(typeof(initKey) == "undefined")
468 {
469 initKey = key.substring(0, 1);
470 if(key.length >= 3)
471 initKey = key.substring(0, 3);
472 }
473 }
474
475 if(newfreq)
476 {
477 newfreq /= Math.pow(2, 16);
478 if(newfreq < 1) newfreq = 1;
479 }
480 freq = Math.round(newfreq) + parseInt(freq);
481
482 this.userCodeHash.setItem(chars, {freq: freq, key: key, initKey: initKey});
483
484 this.userTableChanged = true;
485
486 // FireinputLog.debug(this,"word: " + word);
487 //FireinputLog.debug(this,"chars: " + chars + ", freq: " + freq);
488 //FireinputLog.debug(this,"chars: " + chars + ", key: " + key + ", initKey: " + initKey);
489 return freq;
490 },
491
492 storeUserPhrase: function(userPhrase)
493 {
494 return;
495 }
496
497 });
syntax highlighted by Code2HTML, v. 0.9.1