# biff8 A read-only Zig reader for legacy binary Excel workbooks: `.xls` files from Excel 97 through 2003 (BIFF8), which some sites still export as their only "spreadsheet" download. It unwraps the Compound File Binary ("OLE2") container, decodes the BIFF8 record stream, and gives you each worksheet's cell values. That is all it does. ## Usage ```zig const biff8 = @import("biff8"); var wb = try biff8.Workbook.parse(allocator, bytes); defer wb.deinit(); const sheet = wb.sheet("Positions") orelse return error.NoSuchSheet; for (sheet.rows, 0..) |row, r| { for (row, 0..) |cell, c| switch (cell) { .text => |t| std.debug.print("{d},{d}: {s}\n", .{ r, c, t }), .number => |n| std.debug.print("{d},{d}: {d}\n", .{ r, c, n }), .boolean, .error_code, .empty => {}, }; } ``` `sheet.cell(row, col)` does bounds-safe random access and returns `.empty` outside the populated area. `Sheet` has public fields, so a consumer can build one as a literal in its own tests instead of shipping binary fixtures. `biff8.isCompoundFile(bytes)` is a cheap signature check for content sniffing. Every `.xls` passes it, but so does every other legacy Office file, so it is not proof of a workbook. ## What is decoded | Record | Becomes | |---|---| | LABELSST, LABEL, RSTRING | `.text` (UTF-8) | | NUMBER, RK, MULRK | `.number` | | BOOLERR | `.boolean` or `.error_code` | | FORMULA (+ STRING) | the cached result, as any of the above | Shared strings split across CONTINUE records are handled, including the case where the split falls mid-string and the remainder switches between 1-byte and 2-byte characters. Unpaired UTF-16 surrogates decode as U+FFFD. ## What is not - **Formatting.** Numbers are returned as stored, so a date-formatted cell is an Excel serial day number; telling dates apart needs the number format, which is not decoded. - **Formulas.** Only their cached results. - **Anything but BIFF8.** Excel 95 and earlier fail with `error.UnsupportedBiffVersion`. `.xlsx` is a different format (zipped XML) and fails with `error.NotCompoundFile`. - **Encrypted workbooks.** `error.Encrypted`. - **Writing.** ## Errors `Workbook.parse` returns `biff8.ParseError`: | Error | Meaning | |---|---| | `NotCompoundFile` | Not an OLE2 file at all | | `NoWorkbookStream` | An OLE2 file, but not a workbook (a `.doc`, an `.msg`, ...) | | `UnsupportedBiffVersion` | Excel 95 or older | | `Encrypted` | Password-protected | | `Truncated` | The file ends early, usually an interrupted download | | `CorruptFile` | Internally inconsistent structure | | `OutOfMemory` | | Every sector chain walk is bounded and every declared size is checked before allocating, so hostile input produces an error rather than a hang or a huge allocation. ## Specs - [MS-CFB] Compound File Binary File Format - [MS-XLS] Excel Binary File Format (.xls) Structure ## Development ```sh zig build test zig build coverage # kcov, Linux x86_64/aarch64 ``` Test fixtures are built in code by `src/test_writer.zig` rather than checked in as binary files.