Формат файла .bin Office 2007 - Часть книги

ОГЛАВЛЕНИЕ

Часть книги

Легко понять, как поступить с каждой нужной частью 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 юникода.