Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Читаем Excel 2.0

В одном из последних проектов у меня возникло несколько довольно специфических проблем. Они возникли у меня, могут возникнуть и еще у кого-то. Я их разрешил и надеюсь помогу справиться и еще кому-то. Сегодня об одной из них…

Проблема заключалась в чтении данных из формата Excel 2. Обычно, под ASP для доступа к Excel используется ADODB. Загвоздка в том, что драйвер, используемый системой в этом случае работает с форматом Excel версий 3.0 и выше. Так что мне пришлось искать другой путь.

Попытки найти другой драйвер или использовать DDE ничего не дали, я решил поискать в Интернете описание этого формата и попробовать написать собственный парсер (WinCGI). Выручила поисковая машина Google, просмотрев более 500 ссылок, я нашел то, что искал.

Итак, формат Excel 2 a.k.a. Excel BIFF представляет собой набор записей (record) определенного формата:

| Заголовок записи  | Тело записи
Номер байта      | 0    1    2    3  | 0    1   ...
                -----------------------------------
Содержимое       | XX | XX | XX | XX | XX | XX | ...
                -----------------------------------
                 | тип     | длина   | данные

В первых двух байтах записан числовой тип записи, о нём позже, далее идут два байта, это длина данных, далее — сами данные. Записи — это то, чем мы будет оперировать.

Пример на Delphi, читающий одну запись:

...
var F: File of byte;
    Opcode, Len,
    Code: Word;
    Operand: Array of byte;

begin
        AssignFile(F, 'имя_файла.xls');
        FileMode := 0;
        Reset(F);

        BlockRead (F, Opcode, 2);
        BlockRead (F, Len, 2);
        SetLength (Operand, Len);
        BlockRead (F, Operand[0], Len);
        Proccess (Opcode, Operand); // Функция для обработки записи
        CloseFile (F);
end;

Типов записей около 64, но я, для обработки данных, использовал только 7. Заголовок файла (BOF), где описан версия файла и его тип; размер листа Excel (DIMENSION); пустая запись (BLANK); целое число (INTEGER); строка (LABEL); число с плавающей точкой (NUMBER), совмещенное поле — логический тип или поле ошибки (BOOLERR) и поле формата числа (FORMAT).

Семь типов записей:

const xBOF       = 09;
      xDIMENSION = 00;
      xFORMAT    = $1E;
      xBLANK     = 01;
      xINTEGER   = 02;
      xLABEL     = 04;
      xNUMBER    = 03;
      xBOOLERR   = 05;

Запись типа BOF содержит два слова [1]. В первом записана версия файла (в данном случае «2») и тип файла (обычно это $10 [2] — worksheet). Эта запись — первая в любом файле формата Excel, прежде чем заниматься дальнейшим разбором стоит проверить ее содержимое.

Следующий тип, DIMENSION содержит данные о размере листа данных. Запись этого типа содержит четыре слова: номер первой строки, номер последней строки плюс один, номер первого столбца, номер последнего столбца плюс один. Я использую это поле для выделения места под данные, которые будут преобразованы из Excel.

Тип FORMAT содержит в поле данных строку форматирования в виде Pascal string (т. е. в первом байте длина, далее — строка). Строки формата идентифицируются в порядке следования, начиная с нуля. Тут есть одна тонкость: типы, выбираемые в Excel из списка «Формат ячеек», хранятся, как INTEGER или NUMBER. То, как они будут представлены человеку определяется форматными строками. Форматы даты и времени совпадают с принятыми в функции FormatDateTime среды разработки Delphi, а численные форматы могут быть легко сведены к параметрам функции Format.

BLANK — пустое поле. Как и любой из последующих типов, содержит в первом слове строку, а во втором — столбец ячейки на листе Excel.

INTEGER — целое. В 7 и 8-м байте (в 4-м слове данных) содержится обычное двухбайтовое целое. Как я понимаю, знаковое. В первых 6 битах пятого байта записан номер формата число (см. FORMAT).

LABEL — строка. Начиная с 7-го байта записан Pascal string. Пример чтения этого и следующих типов будет ниже.

NUMBER — число. С 7-го байта записано число с плавающей точкой в формате IEEE. Так же, как и для INTEGER в первых 6 битах 5-го байта записан номер формата.

BOOLERR — в 8-м байте указывает используется ли это поле для булевского типа (записан «0») или содержит ошибку (записан «1»). Само значение, как не трудно догадаться, записано в 7-м бите («0» — ложь, «1» — истина). Я обрабатывал только булевскую часть.

Простой пример чтения LABEL, NUMBER и BOOLERR.

...
Procedure Proccess (Opcode: Word; Op: array of byte);
var Str: ShortString;
    Dbl: Double;

Begin
        if (Opcode=xLABEL) Or (Opcode=xBOOLERR) Or (Opcode=xNUMBER) Then
        Begin
                iX:=Op[2]+Op[3]*256;
                iY:=Op[0]+Op[1]*256;
        End;

        Case OpCode of
        xLABEL:   begin
                        Move (Op[7], Str, SizeOf(Str));
                        MBuff[iY][iX]:= Str;
                  end;

        xBOOLERR: If Op[8]=0 Then MBuff[iY][iX]:= IntToStr(Op[7]) Else
                  MBuff[iY][iX]:= '';

        xNUMBER:  begin
                        Move (Op[7], Dbl, 8);
                        MBuff[iY][iX]:= FloatToStr (Dbl)
                  end;
End;

Для той задачи, которую решал я, этого было вполне достаточно. Более подробную информацию можно найти, правда на английском языке, в файлах по ссылкам внизу заметки.

[1] слово (здесь) — два байта, записанных в обычном, для процессоров Intel, порядке: сначала младший, потом старший. В Delphi — это тип word.
[2] в Паскале запись вида $00 означает шестнадцатеричную систему счисления.