Формат файла .bin Office 2007

ОГЛАВЛЕНИЕ

Новыми форматами файла Office 2007 являются файлы ZIP, содержащими части, некоторые из которых являются XML, некоторые другие являются родными форматами файлов, такими как изображения JPEG, а остальные двоичные части называются частями BIN. Части BIN представляют особый интерес для потребителя или обновителя формата файла, так как нижележащие форматы файла недокументированы (в момент написания, 10 августа 2006), и надо разобраться с несколькими дополнительными форматами файла.

•    Скачать чтение/запись OLE (C++) - 9 Кб
•    Скачать считыватель BIFF12 (C++) - 404 Кб
•    Скачать чтение/запись OLE (C#) - 10 Кб
•    Скачать считыватель BIFF12 (C#) - 404 Кб

Введение

Новыми форматами файла Office 2007 являются файлы ZIP, содержащими части, некоторые из которых являются XML, некоторые другие являются родными форматами файлов, такими как изображения JPEG, а остальные двоичные части называются частями BIN. Части BIN представляют особый интерес для потребителя или обновителя формата файла, так как нижележащие форматы файла недокументированы (в момент написания, 10 августа 2006), и надо разобраться с несколькими дополнительными форматами файла.

Части BIN появляются в ряде случаев. При вставке макроса VBA или объекта OLE в документ Word 2007, Excel 2007 или Powerpoint 2007 представляют интерес одна или несколько частей BIN. Части BIN являются записями zip, состоящими из файлов с расширением .BIN, которые фактически содержат свой собственный формат файла, зависящий от типа MIME(многоцелевые расширения почты Интернета), определенного в части отношений (xxx.rels) :
•    Макросы VBA: vbaProject.bin (тип MIME: application/vnd.ms-office.vbaProject)
•    Объекты OLE: oleObjectxxx.bin (тип MIME: application/vnd.openxmlformats-officedocument.oleObject)

Пример vbaProject.bin при открытии файла .DOCM Word 2007

 

Пример oleObjectxxx.bin при открытии файла .PPTX Powerpoint 2007

Прежде чем детально изучить эти две части BIN, также есть части BIN, введенные новой разновидностью формата файла Excel 2007, именуемого двоичной книгой Excel, являющейся файлом, заканчивающимся на .XLSB. Видимо, ради производительности было решено хранить файл Excel с помощью нескольких частей BIN вместо частей XML. Те части BIN являются подгруппой частей XML, наиболее страдающих от проблем производительности и масштабируемости, заметней всего страдает каждая книга из-за своего произвольного размера. Отчего-то книга, стили и ряд других мелких частей также являются частями BIN несмотря на то, что они вносят малый вклад в общую обработку книги. Опять же, есть нижележащие форматы файла, с которыми придется иметь дело потребителю и реализатору.

Некоторые части BIN из двоичного файла .XLSB Excel 2007 с 3 листами

Наряду с проектами VBA и встроенными объектами OLE, части BIN есть в файлах .XLSB Excel 2007 по следующим причинам:
•    Часть книги 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)

Заметьте, что типы содержимого MIME не различают, хранятся ли фактические файлы в XML или в BIN.

Вне полного списка частей, взятого из спецификаций Ecma, остальные необязательные части формата файла .XLSB Excel 2007 обычно оставляются в виде XML, и, следовательно, такие же, как и у обычных файлов Excel 2007 (.XLSX, .XLSM, ...):
•    Часть темы (theme/themexxx.xml)
•    Часть фонового изображения (drawings/imagexxx.xml)
•    Части основных и специфичных для приложения свойств документа (docProps/core.xml, docProps/app.xml)
•    Часть пользовательских свойств (customproperty.bin)
•    Часть пользовательских отображений XML (xmlMaps.xml)
•    Часть диаграмм (charts/chartxxx.xml)
•    Часть рисунков (drawings/drawingxxx.xml, drawings/legacyDrawingxxx.vml)
•    Часть внешних ссылок книги (externalLinks/externalLinkxxx.xml)
•    Часть метаданных (metadata.xml)
•    Часть ревизий общей книги (revisions/...)
•    Часть одноячеячной таблицы (tables/tableSinceCellsxxx.xml)
•    Часть изменяемых зависмостей (volatileDependencies.xml)

Вышеуказанное значительное исключение – устаревшие рисунки, хранящиеся с применением формата файла VML. Несмотря на то что VML является разметкой XML, его чтение, запись и возможное отображение требуют больших усилий. Когда вставляются устаревшие рисунки, они могут (например, объекты OLE) или не могут (например, комментарий) содержать разметку отношений с другими частями.
Настройки принтера в файлах Excel 2007 всегда хранятся в виде частей BIN, будь то .XLSB или нет.

Чтение или обновление частей vbaProject.bin

В предыдущих версиях Word, Excel и Powerpoint проекты VBA хранились в виде подконтейнерв OLE контейнера документа OLE. Файлы .doc / .dot / .xls / .xlt / .xlm / .xla / .ppt / .pot / .ppm / .ppa являются контейнерами документа OLE. Как показано ниже, был создан документ Word 97, и в него был добавлен макрос VBA. Обратите внимание на подконтейнер макросов. Он содержит контейнер VBA, содержащий несколько потоков, а также еще два потока.

Чтобы просмотреть контейнер документа OLE, можно использовать одну из частей инструментов Visual Studio 6.0, называемую просмотрщик DocFile, или просмотрщик OLE, свободно доступный здесь.

В предыдущих версиях Word макросы VBA хранились в виде подконтейнера OLE (макросы) вместе с несколькими потоками.

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

Вернемся к Word 2007, Excel 2007 и Powerpoint 2007. Можно извлечь запись zip vbaProject.bin из файла с помощью вставленного макроса VBA и открыть его в просмотрщике OLE. Удивляет, что появляется следующее:

 

vbaProject.bin - содержимое определенного выше контейнера макросов.

Для чтения или обновления частей vbaProject.bin нужны вызовы родного API, представленного IStream для потоков и IStorage для контейнеров. Как примечание, обязательное использование вызовов родного API не позволяет исполнять клиентский код в среде частичного доверия, такой как Однократное нажатие: исполнение родного кода предполагает полное доверие.

Вверху статьи находится пример кода, считывающий произвольный контейнер OLE с помощью C++ и C#.

Чтение или обновление частей oleObjectxxx.bin

Сродни частям vbaProject.bin, части oleObjectxxx.bin являются подконтейнерами OLE. Можно просмотреть содержимое файла посредством того же инструмента (просмотрщик файла Doc или аналогичный), и можно прочитать или обновить файл с помощью того же исходного кода, представленного в предыдущем разделе.

В качестве примера создается простой документ Excel 97, в него вставляется объект OLE (к примеру, документ Wordpad), затем он закрывается, и полученный файл .xls просматривается в просмотрщике OLE. Обратите внимание на подконтейнер MBD0032B277 с двумя потоками внутри:

 

В предыдущих версиях офиса встроенные объекты OLE хранятся в виде подконтейнеров (MBD0032B277) файла OLE.

Вернемся к Word 2007, Excel 2007 или Powerpoint 2007. Извлеките часть oleObjectxxx.bin из любого файла, куда был вставлен объект OLE, откройте его в просмотрщике OLE, чтобы увидеть нечто аналогичное:

 

oleObjectxxx.bin – содержимое определенного выше контейнера MBD0032B277.

Части BIN в новых форматах файла содержат разные нижележащие структуры, хоть и имеют общие интерфейсы для ее обхода (IStream/IStorage). Для чтения и обновления частей макросов VBA и частей объектов OLE нужны интерфейсы к IStream и IStorage, и, вероятно, знание нижележащего содержимого потоков (не требуется в случае базовой замены). Это одинаково распространяется на Word, Excel и Powerpoint.

Однако в двоичных книгах Excel 2007 другие части BIN не соблюдают структуру и содержимое.


Чтение частей 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;

Часть книги

Легко понять, как поступить с каждой нужной частью BIN. Можно создать обычный файл Excel 2007, использующий конкретное средство, например, диаграмму, и сохранить его как .XLSX и как .XLSB. Затем можно разархивировать содержимое в отдельных папках, взять части одновременно и попытаться выяснить, которая разметка XML соответствует которому идентификатору записи BIFF12. Сама разметка XML вовсе не обязательна, она лишь облегчает понимание.

Некоторая часть этой работы была проделана для ряда важных частей BIN, но упор делался на том, что действительно необходимо, чтобы убедиться в возможности прочитать содержимое ячеек. Оттого, несмотря на то что записи большей частью идентифицированы и сопоставлены с их братьями из разметки XML, само содержимое записи – нет, поэтому что это не служит цели, или потому что трудно устранить неоднозначность из него. Например, если посмотреть на часть BIN обычной книги ниже, заметны несколько записей без связанной разметки XML:

// Так часть книги выглядит в XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook
xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/5/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <fileVersion lastEdited="4" lowestEdited="4" rupBuild="4017"/>
  <workbookPr defaultThemeVersion="123820"/>
  <bookViews>
    <workbookView xWindow="360" yWindow="60" windowWidth="11295"
    windowHeight="5580"/>
  </bookViews>
  <sheets>
    <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
    <sheet name="Sheet2" sheetId="2" r:id="rId2"/>
    <sheet name="Sheet3" sheetId="3" r:id="rId3"/>
  </sheets>
  <calcPr calcId="122211"/>
  <webPublishing codePage="1252"/>
</workbook>


// Так часть книги выглядит в BIN

83 01 00 80 01 14 04 04 b1 0f 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 99 01 0c 20 00 01 00 ac e3 01 00 00 00 00 00 87 01 00 9e 01
1d 68 01 00 00 3c 00 00 00 1f 2c 00 00 cc 15 00 00 58 02 00 00 00 00
00 00 00 00 00 00 78 88 01 00 8f 01 00 9c 01 28 00 00 00 00 00 00 00
00 01 00 00 00 04 00 00 00 72 00 49 00 64 00 31 00 06 00 00 00 53 00
68 00 65 00 65 00 74 00 31 00 9c 01 28 00 00 00 00 00 00 00 00 02 00
00 00 04 00 00 00 72 00 49 00 64 00 32 00 06 00 00 00 53 00 68 00 65
00 65 00 74 00 32 00 9c 01 28 00 00 00 00 00 00 00 00 03 00 00 00 04
00 00 00 72 00 49 00 64 00 33 00 06 00 00 00 53 00 68 00 65 00 65 00
74 00 33 00 90 01 00 9d 01 19 63 dd 01 00 01 00 00 00 64 00 00 00 fc
a9 f1 d2 4d 62 50 3f 01 00 00 00 6a 96 04 06 00 00 00 00 00 00 9a 01
01 00 a9 04 0b 07 00 03 60 00 00 00 e4 04 00 00 9b 01 01 00 84 01 00


// Таким образом после разбиения части BIN на записи можно сопоставить
// записи с разметкой XML.

// Обратите внимание, что идентификаторы записей являются двумя байтами слева,
// связанная длина записи обрамлена круглыми скобками,
// а за ними идет само содержимое записи.


<workbook>
83 01 (00)

<fileVersion lastEdited="4" lowestEdited="4" rupBuild="4017"/>
(hint : 4017 = 0x0FB1)
80 01 (14) 04 04 b1 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

<workbookPr defaultThemeVersion="123820"/>
(hint : 123820 = 0x0001E3AC)
99 01 (0c) 20 00 01 00 ac e3 01 00 00 00 00 00

<bookViews>
87 01 (00)

<workbookView xWindow="360" yWindow="60" windowWidth="11295"
windowHeight="5580"/>
9e 01 (1d) 68 01 00 00 3c 00 00 00 1f 2c 00 00 cc 15 00 00 58 02 00
00 00 00 00 00 00 00 00 00 78

</bookViews>
88 01 (00)

<sheets>
8f 01 (00)

<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
9c 01 (28) 00 00 00 00 00 00 00 00
           01 00 00 00 sheetid
           04 00 00 00 length of string to follow
                           (in characters, not bytes)
           72 00 49 00 64 00 31 00 relation identifier
           06 00 00 00 length of string to follow
                           (in characters, not bytes)
           53 00 68 00 65 00 65 00 74 00 31 00 sheetname

<sheet name="Sheet2" sheetId="2" r:id="rId2"/>
9c 01 (28) 00 00 00 00 00 00 00 00
           02 00 00 00 sheetid
           04 00 00 00 length of string to follow
                           (in characters, not bytes)
           72 00 49 00 64 00 32 00 relation identifier
           06 00 00 00 length of string to follow
                           (in characters, not bytes)
           53 00 68 00 65 00 65 00 74 00 32 00 sheetname

<sheet name="Sheet3" sheetId="3" r:id="rId3"/>
9c 01 (28) 00 00 00 00 00 00 00 00
           03 00 00 00 sheetid
           04 00 00 00 length of string to follow
                           (in characters, not bytes)
           72 00 49 00 64 00 33 00 relation identifier
           06 00 00 00 length of string to follow
                           (in characters, not bytes)
           53 00 68 00 65 00 65 00 74 00 33 00 sheetname

</sheets>
90 01 (00)

<externalReferences>
e1 02 (00)

<externalReference r:id="rId4" />
e3 02 (0c) 04 00 00 00 length of string to follow
                    (in characters, not bytes)
           72 00 49 00 64 00 34 00
                   string representing a relation identifier

e5 02 (00)

ea 02 1c 02 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff 00 00 00 00
00 00 00 00 00 00 00 00

</externalReferences>
e2 02 (00)

<definedName name="externalrange" comment="">[1]Sheet1!$B$3
</definedName>
27 (3c) 00 00        grbits
        00 00 00
        ff ff ff ff  nametype
        0d 00 00 00  length of string to follow (in characters, not bytes)
        65 00 78 00 74 00 65 00 72 00 6e 00 61 00 6c 00 72 00 61 00
        6e 00 67 00 65 00   defined name
        09 00 00 00  length of formula to follow (in bytes)
        3a 01 00 02 00 00 00 01 00   formula
        00 00 00 00 00 00 00 00

<definedName name="anotherrange">Sheet1!$B$9:$C$10</definedName>
27 (40) 00 00        grbits
        00 00 00
        ff ff ff ff  nametype
        0c 00 00 00  length of string to follow (in characters, not bytes)
        61 00 6e 00 6f 00 74 00 68 00 65 00 72 00 72 00 61 00 6e
        00 67 00 65 00    defined name
        0f 00 00 00  length of formula to follow (in bytes)
        3b 00 00 08 00 00 00 09 00 00 00 01 00 02 00     formula
        00 00 00 00 ff ff ff ff

<definedName name="Database1" localSheetId="1" hidden="1">Sheet2!$A$1:$D$6</definedName>
27 (3a) 01 00        grbits
        00 00 00
        01 00 00 00  nametype
        09 00 00 00  length of string to follow
                        (in characters, not bytes)
        44 00 61 00 74 00 61 00 62 00 61 00 73 00 65
        00 31 00     defined name
        0f 00 00 00  length of formula to follow (in bytes)
        3b 01 00 00 00 00 00 05 00 00 00 00 00 03 00     formula
        00 00 00 00 ff ff ff ff

<definedName name="myrange">Sheet1!$C$2:$D$3</definedName>
27 (36) 00 00        grbits
        00 00 00
        ff ff ff ff  nametype
        07 00 00 00  length of string to follow
                        (in characters, not bytes)
        6d 00 79 00 72 00 61 00 6e 00 67 00 65 00     defined name
        0f 00 00 00  length of formula to follow (in bytes)
        3b 00 00 01 00 00 00 02 00 00 00 02 00 03 00     formula
        00 00 00 00 ff ff ff ff

<definedName name="_xlnm.Print_Area" localSheetId="0">Sheet1!$A$1:$E$7</definedName>
27 (3c) 20 00        grbits
        00 00 00
        00 00 00 00  nametype
        0a 00 00 00  length of string to follow
                        (in characters, not bytes)
        50 00 72 00 69 00 6e 00 74 00 5f 00 41 00 72
        00 65 00 61 00      defined name
        0f 00 00 00  length of formula to follow (in bytes)
        3b 00 00 00 00 00 00 06 00 00 00 00 00 04 00     formula
        00 00 00 00 ff ff ff ff

<definedName name="_xlnm.Print_Titles" localSheetId="0">Sheet1!$2:$3
</definedName>
27 (40) 20 00        grbits
        00 00 00
        00 00 00 00  nametype
        0c 00 00 00  length of string to follow (in characters, not bytes)
        50 00 72 00 69 00 6e 00 74 00 5f 00 54 00 69 00 74 00
        6c 00 65 00 73 00    defined name
        0f 00 00 00  length of formula to follow (in bytes)
        3b 00 00 01 00 00 00 02 00 00 00 00 00 ff 3f     formula
        00 00 00 00 ff ff ff ff

<definedName name="myrange" hidden="1">Sheet1!$B$2:$B$3
</definedName>
27 (36) 01 00        grbits
        00 00 00
        ff ff ff ff  nametype
        07 00 00 00  length of string to follow (in characters, not bytes)
        6d 00 79 00 72 00 61 00 6e 00 67 00 65 00      defined name
        0f 00 00 00  length of formula to follow (in bytes)
        3b 00 00 01 00 00 00 02 00 00 00 01 00 01 00     formula
        00 00 00 00 ff ff ff ff

<calcPr calcId="122211"/>
9d 01 (19) 63 dd 01 00 01 00 00 00 64 00 00 00 fc a9 f1 d2 4d 62 50
3f 01 00 00 00 6a

96 04 (06) 00 00 00 00 00 00

9a 01 (01) 00

<webPublishing codePage="1252"/>
a9 04 (0b) 07 00 03 60 00 00 00 e4 04 00 00

9b 01 (01) 00

</workbook>
84 01 (00)

Как видно, не удалось легко найти разметку, связанную с идентификаторами записей 0x0496, 0x019A и 0x019B выше. Хотя пока это не преграда, далее рассмотрена данная проблема.

Серьезная потеря соответствия

Пример свидетельствует, что человек, написавший сериализатор части BIN книги, знает больше, чем человек, разработавший сериализатор части XML книги. Увы, обратное тоже верно, поскольку разметка XML содержит пространства имен и связанную семантику, по очевидным причинам отсутствующую в части BIN.
Так как Microsoft не предоставил точные спецификации для сериализаторов BIN каждой рассматриваемой части, потребителям и реализаторам формата файла придется сконцентрироваться на воспроизведении структур, которые невозможно понять из-за несоответствия между сериализаторами. Приходится угадывать стнадратные значения объектов, с которыми работаешь, оттого это столь важно. Одна из известных лазеек формата файла, позволяющая вендору повлиять на будущее формата, а также на любой сценарий взаимдействия между платформами Windows и не-Windows.

Как прочитать часть BIN книги с помощью C# или C++ ? Это легко делается за счет приложенного к статье исходного кода.

// как прочитать часть книги на C#

Hashtable h = new Hashtable();

// регистрировать обработчики записи книги
h[C.BIFF12_DEFINEDNAME]            = new DefinedNameRecord();
h[C.BIFF12_FILEVERSION]            = new FileVersionRecord();
h[C.BIFF12_WORKBOOK]               = new WorkbookRecord();
h[C.BIFF12_WORKBOOK_END]           = new WorkbookEndRecord();
h[C.BIFF12_BOOKVIEWS]              = new BookViewsRecord();
h[C.BIFF12_BOOKVIEWS_END]          = new BookViewsEndRecord();
h[C.BIFF12_SHEETS]                 = new SheetsRecord();
h[C.BIFF12_SHEETS_END]             = new SheetsEndRecord();
h[C.BIFF12_WORKBOOKPR]             = new WorkbookPRRecord();
h[C.BIFF12_SHEET]                  = new SheetRecord();
h[C.BIFF12_CALCPR]                 = new CalcPRRecord();
h[C.BIFF12_WORKBOOKVIEW]           = new WorkbookViewRecord();
h[C.BIFF12_EXTERNALREFERENCES]     = new ExternalReferencesRecord();
h[C.BIFF12_EXTERNALREFERENCES_END] = new ExternalReferencesEndRecord();
h[C.BIFF12_EXTERNALREFERENCE]      = new ExternalReferenceRecord();
h[C.BIFF12_WEBPUBLISHING]          = new WebPublishingRecord();

// Этот класс используется для передачи объектов глуюже в поток чтения
Workbook w = new Workbook();

// Заметьте, что часть уже разархивирована и доступна в обычной папке
using (FileStream fs =
    new FileStream(@"..\..\Excel12_files\Book1.xlsb\xl\workbook.bin",
                                      FileMode.Open, FileAccess.Read))
{
  // использовать BCL для загрузки части в буфер byte[]
  byte[] bufferWorkbookPart = new BinaryReader(fs).ReadBytes((int)fs.Length);

  // использовать считыватель BIFF12 reader
  Read(w, h, bufferWorkbookPart);
}

Книга дает список нужных ссылок на листы. Для каждого листа можно посмотреть:
•    Индекс порядка листа. Если изменить порядок вкладок листов в файле Excel, индентификаторы переупорядочиваются, не затрагивая остальной файл (отношения и части).
•    Идентификатор отношения листа, или r:id. Этот идентификатор можно отыскать в связанном файле workbook.bin.rels, чтобы узнать, на какую часть он ссылается.
•    само имя листа, хранится как 4-байтная длина, за которой следует строка UCS2 юникода.


Часть листа

Часть листа описывает значения в ячейках, вместе с всегда применяемыми им формулами, а также описывает увязку объектов с ячейками или диапазонами ячеек (диаграммами, сводными таблицами, ...). Ниже приведен пример разобранного листа, содержащего число двойной точности с плавающей запятой, формулу с плавающей запятой, возвращающую ошибку "деление на нуль", и копию этой ячейки в другом месте:

 

Разбор структуры числовых значений ячеек с и без формул

// Так часть листа выглядит в XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet
    xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/5/main"
           xmlns:r=
           "http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <dimension ref="D3:D6"/>
  <sheetViews>
    <sheetView tabSelected="1" workbookViewId="0">
       <selection activeCell="D4" sqref="D4"/>
    </sheetView>
  </sheetViews>
  <sheetFormatPr defaultRowHeight="15"/>
  <cols>
    <col min="4" max="4" width="12.5703125" customWidth="1"/>
  </cols>
  <sheetData>
    <row r="3" spans="4:4"><c r="D3"><v>
        2.123456789</v></c></row>
    <row r="4" spans="4:4"><c r="D4" t="e"><f>5/E3</f><v>#DIV/0!</v>
    </c></row>
    <row r="6" spans="4:4"><c r="D6" t="e"><v>
    #DIV/0!</v></c></row>
  </sheetData>
  <printOptions/>
  <pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75"
  header="0.3" footer="0.3"/>
  <headerFooter/>
</worksheet>


// Таким образом после разбиения части BIN на записи можно сопоставить
// записи с разметкой XML.

// Обратите внимание, что идентификаторы записей являются двумя байтами слева,
// связанная длина записи обрамлена круглыми скобками,
// а за ними идет само содержимое записи.

<worksheet/>
81 01 (00)

<sheetPr/> (figured out)
93 01 (0f) c9 04 02 00 40 00 00 00 00 00 00 00 00 00 00

<dimension ref="D3:D6"/>
94 01 (10) 02 00 00 00 05 00 00 00 03 00 00 00 03 00 00 00

<sheetViews>
85 01 (00)

<sheetView tabSelected="1" workbookViewId="0">
89 01 (1e) dc 03 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 64
00 00 00 00 00 00 00 00 00 00 00

<selection activeCell="D4" sqref="D4"/>
98 01 (24) 03 00 00 00 02 00 00 00 02 00 00 00 00 00 00 00 01 00 00
00 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00

</sheetView>
8a 01 (00)

</sheetViews>
86 01 (00)

<sheetFormatPr defaultRowHeight="15"/>
e5 03 (0c) ff ff ff ff 08 00 2c 01 00 00 00 00

<cols>
86 03 (00)

<col min="4" max="4" width="12.5703125" customWidth="1"/>
3c (12)
            03 00 00 00 colmin (0-based)
            03 00 00 00 colmax (0-based)
            92 0c 00 00 width * 256
            00 00 00 00 style  (0-based)
            02 00       flags

</cols>
87 03 (00)

<sheetData>
91 01 (00)

<row r="3" spans="4:4"></row>
00 (19) 02 00 00 00 00 00 00 00 2c 01 00 00 00 01 00 00 00 03 00 00
00 03 00 00 00

<c r="D3"><v>2.123456789</v></c>
05 (10)
            03 00 00 00             col   (0-based)
            00 00 00 00             style (0-based)
            1b cb b9 e9 d6 fc 00 40 float (IEEE 8 bytes)

<row r="4" spans="4:4"></row>
00 (19) 03 00 00 00 00 00 00 00 2c 01 00 00 00 01 00 00 00 03 00 00
00 03 00 00 00

<c r="D4" t="e"><f>5/E3</f><v>
#DIV/0!</v></c>
0b (1e)
            03 00 00 00              col   (0-based)
            00 00 00 00              style (0-based)
            07                       boolerr (7 = DIV/0)
            00 00                    grbits
            0b 00 00 00              len of formula to follow in bytes
            1e 05 00 44 02 00 00 00 04 c0 06 formula (1E = ptgTokenInt  (5)
            00 00 00 00                               44 = ptgTokenRefV (E3)
                                                      06 = ptgTokenDiv)

<row r="6" spans="4:4"></row>
00 (19) 05 00 00 00 00 00 00 00 2c 01 00 00 00 01 00 00 00 03 00 00
00 03 00 00 00

<c r="D6" t="e"><v>#DIV/0!</v></c>
03 (09)
            03 00 00 00              col
            00 00 00 00              style
            07                       boolerr (7 = DIV/0)

</sheetData>
92 01 (00)

97 04 (42) 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

<conditionalFormatting sqref="B3:C4">
cd 03 (1c) 01 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 03 00
00 00 01 00 00 00 02 00 00 00

<cfRule type="dataBar" priority="3">
cf 03 (8c) 02 04 00 00 00 03 00 00 00 ff ff ff ff 03 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 67 00 00 00 67 00 00 00 00
00 00 00 ff ff ff ff
    <formula>MAX(IF(ISBLANK($B$3:$C$4), "",
    IF(ISERROR($B$3:$C$4), "", $B$3:$C$4)))</formula>
    67 00 00 00 65 02 00 00 00 03 00 00 00 01 00 02 00 61 81 00 19
    02 0b 00 19 40 00 01 17 00 00 19 08 43 00 65 02 00 00 00 03 00
    00 00 01 00 02 00 61 03 00 19 02 0b 00 19 40 00 01 17 00 00 19
    08 1c 00 19 40 00 01 25 02 00 00 00 03 00 00 00 01 00 02 00 19
    40 00 01 19 08 03 00 22 03 01 00 19 08 03 00 22 03 01 00 42 01
    07 00 00 00 00 00
    <formula>MAX(IF(ISBLANK($B$3:$C$4), "", IF(ISERROR($B$3:$C$4),
    "", $B$3:$C$4)))</formula>
    67 00 00 00 65 02 00 00 00 03 00 00 00 01 00 02 00 61 81 00 19
    02 0b 00 19 40 00 01 17 00 00 19 08 43 00 65 02 00 00 00 03 00
    00 00 01 00 02 00 61 03 00 19 02 0b 00 19 40 00 01 17 00 00 19
    08 1c 00 19 40 00 01 25 02 00 00 00 03 00 00 00 01 00 02 00 19
    40 00 01 19 08 03 00 22 03 01 00 19 08 03 00 22 03 01 00 42 01
    06 00 00 00 00 00

<dataBar>
d3 03 (03) 0a 5a 01

<cfvo type="min" val="0" />
d7 03 (18) 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00

<cfvo type="max" val="0" />
d7 03 (18) 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00

<color rgb="FF638EC6" /> (hint : color encoding = BGR not RGB)
b4 04 (08) 05 ff 00 00 63 8e c6 ff

</dataBar>
d4 03 (00)

</cfRule>
d0 03 (00)

</conditionalFormatting>
ce 03 (00)


<conditionalFormatting sqref="B6:C7">
cd 03 (1c) 01 00 00 00 00 00 00 00 01 00 00 00 05 00 00 00 06 00 00
00 01 00 00 00 02 00 00 00

<cfRule type="colorScale" priority="2">
cf 03 (8c) 02 03 00 00 00 02 00 00 00 ff ff ff ff 02 00 00 00 00 00
00 00 00 00 00 00 00 00 00
00 00 00 67 00 00 00 67 00 00 00 00 00 00 00 ff ff ff ff
    <formula>MAX(IF(ISBLANK($B$6:$C$7), "", IF(ISERROR($B$6:$C$7),
    "", $B$6:$C$7)))</formula>
    67 00 00 00 65 05 00 00 00 06 00 00 00 01 00 02 00 61 81 00 19 02
    0b 00 19 40 00 01 17 00 00 19 08 43 00 65 05 00 00 00 06 00 00 00
    01 00 02 00 61 03 00 19 02 0b 00 19 40 00 01 17 00 00 19 08 1c 00
    19 40 00 01 25 05 00 00 00 06 00 00 00 01 00 02 00 19 40 00 01 19
    08 03 00 22 03 01 00 19 08 03 00 22 03 01 00 42 01 07 00 00 00 00 00
    <formula>MAX(IF(ISBLANK($B$6:$C$7), "", IF(ISERROR($B$6:$C$7),
    "", $B$6:$C$7)))</formula>
    67 00 00 00 65 05 00 00 00 06 00 00 00 01 00 02 00 61 81 00 19 02
    0b 00 19 40 00 01 17 00 00 19 08 43 00 65 05 00 00 00 06 00 00 00
    01 00 02 00 61 03 00 19 02 0b 00 19 40 00 01 17 00 00 19 08 1c 00
    19 40 00 01 25 05 00 00 00 06 00 00 00 01 00 02 00 19 40 00 01 19
    08 03 00 22 03 01 00 19 08 03 00 22 03 01 00 42 01 06 00 00 00 00 00

<colorScale>
d5 03 (00)

<cfvo type="min" val="0" />
d7 03 (18) 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00

<cfvo type="percent" val="50" />
d7 03 (18) 04 00 00 00 00 00 00 00 00 00 49 40 00 00 00 00 00 00 00 00
00 00 00 00

<cfvo type="max" val="0" />
d7 03 (18) 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00

<color rgb="FFF8696B" /> (hint : color encoding = BGR not RGB)
b4 04 (08) 05 ff 00 00 f8 69 6b ff

<color rgb="FFFFEB84" /> (hint : color encoding = BGR not RGB)
b4 04 (08) 05 ff 00 00 ff eb 84 ff

<color rgb="FF63BE7B" /> (hint : color encoding = BGR not RGB)
b4 04 (08) 05 ff 00 00 63 be 7b ff

</colorScale>
d6 03 (00)

</cfRule>
d0 03 (00)

</conditionalFormatting>
ce 03 (00)


<conditionalFormatting sqref="B9:C10">
cd 03 (1c) 01 00 00 00 00 00 00 00 01 00 00 00 08 00 00 00 09 00 00 00 01
00 00 00 02 00 00 00

<cfRule type="iconSet" priority="1">
cf 03 (2e) 06 00 00 00 04 00 00 00 ff ff ff ff 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 ff ff ff ff

<iconSet iconSet="3TrafficLights2">
d1 03 (06) 04 00 00 00 78 00

<cfvo type="percentile" val="0" />
d7 03 (18) 05 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 00
00 00 00

<cfvo type="percentile" val="0" />
d7 03 (18) 05 00 00 00 00 00 00 00 00 80 40 40 01 00 00 00 01 00 00 00 00
00 00 00

<cfvo type="percentile" val="0" />
d7 03 (18) 05 00 00 00 00 00 00 00 00 c0 50 40 01 00 00 00 01 00 00 00 00
00 00 00

</iconSet>
d2 03 (00)

</cfRule>
d0 03 (00)

</conditionalFormatting>
ce 03 (00)

<hyperlink ref="C3" r:id="rId1"/>
ee 03 (28) 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 04 00 00 00 72
00 49 00 64 00 32 00 00 00 00 00 00 00 00 00 00 00 00 00

<printOptions/>
dd 03 (02) 10 00

<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75"
header="0.3" footer="0.3"/>
dc 03 (30) 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

<headerFooter/>
df 03 (1a) 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

e0 03 (00)

</worksheet>
82 01 (00)

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

//Записи ячейки
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;
                // формула, возвращающая идентификатор ошибки

//Надо сделать: найти код операции для встроенных строк (вероятно, это 0x06).

Далее рассказано, как хранятся формулы:

Байткод формулы

Байткод формулы, генерируемый Excel при разборе формул, в основном задокументирован (сравните официальную документацию по всем ptgTokens). Для разбора или вычисления формул надо знать, что они хранятся в обратном порядке (обратная польская нотация), то есть 5+3 хранится как {5,3,+}. В остальных случаях можно использовать существующий простой парсер/вычислитель RPN, написанный на C++ и расположенный тут. Исходя из этого, Excel 2007 вводит следующие изменения:

•    Так как есть гораздо больше строк и столбцов на каждый лист, байткоду формулы приходится находить ноые сегменты для хранения строк в 4 байтах вместо 2 (раньше максимальное число строк = 65536) и хранить столбцы в 2 байтах вместо 1 (раньше максимальное число столбцов = 256). Поэтому, по сравнению с общим кодированием, любая строка теперь является 4-байтным сегментом. Для столбцов сегмент не меняется, потому что на самом деле любая ссылка на столбец ячейки уже хранилась в двух байтах вместо одного. При этом, два самых старших битов двух байтов всегда применялись для различения между относительными строками и абсолютными строками и относительными столбцами и абсолютными столбцами, что позволяет адресовать количество столбцов, вмещающееся в 14 битов. Новое максимальное количество столбцов на каждый лист, разрешенное в Excel 2007, равно 2^14 = 16384. Команда Excel могла бы навсегда решить проблему ограничения числа строк/столбцов с помощью кодирования "переменной длины", как они сделали для структуры записи, но они решали не делать этого. Итак, обобщение:

версия Excel

кодирование строки

кодирование столбца

Excel 97-2003

2 байта

2 байта (бит 14 и 15 хранят флаги абсолютный/относительный)

Excel 2007

4 байта

2 байта (бит 14 и 15 хранят флаги абсолютный/относительный)

•    В Excel 2007 есть новые концепции, такие как возможность адресовать столбцы таблицы (или другие проекции, типа заголовков таблицы) в простых формулах. Это дает новый синтаксис, например, =SUM(Table1[2004]) вместо =SUM(range) or =SUM(A1:A3). Так как таблица в комплекте со столбцами не хранится как скрытые диапазоны, для ее представления требуется новый ptgTokens в байткоде формулы. В примере был разгадан один ptgToken = 0x18, обозначающий Table1[2004] в формуле =SUM(Table1[2004]). Если создать таблицу по имени Table1 с третьим столбцом по имени 2004, формула =SUM(Table1[2004]) хранится с помощью ptgTokens следующим образом:

    09 (2c) 05 00 00 00        column (0-based)
            00 00 00 00                         style (0-based)
            00 00 00 00 00 00 20 40             IEEE float
            10 00                               grbits (0010 = autoCalc)
            12 00 00 00                         len of formula in bytes
                        (=SUM(Table1[2004])
               18 19 00 00 01 00 01 00 00 00 02 00 02 00 ptgTokenTable
               = 18 + ... (Table1[2004])
               19 10 53 8b                      ptgTokenAttr optimized SUM
            00 00 00 00

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


Часть общих строк

Часть общих строк просто индексирует строки, чтобы разложить на множители строки, неоднократно используемые в листах или других частях (например, заголовок диаграммы). Это затрудняет чтение BIFF12, так как часть общих строк надо прочитать перед чтением других частей, таких как листы, в противном случае есть шанс, что будет невозможно разобраться во встречаемых индексах строк.
Часть общих строк также применяется для хранения богатых строк, т.е. строк, где применяется несколько стилей форматирования, иначе называемых "прогоны форматирования".

Тогда как XML-описание прогонов форматирования очень похоже на то, что в Word, BIN-описание очень похоже на старый BIFF, т.е. хранится неформатированная строка, за которой следуют прогоны форматирования, являющиеся 4-байтными парами {позиция, стиль}, определяющими стиль, применяемый от текущей позиции вплоть до заданной позиции в строке (отсчет от нуля). В результате, если не интересуют прогоны форматирования, то строка читается так, словно прогонов форматирования вообще нет. Ниже приведены примеры общих строк:

// BIN общих строк: a -

<sst count="1" uniqueCount="1">
9f 01 (08) 01 00 00 00 01 00 00 00

<si><t>a</t></si>
13 (07)
        00           nb formatting runs
        01 00 00 00  len of string (number of Unicode characters)
        61 00        string "a"

</sst>
a0 01 (00)

// BIN общих строк: a,b, cc -

<sst count="3" uniqueCount="3">
9f 01 (08) 03 00 00 00 03 00 00 00

<si><t>a</t></si>
13 (07)
        00            nb formatting runs
        01 00 00 00   len of string (number of Unicode characters)
        61 00         string "a"

<si><t>b</t></si>
13 (07)
        00            nb formatting runs
        01 00 00 00   len of string (number of Unicode characters)
        62 00         string "b"

<si><t>cc</t></si>
13 (09)
        00            nb formatting runs
        02 00 00 00   len of string (number of Unicode characters)
        63 00 63 00   string "cc"

</sst>
a0 01 (00)


// BIN общих строк: a,b, cc со строкой a, используемой дважды -

<sst count="4" uniqueCount="3">
9f 01 (08) 04 00 00 00 03 00 00 00

<si><t>a</t></si>
13 (07)
        00             nb formatting runs
        01 00 00 00    len of string (number of Unicode characters)
        61 00          string "a"

<si><t>b</t></si>
13 (07)
        00             nb formatting runs
        01 00 00 00    len of string (number of Unicode characters)
        62 00          string "b"

<si><t>cc</t></si>
13 (09)
        00             nb formatting runs
        02 00 00 00    len of string (number of Unicode characters)
        63 00 63 00    string "cc"

</sst>
a0 01 (00)


// BIN общих строк: abcd, где bc красный -

<sst count="1" uniqueCount="1">
9f 01 (08) 01 00 00 00 01 00 00 00

<si><.../t></si>
13 (19)
        01               nb formatting runs
        04 00 00 00      len of string (number of Unicode characters)
        61 00 62 00 63 00 64 00 string "abcd"
        02 00 00 00      formatting run 1 {pos=2, style=0}
        01 00 01 00      formatting run 2 {pos=1, style=1}
        03 00 00 00      formatting run 3 {pos=3, style=0}

</sst>
a0 01 (00)

Задействуются следующие записи:

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

Часть стилей

Часть стилей, подобно части общих строк, является словарем разложенных на множители стилей форматирования, представленных индексами. Самый простой стиль форматирования называется стиль XF, представленный индексом с отсчетом от нуля, группирующий несколько форматирований, таких как узор заливки, рамки, и так далее. Стили ячейки являются разновидностями стилей XF, поверх которых существуюи новые кнцепции в Excel 2007, такие как стили таблицы и темы (темы определены в отдельной части, оставленной в XML даже в двоичном файле .XLSB).

Ниже приведен пример части стилей в XML, а затем в BIN:

// Так часть стилей выглядит в XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet
    xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/5/main">
  <fonts count="1">
    <font>
      <sz val="11"/>
      <color theme="1"/>
      <name val="Calibri"/>
      <family val="2"/>
      <scheme val="minor"/>
    </font>
  </fonts>
  <fills count="2">
    <fill><patternFill patternType="none"/></fill>
    <fill><patternFill patternType="gray125"/></fill>
  </fills>

  <borders count="1">
    <border><left/><right/><top/><bottom/>
    <diagonal/></border>
  </borders>

  <cellStyleXfs count="1">
    <xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
  </cellStyleXfs>

  <cellXfs count="1">
    <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
  </cellXfs>

  <cellStyles count="1">
    <cellStyle name="Normal" xfId="0" builtinId="0"/>
  </cellStyles>

  <dxfs count="0"/>

  <tableStyles count="0" defaultTableStyle="TableStyleMedium9"
            defaultPivotStyle="PivotStyleLight16"/>

  <colors/>
</styleSheet>

// Таким образом после разбиения части BIN на записи можно сопоставить
// записи с разметкой XML.

// Обратите внимание, что идентификаторы записей являются двумя байтами слева,
// связанная длина записи обрамлена круглыми скобками,
// а за ними идет само содержимое записи.


<styleSheet>
96 02 (00)

<fonts>
e3 04 (04) 01 00 00 00

<font>
  <sz val="11"/>
  <color theme="1"/>
  <name val="Calibri"/>
  <family val="2"/>
  <scheme val="minor"/>
</font>
2b (27) dc 00 00 00 90 01 00 00 00 02 00 00 07 01 00 00 00 00 00
ff 02 07 00 00 00 43 00 61 00 6c 00 69 00 62 00 72 00 69 00

</fonts>
e4 04 (00)

<fills count="2">
db 04 (04) 02 00 00 00

<fill><patternFill patternType="none"/></fill>
2d (44) 00 00 00 00 03 40 00 00 00 00 00 ff 03 41 00 00 ff ff
ff ff 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 00 00 00 00 00 00 00
        00 00 00 00 00 00

<fill><patternFill patternType="gray125"/></fill>
2d (44) 11 00 00 00 03 40 00 00 00 00 00 ff 03 41 00 00 ff ff ff ff
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 00 00 00 00 00 00 00
        00 00 00 00 00 00

</fills>
dc 04 (00)

<borders count="1">
e5 04 (04) 01 00 00 00

<border><left/><right/><top/><bottom/>
<diagonal/></border>
2e (33) 00 00 00 01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00
00 00 00 01 00 00 00 00 00 00 00
        00 00 01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00

</borders>
e6 04 (00)

<cellStyleXfs count="1">
f2 04 (04) 01 00 00 00

<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
2f (10) ff ff 00 00 00 00 00 00 00 00 00 00 10 10 00 00

</cellStyleXfs>
f3 04 (00)

<cellXfs count="1">
e9 04 (04) 01 00 00 00

<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
2f (10) 00 00 00 00 00 00 00 00 00 00 00 00 10 10 00 00

</cellXfs>
ea 04 (00)

<cellStyles count="1">
eb 04 (04) 01 00 00 00

<cellStyle name="Normal" xfId="0" builtinId="0"/>
30 (18) 00 00 00 00 01 00 00 ff 06 00 00 00 4e 00 6f 00 72 00 6d 00
61 00 6c 00

</cellStyles>
ec 04 (00)

<dxfs>
f9 03 (04) 00 00 00 00

</dxfs>
fa 03 (00)

<tableStyles count="0" defaultTableStyle="TableStyleMedium9"
            defaultPivotStyle="PivotStyleLight16"/>
fc 03 (50) 00 00 00 00 11 00 00 00 54 00 61 00 62 00 6c 00 65 00 53
00 74 00 79 00 6c 00 65 00 4d 00
65 00 64 00 69 00 75 00 6d 00 39 00 11 00 00 00 50 00 69 00 76 00 6f
00 74 00 53 00 74 00 79 00
6c 00 65 00 4c 00 69 00 67 00 68 00 74 00 31 00 36 00

</tableStyles
fd 03 (00)

<colors>
d9 03 (00)

</colors>
da 03 (00)

</styleSheet>
97 02 (00)

Задействуются следующие записи:

//записи стилей
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;

Часть комментариев

Видимо, комментарии трудно хранить в модели книги. Хотя комментарии имеют свою собственную часть, на них нигде не ссылаются прямо. Лист, к которому предполагается прикрепление одного или больше комментариев, ссылается на устаревшую часть рисунка, разметку VML, описывающую сложную графическую структуру, которая как-то ухитряется установить связь с формой, чтобы нарисовать комментарии. Неясно, почему ожидается, что не только потребители/реализаторы BIN, но и потребители/реализаторы XML будут осмысленно работать с комментариями.

Ниже приведен пример части комментариев в XML и в BIN:

// Так часть комментариев выглядит в XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<comments
    xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/5/main">
  <authors>
    <author>Administrator</author>
  </authors>

  <commentList>
   <comment ref="C3" authorId="0">
     <text>
       <r>
         <rPr><b/><sz val="8"/>
         <color indexed="81"/><rFont val="Tahoma"/>
         <charset val="1"/></rPr>
         <t>Administrator:</t>
       </r>
       <r>
         <rPr><sz val="8"/>
         <color indexed="81"/><rFont val="Tahoma"/>
         <charset val="1"/></rPr>
         <t xml:space="preserve">_x000A_new comment</t>
       </r>
       <r>
         <rPr><sz val="11"/><color theme="1"/>
         <rFont val="Calibri"/><family val="2"/>
         <scheme val="minor"/></rPr>
         <t/>
       </r>
     </text>
   </comment>

  <comment ref="C5" authorId="0">
    <text>
      <r>
        <rPr><b/><sz val="8"/>
        <color indexed="81"/><rFont val="Tahoma"/>
        <charset val="1"/></rPr>
        <t>Administrator:</t>
      </r>
      <r>
        <rPr><sz val="8"/><color indexed="81"/>
        <rFont val="Tahoma"/>
        <charset val="1"/></rPr>
        <t xml:space="preserve">_x000A_a</t>
      </r>
      <r>
        <rPr><b/><sz val="8"/>
        <color indexed="10"/><rFont val="Tahoma"/>
        <family val="2"/></rPr>
        <t>noth</t>
      </r>
      <r>
        <rPr><sz val="8"/><color indexed="81"/>
        <rFont val="Tahoma"/><charset val="1"/></rPr>
        <t>er</t>
      </r>
      <r>
        <rPr><sz val="11"/><color theme="1"/><rFont val="Calibri"/>
    <family val="2"/>
        <scheme val="minor"/></rPr>
        <t/>
      </r>
    </text>
  </comment>
 </commentList>
</comments>

// Таким образом после разбиения части BIN на записи можно сопоставить
// записи с разметкой XML.

// Обратите внимание, что идентификаторы записей являются двумя байтами слева,
// связанная длина записи обрамлена круглыми скобками,
// а за ними идет само содержимое записи.


<comments>
f4 04 (00)

<authors>
f6 04 (00)

<author>Administrator</author>
f8 04 (1e) 0d 00 00 00 41 00 64 00 6d 00 69 00 6e 00 69 00 73 00 74
00 72 00 61 00 74 00 6f 00 72 00

</authors>
f7 04 (00)

<commentList>
f9 04 (00)

<comment ref="C3" authorId="0">
fb 04 (14) 00 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00

<text ...>
fd 04 (49) 01 1a 00 00 00 41 00 64 00 6d 00 69 00 6e 00 69 00 73 00 74
00 72 00 61 00 74 00 6f 00 72 00 3a 00 0a 00 6e 00 65 00 77 00 20 00
63 00 6f 00 6d 00 6d 00 65 00 6e 00 74 00 03 00 00 00 00 00 02 00 0e
00 01 00 1a 00 00 00

</comment>
fc 04 (00)

<comment ref="C5" authorId="0">
fb 04 (14) 00 00 00 00 04 00 00 00 04 00 00 00 02 00 00 00 02 00 00 00

<text ...>
fd 04 (49) 01 16 00 00 00 41 00 64 00 6d 00 69 00 6e 00 69 00 73 00 74
00 72 00 61 00 74 00 6f 00 72 00 3a 00 0a 00 61 00 6e 00 6f 00 74 00
68 00 65 00 72 00 05 00 00 00 00 00 02 00 0e 00 01 00 10 00 03 00 14
00 01 00 16 00 00 00

</comment>
fc 04 (00)

</commentList>
fa 04 (00)

</comments>
f5 04 (00)

Задействуются следующие записи:

//записи комментария
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;

Часть рисунков

Части рисунка хранятся в XML, но есть интересные правила:

•    Диграмма или форма ссылается на часть рисунка в листе таким образом:

<drawing r:id="rId1"/>

•    Комментарий ссылается на устаревшую часть рисунка в листе таким образом:

<legacyDrawing r:id="rId1"/>

•    Объект OLE ссылается на устаревшую часть рисунка и на встроенную часть в листе таким образом:

<drawing r:id="rId1"/>
<oleObjects>
  <oleObject progId="Paint.Picture" shapeId="1025" r:id="rId2"/>
</oleObjects>

•    Лист только ссылается на рисунок как на отдельную часть
•    Отдельная часть (drawings/drawingxxx.xml) не закодирована в BIN
•    Часть рисунка ссылается на диаграмму как на отдельную часть
•    Когда часть рисунка ссылается на объект ole, она также ссылается на медиа-объект (последняя отображенная картинка)
•    Часть диаграммы (charts/chartxxx.xml) не закодирована в BIN


Часть таблицы

Часть таблицы является новой концепцией в Excel 2007, добавляет большие улучшения к концепции объекта List, введенной в Excel 2003. Всегда, когда создается объект таблицы, на него ссылаются в листе следующим образом:

// Фрагмент части листа

...

<tableParts count="1">
94 05 (04) 01 00 00 00

<tablePart r:id="rId1"/>
95 05 (0c) 04 00 00 00 72 00 49 00 64 00 32 00

</tableParts>
96 05 (00)

...

Это прямо связано с отдельной частью, хранящейся в родительской папке tables, известной благодаря идентификатору отношения (rId1) и содержимому файла отношения (_rels/sheetxxx.bin.rels). Ниже приведена сама часть Table:

// Так часть таблицы выглядит в XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/5/main"
          id="1"
          name="Table1"
          displayName="Table1"
          ref="B2:D4"
          totalsRowShown="0">
  <autoFilter ref="B2:D4"/>
  <tableColumns count="3">
    <tableColumn id="1" name="Column1"/>
    <tableColumn id="2" name="2003"/>
    <tableColumn id="3" name="2004"/>
  </tableColumns>
  <tableStyleInfo
          name="TableStyleMedium9"
          showFirstColumn="0"
          showLastColumn="0"
          showRowStripes="1"
          showColumnStripes="0"/>
</table>

// Таким образом после разбиения части BIN на записи можно сопоставить
// записи с разметкой XML.

// Обратите внимание, что идентификаторы записей являются двумя байтами слева,
// связанная длина записи обрамлена круглыми скобками,
// а за ними идет само содержимое записи.


<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/5/main"
          id="1"
          name="Table1"
          displayName="Table1"
          ref="B2:D4"
          totalsRowShown="0">
d7 02 (64) 01 00 00 00 03 00 00 00 01 00 00 00 03 00 00 00 00 00 00
00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 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 00 00
00 00 ff ff ff ff 06 00 00 00 54 00 61 00 62 00 6c 00 65 00 31 00 00
00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff

<autoFilter ref="B2:D4">
a1 01 (10) 01 00 00 00 03 00 00 00 01 00 00 00 03 00 00 00

</autoFilter>
a2 01 (00)

<tableColumns count="3">
d9 02 (04) 03 00 00 00

<tableColumn id="1" name="Column1"/>
db 02 (3e) 01 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff
00 00 00 00 ff ff ff ff 07 00 00 00 43 00 6f 00 6c 00 75 00 6d 00 6e 00
31 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

</tableColumn>
dc 02 (00)

<tableColumn id="2" name="2003">
db 02 (38) 02 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff
00 00 00 00 ff ff ff ff 04 00 00 00 32 00 30 00 30 00 33 00 ff ff ff ff
ff ff ff ff ff ff ff ff ff ff ff ff

</tableColumn>
dc 02 (00)

<tableColumn id="3" name="2004"/>
db 02 (38) 03 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff
00 00 00 00 ff ff ff ff 04
00 00 00 32 00 30 00 30 00 34 00 ff ff ff ff ff ff ff ff ff ff ff ff ff
ff ff ff

</tableColumn>
dc 02 (00)

</tableColumns>
da 02 (00)

<tableStyleInfo name="TableStyleMedium9"
                   showFirstColumn="0"
                   showLastColumn="0"
                   showRowStripes="1"
                   showColumnStripes="0"/>
81 04 (28) 04 00 11 00 00 00 54 00 61 00 62 00 6c 00 65 00 53 00 74 00 79
00 6c 00 65 00 4d 00 65 00 64 00 69 00 75 00 6d 00 39 00

</table>
d8 02 (00)

Задействуются следующие записи:

//записи таблицы
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_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_TABLESTYLEINFO  = 0x0481;
public const int BIFF12_SORTSTATE       = 0x0492;
public const int BIFF12_SORTCONDITION   = 0x0494;
public const int BIFF12_SORTSTATE_END   = 0x0495;

Часть таблицы запроса

Часть таблицы запроса определяет источник данных. На таблицу запроса вообще не ссылаются в части листа. Это неявное поле в части Table, сделанное явным только в файле отношений, связанном с таблицей (i.e.tablexxx.bin.rels). Часть таблицы запроса выглядит так:

<queryTable name="Database1" connectionId="1" ...>
bf 03 (20) 49 1a 10 00 10 00
           01 00 00 00   connectionId
           09 00 00 00   length of string to follow
                           (in characters, not bytes)
           44 00 61 00 74 00 61 00 62 00 61 00 73 00 65 00 31 00
                               query table name

<queryTableRefresh nextId="5">
c1 03 (0a) 17 00 05 00 00 00 00 00 00 00

<queryTableFields count="4">
c7 03 (04) 04 00 00 00

<queryTableField id="1" name="ID" tableColumnId="1">
c9 03 (14) 10 00 00 00
           01 00 00 00 query table field id
           01 00 00 00 table column id
           02 00 00 00 length of string to follow
                           (in characters, not bytes)
           49 00 44 00 query table field name

</queryTableField>
ca 03 (00)

<queryTableField id="2" name="TB_Name" tableColumnId="2">
c9 03 (1e) 10 00 00 00
           02 00 00 00 query table field id
           02 00 00 00 table column id
           07 00 00 00 length of string to follow
                           (in characters, not bytes)
           54 00 42 00 5f 00 4e 00 61 00 6d 00 65 00
                           query table field name

</queryTableField>
ca 03 (00)

<queryTableField id="3" name="TB_AGE" tableColumnId="3">
c9 03 (1c) 10 00 00 00
           03 00 00 00 query table field id
           03 00 00 00 table column id
           06 00 00 00 length of string to follow
                           (in characters, not bytes)
           54 00 42 00 5f 00 41 00 47 00 45 00
                           query table field name

</queryTableField>
ca 03 (00)

<queryTableField id="4" name="TB_COUNTRY" tableColumnId="4">
c9 03 (24) 10 00 00 00
           04 00 00 00 query table field id
           04 00 00 00 table column id
           0a 00 00 00 length of string to follow
                           (in characters, not bytes)
           54 00 42 00 5f 00 43 00 4f 00 55 00 4e 00 54 00 52 00 59 00
                           query table field name

</queryTableField>
ca 03 (00)

</queryTableFields>
c8 03 (00)

</queryTableRefresh>
c2 03 (00)

</queryTable>
c0 03 (00)

Задействуются следующие записи:

//записи таблицы запроса
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;

Часть соединений

Часть соединений является строкой соединения с источником данных. В отличие от того, как части таблицы запроса связываются с частями таблицы, части соединений не связываются с частями таблицы запроса посредством отношений (нет файла querytablexxx.bin.rels). Зато у части таблицы запроса есть атрибут идентификатора соединения в элементе <queryTable> (запись BIFF12_QUERYTABLE). Файлы части соединений предоставляются на уровне книги, т.е. совместно используются по всему листу и связанным объектам, и поэтому части соединений связываются с отношениями книги (workbook.bin.rels). Часть соединений выглядит так:

<connections>
ad 03 (00)

<connection id="1"
               sourceFile="C:\Database1.mdb"
               keepAlive="1"
               name="Database1"
               type="5"
               refreshedVersion="3"
               background="1"
               saveData="1">
c9 01 (51) 03 00 02 00 00 00 51 00 09 00 05 00 00 00 01 00 00 00
01 00 00 00 00 10 00 00 00 43 00
3a 00 5c 00 44 00 61 00 74 00 61 00 62 00 61 00 73 00 65 00 31 00
2e 00 6d 00 64 00 62 00 09 00 00
00 44 00 61 00 74 00 61 00 62 00 61 00 73 00 65 00 31 00

<dbPr connection=Provider=Microsoft.ACE.OLEDB.12.0;
User ID=Admin;Data Source=C:\Database1.mdb;
Mode=Share Deny Write;Extended Properties="";
Jet OLEDB:System database="";Jet OLEDB:Registry Path="";
Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=0;
Jet OLEDB:Global Partial Bulk Ops=2;
Jet OLEDB:Global Bulk Transactions=1;
Jet OLEDB:New Database Password="";Jet OLEDB:Create System
Database=False;Jet OLEDB:Encrypt Database=False;
Jet OLEDB:Don't Copy Locale on Compact=False;
Jet OLEDB:Compact Without Replica Repair=False;
Jet OLEDB:SFP=False;Jet OLEDB:
Support Complex Data=False" command="Table1" commandType="3">
cb 01 (81 09) 03 00 00 00 02 34 02 00 00 50 00 72 00 6f 00 76 00
69 00 64 00 65 00 72 00 3d 00 4d 00
69 00 63 00 72 00 6f 00 73 00 6f 00 66 00 74 00 2e 00 41 00 43 00
45 00 2e 00 4f 00 4c 00 45 00 44 00
42 00 2e 00 31 00 32 00 2e 00 30 00 3b 00 55 00 73 00 65 00 72 00
20 00 49 00 44 00 3d 00 41 00 64 00
6d 00 69 00 6e 00 3b 00 44 00 61 00 74 00 61 00 20 00 53 00 6f 00
75 00 72 00 63 00 65 00 3d 00 43 00
3a 00 5c 00 44 00 61 00 74 00 61 00 62 00 61 00 73 00 65 00 31 00
2e 00 6d 00 64 00 62 00 3b 00 4d 00
6f 00 64 00 65 00 3d 00 53 00 68 00 61 00 72 00 65 00 20 00 44 00
65 00 6e 00 79 00 20 00 57 00 72 00
69 00 74 00 65 00 3b 00 45 00 78 00 74 00 65 00 6e 00 64 00 65 00
64 00 20 00 50 00 72 00 6f 00 70 00
65 00 72 00 74 00 69 00 65 00 73 00 3d 00 22 00 22 00 3b 00 4a 00
65 00 74 00 20 00 4f 00 4c 00 45 00
44 00 42 00 3a 00 53 00 79 00 73 00 74 00 65 00 6d 00 20 00 64 00
61 00 74 00 61 00 62 00 61 00 73 00
65 00 3d 00 22 00 22 00 3b 00 4a 00 65 00 74 00 20 00 4f 00 4c 00
45 00 44 00 42 00 3a 00 52 00 65 00
67 00 69 00 73 00 74 00 72 00 79 00 20 00 50 00 61 00 74 00 68 00
3d 00 22 00 22 00 3b 00 4a 00 65 00
74 00 20 00 4f 00 4c 00 45 00 44 00 42 00 3a 00 45 00 6e 00 67 00
69 00 6e 00 65 00 20 00 54 00 79 00
70 00 65 00 3d 00 35 00 3b 00 4a 00 65 00 74 00 20 00 4f 00 4c 00
45 00 44 00 42 00 3a 00 44 00 61 00
74 00 61 00 62 00 61 00 73 00 65 00 20 00 4c 00 6f 00 63 00 6b 00
69 00 6e 00 67 00 20 00 4d 00 6f 00
64 00 65 00 3d 00 30 00 3b 00 4a 00 65 00 74 00 20 00 4f 00 4c 00
45 00 44 00 42 00 3a 00 47 00 6c 00
6f 00 62 00 61 00 6c 00 20 00 50 00 61 00 72 00 74 00 69 00 61 00
6c 00 20 00 42 00 75 00 6c 00 6b 00
20 00 4f 00 70 00 73 00 3d 00 32 00 3b 00 4a 00 65 00 74 00 20 00
4f 00 4c 00 45 00 44 00 42 00 3a 00
47 00 6c 00 6f 00 62 00 61 00 6c 00 20 00 42 00 75 00 6c 00 6b 00
20 00 54 00 72 00 61 00 6e 00 73 00
61 00 63 00 74 00 69 00 6f 00 6e 00 73 00 3d 00 31 00 3b 00 4a 00
65 00 74 00 20 00 4f 00 4c 00 45 00
44 00 42 00 3a 00 4e 00 65 00 77 00 20 00 44 00 61 00 74 00 61 00
62 00 61 00 73 00 65 00 20 00 50 00
61 00 73 00 73 00 77 00 6f 00 72 00 64 00 3d 00 22 00 22 00 3b 00
4a 00 65 00 74 00 20 00 4f 00 4c 00
45 00 44 00 42 00 3a 00 43 00 72 00 65 00 61 00 74 00 65 00 20 00
53 00 79 00 73 00 74 00 65 00 6d 00
20 00 44 00 61 00 74 00 61 00 62 00 61 00 73 00 65 00 3d 00 46 00
61 00 6c 00 73 00 65 00 3b 00 4a 00
65 00 74 00 20 00 4f 00 4c 00 45 00 44 00 42 00 3a 00 45 00 6e 00
63 00 72 00 79 00 70 00 74 00 20 00
44 00 61 00 74 00 61 00 62 00 61 00 73 00 65 00 3d 00 46 00 61 00
6c 00 73 00 65 00 3b 00 4a 00 65 00
74 00 20 00 4f 00 4c 00 45 00 44 00 42 00 3a 00 44 00 6f 00 6e 00
27 00 74 00 20 00 43 00 6f 00 70 00
79 00 20 00 4c 00 6f 00 63 00 61 00 6c 00 65 00 20 00 6f 00 6e 00
20 00 43 00 6f 00 6d 00 70 00 61 00
63 00 74 00 3d 00 46 00 61 00 6c 00 73 00 65 00 3b 00 4a 00 65 00
74 00 20 00 4f 00 4c 00 45 00 44 00
42 00 3a 00 43 00 6f 00 6d 00 70 00 61 00 63 00 74 00 20 00 57 00
69 00 74 00 68 00 6f 00 75 00 74 00
20 00 52 00 65 00 70 00 6c 00 69 00 63 00 61 00 20 00 52 00 65 00
70 00 61 00 69 00 72 00 3d 00 46 00
61 00 6c 00 73 00 65 00 3b 00 4a 00 65 00 74 00 20 00 4f 00 4c 00
45 00 44 00 42 00 3a 00 53 00 46 00
50 00 3d 00 46 00 61 00 6c 00 73 00 65 00 3b 00 4a 00 65 00 74 00
20 00 4f 00 4c 00 45 00 44 00 42 00
3a 00 53 00 75 00 70 00 70 00 6f 00 72 00 74 00 20 00 43 00 6f 00
6d 00 70 00 6c 00 65 00 78 00 20 00
44 00 61 00 74 00 61 00 3d 00 46 00 61 00 6c 00 73 00 65 00 06 00
00 00 54 00 61 00 62 00 6c 00 65 00
31 00

</dbPr>
cc 01 (00)

</connection>
ca 01 (00)

</connections>
ae 03 (00)

Задействуются следующие записи:

//записи соединения
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;

Часть сводной таблицы

О части сводной таблицы известно следующее:
•    В отличие от других объектов, применяемых к ячейкам, типа таблиц, сводные таблицы не объявляются и не упоминаются в части листа, где они видимы! И это одинаково независимо от того, хранятся сводные таблицы в XML или в BIN.
•    Несмотря на это, файл отношений части листа (_rels/sheetxxx.bin.rels) фактически определяет связь с одной или несколькими сводными таблицами при любой возможности (части сводных таблиц хранятся в отдельной, родительской, папке).
•    Часть сводной таблицы (pivotTables/pivotTablexxx.bin) определяет ее макет и сводные поля, но не фактический источник данных.
•    Как лист, часть сводной таблицы не связывается явно с частью определения сводного кэша (где определяется источник данных), хотя она хранит ссылку в связанном файле отношений (_rels/pivotTablexxx.bin.rels)
•    Часть определения сводного кэша (pivotCache/pivotCacheDefinitionxxx.bin) определяет фактический источник данных сводной таблицы, как в:

// была создана сводная таблица, чьим источником данных является
// содержимое объекта Table1
  <cacheSource type="worksheet">
    <worksheetSource name="Table1" r:id="rId2"/>
  </cacheSource>

Часть настроек принтера

Часть настроек принтера является двоичным дампом структуры WIN32 DEVMODE (подробней смотрите в MSDN). Эта информация хранилась в более ранних версиях Excel (97 и выше) как запись BIFF8 [PLS].

Части индекса и цепи расчета

Они являются кэшами, лишь обслуживающими Excel во время выполнения. Они содержат прямые смещения на содержимое в частях BIN листа и поэтому непригодны для программирования. К счастью, можно изменить части BIN, не беспокоясь о повреждении книги, так как нарушение синхронизации частей индекса и цепи расчета не повредит.

Замечание о защищенных паролем документах

Хотя это строго не связано с форматами файла BIN, если защитить паролем книги Excel 2007, то конечный файл, будь то .XLSX или нет, будет зашифрован с помощью RC4 в контейнере OLE. Весь файл зашифрован в потоке EncryptedPackage. Ниже показан снимок экрана с защищенной паролем книгой, просматриваемой в просмотрщике документа OLE:

 

Защищенная паролем книга Excel 2007 зашифрована в контейнере OLE

Разумеется, не ожидается, что защищенные паролем книги будут использоваться программно.
В каком порядке должны читаться части?
•    Книга
•    Общие строки
•    Стили
•    Лист(ы)
•    Необязательные таблицы, рисунки, ...

Как получить значение ячейки?

При чтении части листа отдельные ячейки входят в состав блока записей внутри BIFF12_SHEETDATA и BIFF12_SHEETDATA_END. Excel хранит ячейки построчно, то есть имеется запись, идентифицирующая заданную строку (включая информацию, такую как стиль строки, высота, скрытая она или нет, ...), далее идет произвольное число фактических записей ячейки, идентифицируемых по хранимому ими значению и по тому, определяется ли их значение формулой. Для каждой записи ячейки предоставляется столбец (включая другие сведения). если ячейка хранит общую строку, то значение получается из индекса путем просмотра таблицы общих строк.

Объекты, такие как гиперссылки, таблицы, диаграмма, именованные диапазоны, сводные таблицы, определены поверх этих ячеек и определены в другом месте, после блока BIFF12_SHEETDATA соответствующей части листа, или в других частях (именованные диапазоны определяются в части книги, поэтому они совместно ипсользуются сквозь все листы, внтуренние и внешние).

Как получить стиль ячейки?

Как сказано ранее, ячейка хранит данные стиля форматирования. Отдельные ячейки хранят стиль форматирования, индекс с отсчетом от нуля в таблице стилей. Вообще, те стили являются стилями отдельной ячейки и ссылаются на коллекцию <cellXfs> индивидуальных стилей <xf>. В свою очередь, каждый <xf> имеет индекс к следующим коллекциям: форматы числа, рамки, шрифты, выравнивание и узор заливки.

Если ячейка хранит встроенную богатую строку или имеет индекс для общей строки, в свою очередь являющейся богатой строкой, то стиль форматирования ячейки определяется прогонами форматирования, хранимыми в составе богатой строки. Каждый прогон форматирования определяет стиль для доли текста.

Последние слова

Считыватель BIFF12, представленный в данной статье, является незавершенной работой над форматом файла .bin Office 2007, который содержит нижележащих форматов файла. Код, представленный на C++ и на C#, является основой библиотеки чтения/записи/манипулирования благодаря тому, что обработчики записи полностью отвечают за чтение/запись/манипулирование соответствующими записями (оттого есть так много классов).

Чтобы превратить существующий исходный код в настоящую библиотеку манипулирования, надо создать экзепмляры отдельных записей. Например, вместо того чтобы делать на C# следующее:

// извлечь связанный обработчик записи для идентификатора записи
// (h – хэш-таблица)
BaseRecord recHandler = (BaseRecord) h[recid];

Надо сделать так:

// создать экземпляр обработчика записи на базе идентификатора записи
// (h – хэш-таблица)
BaseRecord rec = (BaseRecord) Activator.CreateInstance(h[recid].GetType());

И, конечно, реализовать метод Write() для каждого обработчика записи.

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