. 포맷 분석

이 장에서는 고급 파일 관리 기능을 작성한다. 다양한 텍스트 포맷 지원 및 네트워크, 파일 변경 감시 기능을 작성할 것이며 마지막으로 편집 대상 파일들을 그룹화할 수 있는 프로젝트 기능도 작성할 것이다. Dangeun9 프로젝트를 복사하여 Dangeun10 프로젝트를 만들고 실습을 시작하도록 하자. 많은 양의 코드를 작성하게 되지만 양만 많을 뿐 논리적으로 어려운 코드는 별로 없다. 여기까지 실습을 진행해 왔다면 이 장의 코드는 스스로 분석이 가능할 것이다.

알다시피 텍스트 파일은 특별한 서식이 없으며 동질적인 문자들로만 구성되어 있다. 하지만 개행코드를 표현하는 방식에 따라 몇 가지 다른 포맷은 있다. 윈도우즈는 CR(\r) LF(\n)로 줄이 바뀌는 것을 표현하지만 유닉스는 LF로만 개행을 표현하고 매킨토시는 CR로만 개행을 한다. 여기서 CR이란 Carriage Return이라는 뜻으로 줄의 처음으로 이동하는 제어코드고 LF Line Feed라는 뜻으로 다음 줄로 이동하는 제어코드다. 운영체제별로 CR LF를 조합하는 방식이 조금씩 다르다.

개행코드 외에도 문자코드의 길이에 따라 포맷이 구분되기도 하는데 한글은 2바이트로, 영문은 1바이트로 표현하는 방식을 MBCS라고 하며 모든 문자를 2바이트 코드로 표현하는 방식을 유니코드라고 한다. 두 방식 모두 문자와 코드의 대응관계를 규정하는 인코딩 방식에 따라 헤아릴 수 없이 많은 포맷으로 나누어진다. 한글만 해도 완성형, 조합형, 7비트 조합형 등으로 나누어지고 유니코드도 UTF-8, UTF-16 등 몇 가지 포맷이 있다.

텍스트 포맷을 제대로 분류하고 그 구성원리에 대해 설명하자면 한 장을 다 할애해도 부족할 정도다. 하지만 다행히 대부분의 포맷들은 지금은 거의 사용되지 않으며 존재하는 텍스트 파일들은 거의 대부분 ANSI 또는 유니코드 중의 하나다. ANSI 포맷은 영문은 1바이트의 ASCII 코드로, 한글은 2바이트의 완성형으로 표현하는 방식이며 현재 윈도우즈의 표준 포맷이다.

존재하는 모든 텍스트 포맷을 다 지원한다는 것은 사실상 불가능하다. 그래서 텍스트 편집기들은 많이 사용되는 일부 포맷만 제한적으로 지원할 수밖에 없다. ApiEdit는 윈도우즈의 표준 포맷인 ANSI만 유일하게 인식하며 오로지 ANSI 문서만 편집할 수 있다. 개행코드는 반드시 CR/LF 조합으로 되어 있어야 하며 영문은 ASCII 코드만, 한글은 완성형만 읽을 수 있다.

결국 ApiEdit는 유니코드로 된 문서나 매킨토시에서 작성된 문서는 편집하지 못한다. 만약 ANSI 포맷이 아닌 문서를 ApiEdit로 열면 당장 자살해 버릴 것이다. ApiEdit를 한 포맷만 지원하도록 만든 이유는 코드가 간단하기 때문이다. 모든 종류의 문서를 다 편집하자면 코드가 져야 하는 부담은 실로 엄청나다. \r을 찾으면 줄의 끝이라고 판단하며 한글은 2바이트, 영문은 1바이트라고 계산하여 동작하는데 이 가정이 틀려지면 어떻게 되겠는가? 모든 종류의 포맷을 구분하자면 코드는 엄청나게 복잡해질 것이고 속도가 느려짐은 두말할 나위도 없다.

그러나 예상하겠지만 아무 대책도 없이 ApiEdit를 이렇게 만들어 놓은 것은 물론 아니다. 편집코드는 한 포맷만 인식하되 외부의 데이터를 받아들일 때 포맷을 분석해보고 자기가 해석할 수 있는 내부 포맷으로 변환할 수 있다면 결국 다른 포맷도 읽을 수 있게 된다. 편집할 때는 ANSI로만 편집하고 이 데이터를 저장할 때 다시 원래 데이터로 변환해서 저장하면 밖에서 볼 때는 ApiEdit가 다른 포맷을 지원하는 것처럼 보인다.

이런 구조에서 ApiEdit가 어떤 포맷을 지원하는가는 편집코드와는 상관이 없고 변환 코드가 어떤 포맷을 인식할 수 있는가로 결정된다. 편집코드는 오로지 ANSI 포맷밖에 모른다. 되도록이면 많은 포맷을 지원하면 좋겠지만 일단은 가장 흔한 포맷들을 먼저 지원하도록 하자. 변환 코드만 수정하면 지원 포맷의 개수는 차후에도 얼마든지 늘릴 수 있다. ApiEdit.h에 지원 포맷의 ID를 정의한다. 현재는 다음 다섯 가지 포맷을 지원한다.

 

#define AE_FORMAT_WIN 0

#define AE_FORMAT_UNIX 1

#define AE_FORMAT_MAC 2

#define AE_FORMAT_UNICODE 3

#define AE_FORMAT_BINARY 4

 

이 중 이진 포맷에 대해서는 편집기능을 제공하지 않지만 차후의 기능 확장을 위해 일단 구분만 한다. ApiEdit는 파일을 읽을 때 포맷을 분석해보고 분석된 포맷을 이 ID로 기억하며 내부 편집을 마친 후 원래 포맷대로 다시 저장한다. CApiEdit에 현재 편집하고 있는 문서의 포맷을 기억하는 멤버변수를 추가한다.

 

class CApiEdit

{

     ....

private:

     DWORD dwFormat;

public:

     DWORD GetFormat() { return dwFormat; }

     void SetFormat(DWORD aFormat);

};

 

편집 포맷의 ID dwFormat InitDoc에서 다음과 같이 초기화된다.

 

void CApiEdit::InitDoc()

{

     ....

    dwFormat=AE_FORMAT_WIN;

     Invalidate(-1);

}

 

보다시피 ApiEdit의 디폴트 포맷은 윈도우즈 포맷이다. Get 함수는 변수값만 읽어주면 되므로 인라인으로 작성했으며 Set 함수는 다음과 같이 작성한다.

 

void CApiEdit::SetFormat(DWORD aFormat)

{

     if (aFormat != dwFormat) {

          SetModified(TRUE);

          dwFormat=aFormat;

     }

}

 

포맷이 바뀐다고 해서 당장 내부 버퍼가 변하는 것은 아니지만 다시 저장할 필요가 생겼으므로 변경플래그를 세트해놓는다. 호스트는 메뉴에 현재 포맷을 보여준다.

 

void OnInitMenu(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     ....

     if (hActive) {

          ....

          switch (pSi->Ae.GetFormat()) {

          case AE_FORMAT_WIN:

              CheckMenuRadioItem(hMenu,IDM_FILE_WIN,IDM_FILE_UNICODE,IDM_FILE_WIN,MF_BYCOMMAND);

              break;

          case AE_FORMAT_UNIX:

              CheckMenuRadioItem(hMenu,IDM_FILE_WIN,IDM_FILE_UNICODE,IDM_FILE_UNIX,MF_BYCOMMAND);

              break;

          case AE_FORMAT_MAC:

              CheckMenuRadioItem(hMenu,IDM_FILE_WIN,IDM_FILE_UNICODE,IDM_FILE_MAC,MF_BYCOMMAND);

              break;

          case AE_FORMAT_UNICODE:

              CheckMenuRadioItem(hMenu,IDM_FILE_WIN,IDM_FILE_UNICODE,IDM_FILE_UNICODE,MF_BYCOMMAND);

              break;

          }

     }

 

이진 포맷인 경우는 메뉴에 아무 표시도 하지 않도록 했다. 임의의 포맷을 이진 포맷으로 강제로 변환할 수 없기 때문에 메뉴에 이진 포맷 항목이 아예 없다. 상태란에도 현재 포맷을 보여준다.

 

void SetStatusText(int mask,LPCTSTR Mes/*=NULL*/)

{

     ....

     if (mask & 0x10) {

          switch (pSi->Ae.GetFormat()) {

          case AE_FORMAT_WIN:

              lstrcpy(Text,"WIN 포맷");

              break;

          case AE_FORMAT_UNIX:

              lstrcpy(Text,"UNIX 포맷");

              break;

          case AE_FORMAT_MAC:

              lstrcpy(Text,"MAC 포맷");

              break;

          case AE_FORMAT_UNICODE:

              lstrcpy(Text,"UNICODE");

              break;

          case AE_FORMAT_BINARY:

              lstrcpy(Text,"이진 파일");

              break;

          }

          SendMessage(hStatus, SB_SETTEXT, 4, (LPARAM)Text);

     }

     ....

 

문자의 포맷을 설명하는 문자열을 상태란에 출력했다. 현재 ApiEdit는 포맷을 구분할 수 있는 기본적인 준비만 해 둔 상태이다. 아직 포맷을 인식하거나 변환하지는 못한다.