/**
* hangul-sebeol.js
* http://github.com/clee704/hangul-js
* @version 1.1.2
* @copyright Copyright 2013, Choongmin Lee
* @license MIT license
*/
/**
* @namespace hangul.sebeol
*/
(function (hangul, undefined) {
"use strict";
var Wrap = (function () {
var cache = {},
entry;
return function (type, c) {
entry = cache[type] || (cache[type] = {});
return entry[c] || (entry[c] = new Character(type, c));
}
})();
function Character(type, c) {
this.type = type;
this.c = c;
this.code = c.charCodeAt(0);
this.string = type + '[' + c + ']';
}
Character.prototype.toString = function () {
return this.string;
};
/**
* Key mapping between QWERTY and Sebeolsik.
* @member {hangul.Map} hangul.sebeol.map
*/
var map = new hangul.Map();
// deliberatly avoided overlapping keys or values
map.addAll({
'\'': initial('\u314c'), '0': initial('\u314b'), ';': initial('\u3142'),
'h': initial('\u3134'), 'i': initial('\u3141'), 'j': initial('\u3147'),
'k': initial('\u3131'), 'l': initial('\u3148'), 'm': initial('\u314e'),
'n': initial('\u3145'), 'o': initial('\u314a'), 'p': initial('\u314d'),
'u': initial('\u3137'), 'y': initial('\u3139'), '4': medial('\u315b'),
'5': medial('\u3160'), '6': medial('\u3151'), '7': medial('\u3156'),
'8': medial('\u3162'), 'G': medial('\u3152'), 'b': medial('\u315c'),
'c': medial('\u3154'), 'd': medial('\u3163'), 'e': medial('\u3155'),
'f': medial('\u314f'), 'g': medial('\u3161'), 'r': medial('\u3150'),
't': medial('\u3153'), 'v': medial('\u3157'), '/': medialSp('\u3157'),
'9': medialSp('\u315c'), '!': final_('\u3132'), '#': final_('\u3148'),
'$': final_('\u313f'), '%': final_('\u313e'), '1': final_('\u314e'),
'2': final_('\u3146'), '3': final_('\u3142'), '@': final_('\u313a'),
'A': final_('\u3137'), 'C': final_('\u314b'), 'D': final_('\u313c'),
'E': final_('\u3135'), 'F': final_('\u313b'), 'Q': final_('\u314d'),
'R': final_('\u3140'), 'S': final_('\u3136'), 'T': final_('\u313d'),
'V': final_('\u3133'), 'W': final_('\u314c'), 'X': final_('\u3144'),
'Z': final_('\u314a'), 'a': final_('\u3147'), 'q': final_('\u3145'),
's': final_('\u3134'), 'w': final_('\u3139'), 'x': final_('\u3131'),
'z': final_('\u3141'), '"': symbol('\u00b7'), '&': symbol('\u201c'),
'(': symbol('\''), ')': symbol('~'), '*': symbol('\u201d'),
'+': symbol('+'), ',': symbol(','), '-': symbol(')'),
'.': symbol('.'), ':': symbol('4'), '<': extra(','),
'=': symbol('>'), '>': extra('.'), '?': symbol('!'),
'B': symbol('?'), 'H': symbol('0'), 'I': symbol('7'),
'J': symbol('1'), 'K': symbol('2'), 'L': symbol('3'),
'M': symbol('"'), 'N': symbol('-'), 'O': symbol('8'),
'P': symbol('9'), 'U': symbol('6'), 'Y': symbol('5'),
'[': symbol('('), '\\': symbol(':'), ']': symbol('<'),
'^': symbol('='), '_': symbol(';'), '`': symbol('*'),
'{': symbol('%'), '|': symbol('\\'), '}': symbol('/'),
'~': symbol('\u203b')
});
function initial(c) {
return Wrap('initial', c);
}
function medial(c) {
return Wrap('medial', c);
}
// denotes a medial that may make a compound vowel
// (there are two such characters, which are mapped by / and 9 repectively)
function medialSp(c) {
return Wrap('medial-special', c);
}
function final_(c) {
return Wrap('final', c);
}
function symbol(c) {
return Wrap('symbol', c);
}
// to avoid collisions with other symbols when searching inverse
function extra(c) {
return Wrap('extra', c);
}
/**
* Converts the specified text typed in QWERTY to a text that the same
* keystrokes which made the original text would have produced if the input
* method is Sebeolsik Final. It assumes the specified text is typed with
* CapsLock off. If the text contains characters that cannot be typed in
* QWERTY, they are preserved.
* @param {string} text
* @function hangul.sebeol.fromQwerty
*/
function fromQwerty(text) {
var buffer = [],
m = new SebeolAutomaton(buffer);
for (var i = 0; i < text.length; i++) {
m.next(text.charAt(i));
}
m.next();
return buffer.join('');
}
/**
* @constructor
* @name hangul.sebeol.Automaton
*/
function SebeolAutomaton(output) {
/**
* @member hangul.sebeol.Automaton#output
*/
this.output = output;
/**
* @member hangul.sebeol.Automaton#currentBlock
*/
this.currentBlock = undefined;
this._indexes = {'initial': 0, 'medial': 1, 'medial-special': 1, 'final': 2};
this._wrappers = {'initial': initial, 'medial': medial, 'final': final_};
this._jamoBlock = [];
this._jamoQueue = [];
}
/**
* @method hangul.sebeol.Automaton#reset
*/
SebeolAutomaton.prototype.reset = function () {
this.currentBlock = undefined;
this._jamoBlock.length = 0;
this._jamoQueue.length = 0;
};
/**
* @param {string} key
* @method hangul.sebeol.Automaton#next
*/
SebeolAutomaton.prototype.next = function (key) {
var currJamo;
if (key === '\b') {
if (this._jamoQueue.length > 0) {
currJamo = this._jamoQueue.pop();
delete this._jamoBlock[this._indexes[currJamo.type]];
this._renderCurrentBlock();
} else {
this.output.pop();
}
return;
}
if (!map.hasKey(key)) {
this._flush();
if (key !== undefined) this.output.push(key);
return;
}
currJamo = map.get(key);
if (!hangul.isJamo(currJamo.c)) {
this._flush();
this.output.push(currJamo.c);
return;
}
var i = this._indexes[currJamo.type],
jamo = this._jamoBlock,
x = jamo[i];
if (x) {
var d = hangul.composeDoubleJamo(x.c, currJamo.c);
if (d && ((x.type === 'initial' && !jamo[1] && !jamo[2] &&
hangul.isInitial(d)) ||
x.type === 'medial-special')) {
jamo[i] = this._wrappers[currJamo.type](d);
this._renderCurrentBlock();
return;
}
this._flush();
}
jamo[i] = currJamo;
this._jamoQueue.push(currJamo);
this._renderCurrentBlock();
};
SebeolAutomaton.prototype._flush = function () {
if (this.currentBlock !== undefined) {
this.output.push(this.currentBlock);
this.currentBlock = undefined;
}
this._jamoBlock.length = 0;
this._jamoQueue.length = 0;
};
SebeolAutomaton.prototype._renderCurrentBlock = function () {
var jamo = this._jamoBlock,
b;
if (jamo[0]) {
if (jamo[1]) {
if (jamo[2]) {
b = hangul.compose(jamo[0].c, jamo[1].c, jamo[2].c);
} else {
b = hangul.compose(jamo[0].c, jamo[1].c);
}
} else {
b = jamo[0].c;
}
} else if (jamo[1]) {
b = jamo[1].c;
} else if (jamo[2]) {
b = jamo[2].c;
} else {
b = undefined;
}
this.currentBlock = b;
};
/**
* @param {string} text
* @function hangul.sebeol.toQwerty
*/
function toQwerty(text) {
var buffer = [];
for (var i = 0; i < text.length; i++) {
_toQwerty(buffer, text.charAt(i));
}
return buffer.join('');
}
function _toQwerty(buffer, currKey) {
if (hangul.isJamo(currKey)) {
return _putJamo(buffer, currKey, _getWrapper(currKey));
}
if (!hangul.isSyllable(currKey)) {
return buffer.push(map.inverse.get(symbol(currKey)) || currKey);
}
var jamo = hangul.decompose(currKey);
_putJamo(buffer, jamo[0], initial);
_putJamo(buffer, jamo[1], medial);
if (jamo[2]) _putJamo(buffer, jamo[2], final_);
}
function _getWrapper(c) {
// if the character is a consonant and can be either an initial or a
// final, it is not possible to determine whether it is typed as an
// intial or a final; here assumes it is an initial
return hangul.isInitial(c) ? initial : hangul.isMedial(c) ? medial : final_;
}
function _putJamo(buffer, c, wrap) {
var x = wrap(c);
if (map.hasValue(x)) return buffer.push(map.inverse.get(x));
var cc = hangul.decomposeDoubleJamo(c),
wrap2 = wrap === medial ? medialSp : initial;
buffer.push(map.inverse.get(wrap2(cc[0])));
buffer.push(map.inverse.get(wrap(cc[1])));
}
function _flatten(map) {
var buckets = {};
for (var k in map.items) {
var v = map.get(k),
bucket = buckets[v.type] || (buckets[v.type] = {});
bucket[k] = v.c;
}
var flatMap = new hangul.Map();
flatMap.addAll(buckets['extra']);
flatMap.addAll(buckets['symbol']);
flatMap.addAll(buckets['final']);
flatMap.addAll(buckets['medial-special']);
flatMap.addAll(buckets['medial']);
flatMap.addAll(buckets['initial']);
return flatMap;
}
hangul.sebeol = {
map: _flatten(map),
fromQwerty: fromQwerty,
toQwerty: toQwerty,
Automaton: SebeolAutomaton
};
})(hangul);