Source: hangul.js

/**
 * hangul.js
 * http://github.com/clee704/hangul-js
 * @version 1.1.2
 * @copyright Copyright 2013, Choongmin Lee
 * @license MIT license
 */
/**
 * @namespace hangul
 */
var hangul = (function (undefined) {
"use strict";

// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
    if (this === void 0 || this === null) throw new TypeError();
    var t = Object(this);
    var len = t.length >>> 0;
    if (len === 0) return -1;
    var n = 0;
    if (arguments.length > 0) {
      n = Number(arguments[1]);
      if (n !== n) { // shortcut for verifying if it's NaN
        n = 0;
      } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
        n = (n > 0 || -1) * Math.floor(Math.abs(n));
      }
    }
    if (n >= len) return -1;
    var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
    for (; k < len; k++) {
      if (k in t && t[k] === searchElement) return k;
    }
    return -1;
  };
}

/**
 * @constructor
 * @name hangul.Set
 */
function Set() {
  var i;
  this.items = {};
  for (i = 0; i < arguments.length; i++) {
    this.add(arguments[i]);
  }
}

/**
 * Returns true if this set contains the specified object.
 * @param e {object} object whose presence in this set is to be tested
 * @method hangul.Set#has
 */
Set.prototype.has = function (e) {
  return e in this.items;
};

/**
 * Adds the specified object into this set.
 * @param e {object} object to be added this set
 * @method hangul.Set#add
 */
Set.prototype.add = function (e) {
  this.items[e] = 1;
};

/**
 * Constructs a new map, optionally containing the properties of the specified
 * object.
 * @classdesc A simple map supporting an inverse view.
 * @constructor
 * @name hangul.Map
 */
function Map(o, _inverse) {
  this.items = {};
  /**
   * Inverse view of the map.
   * @member {hangul.Map} hangul.Map#inverse
   */
  this.inverse = _inverse || new Map(undefined, this);
  if (o) {
    this.addAll(o);
  }
}

/**
 * @param k {object} key
 * @param v {object} value
 * @method hangul.Map#add
 */
Map.prototype.add = function (k, v) {
  this.items[k] = v;
  this.inverse.items[v] = k;
};

/**
 * @param o {object} object whose properties are to be added to this map
 * @method hangul.Map#addAll
 */
Map.prototype.addAll = function (o) {
  var k;
  for (k in o) {
    this.add(k, o[k]);
  }
};

/**
 * Returns true if this map has a mapping for the specified key.
 * @param k {object} key
 * @method hangul.Map#hasKey
 */
Map.prototype.hasKey = function (k) {
  return k in this.items;
};

/**
 * Returns true if this map has a mapping for the specified value.
 * @param v {object} value
 * @method hangul.Map#hasValue
 */
Map.prototype.hasValue = function (v) {
  return v in this.inverse.items;
};

/**
 * Returns the associated object for the specified key or undefined
 * if there is no mapping for the key.
 * @param k {object} key
 * @method hangul.Map#get
 */
Map.prototype.get = function (k) {
  return this.items[k];
};

/**
 * List of modern hangul jamo (U+3131-U+3163).
 * @member {hangul.Map} hangul.jamo
 */
var jamo = collectJamo(0x3131, 0x3163);

/**
 * List of modern hangul initial jamo. Actually some of these charaters are
 * not just initials, but can also be final jamo. Thus many characters in this
 * list overlap with the characters in {@link hangul.finals}.
 * @member {hangul.Map} hangul.initials
 */
var initials = collectJamo(0x3131, 0x314e,
    [2, 4, 5, 9, 10, 11, 12, 13, 14, 15, 19]);

/**
 * List of modern hangul medials.
 * @member {hangul.Map} hangul.medials
 */
var medials = collectJamo(0x314f, 0x3163);

/**
 * List of modern hangul finals. The details are the same as
 * {@link hangul.initials}. The list does not include a filler.
 * @member {hangul.Map} hangul.finals
 */
var finals = collectJamo(0x3131, 0x314e, [7, 18, 24]);

function collectJamo(from, to, exclude) {
  var map = new Map(),
      length = to - from + 1;
  for (var i = 0, j = 0; i < length; i++) {
    if (!exclude || exclude.indexOf(i) < 0) {
      map.add(j++, String.fromCharCode(i + from));
    }
  }
  return map;
}

/**
 * Returns true if the first character of the specified string represents
 * modern hangul characters (U+3131-U+3163 and U+AC00-U+D7A3; no support for
 * the "Hangul Jamo", "Hangul Jamo Extended-A", "Hangul Jamo Extended-B"
 * blocks).
 * @param {string} s
 * @function hangul.isHangul
 */
function isHangul(s) {
  var c = s && s.charAt && s.charAt(0);
  return jamo.hasValue(c) || isSyllable(c);
}

/**
 * Returns true if the first character of the specified string represents
 * modern hangul syllables (U+AC00-U+D7A3).
 * @param {string} s
 * @function hangul.isSyllable
 */
function isSyllable(s) {
  var code = s && s.charCodeAt && s.charCodeAt(0);
  return 0xac00 <= code && code <= 0xd7a3;
}

/**
 * Returns true if the first character of the specified string represents
 * modern jamo (U+3131-U+3163).
 * @param {string} s
 * @function hangul.isJamo
 */
function isJamo(s) {
  return jamo.hasValue(s && s.charAt && s.charAt(0));
}

/**
 * Returns true if the first character of the specified string represents
 * modern hangul initials.
 * @param {string} s
 * @function hangul.isInitial
 */
function isInitial(s) {
  return initials.hasValue(s && s.charAt && s.charAt(0));
}

/**
 * Returns true if the first character of the specified string represents
 * modern hangul medials.
 * @param {string} s
 * @function hangul.isMedial
 */
function isMedial(s) {
  return medials.hasValue(s && s.charAt && s.charAt(0));
}

/**
 * Returns true if the first character of the specified string represents
 * modern hangul finals.
 * @param {string} s
 * @function hangul.isFinal
 */
function isFinal(s) {
  return finals.hasValue(s && s.charAt && s.charAt(0));
}

/**
 * Returns the initial of the first chacater of the specified string.
 * Returns undefined if the character is not a hangul syllable.
 * @param {string} s
 * @function hangul.getInitial
 */
function getInitial(s) {
  var code = s && s.charCodeAt && s.charCodeAt(0);
  return initials.get(Math.floor((code - 0xac00) / 28 / 21));
}

/**
 * Returns the medial of the first chacater of the specified string.
 * Returns undefined if the character is not a hangul syllable.
 * @param {string} s
 * @function hangul.getMedial
 */
function getMedial(s) {
  var code = s && s.charCodeAt && s.charCodeAt(0);
  return medials.get(Math.floor((code - 0xac00) / 28) % 21);
}

/**
 * Returns the final of the first chacater of the specified string, or
 * an empty string '' if the syllable has no final jamo. Returns undefined
 * if the character is not a hangul syllable.
 * @param {string} s
 * @function hangul.getFinal
 */
function getFinal(s) {
  var code = s && s.charCodeAt && s.charCodeAt(0),
      i = (code - 0xac00) % 28;
  return i > 0 ? finals.get(i - 1) : i === 0 ? '' : undefined;
}

/**
 * Decomposes the first character of the specified string into constituent
 * jamo and returns them as an array of length 3 (or 2 if there is no final).
 * They are obtained using {@link hangul.getInitial}, {@link hangul.getMedial}
 * and {@link hangul.getFinal}. Returns undefined if the character is not a
 * hangul syllable.
 * @param {string} s
 * @function hangul.decompose
 */
function decompose(s) {
  var c = s && s.charAt && s.charAt(0);
  if (!isSyllable(c)) {
    return undefined;
  }
  var jamo = [getInitial(c), getMedial(c), getFinal(c)];
  if (jamo[2] === '') {
    jamo.pop();
  }
  return jamo;
}

/**
 * Composes from the specified constituent jamo a hangul syllable. Use
 * undefined or an empty string '' for the final filler. Returns undefined if
 * any of the arguments are not a modern jamo, except for the final which can
 * also be either undefined or an empty string.
 * @param {string} ini initial
 * @param {string} med medial
 * @param {string} fin final (optional)
 * @function hangul.compose
 */
function compose(ini, med, fin) {
  var x = initials.inverse.get(ini),
      y = medials.inverse.get(med),
      z = fin === undefined || fin === '' ? 0 : finals.inverse.get(fin) + 1,
      c = String.fromCharCode(0xac00 + (x * 21 + y) * 28 + z);
  return isSyllable(c) ? c : undefined;
}

/**
 * List of modern hangul double jamo (clusters and compounds).
 */
var doubleJamo = new Map({
  '\u3133': '\u3131\u3145', '\u3135': '\u3134\u3148',
  '\u3136': '\u3134\u314e', '\u313a': '\u3139\u3131',
  '\u313b': '\u3139\u3141', '\u313c': '\u3139\u3142',
  '\u313d': '\u3139\u3145', '\u313e': '\u3139\u314c',
  '\u313f': '\u3139\u314d', '\u3140': '\u3139\u314e',
  '\u3144': '\u3142\u3145', '\u3132': '\u3131\u3131',
  '\u3138': '\u3137\u3137', '\u3143': '\u3142\u3142',
  '\u3146': '\u3145\u3145', '\u3149': '\u3148\u3148',
  '\u3158': '\u3157\u314f', '\u3159': '\u3157\u3150',
  '\u315a': '\u3157\u3163', '\u315d': '\u315c\u3153',
  '\u315e': '\u315c\u3154', '\u315f': '\u315c\u3163',
  '\u3162': '\u3161\u3163'
});

/**
 * Composes from the specified jamo a double jamo. Returns undefined if
 * the specified jamo do not make a double jamo.
 * @param {string} c1
 * @param {string} c2
 * @function hangul.composeDoubleJamo
 */
function composeDoubleJamo(c1, c2) {
  return doubleJamo.inverse.get(c1 + c2);
}

/**
 * Decomposes the specified double jamo into two jamo and returns them as an
 * array of length 2. Returns undefined if the specified jamo is not a double
 * jamo.
 * @param {string} c
 * @function hangul.decomposeDoubleJamo
 */
function decomposeDoubleJamo(c) {
  var cc = doubleJamo.get(c);
  return cc === undefined ? cc : [cc.charAt(0), cc.charAt(1)];
}

var iotizedVowels = new Set(
  '\u3163', '\u3151', '\u3152', '\u3155', '\u3156', '\u315b', '\u3160'
);

/**
 * Returns true if the first character of the specified string represents
 * a iotized vowel (including the close front vowel) that may cause
 * palatalization.
 * @param {string} s
 * @function hangul.isIotizedVowel
 */
function isIotizedVowel(s) {
  return iotizedVowels.has(s && s.charAt && s.charAt(0));
}

return {
  Set: Set,
  Map: Map,
  jamo: jamo,
  initials: initials,
  medials: medials,
  finals: finals,
  isHangul: isHangul,
  isSyllable: isSyllable,
  isJamo: isJamo,
  isInitial: isInitial,
  isMedial: isMedial,
  isFinal: isFinal,
  getInitial: getInitial,
  getMedial: getMedial,
  getFinal: getFinal,
  decompose: decompose,
  compose: compose,
  composeDoubleJamo: composeDoubleJamo,
  decomposeDoubleJamo: decomposeDoubleJamo,
  isIotizedVowel: isIotizedVowel
};

})();