. ApiEdit와 이진 포맷

SetText 함수를 보면 이진 파일 포맷일 때도 별도의 변환없이 바로 읽도록 되어 있다. 왜냐하면 이진 포맷은 텍스트 포맷과 호환되지 않으므로 변환할 수도 없고 변환할 필요도 없다. 텍스트 편집기로 이진 파일을 열었다는 것은 사용자가 실수를 한 것이므로 그냥 있는 그대로 보여주기만 하면 된다. 화면에 엉뚱한 문자들이 마구 출력되는데 이런 상황을 GIGO(Garbage In Garbage Out)라고 하며 우리말로 번역하자면 니가 잘못했잖아! 투덜투덜이라고 할 수 있다.

텍스트를 편집하는 프로그램으로 텍스트가 아닌 파일을 열었다는 것은 프로그램의 능력치 이상을 요구한 사용자의 실수이다. 이럴 경우 프로그램이 택할 수 있는 방법은 아예 열지 못하도록 거부한다거나 아니면 결과야 어쨌든 대충이라도 읽을 수 있도록 일단 열어 주거나 둘 중 하나이다. 조금 친절한 척 하려면 실수하셨군요! 정도의 메시지박스를 곁들이는 것도 좋다.

당근은 이진 파일이라도 굳이 거부는 하지 않도록 했는데 왜냐하면 이진 파일 속에 일부 포함된 텍스트를 확인하기 위해 의도적으로 파일을 열었을 수도 있고 원래 텍스트 파일인데 파일이 조금 손상되어 이진 파일이 되어 버린 파일도 존재하기 때문이다. 또한 악의없이 실수로 이진 파일을 열었을 수도 있으므로 일단 이진 파일을 여는 것을 허용하기로 한다. 하지만 ApiEdit가 원래부터 이진 파일을 고려하고 작성된 컨트롤이 아니기 때문에 많은 문제점이 있다.

 

ApiEdit 0을 문서의 끝으로 인식하며 이 문자가 나오면 여기가 문서의 끝이라고 생각한다. 그래서 중간에 0이 있는 이진 파일을 열었을 경우 0 이전까지만 화면에 출력되며 그 이후는 잘려서 보이지 않는다. Insert 함수는 TCHAR *형의 데이터를 전달받아 이 문자열 길이만큼만 문서에 삽입하기 때문이다. 이진 파일을 끝까지 다 보이도록 하려면 0을 무조건 문서 끝이라고 가정해서는 안되면 doclen을 문서 끝으로 생각해야 한다. 또한 Insert, Delete, CopyString 함수와 취소 레코드를 모두 이진 포맷에 맞게 바꿔야 하며 정렬, 이동, 선택 루틴들도 모두 수정되어야 한다.

문자가 아닌 제어코드들이나 폰트에 정의되어 있지 않는 코드들은 이상한 그림 문자들이 출력된다. ApiEdit는 개행코드와 탭만 제어코드로 인정하고 그 나머지는 모두 TextOut으로 화면에 뿌리기만 하며 한글 폰트에 없는 문자는 디폴트 문자로 출력된다. 이는 어쩔 수 없는 일이며 해결할 필요가 없다.

개행코드는 반드시 CR, LF가 연속되어 있어야 한다. 제대로 된 유닉스나 매킨토시 문서는 변환한 후 편집하므로 문제가 되지 않지만 CR, LF 조합이 마구 섞여 있는 문서는 포맷 분석을 정확하게 할 수 없으므로 변환이 제대로 되지 않는다. 만약 CR만 있고 LF가 없다면 CR 다음의 임의의 한 문자를 LF로 가정하도록 되어 있으며 LF만 있고 CR이 없으면 개행은 되지 않고 LF가 그래픽문자로 출력된다. 포맷판별 및 변환루틴이 좀 더 정교해져야 한다.

아주 특수한 경우로 DBCS 문자의 뒤 문자가 제어문자인 경우, 특히 탭인 경우 출력상의 문제가 있다. 예를 들어 80 09 b0 a1의 데이터가 있을 경우 정렬루틴은 8009 DBCS 문자로 해석하여 한 줄에 있는 문자로 정렬하게 된다. 그러나 DrawLine은 문자의 길이에 상관없이 세그먼트를 나누기 때문에 09를 만났을 때 이 자리를 탭으로 가정해버리고 세그먼트를 나누게 된다. DBCS 문자의 경계에서 세그먼트가 나누어졌기 때문에 MyGetTextExtent 함수가 이 세그먼트의 길이를 계산할 때 8009를 한 문자로 인식해서 세그먼트 경계를 넘어서게 된다. 8009에는 문자가 할당되어 있지 않으므로 정상적인 텍스트 파일이라면 이런 코드는 존재할 수 없지만 이진 파일에는 존재할 수 있다는 점이 문제다. 결국 이런 문자코드를 만나면 당근의 안부는 장담할 수 없다.

이진 파일이 화면에 어떻게 보이는가는 크게 중요하지 않다. 사용자들도 이 파일이 텍스트 파일이 아니라는 것은 알기 때문에 이해할 것이다. 그러나 만약 이 파일을 편집한 후 저장해버리면 0 이후의 문자는 원래 없었던 것처럼 잘려 나가 버린다. ApiEdit는 텍스트 편집 컨트롤이지 이진 편집 컨트롤이 아니기 때문이다.

 

비록 ApiEdit가 이진 파일을 다루는 컨트롤은 아니지만 실수든 고의든 사용자는 이진 파일을 열 수 있으므로 이런 문제들에 대해 어느 정도의 방어 태세를 갖출 필요가 있다. 우선 이진 파일을 편집하게 내버려 두어서는 안되므로 파일을 열자 마자 읽기전용 상태로 변경하는 것이 좋다.

 

BOOL OpenFromFile(TCHAR *Path,BOOL bReadOnly/*=FALSE*/,BOOL bBrowse/*=FALSE*/)

{

     ....

     if (bReadOnly || (GetFileAttributes(Path) & FILE_ATTRIBUTE_READONLY)) {

          pSi->Ae.SetReadOnly(TRUE);

     }

 

    if (pSi->Ae.GetFormat() == AE_FORMAT_BINARY) {

        pSi->Ae.SetReadOnly(TRUE);

    }

 

이진 포맷으로 판별되면 읽기전용 플래그를 설정하여 편집을 일단 금지하였다. 물론 이렇게 해도 사용자가 메뉴를 통해 읽기전용 설정을 풀고 편집할 수 있지만 그것까지 책임질 필요는 없다. 다음은 DBCS의 경계에 세그먼트가 걸치는 문제를 해결하기 위해 DrawLine을 다음과 같이 수정한다.

 

int CApiEdit::DrawLine(HDC hdc, int Line)

{

     ....

     for (;;) {

          for (len=0;;) {

              ....

           if (IsDBCS(nowoff+len)) {

               len+=2;

           } else {

               len++;

           }

          }

 

          if (bInSel && (GetFocus()==hWnd || HideSelType!=0 || bFindSelect)) {

          ....

 

세그먼트의 길이를 증가시킬 때 DBCS문자는 아예 2바이트 건너 뛰도록 함으로써 문자의 경계에서 세그먼트를 나누지 않도록 했다. 문제 해결을 위한 코드인데 점검해야 할 횟수를 줄임으로써 일종의 최적화 효과도 있다.

이진 파일 지원을 위해 몇 가지 조치를 취했지만 그래도 ApiEdit는 이진 파일에 대한 지원이 무척 미약하다. 정렬, 이동, 출력 등 모든 루틴의 코드를 수정해야 하지만 그렇게 하지 않는 이유는 어떻게 하더라도 근본적인 해결책이 되지 못하기 때문이다. 텍스트 편집기로 이진 파일을 보거나 편집하려면 결국은 별도의 이진 모드를 정의해야 하며 ApiEdit가 아닌 다른 컨트롤이 필요하기 때문이다. 일부 편집기들은 16진수 형태로 이진 파일을 보여주고 편집할 수 있는 헥사 기능을 제공하는데 당근도 다음 버전에서 헥사 모드(Hexa Mode)를 제공할 것이다. 이번 버전의 스팩에서는 이 기능이 제외되었으므로 당분간은 읽기전용 상태로 내용만 볼 수 있도록 해놓았다.