| build | ||
| src | ||
| .gitignore | ||
| .mise.toml | ||
| .pre-commit-config.yaml | ||
| build.zig | ||
| build.zig.zon | ||
| LICENSE | ||
| README.md | ||
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
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..xlsxis a different format (zipped XML) and fails witherror.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
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.