Backends PHP

웹개발자를 위한 문자 인코딩 기초 #2 – EUC-KR

한글 문자 인코딩

ASCII는 대소문자를 합하여도 52개 밖에 되지 않는 알파벳을 사용하는 영어를 위한 인코딩 방식이었습니다. 그러나 불행히도 아랍어·한자·한글과 같이 문자 개수가 많은 언어는 1바이트 만으로 표시하기에는 공간이 부족합니다.

이 때문에 2바이트를 사용하여 문자를 표시하는 인코딩 방식들이 언어마다 등장하기 시작하였습니다. 그 중에서 가장 유명한 것이 동아시아 언어들을 위하여 만들어진 Extended Unix Code(약칭 EUC)인데, 94개의 값을 2바이트씩 붙여서 사용하는 방법입니다. 이 방법으로 중국 간체를 위한 EUC-CN, 대만 번체를 위한 EUC-TW, 일본어를 위한 EUC-JP 등이 만들어졌습니다.

이 글에서 소개할 EUC-KR은 EUC 중에서도 한글을 위하여 만들어진 인코딩 방식이며, 현재도 널리 사용되고 있습니다.

EUC-KR의 시각적 구조

EUC-KR Map (출처:위키피디아)

EUC-KR 방식에서 문자를 표시하는 규칙은 크게 2가지로 나누어 설명할 수 있습니다.

첫 번째, 영문과 숫자 및 기본적인 특수문자는 ASCII 코드와 똑같이 0×00~0x7F까지 사용합니다. 단 예외가 하나 있는데, 역슬래시(\) 기호만은 원화 기호(₩)로 바꾸어서 사용합니다. 이렇게 ASCII 코드를 똑같이 사용하면서 역슬래시만 원화 기호로 바꿔서 쓰는 방식을 KS X 1003라고 합니다. 이 글자들은 ASCII 코드와 같이 1바이트만 사용하여서 표시합니다.

두 번째, 한글과 한자 및 추가적인 특수문자들은 2바이트를 연달아서 사용합니다. 단, 이 때 두 개의 바이트는 모두 ASCII 코드와 겹치지 않는 0xA1~0xFE 사이의 값만을 사용합니다. 서로 사용하는 값의 영역이 다르기 때문에, EUC-KR로 작성된 문자열은 중간에 한 바이트를 가져오거나 중간에 한 바이트가 빠져도 그것이 영문·숫자인지 한글인지를 구별해낼 수 있습니다.

 

EUC-KR Table을 통해 확인하는 숫자와 문자 간의 관계

위의 설명으로는 아직까지 감이 오지 않는다면, 역시 예제를 통해 알아보는 것이 가장 빠른 길입니다. EUC-KR에도 ASCII와 마찬가지로 어떤 값이 어떤 문자에 해당하는지 정리해 둔 EUC-KR Table이 있습니다.

2바이트씩 묶어서 한 글자를 표시하기 때문에 다소 복잡해 보이지만, 요령은 ASCII Table과 거의 비슷합니다. 표에서 가장 앞에 있는 a1 a0라는 것은 그 줄에서 가장 왼쪽에 있는 글자의 16진수 코드가 [0xA1 0xA0]라는 것을 의미합니다. 그 다음부터 오른쪽으로 가면서 [0xA1 0xA1], [0xA1 0xA2] 순으로 값이 늘어납니다.

EUC-KR Code 예제

이를테면 “밀가루3g”이라는 단어를 EUC-KR로 표시하기 위해서는, 먼저 첫 번째 글자인 ‘밀’을 EUC-KR Table에서 찾아보아야 합니다. ‘밀’은 b9 d0라고 표시된 줄의 가장 왼쪽에 있으므로 16진수로 [0xB9 0xD0]가 됩니다. 같은 요령으로 ‘가’라는 문자는 b0 a0라고 표시된 줄의 2번째 칸에 있으므로, 16진수로 [0xB0 0xA1]이 됩니다. 또한 ‘루’라는 문자는 b7 e0라고 표시된 줄의 8번째 칸에 있으므로, 16진수로 [0xB7 0xE7]이 됩니다.

그렇다면 3과 g은 어떻게 해야할까요. 앞서 언급하였듯이 EUC-KR에서는 영문과 숫자 및 기본적인 특수문자는 ASCII 코드와 동일하게 사용합니다. 따라서 3과 g은 ASCII Table에서 16진수로 얼마인지를 찾으면 알 수 있습니다.

echo '';
$str = "\xB9\xD0\xB0\xA1\xB7\xE7\x33\x67";
echo $str;		// 밀가루3g

PHP 상에서 이것을 확인해보는 방법은 역시 ASCII 코드와 동일합니다. 위와 같이 16진수 값을 문자열로 만들고 출력해보면, 우리가 EUC-KR Table에서 찾았던 대로 “밀가루3g”이라는 문자열이 나오는 것을 볼 수 있습니다. 다만 웹브라우저가 이 페이지가 EUC-KR인지 알지 못할 수도 있으므로, 첫 번째 라인에 meta 태그로 인코딩이 EUC-KR임을 확실하게 해준 것 뿐입니다.

 

EUC-KR 문자열과 정규표현식

EUC-KR Table을 잘 살펴보셨다면 특수문자, 한글, 한자로 사용하는 영역이 확연하게 구분되어 있다는 사실을 금방 깨달을 수 있습니다. 이 사실을 잘 이용하면 정규표현식을 통해 여러 가지 응용이 가능합니다. 이를테면 아래처럼 인자로 주어진 문자열에서 한글을 제외한 모든 문제를 없애는 함수도 작성할 수 있을 것입니다.

function remainOnlyKor($str)
{
	$reg = '/^[\xB0-\xC8][\xA1-\xFE]/';
	$rts = '';

	while(strlen($str))
	{		
		if(preg_match($reg, $str) === 1)
		{
			$rts .= substr($str, 0, 2);
			$str  = substr($str, 2);
		}
		else
		{
			$str  = substr($str, 1);
		}
	}

	return $rts;
}

$s = '꿈은★이루어진다! 2012 한·일 World Cup';
echo remainOnlyKor($s);		// 꿈은이루어진다한일

정규표현식을 자세히 살펴보시기 바랍니다. EUC-KR Table을 잘 찾아보면 한글의 첫 번째 바이트는 모두 0xB0와 0xC8 사이이고, 두 번째 바이트는 모두 0xA1과 0xFE 사이에 위치하고 있음을 확인할 수 있습니다. 위 함수는 문자열을 앞에서부터 잘라가면서 가장 처음의 2 바이트가 위 16진수 영역 안에 들어가는지를 확인합니다. 만약 그렇다면 $rts 변수에 붙여넣고, 그렇지 않다면 앞의 한 바이트를 잘라내고 다시 반복문을 시작합니다. 결과적으로 이 반복문이 끝나게 되면 $rts 변수에는 한글로 된 문자만 남게 될 것입니다.

(물론 위 함수는 이해하기 쉽도록 반복문으로 작성하였으나, 사실 문자열 연산이 지나치게 자주 이루어진다는 점에서 개선할 여지가 있습니다. 그러나 16진수가 문자로 표시되는 위의 원리를 알고 있다면, 추후에는 unpack 함수 등을 이용하여 여러분 스스로 함수를 충분히 개선할 수 있을 것입니다.)

function strLenEucKr($str)
{
	$regEn = '/^[\x00-\x7F]/';
	$regKr = '/^[\xA1-\xFE][\xA1-\xFE]/';
	$count = 0;

	while(strlen($str))
	{		
		if(preg_match($regEn, $str) === 1)
		{
			$count++;
			$str  = substr($str, 1);
		}
		elseif(preg_match($regKr, $str) === 1)
		{
			$count += 2;
			$str    = substr($str, 2);
		}
		else
		{
			$str  = substr($str, 1);
		}
	}

	return $count++;
}

위의 예제코드는 인자로 주어진 문자열의 글자수를 반환하는 함수입니다. PHP의 내장함수인 strlen이 이미 있는데 왜 이 함수가 필요한가에 대한 의문이 있을 수 있는데, 사실 strlen은 주어진 문자열이 몇 바이트인지를 반환하는 함수입니다. 때문에 한글, 한자 및 특수문자를 2바이트로 표시하는 EUC-KR에서는 strlen 함수를 사용하면 실제 글자수보다 더 큰 숫자가 반환됩니다.

위 함수의 원리도 기본적으로는 앞서 보았던 remainOnlyKor() 함수와 같습니다. 반복문을 돌면서 문자열을 앞에서부터 차근차근 ASCII 코드(0x00~0x7F 사이 값)인지 EUC-KR로 표시된 문자(2바이트 연달아 위치한 0xA1-0xFE 사이 값)인지를 확인한 후, 맞다면 $count는 1씩만 증가시킵니다.

 

EUC-KR의 치명적인 약점 : 한글이 2,350자?!

그런데 EUC-KR에는 한가지 치명적인 약점을 안고 있습니다. 특수문자나 한자를 함께 배치하다보니 자리가 모자라서, 한글을 2,350자 밖에는 할당하지 못한 것입니다. 실제 초성과 중성, 종성을 조합한 한글의 개수는 11,173자에 달하는데, EUC-KR에서는 그 중 자주 사용하는 문자를 제외한 대부분을 생략해버렸습니다.

이 때문에 ‘왜날‘, ‘방각하’, ‘앤롤’ 같이 자주 사용되지 않는 문자들은 EUC-KR에서 입력하면 ?로 표시되는 등 깨지기 일쑤입니다. 실제로 이 굵은 글자들을 EUC-KR Table에서 찾아보면 배정되어 있는 16진수가 없는 것을 알 수 있습니다.

이 때문에 나머지 글자들을 보완한 문자 인코딩 방식이 우후죽순처럼 등장하게 되는데, 그 중에서 현재 사실상 유일하게 사용되고 있는 문자코드는 CP949라고 하는 방식입니다. CP949에 대한 이야기는 다음 글에서 이어집니다.

Leave a Reply

Your email address will not be published. Required fields are marked *