Формат файла .bin Office 2007 - Чтение частей BIN Excel 2007

ОГЛАВЛЕНИЕ

Чтение частей BIN Excel 2007

Остальная статья описывает некоторые части BIN. Представлен исходный код на C++ и C# для чтения (и, возможно, записи) тех частей BIN. Вдобавок к частям проекта VBA и частям объектов OLE, описанных в первой части статьи, далее описаны следующие части:
•    часть книги workbook.bin (тип MIME: application/vnd.ms-excel.workbook)
•    часть словаря стилей styles.bin (тип MIME: application/vnd.ms-excel.styles)
•    для каждого листа,
     o    индексная часть worksheets/binaryIndexxx.bin (тип MIME: application/vnd.ms-excel.binIndexWs)
     o    часть листа worksheets/sheetxxx.bin (тип MIME: application/vnd.ms-excel.worksheet)
     o    необязательная часть настроек принтера printerSettings/printerSettingsxxx.bin (тип MIME: application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings)
•    необязательная часть цепи расчета calcChain.bin (тип MIME: application/vnd.ms-excel.calcChain)
•    необязательные части комментариев commentsxxx.bin (тип MIME: application/vnd.ms-office.legacyDrawing)
•    необязательные части таблиц tables/tablexxx.bin (тип MIME: application/vnd.ms-excel.table)
•    необязательные части соединений connections.bin (тип MIME: application/vnd.ms-excel.connections)
•    необязательные части листа диаграмм, листа диалога, макротаблицы (chartsheets/sheetxxx.bin, dialogsheets/sheetxxx.bin, macrosheets/sheetxxx.bin) (тип MIME: application/vnd.ms-excel.chartsheet, application/vnd.ms-excel.dialogsheet, application/vnd.ms-excel.macrosheet)
•    необязательные части сводной таблицы (pivotTables/pivotTablexxx.bin) (тип MIME: application/vnd.ms-excel.pivotTable)
•    необязательные части определения и записей кэша сводной таблицы (pivotCache/pivotCacheDefinitionxxx.bin,pivotCache/pivotCacheRecordsxxx.bin) (тип MIME: application/vnd.ms-excel.pivotCacheDefinition, application/vnd.ms-excel.pivotCacheRecords)
•    необязательные таблицы запроса (queryTables/queryTablexxx.bin) (тип MIME: application/vnd.ms-excel.queryTable)

Перечисленный частей достаточно для чтения и, возможно, обновления произвольных ячеек вкупе со связанным форматированием в книге Excel 2007.

Знакомство с BIFF12

Каждая часть BIN может состоять из своей собственной нижележащей структуры. К счастью, большинство частей BIN имеют общую структуру под названием BIFF12. Это слово составлено на основе названия двоичного формата файла более старых версий Excel, где BIFF означает двоичный формат файла переноса. История BIFF заслуживает отдельной статьи, но самый интересный факт заключается в том, что последним известным серьезным пересмотром был BIFF8, введенный в Excel 97. Видимо, команду Excel в Microsoft так ранила путаница с форматом файла, возникшая, когда клиентов вынудили перейти с Excel 95 на Excel 97, два разных формата файла, что они вообще не рассматривали повторное изменение версии формата файла, несмотря на добавление множества новых записей в BIFF8 в Excel 2000, Excel XP и Excel 2003. Excel 2007 продолжает эту тенденцию в том смысле, что, если сохранить файл Excel 2007 как файл, совместимый с "Excel97-2003", то Excel 2007 кучу своих свойств в новых записях BIFF8, тем самым обеспечивая многократное преобразование документа из формата в формат. BIFF является частью потока OLE внутри контейнера документа OLE. Проекты VBA, объекты OLE, свойства сводки документа и карты XML (Excel 2003) также хранятся как отдельные потоки OLE. BIFF8 является последовательностью записей, где каждая запись идентифицируется с помощью двух байтов, за которыми следует длина записи тоже в двух байтах, за которыми следует само содержимое записи. Вот и все, что есть. Чтобы получить новейшую открытую документацию BIFF8 от Microsoft (в комплекте с документацией MSO), купите компакт-диски библиотеки MSDN от марта 1998. BIFF12 вроде наследует его, но вносит некоторые интересные изменения, такие как игнорирование существующих идентификаторов записей BIFF8.

Как BIFF8, BIFF12 состоит из последовательности записей, состоящих из идентификатора, длины и самого содержимого записи. Отличие заключается в том, что идентификаторы записей и длина записи кодируются с помощью методики переменной длины. Она работает следующим образом: считывается первый байт идентификатора записи. Если самый старший бит этого байта установлен в 1, то считывается следующий байт, вплоть до максимума из 4 байтов (т.е. идентификатор записи всегда хранится в DWORD). Этот самый старший бит не имеет значения, следовательно, для построения идентификатора записи должен произойти надлежащий сдвиг.

Например, если считывается байт 0x80, то установлен самый старший бит, и надо считать следующий байт. Допустим, другой байт равен 0x01, самый старший бит не установлен, следовательно, получается идентификатор записи. Результат сдвига в сторону - идентификатор записи 0x0180. Сопоставление этого идентификатора записи с несколькими известными идентификаторами записей осуществляется с использованием несдвинутого идентификатора записи, при этом дополнительно выясняются идентификаторы записей в шестнадцатеричном дампе BIFF12 прямым образом.

Для длины записи это работает так же. Должен произойти сдвиг влево в содержащем DWORD, чтобы создать правильную длину.

СтруктуразаписиBIFF12

идентификатор записи(переменной длины)

длина записи(переменной длины)

содержимое записи (размер определяется длиной записи)

идентификатор записи(переменной длины)

длина записи(переменной длины)

содержимое записи (размер определяется длиной записи)

...

...

...

Чтение записей BIFF12

Поскольку Windows использует прямой порядок байтов, идентификаторы записей и любое 2-байтное, 4-байтное или 8-байтное значение должны читаться справа налево. При использовании C#, если часть BIN хранится в массиве byte[], то надо обеспечить надлежащие функции-помощники для декодирования такой структуры. Ниже показано, как декодировать слова (2 байта), двойные слова (4 байта), числа с плавающей запятой одинарной точности (4 байта), числа с плавающей запятой двойной точности (8 байт) и строки:

public static UInt16 GetWord(byte[] buffer, UInt32 offset)
{
  UInt16 val = (UInt16) (buffer[offset + 1] << 8);
  val += (UInt16) (buffer[offset + 0]);
  return val;
}

public static UInt32 GetDword(byte[] buffer, UInt32 offset)
{
  return ((UInt32)(buffer[offset + 3]) << 24) +
         ((UInt32)(buffer[offset + 2]) << 16) +
         ((UInt32)(buffer[offset + 1]) << 8) +
         ((UInt32)(buffer[offset + 0]));
}

public double GetDouble(byte[] buffer, UInt32 offset)
{
  double d = 0;

  // ReadDouble() может считать IEEE 8-байтное вещественное число двойной точности
  // прямо из буфера
  using (MemoryStream mem = new MemoryStream())
  {
    BinaryWriter bw = new BinaryWriter(mem);

    for (UInt32 i = 0 ; i < 8; i++)
      bw.Write(buffer[offset + i]);

    mem.Seek(0,SeekOrigin.Begin);

    BinaryReader br = new BinaryReader(mem);
    d = br.ReadDouble();
    br.Close();
    bw.Close();
  }

  return d;
}

public static String GetString(byte[] buffer,
                                UInt32 offset, UInt32 len)
{
  StringBuilder sb = new StringBuilder((int)len);
  for (UInt32 i = offset; i < offset + 2 * len; i += 2)
    sb.Append((Char)GetWord(buffer, i));
  return sb.ToString();
}

public static bool GetRecordID(byte[] buffer,
                        ref UInt32 offset, ref UInt32 recid)
{
  recid = 0;

  if (offset >= buffer.Length)
    return false;
  byte b1 = buffer[offset++];
  recid = (UInt32)(b1 & 0x7F);

  if ((b1 & 0x80) == 0)
    return true;

  if (offset >= buffer.Length)
    return false;
  byte b2 = buffer[offset++];
  recid = ((UInt32)(b2 & 0x7F) << 7) | recid;

  if ((b2 & 0x80) == 0)
    return true;

  if (offset >= buffer.Length)
    return false;
  byte b3 = buffer[offset++];
  recid = ((UInt32)(b3 & 0x7F) << 14) | recid;

  if ((b3 & 0x80) == 0)
    return true;

  if (offset >= buffer.Length)
    return false;
  byte b4 = buffer[offset++];
  recid = ((UInt32)(b4 & 0x7F) << 21) | recid;

  return true;
}

Как примечание, структура строк, хранящихся внутри записей, следующая: 4 байта для длины (закодирована с прямым порядком байтов), определяющей число идущих далее символов строки (не байтов), за чем следует указанное число символов юникода (2 байта каждый, тоже закодированы с прямым порядком байтов). Строки никогда не заканчиваются нулем, и при анализе структуры вообще не встречались строки, закодированные в чем-то, отличном от юникода.

Имея это, можно прочитать структуру BIFF12 с помощью следующего кода:

// Основная часть BIFF12 – последовательность записей BIFF12
// Запись BIFF12 является идентификатором записи, за которым идет длина записи,
// за которым идет само содержимое
// Идентификатор записи хранится в переменной длине
// Длина записи тоже хранится в переменной длине
// Содержимое записи – произвольные содержимое, чья нижележащая структура
// связана с идентификатором записи, и определена раз и навсегда
// реализаторами формата файла
// Обработчик записи разбирает
// любую нижележащую структуру записи

UInt32 offset = 0;

while (offset < buffer.Length)
{
  UInt32 recid = 0;
  UInt32 reclen = 0;

  if (!BaseRecord.GetRecordID(buffer, ref offset, ref recid) ||
      !BaseRecord.GetRecordLen(buffer, ref offset, ref reclen))
  {
    Console.WriteLine("***Damaged buffer***");
    break;
  }

  // h – хэш-таблица, регистрирующая обработчики записи
  BaseRecord recHandler = (BaseRecord) h[recid];

  if (recHandler != null)
  {

    Console.Write( String.Format("<{0}>\r\n[rec=0x{1:X} len=0x{2:X}]",
                   recHandler.GetTag(), recid, reclen) );

    for (int i = 0; i < reclen; i++)
    {
      Console.Write( String.Format(" {0:X2}", buffer[offset + i]) );
    }

    Console.WriteLine();

    // декодировать само содержимое записи
    //   и, возможно, нижележащую структуру при ее наличии
    recHandler.Read(buffer, ref offset, recid, reclen, h, w);

    if (offset == UInt32.MaxValue)
    {
      Console.WriteLine("***Damaged buffer***");
      break;
    }

  }
  else
  {
    Console.Write( String.Format("[rec=0x{0:X}
                                len=0x{1:X}]", recid, reclen) );

    // неясно, что это такое, содержимое просто выгружается в шестнадцатеричный

    for (int i = 0; i < reclen; i++)
    {
      Console.Write( String.Format(" {0:X2}", buffer[offset + i]) );
    }

    Console.WriteLine();
  }

  offset += reclen;

  Console.WriteLine();
}

При применении этого кода к части BIN листа он выдает следующее:

// Здесь сказано, как читать то, что идет далее


// Для каждой записи, известной считывателю BIFF12, придуман
// тег разметки XML, связанный с записью.
// Это дает сведения о том, как заменить часть XML на
// часть BIN и наоборот и более понятно.

// Далее идут квадратные скобки, окаймляющие идентификатор записи
// и связанную длину
// Далее идет само содержимое записи
// (т.е. ничего, если длина нулевая)
// Всегда запись имеет нижележащую структуру
// (как и в случае <sheetData>, структура
//  декодируется, и предоставляется удобочитаемая информация).

*** Выгрузка части листа

<worksheet>
[rec=0x181 len=0x0]

<sheetPr>
[rec=0x193 len=0xF] C9 04 02 00 40 00 00 00 00 00 00 00 00 00 00
        info :   <tabColor rgb=.../>
        info :   <outlinePr showOutlineSymbols=.../>
        info :   <pageSetUpPr .../>
        info : </sheetPr>

<dimension>
[rec=0x194 len=0x10] 04 00 00 00 04 00 00 00 00 00 00 00 07 00 00 00
        info : r1=4, c1=0, r2=4, c2=7

<sheetViews>
[rec=0x185 len=0x0]

<sheetView>
[rec=0x189 len=0x1E] DC 03 00 00 00 00 01 00 00 00 00 00 00 00 40
00 00 00 64 00 00 00 00 00 00 00 00 00 00 00

<selection>
[rec=0x198 len=0x24] 03 00 00 00 04 00 00 00 00 00 00 00 00 00 00
00 01 00 00 00 04 00 00 00 04 00 00 00 00 00 00 00 FF 3F 00 00

</sheetView>
[rec=0x18A len=0x0]

</sheetViews>
[rec=0x186 len=0x0]

<sheetFormatPr>
[rec=0x3E5 len=0xC] FF FF FF FF 08 00 2C 01 00 00 00 01

<cols>
[rec=0x386 len=0x0]
        info : colmin=1, colmax=2, width=9,140625, style=1,
            outline=false, resize=false, hidden=false
        info : colmin=4, colmax=5, width=9,140625, style=2,
            outline=false, resize=false, hidden=false
        info : colmin=6, colmax=6, width=9,140625, style=2,
            outline=true, resize=true, hidden=false
        info : colmin=7, colmax=7, width=9,140625, style=0,
            outline=true, resize=true, hidden=false
        info : colmin=8, colmax=8, width=0, style=0,
            outline=false, resize=true, hidden=true
        info : colmin=9, colmax=9, width=11, style=0,
            outline=false, resize=true, hidden=false

</cols>
[rec=0x387 len=0x0]

<sheetData>
[rec=0x191 len=0x0]

        info : row=4, height=405, style=0, outline=false,
        resize=true, hidden=false
        info : col=0, style=0, v:stringindex=0 v:string=a

</sheetData>
[rec=0x192 len=0x0]

[rec=0x497 len=0x42] 00 00 00 00 00 00 01 00 00 00 01 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 01 00 00 00

<printOptions>
[rec=0x3DD len=0x2] 10 00

<pageMargins>
[rec=0x3DC len=0x30] 66 66 66 66 66 66 E6 3F 66 66 66 66 66
66 E6 3F 00 00 00 00 00 00 E8 3F 00 00 00 00 00 00 E8 3F 33
33 33 33 33 33 D3 3F 33 33 33 33 33 33 D3 3F

<pageSetup>
[rec=0x3DE len=0x22] 01 00 00 00 64 00 00 00 2C 01 00 00 2C
01 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 00 00

<headerFooter>
[rec=0x3DF len=0x1A] 0C 00 FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF

[rec=0x3E0 len=0x0]

</worksheet>
[rec=0x182 len=0x0]

Записи BIFF12 для нескольких частей BIN

Каковы некоторые из важных записей?

// Записи книги
public const int BIFF12_DEFINEDNAME            = 0x27;
public const int BIFF12_FILEVERSION            = 0x0180;
public const int BIFF12_WORKBOOK               = 0x0183;
public const int BIFF12_WORKBOOK_END           = 0x0184;
public const int BIFF12_BOOKVIEWS              = 0x0187;
public const int BIFF12_BOOKVIEWS_END          = 0x0188;
public const int BIFF12_SHEETS                 = 0x018F;
public const int BIFF12_SHEETS_END             = 0x0190;
public const int BIFF12_WORKBOOKPR             = 0x0199;
public const int BIFF12_SHEET                  = 0x019C;
public const int BIFF12_CALCPR                 = 0x019D;
public const int BIFF12_WORKBOOKVIEW           = 0x019E;
public const int BIFF12_EXTERNALREFERENCES     = 0x02E1;
public const int BIFF12_EXTERNALREFERENCES_END = 0x02E2;
public const int BIFF12_EXTERNALREFERENCE      = 0x02E3;
public const int BIFF12_WEBPUBLISHING          = 0x04A9;

// Записи листа
public const int BIFF12_ROW             = 0x00;
public const int BIFF12_BLANK           = 0x01;
public const int BIFF12_NUM             = 0x02;
public const int BIFF12_BOOLERR         = 0x03;
public const int BIFF12_BOOL            = 0x04;
public const int BIFF12_FLOAT           = 0x05;
public const int BIFF12_STRING          = 0x07;
public const int BIFF12_FORMULA_STRING  = 0x08;
public const int BIFF12_FORMULA_FLOAT   = 0x09;
public const int BIFF12_FORMULA_BOOL    = 0x0A;
public const int BIFF12_FORMULA_BOOLERR = 0x0B;
public const int BIFF12_COL             = 0x3C;
public const int BIFF12_WORKSHEET       = 0x0181;
public const int BIFF12_WORKSHEET_END   = 0x0182;
public const int BIFF12_SHEETVIEWS      = 0x0185;
public const int BIFF12_SHEETVIEWS_END  = 0x0186;
public const int BIFF12_SHEETVIEW       = 0x0189;
public const int BIFF12_SHEETVIEW_END   = 0x018A;
public const int BIFF12_SHEETDATA       = 0x0191;
public const int BIFF12_SHEETDATA_END   = 0x0192;
public const int BIFF12_SHEETPR         = 0x0193;
public const int BIFF12_DIMENSION       = 0x0194;
public const int BIFF12_SELECTION       = 0x0198;
public const int BIFF12_COLS            = 0x0386;
public const int BIFF12_COLS_END        = 0x0387;
public const int BIFF12_CONDITIONALFORMATTING = 0x03CD;
public const int BIFF12_CONDITIONALFORMATTING_END = 0x03CE;
public const int BIFF12_CFRULE          = 0x03CF;
public const int BIFF12_CFRULE_END      = 0x03D0;
public const int BIFF12_ICONSET         = 0x03D1;
public const int BIFF12_ICONSET_END     = 0x03D2;
public const int BIFF12_DATABAR         = 0x03D3;
public const int BIFF12_DATABAR_END     = 0x03D4;
public const int BIFF12_COLORSCALE      = 0x03D5;
public const int BIFF12_COLORSCALE_END  = 0x03D6;
public const int BIFF12_CFVO            = 0x03D7;
public const int BIFF12_PAGEMARGINS     = 0x03DC;
public const int BIFF12_PRINTOPTIONS    = 0x03DD;
public const int BIFF12_PAGESETUP       = 0x03DE;
public const int BIFF12_HEADERFOOTER    = 0x03DF;
public const int BIFF12_SHEETFORMATPR   = 0x03E5;
public const int BIFF12_HYPERLINK       = 0x03EE;
public const int BIFF12_DRAWING         = 0x04A6;
public const int BIFF12_LEGACYDRAWING   = 0x04A7;
public const int BIFF12_COLOR           = 0x04B4;
public const int BIFF12_OLEOBJECTS      = 0x04FE;
public const int BIFF12_OLEOBJECT       = 0x04FF;
public const int BIFF12_OLEOBJECTS_END  = 0x0580;
public const int BIFF12_TABLEPARTS      = 0x0594;
public const int BIFF12_TABLEPART       = 0x0595;
public const int BIFF12_TABLEPARTS_END  = 0x0596;

//Записи общих строк
public const int BIFF12_SI              = 0x13;
public const int BIFF12_SST             = 0x019F;
public const int BIFF12_SST_END         = 0x01A0;

//Записи стилей
public const int BIFF12_FONT            = 0x2B;
public const int BIFF12_FILL            = 0x2D;
public const int BIFF12_BORDER          = 0x2E;
public const int BIFF12_XF              = 0x2F;
public const int BIFF12_CELLSTYLE       = 0x30;
public const int BIFF12_STYLESHEET      = 0x0296;
public const int BIFF12_STYLESHEET_END  = 0x0297;
public const int BIFF12_COLORS          = 0x03D9;
public const int BIFF12_COLORS_END      = 0x03DA;
public const int BIFF12_DXFS            = 0x03F9;
public const int BIFF12_DXFS_END        = 0x03FA;
public const int BIFF12_TABLESTYLES     = 0x03FC;
public const int BIFF12_TABLESTYLES_END = 0x03FD;
public const int BIFF12_FILLS           = 0x04DB;
public const int BIFF12_FILLS_END       = 0x04DC;
public const int BIFF12_FONTS           = 0x04E3;
public const int BIFF12_FONTS_END       = 0x04E4;
public const int BIFF12_BORDERS         = 0x04E5;
public const int BIFF12_BORDERS_END     = 0x04E6;
public const int BIFF12_CELLXFS         = 0x04E9;
public const int BIFF12_CELLXFS_END     = 0x04EA;
public const int BIFF12_CELLSTYLES      = 0x04EB;
public const int BIFF12_CELLSTYLES_END  = 0x04EC;
public const int BIFF12_CELLSTYLEXFS    = 0x04F2;
public const int BIFF12_CELLSTYLEXFS_END = 0x04F3;

//Записи комментария
public const int BIFF12_COMMENTS        = 0x04F4;
public const int BIFF12_COMMENTS_END    = 0x04F5;
public const int BIFF12_AUTHORS         = 0x04F6;
public const int BIFF12_AUTHORS_END     = 0x04F7;
public const int BIFF12_AUTHOR          = 0x04F8;
public const int BIFF12_COMMENTLIST     = 0x04F9;
public const int BIFF12_COMMENTLIST_END = 0x04FA;
public const int BIFF12_COMMENT         = 0x04FB;
public const int BIFF12_COMMENT_END     = 0x04FC;
public const int BIFF12_TEXT            = 0x04FD;

//Записи таблицы
public const int BIFF12_AUTOFILTER      = 0x01A1;
public const int BIFF12_AUTOFILTER_END  = 0x01A2;
public const int BIFF12_FILTERCOLUMN    = 0x01A3;
public const int BIFF12_FILTERCOLUMN_END= 0x01A4;
public const int BIFF12_FILTERS         = 0x01A5;
public const int BIFF12_FILTERS_END     = 0x01A6;
public const int BIFF12_FILTER          = 0x01A7;
public const int BIFF12_TABLE           = 0x02D7;
public const int BIFF12_TABLE_END       = 0x02D8;
public const int BIFF12_TABLECOLUMNS    = 0x02D9;
public const int BIFF12_TABLECOLUMNS_END= 0x02DA;
public const int BIFF12_TABLECOLUMN     = 0x02DB;
public const int BIFF12_TABLECOLUMN_END = 0x02DC;
public const int BIFF12_TABLESTYLEINFO  = 0x0481;
public const int BIFF12_SORTSTATE       = 0x0492;
public const int BIFF12_SORTCONDITION   = 0x0494;
public const int BIFF12_SORTSTATE_END   = 0x0495;

//Записи таблицы запроса
public const int BIFF12_QUERYTABLE            = 0x03BF;
public const int BIFF12_QUERYTABLE_END        = 0x03C0;
public const int BIFF12_QUERYTABLEREFRESH     = 0x03C1;
public const int BIFF12_QUERYTABLEREFRESH_END = 0x03C2;
public const int BIFF12_QUERYTABLEFIELDS      = 0x03C7;
public const int BIFF12_QUERYTABLEFIELDS_END  = 0x03C8;
public const int BIFF12_QUERYTABLEFIELD       = 0x03C9;
public const int BIFF12_QUERYTABLEFIELD_END   = 0x03CA;

//Записи соединения
public const int BIFF12_CONNECTIONS           = 0x03AD;
public const int BIFF12_CONNECTIONS_END       = 0x03AE;
public const int BIFF12_CONNECTION            = 0x01C9;
public const int BIFF12_CONNECTION_END        = 0x01CA;
public const int BIFF12_DBPR                  = 0x01CB;
public const int BIFF12_DBPR_END              = 0x01CC;