본문 바로가기

trouble-shooting

유니코드 정규화 문제 (NFC, NFD)

db에 있는 텍스트를 파일로 저장하는 기능을 개발했다. 그런데 파일의 텍스트가 자모음 분리가 되는 것 아닌가.

이유는 유니코드 정규화와 관련된 문제였다. 

 

해결방법부터 보자면 아래와 같다.

import unicodedata

text = '...'
nfc_text = unicodedata.normalize('NFC', text)

이렇게 NFC 정규화를 해준 후 저장하면 문제가 없다.

 

그럼 유니코드 정규화란 뭔지 보겠다.

우선 유니코드는 세상의 모든 문자에 대해 숫자를 대응시키는 규격이라고 보면 된다. python에서는 ord함수로 간단히 유니코드를 확인할 수 있다.

text = '가'
unicode = ord(text) #44032

보시다시피 '가' 라는 글자에 숫자가 배정돼있다. 그럼 'ㄱ' 과 'ㅏ'는 어떻게 표현될까. 물론 각각의 자모음에도 서로 다른 유니코드가 배정된다.

ord('ㄱ') #12593
ord('ㅏ') #12623

 

그럼 'ㄱ' 과 'ㅏ' 를 입력하면 컴퓨터에서는 어떻게 처리할까. 이것에 대한 해답이 유니코드 정규화다. NFC정규화는 'ㄱ' 과 'ㅏ'를 합친 '가' 라는 완성형 문자에 대한 유니코드를 할당한다. 반면에 NFD정규화는 'ㄱ' 따로 'ㅏ' 따로 저장하게 된다. 참고로 NFD에서 'ㄱ', 'ㅏ' 는 각각 NFC에서와 다른 유니코드를 갖는다. 완성형이 아닌 조합형이기 때문이다.

 

좀 더 자세히 설명하면 위의 예시에서 'ㄱ'은 받침이 아닌 모음과 결합되는 'ㄱ'이고 'ㅏ'는 단독으로 쓰이지 않은 자음과 결합된 'ㅏ' 이다. 코드로 직접 확인해보자.

import unicodedata as ud

def print_ord(text):
    for c in text:
        print(ord(c), end=' ')
    print()

def compare(text):
    n_list = ['NFC', 'NFD']
    for n_type in n_list:
        n_text = ud.normalize(n_type, text)
        print(n_type, end=': ')
        print_ord(n_text)

text = '가'
compare(text)

# 실행결과
# NFC: 44032 
# NFD: 4352 4449

 

위의 함수를 그대로 사용하여 '응' 을 확인해보자 NFD 결과를 보면 위의 자음 'ㅇ' 과 받침 자음 'ㅇ' 의 유니코드가 다름을 확인할 수 있다.

...
compare('응')

# 실행결과
# NFC: 51025 
# NFD: 4363 4467 4540