[gksdudConverter] 한영타 변환기 개발기록 3 : 한글 -> 영어 타자 변환하기, javascript 한글 자소 분리하기
우선 issue를 발행하고 todo에 있는 것들을 소 issue로 발행해 순차적으로 해결해야겠다.
1. 한글 자소 분리
[(초성) × 588 + (중성) × 28 + (종성)] + 44032(=0xAC00, "가"에 해당하는 코드 값)
한글에 대한 유니코드 공식은 얻었으나, 각 자리에 대한 값을 얻기위한 공식을 찾는 것이 조금 귀찮았다...^^ 우리에겐 구교수님이 계시니 바로 검색해봄.
역시나 세상엔 천재들이 많다!! -> 공식 참고한 곳
이제 한글을 받아오면 자소를 분리하는 공식을 얻었으니 이를 적용해보자.
// 첫 문자인 "가"에 해당하는 값
const korGAUnicode = 0xAC00;
// 초성, 중성, 종성 시작 값
const [firstInitial, firstMedial, firstFinal] = [0x1100, 0x1161, 0x11A7];
// 초성, 중성, 종성 개수
const [initialCount, medialCount, finalCount] = [18, 21, 28];
우선 자소 분리 시 필요한 값을 선언해놓았다.
// 첫 문자인 "가"에 해당하는 값
const korGAUnicode = 0xAC00;
// 초성, 중성, 종성 시작 값
const [firstInitial, firstMedial, firstFinal] = [0x1100, 0x1161, 0x11A7];
// 초성, 중성, 종성 개수
const [initialCount, medialCount, finalCount] = [18, 21, 28];
/**
* 글자에 해당하는 unicode 값을 구한다.
*
* @since 2023-01-29
* @param {String} letter unicode로 변환할 글자
* @returns {number} unicode
*/
const getUnicode = (letter) => {
if (letter.length !== 1) {
throw new Error("매개 변수는 한 글자여야 합니다.");
}
return letter.codePointAt(0);
}
/**
* unicode가 가지고 있는 초성이 jomo block에서 몇 번째 글자인지 구한다.
*
* @since 2023-01-29
* @param {Number} unicode 초성 순서 값을 구할 글자 unicode
* @returns {number} 초성 순서(순서는 Hangul Jamo Unicode block 순서에 의거함)
* @link https://en.wikipedia.org/wiki/Hangul_Jamo_(Unicode_block)
*/
const getInitialIndex = (unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
return Math.floor((unicode - korGAUnicode) / (medialCount * finalCount));
}
/**
* unicode가 가지고 있는 중성이 jomo block에서 몇 번째 글자인지 구한다.
*
* @since 2023-01-29
* @param {Number} unicode 중성 순서 값을 구할 글자 unicode
* @returns {number} 중성 순서(순서는 Hangul Jamo Unicode block 순서에 의거함)
* @link https://en.wikipedia.org/wiki/Hangul_Jamo_(Unicode_block)
*/
const getMedialIndex = (unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
return Math.floor((unicode - korGAUnicode) / finalCount % medialCount);
}
/**
* unicode가 가지고 있는 종성이 jomo block에서 몇 번째 글자인지 구한다.
*
* @since 2023-01-29
* @param {Number} unicode 종성 순서 값을 구할 글자 unicode
* @returns {number} 종성 순서(순서는 Hangul Jamo Unicode block 순서에 의거함)
* @link https://en.wikipedia.org/wiki/Hangul_Jamo_(Unicode_block)
*/
const getFinalIndex = (unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
return Math.floor((unicode - korGAUnicode) % finalCount);
}
초성, 중성, 종성 값을 구하는 함수이다.
글자 별로 unicode 값을 구한 뒤, 그 값으로 자소 분리를 할 수 있게끔 하였다.
초성,중성,종성 값을 분리하는 함수를 개별로 작성할 지, 함수를 하나로 두고 매개변수로 어떤 값을 구하는 것인지 판별할 지에 대해서 고민을 했다.
// 예시 코드
const getGraphemeIndex = (direction, unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
switch (direction) {
case "initial":
return Math.floor((unicode - korGAUnicode) / (medialCount * finalCount));
case "medial":
return Math.floor((unicode - korGAUnicode) / finalCount % medialCount);
case "final":
return Math.floor((unicode - korGAUnicode) % finalCount);
default:
throw new Error("잘못된 요청입니다.");
}
}
매개 변수로 판별 후 로직을 진행하는 함수의 예시이다.
direction 값에 대한 유효성 검사를 추가해야할뿐더러, 어떤 값을 넣어줘야하는지 한번에 알기 어렵다.
뭐 객체 freeze()를 이용해 Enum을 만들면 가능할지도 모르지만 개인적으로 이 함수는 그렇게까지 할 필요는 없을 것 같아서 함수를 각각 분리해줬다.
글을 적으면서 생각했는데, 두개의 방법을 합쳐도 될 것 같다.
getGraphemeIndex 함수를 객체로 선언 후 key 값으로 초성, 중성, 종성을 넣어주고, 그에 해당하는 함수를 value에 넣어주면 추후에 관리하기도 편할 듯?
리팩토링 해봐야겠다.
우선 결과부터 보자!!
<script>
window.addEventListener('DOMContentLoaded', function() {
console.log(getGraphemeList("한글 자소 분리12ab"));
});
</script>
한글만 초,중,종성으로 분리되었다.
한글이 아니면 한글 여부를 false로 한 뒤 초성 값에 문자를 그대로 넣어주었다.
"use strict";
/* 구현 순서
1. 단어를 한 글자씩 쪼갠다.
2. 쪼갠 글자를 유니코드로 변환한다.
3. 유니코드에서 초성~종성의 값을 구한다.
*/
// 첫 문자인 "가"에 해당하는 값
const korGAUnicode = 0xAC00;
// 초성, 중성, 종성 시작 값
const [firstInitial, firstMedial, firstFinal] = [0x1100, 0x1161, 0x11A7];
// 초성, 중성, 종성 개수
const [initialCount, medialCount, finalCount] = [18, 21, 28];
/**
* 한국어 자소 분리를 진행한다.
*
* param을 한 글자씩 쪼개어 한국어일 경우에만 자소 분리를 수행한다.
*
* @since 2023-01-29
* @param {String} word 자소 분리를 진행할 한국어
* @returns {Object[]} {isKorean: Boolean, initial: String, medial: String||null, final: String||null}
*/
const getGraphemeList = (word) => {
if (word.length < 1) {
throw new Error("매개 변수는 한 글자이상이어야 합니다.");
}
return splitWord(word).reduce((arr, letter) => {
if (letter.match(/[ㄱ-ㅎ|가-힣]/g)) { // 한글일 때만 자소 분리
let {initial, medial, final} = splitGrapheme(getUnicode(letter));
arr.push({
isKorean: true,
initial: getInitialLetter(initial),
medial: getMedialLetter(medial),
final: getFinalLetter(final)
});
} else {
arr.push({
isKorean: false,
initial: letter
});
}
return arr;
}, []);
}
/**
* unicode에 해당하는 한글을 반환한다.
*
* @since 2023-01-29
* @param {Number} unicode 한글로 변환할 unicode
* @returns {string} 한글
*/
const getLetterFromUnicode = (unicode) => {
return String.fromCharCode(unicode);
}
/**
* unicode에 해당하는 초성 값을 반환한다.
*
* @since 2023-01-29
* @param {Number} unicode 한글로 변환할 초성 unicode
* @returns {string} 한글(초성)
*/
const getInitialLetter = (unicode) => {
return getLetterFromUnicode(unicode + firstInitial);
}
/**
* unicode에 해당하는 중성 값을 반환한다.
*
* @since 2023-01-29
* @param {Number} unicode 한글로 변환할 중성 unicode
* @returns {string} 한글(중성)
*/
const getMedialLetter = (unicode) => {
return getLetterFromUnicode(unicode + firstMedial);
}
/**
* unicode에 해당하는 종성 값을 반환한다.
*
* @since 2023-01-29
* @param {Number} unicode 한글로 변환할 종성 unicode
* @returns {string} 한글(종성)
*/
const getFinalLetter = (unicode) => {
return getLetterFromUnicode(unicode + firstFinal);
}
/**
* 단어를 한 글자씩 쪼개어 배열로 반환한다.
*
* @since 2023-01-29
* @param {String} word 쪼갤 단어
* @returns {String[]} 한 글자씩 쪼개진 결과
*/
const splitWord = (word) => {
return [...word];
}
/**
* unicode에 해당하는 글자를 자소 분리한다.
*
* @since 2023-01-29
* @param {Number} unicode 자소 분리할 글자
* @returns {Object} {initial, medial, final} 자소 분리 결과
*/
const splitGrapheme = (unicode) => {
let initial = getInitialIndex(unicode);
let medial = getMedialIndex(unicode);
let final = getFinalIndex(unicode);
return {initial, medial, final};
}
/**
* 글자에 해당하는 unicode 값을 구한다.
*
* @since 2023-01-29
* @param {String} letter unicode로 변환할 글자
* @returns {number} unicode
*/
const getUnicode = (letter) => {
if (letter.length !== 1) {
throw new Error("매개 변수는 한 글자여야 합니다.");
}
return letter.codePointAt(0);
}
/**
* unicode가 가지고 있는 초성이 jomo block에서 몇 번째 글자인지 구한다.
*
* @since 2023-01-29
* @param {Number} unicode 초성 순서 값을 구할 글자 unicode
* @returns {number} 초성 순서(순서는 Hangul Jamo Unicode block 순서에 의거함)
* @link https://en.wikipedia.org/wiki/Hangul_Jamo_(Unicode_block)
*/
const getInitialIndex = (unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
return Math.floor((unicode - korGAUnicode) / (medialCount * finalCount));
}
/**
* unicode가 가지고 있는 중성이 jomo block에서 몇 번째 글자인지 구한다.
*
* @since 2023-01-29
* @param {Number} unicode 중성 순서 값을 구할 글자 unicode
* @returns {number} 중성 순서(순서는 Hangul Jamo Unicode block 순서에 의거함)
* @link https://en.wikipedia.org/wiki/Hangul_Jamo_(Unicode_block)
*/
const getMedialIndex = (unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
return Math.floor((unicode - korGAUnicode) / finalCount % medialCount);
}
/**
* unicode가 가지고 있는 종성이 jomo block에서 몇 번째 글자인지 구한다.
*
* @since 2023-01-29
* @param {Number} unicode 종성 순서 값을 구할 글자 unicode
* @returns {number} 종성 순서(순서는 Hangul Jamo Unicode block 순서에 의거함)
* @link https://en.wikipedia.org/wiki/Hangul_Jamo_(Unicode_block)
*/
const getFinalIndex = (unicode) => {
if (typeof unicode !== "number") {
throw new Error("unicode는 숫자여야합니다.");
}
return Math.floor((unicode - korGAUnicode) % finalCount);
}
[gksdudConverter] 한영타 변환기 개발기록 4: 확장앱 용 프로젝트 설계를 다시 하다. (0) | 2023.01.30 |
---|---|
[gksdudConverter] 한영타 변환기 개발기록 2 : 구현 방법 모색하기 (2) | 2023.01.29 |
[gksdudConverter] 한영타 변환기 개발기록 1 : 개발 이유와 구현 방법 떠올리기 (0) | 2023.01.29 |