HotXLS는 Excel 2.0, 3.0, 4.0이 작성한 워크북을 Delphi와 C++Builder에서 직접 엽니다. 이 파일들은 이후의 모든 .xls가 쓰는 OLE 복합 문서 컨테이너보다 앞선 시대의 것이라 스토리지 래퍼가 전혀 없는 원시 BIFF 레코드 스트림이며, BIFF8용으로 만들어진 리더는 그 안에서 알아볼 수 있는 구조를 단 하나도 찾지 못합니다. 이런 파일을 여는 데는 다른 워크북과 동일한 Open 호출을 사용하며, 리더가 형식을 감지해 경로를 바꿔 탑니다
이런 파일이 여전히 등장한다는 것, 그것만이 이 모든 이야기가 중요한 이유입니다. 엔지니어링 아카이브, 정부 기록 보존, 1993년에 작성된 제어 소프트웨어를 쓰는 계측기의 실험실 데이터, 오래 운영되어 온 회계 시스템 모두가 BIFF2와 BIFF4 워크북을 남겨두었습니다. 최신 Excel은 보안상의 이유로 레거시 컨버터를 제거한 탓에 이 중 여러 파일을 아예 열기를 거부하며, 그 결과 아무도 가진 도구로는 읽을 수 없는 데이터 세트가 남게 됩니다
OLE 이전 워크북은 무엇이 다를까?
Excel 5.0 이후의 모든 .xls는 OLE2 복합 파일, 즉 파일 안에 든 작은 파일 시스템이며, 워크북은 Workbook이나 Book이라는 이름의 스트림 안에 살고 있습니다. 이를 파싱하는 작업은 그 컨테이너를 파싱하는 데서 시작되며, 이는 Pascal에서 다루는 복합 파일 바이너리 형식에서 설명합니다
BIFF2부터 BIFF4까지는 컨테이너가 없습니다. 파일은 곧바로 BOF 레코드로 시작하고, 이 BOF의 레코드 번호가 세대를 인코딩합니다. BIFF2는 $0009, BIFF3는 $0209, BIFF4는 $0409입니다. HotXLS는 원시 경로로 넘어가기 전에 4바이트에서 6바이트 사이여야 하는 BOF 본문 길이와, 워크시트라면 $0010, 차트라면 $0020, 매크로 시트라면 $0040인 서브스트림 타입을 검증합니다. 이 검증이 손상되었거나 잘못 식별된 파일이 아주 오래된 워크북으로 해석되는 것을 막아줍니다
세 세대, 세 가지 레코드 레이아웃
셀 레코드는 세대 간 차이가 가장 뚜렷하게 드러나는 지점입니다. BIFF2는 blank, integer, number, label, boolean-or-error 셀에 대해 $0001부터 $0005까지 낮은 레코드 번호의 연속된 블록을 차지하며, 각 본문은 이후 버전이 확장 서식 인덱스를 두는 자리에 3바이트짜리 속성 필드를 둡니다. BIFF3와 BIFF4는 이를 버리고 BIFF5의 레코드 번호와 레이아웃인 $0201, $0203, $0204, $0205를 2바이트 XF 인덱스와 함께 재사용합니다
이 마지막 세부 사항이 구체적이면서도 진단하기 쉽지 않은 실패를 일으킵니다. BIFF3나 BIFF4의 LABEL 레코드는 구조적으로 BIFF5의 대응 레코드와 동일해서, 행과 열 다음에 서식 인덱스, 그다음에 문자 개수가 옵니다. BIFF2 레이아웃을 가정하고 작성한 리더는 2바이트를 덜 읽어 레코드 끝을 넘어가면서 그 이후를 전부 잘못 해석하게 됩니다. 증상은 예외가 아니라, 그럴듯한 쓰레기 값이 담긴 채로 읽히는 워크북입니다
수식 레코드는 세 세대 모두에서 병렬로 번호가 매겨지며 $0006, $0206, $0406입니다. 수식이 문자열 결과를 낼 때는 그 문자열이 뒤이은 별도의 레코드, $0007이나 $0207로 옵니다. BIFF2 형태는 2바이트 대신 1바이트짜리 길이 접두사를 씁니다
수식이 텍스트가 아니라 값으로 돌아오는 이유
HotXLS는 이런 파일에 담긴 수식의 캐시된 결과를 읽으며, 수식 표현식을 재구성하려 시도하지 않습니다. 이는 언젠가 채워질 빈틈이 아니라 의도적인 경계입니다
BIFF2부터 BIFF4까지 파싱된 표현식이 쓰는 토큰 인코딩은 겉모습만이 아니라 근본적으로 BIFF5 이후와 다릅니다. 토큰 길이의 접두사 방식이 다르고, 참조 토큰의 크기가 다르며, 함수 인덱스 표는 세대마다 번호가 다시 매겨졌습니다. 이 바이트들을 BIFF8용 표현식 번역기에 통과시키면 틀린 수식이 아니라 무작위 수식이 나옵니다. 캐시된 값을 읽으면 Excel이 마지막으로 계산해 둔 숫자나 문자열을 얻게 되는데, 이것이 아카이브 마이그레이션이 실제로 필요로 하는 것입니다
캐시된 값은 레코드 안 세대에 따라 다른 오프셋에 있습니다. BIFF2는 7바이트, BIFF3와 BIFF4는 6바이트입니다. 문자열, 불리언, 오류, 빈 값 같은 특수 값은 $FFFF 마커 워드와 판별자로 인코딩되며, 이는 이후의 BIFF 세대들도 그대로 유지한 관례입니다
파일 열기
호출하는 코드는 특별할 것이 없으며, 바로 그 점이 핵심입니다. 감지는 Open 내부에서 일어납니다:
uses
lxHandle;
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
R, C: Integer;
V: Variant;
begin
Book := TXLSWorkbook.Create;
try
if Book.Open('archive\1993-inventory.xls') <> 1 then
begin
Writeln('unreadable - quarantine for manual review');
Exit;
end;
Sheet := Book.Sheets[1]; // Sheets[] is 1-based
for R := Sheet.UsedRange.FirstRow + 1 to Sheet.UsedRange.LastRow + 1 do
for C := Sheet.UsedRange.FirstCol + 1 to Sheet.UsedRange.LastCol + 1 do
begin
V := Sheet.Cells[R, C].Value;
if not VarIsEmpty(V) then
Writeln(Format('R%dC%d = %s', [R, C, VarToStr(V)]));
end;
finally
Book.Free;
end;
end;
이 루프의 인덱스 산술에 주의하십시오. UsedRange의 경계는 0부터 시작하는 반면 시트 컬렉션과 셀 접근은 모두 1부터 시작하는데, 이 불일치는 현재 API보다 앞서 존재했고 호환성을 위해 그대로 남아 있습니다. 이 보정을 잊으면 엉뚱한 사각형을 감사하면서도 이상한 점을 하나도 보고하지 않게 됩니다. 파일을 아예 로드하지 않고도 할 수 있는 저렴한 사전 점검은 가벼운 워크북 검사에서 다룹니다
얻을 수 없는 것, 그리고 그에 대해 해야 할 일
서식은 해석되지 않습니다. HotXLS는 이 세대들의 XF와 FONT 레코드를 파싱하지 않으므로 글꼴, 색상, 테두리, 숫자 서식은 사용할 수 없고, Excel이 한때 날짜로 표시하던 셀은 원시 일련번호 그대로 돌아옵니다
이 마지막 문제는 리더가 아니라 여러분의 코드에서 처리해야 하며, 그 이유는 정직합니다. BIFF2부터 BIFF4까지의 숫자 서식은 자동 날짜 판별을 맡길 만큼 신뢰할 수 없습니다. 다섯 자리 숫자로 된 열은 날짜일 수도 있고 부품 번호일 수도 있습니다. 워크북의 날짜 체계를 사용해 의도적으로 변환하십시오. 그 규칙은 날짜 일련번호, 1904 체계, 숫자 서식에서 설명합니다:
// Decide per column, never per value: a five-digit number can be a
// date or a part number, and the legacy format will not tell you
if ColumnHoldsDates(C) then
begin
// The two date systems are 1462 days apart, so the same serial
// denotes two dates four years apart. Read the system from the
// workbook rather than assuming one
if Book.Date1904 then
Writeln(DateToStr(SerialToDate1904(V)))
else
Writeln(DateToStr(SerialToDate1900(V)));
end
else
Writeln(VarToStr(V));
전체 그림을 완성하는 구조적인 참고 사항이 두 가지 더 있습니다. 암호 보호와 코드 페이지 레코드는 워크북 수준 스트림이 아니라 단일 워크시트 스트림 안에 등장하는데, 애초에 워크북 수준 스트림이라는 것이 없기 때문에 워크시트 컨텍스트에서 인식해야 합니다. 그리고 BIFF2부터 BIFF4까지의 파일은 정확히 하나의 시트 서브스트림만 담고 있습니다. 다중 시트 워크북은 형식이 컨테이너를 갖추기 전까지는 존재하지 않았습니다
따라서 실용적인 마이그레이션 경로는 두 단계입니다. 레거시 파일을 읽어 값을 얻은 다음, 그 값을 여러분이 직접 적용한 서식과 함께 담는 최신 워크북을 작성하십시오. 레거시 읽기, 최신 형식 쓰기, 그 사이의 모든 것은 Delphi와 C++Builder용 하나의 라이브러리 안에서 이루어지며, HotXLS Delphi 스프레드시트 컴포넌트 페이지에서 설명합니다