UNPKG

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