본문 바로가기
Application/Delphi Lecture

한글의 자모 분해

by 현이빈이 2008. 7. 24.
반응형

한글의 자모 분해

목표:

한글을 초성, 중성, 종성으로 분해합니다. 즉 '갈'이라는 글자를 'ㄱ','ㅏ','ㄹ'로 분해 하는 함수를 만들려고 합니다.

변경사항:

[2000/11/8] UniCode 변수 타입이 Integer로 되어 있던 버그를 수정했습니다.

배경지식:

Windows에서 사용되는 한글은 완성형입니다. 완성형의 중요한 특징은 한글이 위치한 완성형 코드값이 각각의 초성, 중성, 종성과 아무런 연관이 없다는 것이죠. 조합형의 경우는 한글자(2바이트)영역 내에서 상위 1비트(MSB)를 제외한 15비트에서 각각 5비트씩을 초성, 중성, 종성의 영역으로 하여 조합이 가능합니다. (그래서 조합형이라고 부르는 것이죠.) 같은 이유로 조합형 글자를 가지고 초성, 중성, 종성을 분해 하는 것도 쉽게 가능합니다. 그러나 완성형의 경우 실생활에서 자주 쓰이는 한글 글자들을 가나다 순으로 나열한 것에 지나지 않기 때문에 직접적으로 조합과 분해가 가능하지 않습니다.

따라서 한글의 자모 분해를 위해서는 완성형 한글을 일단 조합형으로의 변환 후 이를 다시 한글 자모로 분해하는 방법이 좋은 해결 방법이라고 봅니다. 그러나 이 경우 완성형>조합형 변환을 위한 큰 사이즈의 매핑 테이블이 필요하게 되고 프로그래밍 하기에는 조금 귀찮은 일이 될 겁니다. 그렇다면 이러한 매핑 테이블을 이용하지 않고 자모 분해 함수를 구현할 수는 없는걸까요. 물론 방법이 있습니다. Windows 95에서부터 일부 지원하기 시작한 Unicode (WideChar라고도 합니다.)를 이용하는 것이죠.

Unicode란 세계의 수많은 문자셋을 포함하는 2바이트 인덱스 체계를 갖는 거대한 문자 코드셋입니다. Unicode가 표준으로 자리잡음으로서 Windows에서도 내부적으로는 반드시 이 코드를 사용하지 않지만 변환루틴들을 제공하게 된것이죠. (Unicode에 관한 더 자세한 정보는 www.unicode.org 웹 페이지를 방문해 보십시오.) Windows API에서 제공하는 MultiByte <-> WideChar 변환루틴은 다음의 두가지가 있습니다.

int MultiByteToWideChar(

    UINT CodePage,	// code page 
    DWORD dwFlags,	// character-type options 
    LPCSTR lpMultiByteStr,	// address of string to map 
    int cchMultiByte,	// number of characters in string 
    LPWSTR lpWideCharStr,	// address of wide-character buffer 
    int cchWideChar 	// size of buffer 
   );	
 
int WideCharToMultiByte(

    UINT CodePage,	// code page 
    DWORD dwFlags,	// performance and mapping flags 
    LPCWSTR lpWideCharStr,	// address of wide-character string 
    int cchWideChar,	// number of characters in string 
    LPSTR lpMultiByteStr,	// address of buffer for new string 
    int cchMultiByte,	// size of buffer 
    LPCSTR lpDefaultChar,	// address of default for unmappable characters  
    LPBOOL lpUsedDefaultChar 	// address of flag set when default char. used 
   );	

다시 한글의 얘기로 돌아와서 그럼 Unicode에서는 한글을 어떻게 처리할까요. Unicode문자표를 보면 한글은 조합형과 유사한 방식으로 처리됨을 알 수 있습니다. 다음을 보시죠.

 

글자 코드값(헥사) 계산식
AC00h AC00h + (0*21 + 0)*28 + 0
AC01h AC00h + (0*21 + 0)*28 + 1
AC1Ch AC00h + (0*21 + 1)*28 + 0
AE4Ch AC00h + (1*21 + 0)*28 + 0
B7FCh AC00h + (5*21 + 4)*28 + 16

표에 나타났듯이 각각의 한글 글자의 코드값은 계산식 "AC00h + (a*21 + b)*28 + c"에 의해 표현됨을 알 수 있습니다. 여기서 a는 초성의 번호, b는 중성의 번호, c는 종성의 번호를 나타내며 각각의 순서는 다음과 같습니다.

번호:  0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
초성:  ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ
중성:  ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ
종성:    ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ

(종성의 첫번째는 종성이 없는 경우, 예를 들어 '가'와 같은 경우를 나타냅니다.)

코드 값으로 계산식 "AC00h + (a*21 + b)*28 + c"의 a,b,c 값을 구하는 것은 다음과 같은 순서로 하면 됩니다.

c := Code - $AC00;
a := c div (21 * 28);
c := c mod (21 * 28);
b := c div 28;
c := c mod 28;

자, 그러면 이제 프로그래밍 하는 것만 남았군요. :-)

코드:

// HanDivPas, HanDiv
// Programmed by Jounlai Cho (jounlai@yahoo.com)
const
  ChoSungTbl:  PChar = 'ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ';
  JungSungTbl: PChar = 'ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ';
  JongSungTbl: PChar = '  ㄱㄲㄳㄴㄵㄶㄷㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅄㅅㅆㅇㅈㅊㅋㅌㅍㅎ';
  UniCodeHangeulBase = $AC00;
  UniCodeHangeulLast = $D79F;

// function HanDiv: 한글을 자모로 분해
// Han: 변환할 글자, PChar 타입
// Han3: 분해된 자모, PChar 타입
// (Result): 함수 호출 성공(True), 실패(False)
// *주의: 변수 Han과 Han3이 가리키는 메모리 공간은 각각 2, 6바이트가
// 할당되어 있어야 한다. 그리고 Han과 Han3 모두 null-종료 문자열이 아니다.
function HanDiv(const Han: PChar; Han3: PChar): Boolean;
var
  UniCode: Word;
  ChoSung, JungSung, JongSung: Integer;
begin
  Result := False;

  MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, Han, 2, @UniCode, 1);

  if (UniCode < UniCodeHangeulBase) or
     (UniCode > UniCodeHangeulLast) then Exit;

  UniCode := UniCode - UniCodeHangeulBase;
  ChoSung := UniCode div (21 * 28);
  UniCode := UniCode mod (21 * 28);
  JungSung := UniCode div 28;
  UniCode := UniCode mod 28;
  JongSung := UniCode;

  StrLCopy(Han3, ChoSungTbl + ChoSung * 2, 2);
  StrLCopy(Han3 + 2, JungSungTbl + JungSung * 2, 2);
  StrLCopy(Han3 + 4, JongSungTbl + JongSung * 2, 2);

  Result := True;
end;

// function HanDivPas: 한글을 자모로 분해
// Src: 변환할 글자, 예: '갈'
// (Result): 분해된 자모, 예: 'ㄱㅏㄹ', 함수 호출 실패의 경우 ''을 반환한다.
function HanDivPas(const Src: String): String;
var
  Buff: array[0..6] of Char;
begin
  Result := '';
  if Length(Src) = 2 then begin
    if HanDiv(PChar(Src), Buff) then begin
      Buff[6] := #0;
      Result := String(Buff);
    end;
  end;
end;
반응형