개요
이 글은 컴퓨터 세상과 javascript에서 한글이 다뤄지는 방식을 알아봅니다. 오로지 대소문자 52자와 공백문자, 문장부호만 있으면 되는 영어와는 달리 한글은 자음과 모음의 조합으로 이루어져 있습니다. 한글은 그래서, 완성형과 조합형 두 가지 형태로 저장됩니다.
'가' 부터 '힣' 까지의 11,172 자의 글자가 U+ AC00 ~ D7FF의 유니코드 공간을 할당받아 점유 중이고, 초성 'ㄱ'~'ㅎ' 와 모음(중성)과 종성이 U+ 1100 ~ 11FF의 공간을 점유중입니다. 이외에도 다른 한글관련 유니코드들이 있습니다.
조합형 글자들의 Normalization
문자의 Equivalence
이런 조합의 문제는 한글만이 겪는 것이 아닙니다. "가" 와 "ㄱ"+"ㅏ"가 동등한 것과 같은 성질을 동등성(Equivalency)이라 부르는데요. 유니코드 표준에서는 아래의 두 가지 동등성을 고민합니다.
첫번째, Canonical Equivalence 입니다. 엄밀히 똑같이 두 글자가 동등함을 의미합니다.
두번째. Compatibility Equivalence입니다. Canoncial Equivalence에 비해 엄격하지는 않으나, 두 글자가 호환되어 동등하게 취급될 수 있음을 의미합니다. 주로 오른쪽의 기본 문자들이 비주얼적인 표현을 위해 왼쪽의 모습으로 변경된 형태임을 확인할 수 있습니다.
Canonical에서는 화살표가 양방향이지만, Compatibility에서는 단방향입니다. 따라서 우리는 세 가지 변환을 찾아볼 수 있습니다.
- Canonical Decomposition(분해)
- Canonical Composition(조합)
- Compatibility Decomposition(분해)
Normalization Form
우리는 이 변환들을 Normalization이라 부르고, Normalization의 결과물들 중 4가지의 Normalization Form 을 유니코드 문자를 저장하는 표준으로 사용합니다.
NFD, NFC, NFKD, NFKC 이렇게 4가지의 변환이 있습니다. 더 이상 들어가면 복잡하니, 아래의 표를 참고하는 것으로 마무리하겠습니다.
맥북과 윈도우 간 파일을 주고 받을 때, 한글 파일명이 깨지는 경험을 하신적이 있으실 수 있는데요. macOS에서는 주로 NFD로, Linux/GNU 시스템과 Windows에서는 주로 NFC로 한글을 저장하기 때문입니다.
javascript 에서의 작동 예시
javascript에서 위 내용들을 확인해볼 수 있습니다. 크롬이시라면 F12에 콘솔을 열어 바로 확인해보세요.
"안녕".normalize("NFC").length
// 2
"안녕".normalize("NFD").length
// 6
"안녕".normalize("NFKC").length
// 2
"안녕".normalize("NFKD").length
// 6
[..."안녕"]
// (2) ['안', '녕']
[..."안녕".normalize("NFC")]
// (2) ['안', '녕']
[..."안녕".normalize("NFD")]
// (6) ['ᄋ', 'ᅡ', 'ᆫ', 'ᄂ', 'ᅧ', 'ᆼ']
[..."안녕".normalize("NFKC")]
// (2) ['안', '녕']
[..."안녕".normalize("NFKD")]
// (6) ['ᄋ', 'ᅡ', 'ᆫ', 'ᄂ', 'ᅧ', 'ᆼ']
이렇게 한글을 완성형과 조합형으로 분리해볼 수 있습니다. 이 때, 주의해야 할 점은 "안"의 받침 "ㄴ"과 "녕"의 초성 "ㄴ"은 다른 글자라는 것입니다.
[..."안녕".normalize("NFD")][3] === [..."안녕".normalize("NFD")][4]
//false
[..."안녕".normalize("NFD")][3].charCodeAt()
// 4354 -> U+1162
[..."안녕".normalize("NFD")][4].charCodeAt()
// 4455 -> U+1167
//유니코드는 16진법
charCodeAt() 함수를 통해 확인한 유니코드 키값이 다른것을 확인 가능합니다.
"쿠키".includes("ㅋ")
// false
"쿠키".includes("쿠")
// true
"쿠키".includes("쿸")
// false
기능을 개발하다보면 "ㅋ"로 "쿠키"라는 단어를 서치하고 싶을 수 있습니다. 일반적으로 "쿠키".includes("ㅋ")의 값은 False 입니다.
"쿠키".normalize("NFKD").includes("ㅋ".normalize("NFKD"))
// true
"쿠키".normalize("NFKD").includes("쿠".normalize("NFKD"))
// true
"쿠키".normalize("NFKD").includes("쿸".normalize("NFKD"))
//false
글자들을 NFKD로 변환함으로써 우리가 원하는 기능을 구현할 수 있습니다. 아쉬운 점은 "쿸"으로 "쿠키"를 탐색할 수는 없다는 점이네요. "키"의 "ㅋ"은 초성이지만, "쿸"의 "ㅋ"는 받침이라 유니코드 키값이 다르기 때문입니다.
"쿠키".normalize("NFKD").includes("ㅋ".normalize("NFKD"))
// true
"쿠키".normalize("NFD").includes("ㅋ".normalize("NFD"))
// false
주의해야 할점은 NFD로 변환해서는 우리가 원하는 결과를 얻지 못한다는 것인데요.
[..."쿠키".normalize("NFD")].map(i => i.charCodeAt().toString(16))
// (4) ['110f', '116e', '110f', '1175']
"ㅋ".normalize("NFD").charCodeAt().toString(16)
// '314b'
"ㅋ".normalize("NFKD").charCodeAt().toString(16)
// '110f'
NFD로 변환했을 때에는 우리가 조합형 한글을 쓸때와는 다른 영역의 유니코드 키값으로 변환되기 때문입니다.
toString(16) 은 해당 값을 16진법으로 변환해주는 함수입니다.
그리고 javascript에서 한글을 쓰려는 라이브러리가 하나 있어 소개하고 글을 마칩니다.
'개발이야기' 카테고리의 다른 글
Node.js에서 csv 파일 다루기 및 ios-윈도우 간 한글 깨짐 문제 해결 (1) | 2024.01.06 |
---|---|
타입스크립트: ts2322 error 해결을 위한 서브타입 관련 개념 총정리 (1) | 2023.11.26 |
HTTP의 역사: 0.9부터 3.0까지 (1) | 2023.09.14 |
데코레이터(Decorator) (1) | 2023.09.05 |
Artillery와 함께, 웹 부하테스트 빠르게 익히기 (0) | 2023.08.12 |