UNPKG

14.5 kBJavaScriptView Raw
1/**
2 * Hangul.js
3 * https://github.com/e-/Hangul.js
4 *
5 * Copyright 2014, Jaemin Jo
6 * under the MIT license.
7 */
8
9(function(){
10 'use strict';
11 var CHO = [
12 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ',
13 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ',
14 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ',
15 'ㅍ', 'ㅎ'
16 ],
17 JUNG = [
18 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ',
19 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', ['ㅗ', 'ㅏ'], ['ㅗ', 'ㅐ'],
20 ['ㅗ', 'ㅣ'], 'ㅛ', 'ㅜ', ['ㅜ','ㅓ'], ['ㅜ','ㅔ'], ['ㅜ','ㅣ'],
21 'ㅠ', 'ㅡ', ['ㅡ', 'ㅣ'], 'ㅣ'
22 ],
23 JONG = [
24 '', 'ㄱ', 'ㄲ', ['ㄱ','ㅅ'], 'ㄴ', ['ㄴ','ㅈ'], ['ㄴ', 'ㅎ'], 'ㄷ', 'ㄹ',
25 ['ㄹ', 'ㄱ'], ['ㄹ','ㅁ'], ['ㄹ','ㅂ'], ['ㄹ','ㅅ'], ['ㄹ','ㅌ'], ['ㄹ','ㅍ'], ['ㄹ','ㅎ'], 'ㅁ',
26 'ㅂ', ['ㅂ','ㅅ'], 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
27 ],
28 HANGUL_OFFSET = 0xAC00,
29 CONSONANTS = [
30 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄸ',
31 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ',
32 'ㅁ', 'ㅂ', 'ㅃ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ',
33 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
34 ],
35 COMPLETE_CHO = [
36 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ',
37 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ',
38 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
39 ],
40 COMPLETE_JUNG = [
41 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ',
42 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ',
43 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ',
44 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ'
45 ],
46 COMPLETE_JONG = [
47 '', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ',
48 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ',
49 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
50 ],
51 COMPLEX_CONSONANTS = [
52 ['ㄱ','ㅅ','ㄳ'],
53 ['ㄴ','ㅈ','ㄵ'],
54 ['ㄴ','ㅎ','ㄶ'],
55 ['ㄹ','ㄱ','ㄺ'],
56 ['ㄹ','ㅁ','ㄻ'],
57 ['ㄹ','ㅂ','ㄼ'],
58 ['ㄹ','ㅅ','ㄽ'],
59 ['ㄹ','ㅌ','ㄾ'],
60 ['ㄹ','ㅍ','ㄿ'],
61 ['ㄹ','ㅎ','ㅀ'],
62 ['ㅂ','ㅅ','ㅄ']
63 ],
64 COMPLEX_VOWELS = [
65 ['ㅗ','ㅏ','ㅘ'],
66 ['ㅗ','ㅐ','ㅙ'],
67 ['ㅗ','ㅣ','ㅚ'],
68 ['ㅜ','ㅓ','ㅝ'],
69 ['ㅜ','ㅔ','ㅞ'],
70 ['ㅜ','ㅣ','ㅟ'],
71 ['ㅡ','ㅣ','ㅢ']
72 ],
73 CONSONANTS_HASH,
74 CHO_HASH,
75 JUNG_HASH,
76 JONG_HASH,
77 COMPLEX_CONSONANTS_HASH,
78 COMPLEX_VOWELS_HASH
79 ;
80
81
82 function _makeHash(array){
83 var length = array.length,
84 hash = {0 : 0}
85 ;
86 for (var i = 0; i < length; i++) {
87 if(array[i])
88 hash[array[i].charCodeAt(0)] = i;
89 }
90 return hash;
91 }
92
93 CONSONANTS_HASH = _makeHash(CONSONANTS);
94 CHO_HASH = _makeHash(COMPLETE_CHO);
95 JUNG_HASH = _makeHash(COMPLETE_JUNG);
96 JONG_HASH = _makeHash(COMPLETE_JONG);
97
98 function _makeComplexHash(array){
99 var length = array.length,
100 hash = {},
101 code1,
102 code2
103 ;
104 for (var i = 0; i < length; i++) {
105 code1 = array[i][0].charCodeAt(0);
106 code2 = array[i][1].charCodeAt(0);
107 if (typeof hash[code1] === 'undefined') {
108 hash[code1] = {};
109 }
110 hash[code1][code2] = array[i][2].charCodeAt(0);
111 }
112 return hash;
113 }
114
115 COMPLEX_CONSONANTS_HASH = _makeComplexHash(COMPLEX_CONSONANTS);
116 COMPLEX_VOWELS_HASH = _makeComplexHash(COMPLEX_VOWELS);
117
118 function _isConsonant(c) {
119 return CONSONANTS_HASH[c];
120 }
121
122 function _isCho(c){
123 return typeof CHO_HASH[c] !== 'undefined';
124 }
125
126 function _isJung(c){
127 return typeof JUNG_HASH[c] !== 'undefined';
128 }
129
130 function _isJong(c){
131 return typeof JONG_HASH[c] !== 'undefined';
132 }
133
134 function _isHangul(c /* code number */){
135 return 0xAC00 <= c && c <= 0xd7a3;
136 }
137
138 function _isJungJoinable(a,b){
139 return (COMPLEX_VOWELS_HASH[a] && COMPLEX_VOWELS_HASH[a][b]) ? COMPLEX_VOWELS_HASH[a][b] : false;
140 }
141
142 function _isJongJoinable(a,b){
143 return COMPLEX_CONSONANTS_HASH[a] && COMPLEX_CONSONANTS_HASH[a][b] ? COMPLEX_CONSONANTS_HASH[a][b] : false;
144 }
145
146 var disassemble = function(string){
147 if (typeof string === 'object') {
148 string = string.join('');
149 }
150
151 var result = [],
152 length = string.length,
153 cho,
154 jung,
155 jong,
156 code
157 ;
158
159 for (var i = 0; i < length; i++) {
160 code = string.charCodeAt(i);
161 if (_isHangul(code)) { // 완성된 한글이면
162 code -= HANGUL_OFFSET;
163 jong = code % 28;
164 jung = (code - jong) / 28 % 21;
165 cho = parseInt((code - jong) / 28 / 21);
166 result.push(CHO[cho]);
167 if (typeof JUNG[jung] === 'object') {
168 result = result.concat(JUNG[jung]);
169 } else {
170 result.push(JUNG[jung]);
171 }
172 if (jong > 0) {
173 if(typeof JONG[jong] === 'object') {
174 result = result.concat(JONG[jong]);
175 } else {
176 result.push(JONG[jong]);
177 }
178 }
179 } else if (_isConsonant(code)) { //자음이면
180 var r;
181 if (_isCho(code)) {
182 r = CHO[CHO_HASH[code]];
183 } else {
184 r = JONG[JONG_HASH[code]];
185 }
186 if (typeof r == 'string') {
187 result.push(r);
188 } else {
189 result = result.concat(r);
190 }
191 } else if (_isJung(code)) {
192 var r = JUNG[JUNG_HASH[code]];
193 if (typeof r == 'string') {
194 result.push(r);
195 } else {
196 result = result.concat(r);
197 }
198 } else {
199 result.push(string.charAt(i));
200 }
201 }
202 return result;
203 };
204
205 var assemble = function(array){
206 if (typeof array === 'string') {
207 array = disassemble(array);
208 }
209
210 var result = [],
211 length = array.length,
212 code ,
213 stage = 0,
214 complete_index = -1, //완성된 곳의 인덱스
215 previous_code
216 ;
217
218 function _makeHangul(index){ // complete_index + 1부터 index까지를 greedy하게 한글로 만든다.
219 var code,
220 cho,
221 jung1,
222 jung2,
223 jong1 = 0,
224 jong2,
225 hangul = ''
226 ;
227 if (complete_index + 1 > index) {
228 return;
229 }
230 for (var step = 1; ; step++) {
231 if (step === 1) {
232 cho = array[complete_index + step].charCodeAt(0);
233 if (_isJung(cho)) { // 첫번째 것이 모음이면 1) ㅏ같은 경우이거나 2) ㅙ같은 경우이다
234 if (complete_index + step + 1 <= index && _isJung(jung1 = array[complete_index + step + 1].charCodeAt(0))) { //다음것이 있고 모음이면
235 result.push(String.fromCharCode(_isJungJoinable(cho, jung1)));
236 complete_index = index;
237 return;
238 } else {
239 result.push(array[complete_index + step])
240 complete_index = index;
241 return;
242 }
243 } else if (!_isCho(cho)) {
244 result.push(array[complete_index + step]);
245 complete_index = index;
246 return;
247 }
248 hangul = array[complete_index + step];
249 } else if (step === 2) {
250 jung1 = array[complete_index + step].charCodeAt(0);
251 if (_isCho(jung1)) { //두번째 또 자음이 오면 ㄳ 에서 ㅅ같은 경우이다
252 cho = _isJongJoinable(cho, jung1);
253 hangul = String.fromCharCode(cho);
254 result.push(hangul);
255 complete_index = index;
256 return;
257 } else {
258 hangul = String.fromCharCode((CHO_HASH[cho] * 21 + JUNG_HASH[jung1]) * 28 + HANGUL_OFFSET);
259 }
260 } else if (step === 3) {
261 jung2 = array[complete_index + step].charCodeAt(0);
262 if (_isJungJoinable(jung1, jung2)) {
263 jung1 = _isJungJoinable(jung1, jung2);
264 } else {
265 jong1 = jung2;
266 }
267 hangul = String.fromCharCode((CHO_HASH[cho] * 21 + JUNG_HASH[jung1]) * 28 + JONG_HASH[jong1] + HANGUL_OFFSET);
268 } else if (step === 4) {
269 jong2 = array[complete_index + step].charCodeAt(0);
270 if (_isJongJoinable(jong1, jong2)) {
271 jong1 = _isJongJoinable(jong1, jong2);
272 } else {
273 jong1 = jong2;
274 }
275 hangul = String.fromCharCode((CHO_HASH[cho] * 21 + JUNG_HASH[jung1]) * 28 + JONG_HASH[jong1] + HANGUL_OFFSET);
276 } else if (step === 5) {
277 jong2 = array[complete_index + step].charCodeAt(0);
278 jong1 = _isJongJoinable(jong1, jong2);
279 hangul = String.fromCharCode((CHO_HASH[cho] * 21 + JUNG_HASH[jung1]) * 28 + JONG_HASH[jong1] + HANGUL_OFFSET);
280 }
281
282 if (complete_index + step >= index) {
283 result.push(hangul);
284 complete_index = index;
285 return;
286 }
287 }
288 }
289
290 for (var i = 0 ; i < length ; i++) {
291 code = array[i].charCodeAt(0);
292 if (!_isCho(code) && !_isJung(code) && !_isJong(code)){ //초, 중, 종성 다 아니면
293 _makeHangul(i-1);
294 _makeHangul(i);
295 stage = 0;
296 continue;
297 }
298 //console.log(stage, array[i]);
299 if (stage == 0) { // 초성이 올 차례
300 if (_isCho(code)) { // 초성이 오면 아무 문제 없다.
301 stage = 1;
302 } else if (_isJung(code)) {
303 // 중성이오면 ㅐ 또는 ㅘ 인것이다. 바로 구분을 못한다. 따라서 특수한 stage인 stage4로 이동
304 stage = 4;
305 }
306 } else if (stage == 1) { //중성이 올 차례
307 if (_isJung(code)) { //중성이 오면 문제없음 진행.
308 stage = 2;
309 } else { //아니고 자음이오면 ㄻ같은 경우가 있고 ㄹㅋ같은 경우가 있다.
310 if (_isJongJoinable(previous_code, code)) {
311 // 합쳐질 수 있다면 ㄻ 같은 경우인데 이 뒤에 모음이 와서 ㄹ마 가 될수도 있고 초성이 올 수도 있다. 따라서 섣불리 완성할 수 없다. 이땐 stage5로 간다.
312 stage = 5;
313 } else { //합쳐질 수 없다면 앞 글자 완성 후 여전히 중성이 올 차례
314 _makeHangul(i-1);
315 }
316 }
317 } else if (stage == 2) { //종성이 올 차례
318 if (_isJong(code)) { //종성이 오면 다음엔 자음 또는 모음이 온다.
319 stage = 3;
320 } else if (_isJung(code)) { //그런데 중성이 오면 앞의 모음과 합칠 수 있는지 본다.
321 if (_isJungJoinable(previous_code, code)) { //합칠 수 있으면 여전히 종성이 올 차례고 그대로 진행
322 } else { // 합칠 수 없다면 오타가 생긴 경우
323 _makeHangul(i-1);
324 stage = 4;
325 }
326 } else { // 받침이 안되는 자음이 오면 ㄸ 같은 이전까지 완성하고 다시시작
327 _makeHangul(i-1);
328 stage = 1;
329 }
330 } else if (stage == 3) { // 종성이 하나 온 상태.
331 if (_isJong(code)) { // 또 종성이면 합칠수 있는지 본다.
332 if (_isJongJoinable(previous_code, code)) { //합칠 수 있으면 계속 진행. 왜냐하면 이번에 온 자음이 다음 글자의 초성이 될 수도 있기 때문
333 } else { //없으면 한글자 완성
334 _makeHangul(i-1);
335 stage = 1; // 이 종성이 초성이 되고 중성부터 시작
336 }
337 } else if (_isCho(code)) { // 초성이면 한글자 완성.
338 _makeHangul(i-1);
339 stage = 1; //이 글자가 초성이되므로 중성부터 시작
340 } else if (_isJung(code)) { // 중성이면 이전 종성은 이 중성과 합쳐지고 앞 글자는 받침이 없다.
341 _makeHangul(i-2);
342 stage = 2;
343 }
344 } else if (stage == 4) { // 중성이 하나 온 상태
345 if (_isJung(code)) { //중성이 온 경우
346 if(_isJungJoinable(previous_code, code)) { //이전 중성과 합쳐질 수 있는 경우
347 _makeHangul(i);
348 stage = 0;
349 } else { //중성이 왔지만 못합치는 경우. ㅒㅗ 같은
350 _makeHangul(i-1);
351 }
352 } else { // 아니면 자음이 온 경우.
353 _makeHangul(i-1);
354 stage = 1;
355 }
356 } else if (stage == 5) { // 초성이 연속해서 두개 온 상태 ㄺ
357 if (_isJung(code)) { //이번에 중성이면 ㄹ가
358 _makeHangul(i-2);
359 stage = 2;
360 } else {
361 _makeHangul(i-1);
362 stage = 1;
363 }
364 }
365 previous_code = code;
366 }
367 _makeHangul(i-1);
368 return result.join('');
369 };
370
371 var search = function(a, b){
372 var ad = disassemble(a).join(''),
373 bd = disassemble(b).join('')
374 ;
375
376 return ad.indexOf(bd);
377 };
378
379 function Searcher(string) {
380 this.string = string;
381 this.disassembled = disassemble(string).join('');
382 }
383
384 Searcher.prototype.search = function(string) {
385 return disassemble(string).join('').indexOf(this.disassembled);
386 };
387
388 var endsWithConsonant = function (string) {
389 if (typeof string === 'object') {
390 string = string.join('');
391 }
392
393 var code = string.charCodeAt(string.length - 1);
394
395 if (_isHangul(code)) { // 완성된 한글이면
396 code -= HANGUL_OFFSET;
397 var jong = code % 28;
398 if (jong > 0) {
399 return true;
400 }
401 } else if ((typeof _isConsonant(code)) !== 'undefined') { //자음이면
402 return true;
403 }
404 return false;
405 };
406
407 var hangul = {
408 disassemble: disassemble,
409 assemble: assemble,
410 search: search,
411 Searcher: Searcher,
412 endsWithConsonant: endsWithConsonant,
413 isHangul: function(c){
414 if (typeof c === 'string')
415 c = c.charCodeAt(0);
416 return _isHangul(c);
417 },
418 isConsonant: function(c){
419 if (typeof c === 'string')
420 c = c.charCodeAt(0);
421 return (typeof _isConsonant(c)) !== 'undefined';
422 },
423 isVowel: function(c){
424 if (typeof c === 'string')
425 c = c.charCodeAt(0);
426 return _isJung(c);
427 },
428 isCho: function(c){
429 if (typeof c === 'string')
430 c = c.charCodeAt(0);
431 return _isCho(c);
432
433 },
434 isJong: function(c){
435 if (typeof c === 'string')
436 c = c.charCodeAt(0);
437 return _isJong(c);
438 }
439 };
440
441 if (typeof define == 'function' && define.amd) {
442 define(function(){
443 return hangul;
444 });
445 } else if (typeof module !== 'undefined') {
446 module.exports = hangul;
447 } else {
448 window.Hangul = hangul;
449 };
450})();
451