한글의 음소분리 문제

한글 문자열의 초/중/종성을 분리하는 예제를 포스팅한 적이 있는데, 이 때는 미처 알지 못했지만, 중요하게 놓친 문제가 있다. 그러니까 초/중/종성에 해당하는 자모와 각각의 낱자가 다른 글자라는 점이다.

예를 들어 ‘한'(U+D55C)자를 보자. 이 글자는 ‘ㅎ’, ‘ㅏ’, ‘ㄴ’ 의 세가지 자모로 분리된다. 이 때 ‘ㅎ’이 초성일 때와 종성일 때에는 같은 ‘ㅎ’으로 보이기는 해도, 같은 코드가 아니다. (그리고 지금 ‘ㅎ’으로 낱자로 쓰고 있는 이 글자 역시 같은 코드가 아니다.

‘ㅎ’을 표현하는 방식에는 다음 세 가지 방식이 있다.

  1. 낱자로서의 ‘ㅎ’ (U+314E)
  2. 초성 자모로서 ‘ㅎ’ (U+1112)
  3. 종성 자모로서 ‘ㅎ’ (U+11C2)

이는 유니코드에서는 완성형과 조합형 한글을 모두 지원하기 때문이다. ‘가’에서부터 ‘힣’에 이르는 자모로 조합가능한 모든 한글글자는 Hangul Syllables 블럭에 정의되어 있다. 그리고 이러한 Syllables를 조합하는데 사용되는 자모들은 모두 Hangul Jamo에 정의된다. 낱자로서의 자모는 Hangul Compatibility Jamo 에 정의되어 있다. 기존의 초성 구하기 코드에서 구하는 답은 자모 코드에 해당한다.

이 자모 코드는 기본적으로 서로 결합하여 완성된 글자(Hangul Syllables)를 구성하는데 쓰인다. 따라서 자모 문자의 코드값으로부터 문자를 얻어서 출력했을 때에는 각 자모 낱자의 출력결과와 구분할 수 없기는 하나 정확한 답이 아닌 것이다.

실질적으로 이 문제는 유니코드를 잘 지원하는 언어일수록 치명적이다. 예를 들어서 “안녕하세요”의 초성 자모만 구하면 “ㅇㄴㅎㅅㅇ”를 기대하게 된다. 그런데 Swift의 경우 같은 로직으로 초성 자모를 구해서 문자(Character)로 변환 후 이들을 모아서 출력하면 “ㅇㄶㅅㅇ”가 출력된다. 자모 ‘ㄴ’과 ‘ㅎ’은 더해지면 ‘ㄶ’이 되는 것이다. 이를 정확하게 출력하려면 Hangul Jamo 의 문자를 Hangul Compatibility Jamo 블럭의 문자로 변환해야 한다.

유감스럽게도 이 두 블럭의 문자 코드는 간단한 수식으로 변환할 수 없다. 한글 자모는 ㄱ, ㄲ, ㄴ, ㄷ, ㄸ … 이런 순서이고 조합형 자모의 경우에는 ㄱ, ㄲ, ㄳ, ㄴ, ㄵ, ㄶ, ㄷ, … 이렇게 되기 때문에 순서가 거의 뒤죽박죽으로 보일 지경이다.

이를 해결하는 방법은 각 자모 낱자의 이름을 활용해서 변환 맵을 만드는 것이다. 유니코드 코드 테이블을 제공하는 페이지들을 검색해보면 각 자모의 이름이 붙어 있다. 예를 들어 Hangul Jamo 블럭의 문자 목록에는 U+1100, ㄱ, Hangul Choseong Kiyeok 이라는 식으로 데이터가 들어있다. 이제 Hangul Jamo 와 Hangul Compatibility Jamo 테이블의 자료를 좍 긁어와서 편집기에서 이런저런 변환을 좀 거쳐서 1100, Kiyeok 을 추출하여 맵을 만든다. 예를 들어 낱자모 ‘ㄱ’ 은 “Hangul Choseong Kiyeok”, “Hangul Jongseong Kiyeok”, “Hangul Letter Kiyeok” 등 다양한 코드가 있는데 이 때 모두 이름에 ‘Kiyeok’이 들어가 있다. 이를 사용해서 자모 ‘ㄱ’에 대해서 그 코드값으로부터 영문이름, 다시 낱자의 코드값을 다음과 같이 0x1100 > ‘Kiyeok’ > 0x3131 찾을 수 있다.

다음 코드는 자모 및 낱자의 이름과 코드를 변환하기 위한 맵을 사전으로 만든 데이터와 이를 활용해서 한글문자열에서 초성을 분리한 다음 낱자로 변환하여 반환하는 함수를 구현한 파이썬 예제이다.