본문 바로가기

3.구현/VC++

텍스트 문서에 대한 처리

텍스트 문서 처리하는 작업이 생각만큼 쉽지 않다.
단순 텍스트라고 해서 예전 ASCII 코드 값만 생각할지 모르겠지만, 다양한 포멧의 텍스트 문서가 있다.
그렇기에 그에 따른 처리도 달라질 수 밖에 없다. 이미지가 JPG인지 GIF인지에 따라 처리를 달리하는 경우와 같다.

작성자: Ospace (ospace114@empal.com) http://ospace.tistory.com

텍스트 문서의 포멧이라고 하면 의아해할 수 있지만, 제가 말하고자하는 부분은 유니코드 관련된 부분이다. 즉, 다양한 인코딩 방식이 있다.

예를 들어

  • 영어권(일반텍스트): ascii
  • 비영어권: Unicode, UTF-8

기타로 다음과 같은 것이 있다. LE와 BE는 Little Endian과 Big Endian 차이이다.

  • UTF-16LE, UTF-16BE, UTF-32LE, UTF32-BE

일반텍스트

일반 텍스트는 ASCII문자를 기반으로 했으며, 영어권 텍스트를 표현하는데 적합하다.
그러나 그외 아시아권 등의 나라에서는 이를 이용해서 표현하는데 한계가 있었다. 과도기적으로 EBCDIC가 있었지만, 이것도 일부만 지원할 뿐 근본적인 해결책이 아니다. 그래서 나타난 텍스트 인코딩 형식이 Unicode와 UTF-8등 이다.

이전에는 UTF-8도 Unicode의 일부라고 생각했었는데 실제로는 아니다.

만약 ASCII 문자로 한글을 입력할 수 없습니다. 다행이 윈도우즈에 노트패드에서는 알아서 한글을 인코딩해서 처리하기 때문에 걱정은 없습니다. 실제 노트패드에서 사용하는 인코딩 방식은 아직 확인하지 못했습니다. ㅡ.ㅡ;

Unicode

일반 유니코드는 한 글자당 2Byte 크기를 고정 크기를 가지며, 전세계의 문자를 65,536개수로 표현할 수 있습니다.
이 방식은 최근 MS Windows인 2000/XP 에서 내부적으로 일반 유니코드를 사용하고 외부적으로 때에 따라서 일반 텍스트를 사용하기도 합니다. 이 방식은 운영체제에서 사용되는 형식으로 웹에서는 적합한 형식이 아닙니다. 운영체제라고 해도 윈도에서 채택하고, 리눅스 등에서는 UTF-8를 사용하고 있습니다. 이 방식은 완성형 글자를 사용하기에 한국어에서 글자 표현에 한계가 있어서 리눅스 진영에서는 기피하고 있는 경향이 있습니다.

UTF-8

UTF-8 유니모드는 한 글자당 차지하는 크기가 글자 종류별로 틀립니다. 영문/숫자/기호는 1Byte, 비영어권 글자는 2Byte 이상의 크기를 가집니다. 이 방식은 웹에서 많이 쓰이는 형식입니다.

위의 세가지 텍스트 형식 중에서 주목할 형식이 이 형식입니다. 왜냐하면 일반 프로그래밍을 하다보면 가장 많이 다루는 형식이기 때문입니다. 그러나 실제로는

실제 텍스트 처리

필자도 현재 텍스트 입출력하던 중에 데이터를 제대로 저장한 후에 다시 읽어서 저장된 데이터를 재확인하는데에서 에러가 발생하더군요. 즉, 저장할때 인코딩 방식과 읽어올 때 인코딩 방식에 차이가 발생한 것입니다.

필자가 경험한 내용을 다시 자세히 설명하면, 다른 개발자가 짜놓은 프로그램을 수정 중에 있었다.
문자열을 제너릭 문자열(일반문자열과 유니코드문자열을 하나의 문자열 타입으로 사용)로 모든 코드를
수정중에 있었다. 기존 코드는 일부 되지 않았던 부분이 있었다.

텍스트 파일 기록 부분에 다음과 같이 되었다.

BOM (봄; Byte Order Mark)은 '바이트 순서 표시'입니다.

유니코드가, little-endian 인지 big-endian 인지 아니면 UTF-8 인지 쉽게 알 수 있도록, 유니코드 파일이 시작되는 첫부분에 보이지 않게, 2~3바이트의 문자열을 추가하는데 이것을 BOM이라고 합니다. 텍스트 에디터 화면에서는 보이지 않고, 헥사 에디터(Hex Editor)로 열었을 때만 보입니다. BOM은 항상 있지는 않기에 주의가 필요하다.

// 출력 파일 생성
if (fOut.Open(m_sbdLogPathname, CFile::modeCreate | CFile::modeWrite) == FALSE) {
  return;
}

위와 같이 했을 경우 텍스트 저장 결과가 다음과 같다.

Unicode_01.PNG

중간에 이상한 문자가 들어가 있다. 이를 바이너리 코드로 봤을 경우는 다음과 같다.

Unicode_00.PNG

자세히 보면 2바이트씩 한글자에 할당된 유니코드이다. 앞의 예제는 유니코드를 고려하지 않았기에 다음과 같이 수정했다.

// 파일 열기 속성 설정
UINT openFlags = CFile::modeCreate | CFile::modeWrite | CFile::typeText;

#ifdef _UNICODE
openFlags |= CStdioFileEx::modeWriteUnicode;
#endif

// 출력 파일 생성
if (m_fOut.Open(m_sbdLogPathname, openFlags) == FALSE) {
    return;
}

위와 같이해서 텍스트 저장결과를 보면 다음과 같다.

Unicode_02.PNG

이번에 제대로 보인다. 무엇인 문제인가? 다시 테스트를 이진모드로 보자.

Unicode_03.PNG

첫번째 문자가 이상해 졌다. 보이지 않았던 코드가 보인다. 이진 코드를 보면 "FF FE" 라는 값이 보인다.

이 값빼고는 앞에서 출력된 결과와는 틀린게 없다. 도대체 문자열에 이상한 값이 왜 들어갔는가?
이를 알기위해서는 BOM(봄; Byte Order Mark)를 알아야 합니다. BOM은 바이트 정렬 방식을 표시해둔 기호정도로 이해하면 됩니다.

그럼 바이트를 정렬 방법이 문제가 됩니다. 앞의 "FF FE" 값의 의미를 알아 봅시다.

  • BOM: FF FE - little-endian
  • BOM: FE FF - big-endian
  • BOM: EF BB BF - UTF-8

바로 첫번째에 있는 little-endian입니다. little-endian과 big-endian는 데이터를 저장할때 각 데이터 저장 순서를 표현합니다. 데이터는 1바이트가 기본크기입니다. 우리가 생각시 데이터 저장시 값 순서대로 저장되었겠지 생각할 수 있습니다. 이 방식이 big-endian입니다.
그러나 실제 데이터 처리시 반드시 big-endian만 있는 것이 아닙니다. 위 결과에서도 little-endian 방식입니다. 예를 들어, 첫번째 문자 "S"를 보면 "53 00"으로 표시되었습니다. 앞의 "53"이 실제 아스키 코드에서 "S" 값입니다. 실제 데이터는 "0053" 값으로 결국 "53" 값이되어 "S" 문자를 표현합니다.
이에 대해 자세한 이야기는 다른 웹페이지를 참조하세요. 더 쉽게 설명한 곳이 많습니다.

앞의 BOM를 보면 마지막에 UTF-8이 이상합니다. UTF-8은 앞에서 유니코드 문자형 중에 하나라고 했는데 바이트 정렬 방식 하나로 나오네요. 이는 바이트 정렬 방식이라 보기보다는 포멧형태로 보는게 타당합니다.
다음은 AcroEditor라는 프로그램에서 UTF-8로 변환해서 저장한 결과입니다.

Unicode_04.PNG

이번에는 한국어를 넣었습니다. 이는 이유가 있습니다. 왜 그런지 생각해보세요.

Unicode_06.PNG

위의 UTF-8을 인지하지 못하여 일반 텍스트로 인식했을 때 문제입니다. 영어는 이상없는데 한글은 깨져서 보이네요. 한번 바이너리 코드를 보도록 하겠습니다.

Unicode_05.PNG

앞에서 BOM을 봤으니깐 UTF-8이니 "EF BB BF" 값이 올줄 알았는데 전혀 없습니다. 바로 텍스트 내용이 나오고 있습니다. 이는 "Total Commander"에 파일 보기 기능을 이용한 것인데, "Total Commander"에서는 앞에 BOM이 없으니 일반 텍스트로 인식한 것입니다. 이건 프로그램 마다 차이가 있을 것입니다. 그러나 AcroEditor에서는 이상없이 잘 보입니다. 이런 것은 보통 UTF-8은 전에 부터 앞에 BOM이 없이 사용해왔습니다. 오히려 BOM을 붙이면 예전 프로그램에서 이 값을 인식하지 못해서 문제가 발생할 가능성이 높습니다.

앞에 텍스트 예제에서 한글이 들어간 것은 내부에 3Byte 값이 들어 있음을 보여주기 위해서 입니다. BOM이 필요 없다고 느껴질지 모르겠는데 붙여주는 것이 가장 좋습니다. 영어와 숫자가 있는 문서는 일반 텍스트인지 UTF-8인지 구분하기 힘들기 때문이죠.
그러나 참조 사이트에서는 웹 상에 올릴때에는 HTML, CSS, XML 파일을 작성하는 경우는 BOM이 없는 UTF-8를 사용하라고 권하고 있습니다. 있는데 더 문제가 발생할 가능성이 있다고 하네요. 이런 이유는 텍스트를 처리하는 로직에서 UTF-8의 BOM 값을 인지하지 못하기 때문이 아닐까 생각합니다. 앞으로는 이부분이 수정이 필요할 것 같네요. 컴퓨터에서 애매모호한 구분은 절대로 있을 수 없으니깐요. 그렇지 않다면, 시스템 내부적으로 문제 발생 요소를 많이 가지게 되기 때문입니다.

이야기가 조금 길어졌네요. 다시 본론으로 들어와서 CStdioFileEx를 이용해서 유니코드로 잘 저장했습니다.
그러나, 텍스트를 불러오는 부분에서 문제가 발생했습니다.

TCHAR strBuf[1024];

// 파일 열기
FILE *fp = _tfopen(_T("파일이름"), _T("rb"));
_fgetts(strBf, 1024, fp); // 문자열 획득

앞에서 유니코드로 저장된 문자을 읽었을 경우, 텍스트 처음에 있는 BOM도 같이 불러오게 된다. 그래서 데이터 값이 서로 어긋나게 되어 제대로 처리할 수 없게 되었다.
혹시나 해서 바이너리 형태로 읽기 때문이라고 생각해서 텍스트 형태로 읽었다. 그러나 이번에는 원래 "SBD"값이 아닌 원지않았던 한 글자를 읽어왔다. _tfopen에서도 텍스트 문자열 처리가 완벽하지 않다는 결과를 보여준다.

그렇기에 앞에서 CStdioFileEx를 이용했기에 CStdioFile로 읽어오면 되겠다고 생각해서 다시 변경해서.

CStdioFile readFile(_T("파일명"), CFile::typeText | CFile::modeRead);
readFile.ReadString(strBuf, 1024);

다시 유니코드를 읽어 오니 마찬가지로 이상한 값만 불러오게 된다. 약간의 공황상태...
인터넷 검색을 해서 이것저것 찾아보다가 CStdioFileEx관련 덧글[4]이 올라온 사이트를 찾게 되었다.
_tfopen과 CStdioFile는 유니코드를 다룰 수 없다(ㅡ.ㅡㅋ). 이런 된장. 욕이 나오는 군요.
항상 어려운 고초를 격어야 이런 것을 알 수 있는 것인지...
결국 그곳에서도 유니코드를 다룰려면 CStdioFileEx를 사용하라고 합니다. 그리고 CStdioFileEx는 윈도우 기본 제공이 아닙니다. 받아서 설치해서 사용해야되죠. ㅡ.ㅡ; CStdioFileEx는 CodeProject에서 받을 수 있습니다.[5]

그렇게 해서 다시 코딩을 하면

CStdioFileEx readFile(_T("파일명"), CFile::typeText | CFile::modeRead);
CString strBuf;
readFile.ReadString(strBuf);

약간 코드가 바뀌었습니다. ReadString() 형식이 CString형 인자로 사용하게 변경되었네요.
이렇게 해서 텍스트를 불러오니 아주 잘 읽습니다. 이 결론을 얻으려고 앞에 수많은 글을 적었네요.

혹시나 해서 적는 글인데, 앞에서 CStdioFileEx에 열기 플래그에서 CStdioFileEx::modeWriteUnicode"를 빼고 처리하면 잘된다고 생각할 수 있는데, 이건 착각입니다. 한글이 들어가면 해당 문자를 기록이 되지 않는 문제가 발생합니다. 잘못하면 저장되는 도중에 데이터가 사라질 수 있습니다. 그렇기에 한글이 들어가면 반드시 유니코드 형식으로 텍스트를 저장해야됩니다.

결국 텍스트 형식이 있고 그안에 그 형식을 구분하기 위한 값이 있으며, 예전 프로그램에서는 이를 처리할 수 없으며, 이를 처리가 가능한 프로그램을 사용해야 한다라는 결론을 얻게되었네요.

추가로 UTF-8에 대해서 더 이야기하면, UTF-8은 기호와 영문자는 1Byte이고 한글등 영어권을 제외한 나라에서는 한글자당 3Byte의 글자를 사용하게 된다. 그러나 이것은 정확히 3Byte는 아니다.

덧글:
간혹 제가 잘못 이해하여 틀린 부분은 메일이나 덧글로 올려주세요. 꼭~

참조

[1]유니코드의 BOM (http://mwultong.blogspot.com/2006/05/qna-unicode-bom-byte-order-mark.html)
[2]일반 유니코드(Unicode)와 'UTF-8 유니코드'의 차이점(http://mwultong.blogspot.com/2006/05/unicode-utf-8.html)
[3]'리틀 엔디안 유니코드'와 '빅 엔디안 유니코드'의 차이점(http://mwultong.blogspot.com/2006/05/little-endian-big-endian.html)
[4]CStdioFileEx관련 대화내용 (http://www.eggheadcafe.com/software/aspnet/30163916/cstdiofile-readline.aspx)
[5]CStdioFile-derived class for multibyte and Unicode reading and writing (http://www.codeproject.com/file/stdiofileex.asp)
[6]Unicode를 네트웍상에 송수신에 대한 대화(http://kldp.org/node/28659)

반응형