일모도원(日暮途遠) 개발자
[문자] 한글 자음 모음이 분리되는 현상과 NFD/NFC 본문
이문제는 보통 Mac과 Window간의 파일을 공유할때 파일명에서 많이 발생한다.
난 TMDB라는 영화싸이트의 API에 영화제목을 URL인코딩하여 넘겨주고 영화정보를 받아올때 이문제가 발생했다.
영화제목에 한글이 들어있으면 영화정보를 못 받아왔다.
이는 한글 URL인코딩이 제대로 되지 않아서 그렇다. NFC로 정규화해서 보내야 하는데 NFD로 보내서 그렇다.
Mac OS 에서는 "기생충.srt" 라고 이름을 지으면, 내부적으로 "ㄱㅣㅅㅐᆼㅊㅜᆼ.srt" 로 한글을 자음과 모음을 분리한 유니코드로 저장해 놓고 이것을 보여줄 때 "기생충.srt" 이라고 조합해서 보여준다.
반면 Windows 에서는 "기생충.srt"이라고 파일명을 지으면 실제로 "기생충.srt"으로 조합된 글자의 유니코드를 저장한다.
즉 Mac OS 에서는 NFD(Normalization Form Canonical Decomposition, 정준 분해) 방식을 사용하고,
Windows 에서는 NFC(Normalization Form Canonical Composition, 정준 결합 ) 방식을 사용한다.
Canonical이란 단어는 낯선 단어이다. 사전을 보면 "정본(定本)에 속하는, 고전으로 여겨지는, 표준/기준이 되는"의 뜻이 나온다. 챗GPT에 물어보니 "canonical" refers to a standardized or normalized form of representing characters"라고 한다. 즉 문자를 표현하는 표준화(標準化, standardized) 또는 정규화(正規化, normalized)된 형식이라는 뜻이다.
정준(定準)은 정(定)해진 표준(標準)이라는 뜻이다.
정준분해(定準分解)란 글자를 풀어서 저장한다는 뜻이고, 정준 결합(結合)은 글자를 결합된 상태로 저장한는 뜻이다.
"기" 라는 글자가 있을때, "ㄱ(기역) + ㅣ(이)"로 풀어서 저장하면 정준분해(NFD)고, "기" 한글자로 저장하면 정준결합(NFC)이다.
이는 한국어 뿐만 아니라 여러 언어에서도 나타날수 있는데, café의 é는 e + ́ 로 분해할수도 있다.
난 Mac을 사용하고 있으니 파일시스템은 NFD(정준 분해)를 사용하고 있는것이다.
"기생충"을 예를 들어보자.
https://unicode-table.com/ 싸이트에서 "기"를 검색해보면 아래처럼 나온다.
https://unicode-table.com/en/AE30/
"기"의 유니코드 값은 U+AE30이고 이를 UTF-8로 인코딩하면 EA B8 B0이 된다.
즉 "기생충"를 NFC로 나타내면 %EA%B8%B0%EC%83%9D%EC%B6%A9 이다
기 : %EA%B8%B0
생 : %EC%83%9D
충 : %EC%B6%A9
"ㄱ"으로 위 사이트에서 검색하면 한글 레터 기역 U+3131이 나오는데 이건 아니다. 왜 한글 레터만 검색되는지는 모르겠다.
한글 자모는 https://www.unicode.org/charts/ 에서 볼수 있는데, 한글 자모 테이블에서 보면 "ㄱ"은 1100이다.
https://unicode-table.com/en/1100/ 에서 보면 아래처럼 나온다.
"기생충"를 NFD로 나타내면
"ㄱ"의 유니코드 값은 U+1100이고 UTF-8로 인코딩한 값은 E1 84 80이다.
"ㅣ"의 유니코드 값은 U+1100이다 UTF-8로 인코딩한 값은 E1 85 B5이다.
"기"의 유니코드 값은 U+AE30이고 이를 UTF-8로 인코딩하면 EA B8 B0이 된다.
즉 기생충은 아래와 같다.
%E1%84%80%E1%85%B5%E1%84%89%E1%85%A2%E1%86%BC%E1%84%8E%E1%85%AE%E1%86%BC
하나씩 보면 이렇다.
ㄱ : %E1%84%80
ㅣ : %E1%85%B5
ㅅ : %E1%84%89
ㅐ : %E1%85%A2
ᆼ : %E1%86%BC
ㅊ : %E1%84%8E
ㅜ : %E1%85%AE
ᆼ : %E1%86%BC
즉 "기"를 NFC로 나타내면 %EA%B8%B0이고 NFD로는 %E1%84%80%E1%85%B5다.
기 : %EA%B8%B0
ㄱ : %E1%84%80
ㅣ : 80%E1%85%B5
아래 URL인코딩 싸이트로 가서 확인해보자.
http://www.hipenpal.com/tool/url-encode-and-decode-in-korean.php
TMDB API는 NFC로 변환하여 URL인코딩에 사용해야 한다.
스위프트와 자바에서는 아래와 같은 방법으로 처리하면 된다.
XCode (맥)
파일명에서 읽어온 "기생충"은 NFD고 XCode에서 하드코딩으로 "기생충"을 입력하면 NFC다.
NFC로 변환하기 위해서는 아래중 하나를 사용하면 된다.
precomposedStringWithCanonicalMapping (문자열을 NFC로 정규화함)
precomposedStringWithCompatibilityMapping (문자열을 NFKC로 정규화함)
만약 NFD로 변환하고 싶으면 아래중 하나를 사용하자.
decomposedStringWithCanonicalMapping (문자열을 NFD로 정규화함)
decomposedStringWithCompatibilityMapping (문자열을 NFKD로 정규화함)
let title = "기생충"
let NFC = title.precomposedStringWithCanonicalMapping
let NFKC = title.precomposedStringWithCompatibilityMapping
let NFD = title.decomposedStringWithCanonicalMapping
let NFKD = title.decomposedStringWithCompatibilityMapping
Android (맥, Java)
Normalizer클래스를 이용한다.
Normalizer.normalize(정규화할 문자열, Normalizer.Form.NFC); //NFC로 변환한다.
String movieName = "기생충";
String movieNameNFC = Normalizer.normalize(movieName, Normalizer.Form.NFC);
String movieNameNFD = Normalizer.normalize(movieName, Normalizer.Form.NFD);