@genomdev/xlsx
Excel workbooks (.xlsx and .xls) — parser, model, content adapter and viewer
194 exported symbols across 2 entry points
@genomdev/xlsx— 170 exports@genomdev/xlsx/view— 24 exports
@genomdev/xlsx
Classes
Collects a sheet's drawing records as they stream past. The three record kinds arrive interleaved and none of them is self-contained, so this accumulates and joins at the end rather than producing an object per record.
A reader over a record's pieces that knows where they join. The shared string table is one logical byte stream cut into records at arbitrary points — a string's length can be split across the cut, let alone its characters. This walks the pieces as though they were continuous, and answers `atBoundary` so the string reader can do the one thing that is not continuous: read a fresh options byte after every join.
RC4 as a keystream generator: the cipher is symmetric, so one direction.
A cursor over a BIFF stream. Records are read one at a time rather than collected: a workbook stream is tens of megabytes and its sheets are read on demand, so materialising every record of every sheet at open time would undo the laziness the model is built around.
The style tables as they are being filled in, before the resolver sees them.
Functions
The format code of a built-in id, or `undefined` when the id is not reserved.
`A` becomes 0, `AA` becomes 26.
0 becomes `A`, 26 becomes `AA`.
Compiles a format code, or returns the cached compilation.
Builds an evaluation context over a workbook. Every sheet is loaded, because a formula on one sheet may read any other and the engine's questions are synchronous. That is the one place where calculation is at odds with the viewer's laziness, and it is why calculation is asked for rather than assumed.
A ready-made evaluator over a workbook, for callers that want one call.
A function that answers "is this predicate true here". The shape the renderer wants: it has a formula and a cell, and needs a value. Everything else — building the context, loading the sheets, turning a result back into a cell value — happens once, here, rather than at the call site.
The inverse: a `Date` as the serial number Excel would store for it.
Decodes an RK number. Two tricks in thirty bits. The low bit says "divide by a hundred", which stores money exactly. The next bit chooses between a signed thirty-bit integer and *the top thirty bits of a double* — the low thirty-four bits of the mantissa being zero for every number a person typed. Together they cover the overwhelming majority of numeric cells in four bytes instead of eight.
Deciphers a workbook stream in place, returning a plaintext copy. Walked from byte zero rather than from `FILEPASS`, because the block the key is derived for is the *absolute* stream position divided by 1024 — starting the walk later and counting from there aligns the keystream to the wrong offset, and every byte after the first kilobyte comes out wrong while the first kilobyte looks perfect.
Derives the five bytes every block key is made from. The shape of it is the part worth writing down: the password's hash is truncated to five bytes and then interleaved with the salt *sixteen times* into a 336-byte buffer, which is hashed again. The repetition is the whole work factor — it is what a 1997 machine could afford — and getting the count or the order wrong produces a key that verifies against nothing.
An empty stylesheet, for a workbook that has no `styles.xml` at all.
Computes the value of one formula. The entry point for everything that has no cached answer: a conditional formatting predicate, a data validation, a cell whose `<f>` came without a `<v>`.
Содержимое книги Excel — без зонтика и без чужих форматов в графе. Для того, кто знает, какой у него файл. Принимает и уже открытый документ: вьюверу незачем разбирать файл второй раз, чтобы поискать в нём.
`{ row: 0, column: 0 }` becomes `A1`.
Excel's `General`. Not "the number as JavaScript prints it": Excel keeps about eleven significant digits and falls back to scientific notation outside a fixed range, which is why `0.1 + 0.2` shows as `0.3` in a spreadsheet and as something longer in a console.
Formats a value the way Excel would with the given code. `code` is the format string, not the id: resolving an id through the workbook's `numFmts` and the built-in table happens in the style layer, which is the only place that knows both.
The name a function index stands for, or a placeholder that keeps it visible.
Looks up an indexed colour, falling back to the default palette.
Whether a format code shows a date or a time rather than a number.
Whether the code contains a text section, which is what makes it apply to strings.
MD5, sixteen bytes out.
Puts the corners in order, so a range written backwards still works.
Opens a workbook. The whole stream is read into memory rather than sliced on demand. A `.xls` is capped at 65,536 rows of 256 columns and in practice is a few megabytes; the compound file's sectors are scattered, so a lazy read would be a seek per sector for no benefit anybody could measure.
Opens an Excel workbook.
Packs a cell position into one number. A `Map` keyed by `${row},${column}` allocates a string per lookup, and the renderer does one per visible cell per frame. The column fits in fourteen bits and the row in twenty, so both fit in a double with room to spare.
`A1` becomes `{ row: 0, column: 0 }`; anything else returns `undefined`.
`xl/comments1.xml`. The text of a comment is rich, and its author is an index into a list at the top of the part. The box it is drawn in lives somewhere else entirely — in a VML part that predates the format by a decade — and is not read: a viewer that shows the note on hover does not need the coordinates of a box that was hidden anyway.
Parses `A1:C5`, `A1` or a whole-column/row range such as `A:C` or `2:4`. Whole-column ranges are what an autofilter and most conditional formatting rules are written as, so refusing them would lose the feature rather than one odd file. They come back bounded by the sheet limits.
Parses a space-separated list of ranges, as `sqref` attributes hold.
`xl/tables/tableN.xml`. A table is a named, styled range with a header row, an optional totals row and filter buttons. It matters to a viewer because the banded style comes from the table rather than from the cells: strip the table and the rows lose their alternating fill.
Places a font in the table, leaving the hole at index four. Called once per `FONT` record in file order. The fifth record becomes font six, and the slot in between is filled with a copy of the first so that a file that does refer to it — corrupt, or written by something that never heard of the hole — gets a readable cell rather than nothing.
Reads a byte string in the workbook's code page — BIFF5 and earlier. There is no flag byte and no per-string encoding: the whole workbook is in one code page, named by a `CODEPAGE` record that a file written by Excel 5 for a Western locale often omits entirely. 1252 is the assumption then, and it is the right one often enough that the alternative — refusing to read the file — would be worse.
Reads one `CF` record.
A `ByteReader` over a record's payload. The convenience is worth the line.
Reads a `FILEPASS` record. The first word chooses between the Excel 95 exclusive-or obfuscation and the RC4 of Excel 97 and later; for RC4 the version that follows chooses again, between the original scheme and the CryptoAPI one Excel 2002 added.
Reads a `FONT` record. The slot the record lands in is not the slot it was read in — see the hole at four, above — so this appends and the caller never indexes by arrival order.
Reads a `FORMAT` record: a number format code and the id cells refer to it by.
Reads the globals substream, leaving the cursor after its `EOF`. The version comes from the `BOF` that opened it, and almost every record below is read differently depending on it — which is why it is threaded through rather than looked up.
Reads a `PALETTE` record: the workbook's replacement for the default colours. The record holds fifty-six colours and they begin at index eight — the first eight are fixed and cannot be redefined, which is why a palette written into slot zero recolours nothing.
Reads one `<si>` or `<is>` subtree. Must be called while positioned on the container's start element; it consumes through the matching end element. Phonetic guides (`<rPh>`) are skipped. They hold the reading of a Japanese word and are shown above the text, not in it — concatenating them produces a cell that reads the same thing twice.
Reads one worksheet, starting at the `BOF` the sheet directory pointed at.
Reads a string from the shared string table, across record boundaries. The whole reason `PartReader` exists. A string may be cut in half by the end of a record, and the half in the `CONTINUE` begins with a flag byte of its own that can disagree with the first one — a string whose first eighty characters are Latin-1 and whose remainder is UTF-16 is not a corrupt file, it is what Excel writes when a string crosses the boundary in the middle of an accented word.
Reads a `STYLE` record: the name of one of the entries in the XF table.
The body of an `XLUnicodeString` whose length was read elsewhere.
Reads an `XLUnicodeString` whose character count is one or two bytes. `rich` and `phonetic` say whether the flag byte is allowed to announce those trailers. Most strings may; a few records store a string whose flag byte can only mean compression, and reading a trailer there would consume the next field.
Reads a string that is one shape or the other depending on the version. Records that exist in both generations — `BOUNDSHEET`, `FORMAT`, `NAME`, `FONT` — write their strings this way, and the version is the only thing that says which.
Reads an `XF` record — twenty bytes that say everything about a cell's look. The six "not parent" bits are this format's version of `applyFont` and its neighbours, and they read the same way round: set means the value beside them is the format's own rather than something to inherit. The fill and the border come back beside the record rather than in it, because the model addresses them by index into deduplicated tables and only the caller holds those.
Renders a token stream as a formula, without the leading equals sign. Returns `undefined` rather than a broken string when the stream cannot be walked. A cell whose formula we failed to read still has the value Excel computed for it, and showing that value with no formula is a better answer than showing a fragment of one.
Converts a serial number to a JavaScript `Date` in UTC.
Converts a serial number to calendar parts. Returns `undefined` for values Excel itself refuses to show as a date: negative serials in the 1900 system produce `#####`, not a date before the epoch.
Turns the file's `(index, font)` pairs into the model's runs. The file marks where each run *starts* and the model carries the text of each run, so the last run has to be closed against the length of the string — and a run list that does not start at zero implies an unformatted run before it, which is how "one bold word in the middle" is stored.
Rewrites a formula as if it had been filled from one cell to another. Relative references move with the cell and absolute ones (`$A$1`) do not, which is the whole distinction the dollar signs exist for. A reference that would move off the sheet becomes `#REF!`, as it does in Excel.
Checks a password against the verifier the file carries. Sixteen bytes of random data and their hash, both enciphered under block zero. Deciphering them and hashing the first gives the second exactly when the password is right — which is how Excel answers instantly rather than by trying to parse the workbook.
Reading a workbook as content. A sheet is a table, which sounds like the easy case and is the one everybody gets wrong. Four things have to be right or the output is worse than useless: Values go through their number format. The cell holds 45306 and the sheet shows `15 January 2024`, and a search for the date has to find it. This is the difference between extracting a workbook and extracting its storage. Merged cells are filled, not blanked. Excel keeps the value in the top-left of a merge and nothing anywhere else; a naive reader produces a table whose header row is one word followed by nine empty columns. The used range is not the used range. Excel's idea of it is generous — a sheet whose author once typed in `ZZ4000` reports four thousand rows of nothing — so the real extent is measured from the cells that have content. Hidden sheets are skipped. They hold the lookup tables behind the dropdowns, and they are noise in every index they land in.
Interfaces
A string and the formatting runs it carried, if any.
One entry of the sheet directory.
A cell. Row and column are stored flat rather than in a nested address object: a large sheet has millions of these, and the object that would hold two numbers costs more than the numbers.
Zero-based cell position.
A comment or a threaded note attached to a cell.
A fully resolved format: what a cell actually looks like.
How big a cell is, which is what turns a fractional offset into a length.
Where a formula sits, which is what a relative token is relative to.
A rectangular range, inclusive on both ends and zero-based.
One `xf` as written, before its parent style is folded in.
A colour as a workbook writes it. Four ways of saying the same thing, and a file will use all four. `rgb` is literal; `theme` points into the theme's colour scheme and is what a modern file uses so that changing the theme restyles the workbook; `indexed` is the pre-2007 palette; `auto` means "whatever the window text colour is". `tint` lightens or darkens whichever of them was given.
A `<col>` element: formatting for a span of columns.
A conditional formatting threshold (`cfvo`).
Whole and fractional parts of a serial number, in calendar terms.
A defined name: a range or a formula the workbook gave a name to.
A differential format: the parts a conditional rule or a table style changes. Everything is optional by construction — a rule that paints the background red must leave the font alone, and the only way to say that is to say nothing.
An image, chart or shape anchored to the grid.
How a workbook is protected, as `FILEPASS` states it.
The result of formatting a value with a format code.
What a formula needs from the workbook around it to name things.
What a shape's blip index resolves to.
A named style, as the style gallery lists it.
A frozen or split pane.
What the sheet part yields, before the parts it points at are resolved.
One formatting run of a rich string. The font is inline rather than an index: a shared string writes its formatting into itself (`<rPr>`) instead of pointing at the font table, so a cell whose text is half bold carries two runs with two fonts of their own.
What a save needs of a workbook, beyond the model.
A workbook, saved. ## Where this starts, and why it starts there The same claim the Word writer makes first, and the same reason it is worth making first: **a workbook opened and saved unedited comes back byte for byte.** Every part is copied through as the compressed bytes the archive already held — the sheets, the shared strings, the styles, the calculation chain, the pivot caches, the VBA project, the query tables, the custom XML a line-of-business application put there — and nothing here ever looks inside them. What cannot be looked at cannot be lost. That is not a small claim for a spreadsheet. A workbook is a graph of parts that refer to each other by index — a cell's style is a number into `cellXfs`, a string is a number into `sharedStrings`, a formula names a defined name that names a table that names a part — and a writer that regenerates one of them has to regenerate every index that points at it. A writer that copies has to regenerate nothing, which is why it is the floor to build on rather than a step on the way to something better. ## What this does not do yet Change anything. There is no `SheetData` written back here: the model has no source spans, so there is no way to rebuild one sheet and leave the rest of its part as it stood, and rewriting a sheet whole from a model that does not hold `xml:space`, the extension lists or a hundred attributes nobody reads would lose the parts of it nobody has read. See `write/save.ts` in `@genomdev/docx` for what the finished shape is. So this is the foundation and it is honest about being one. What it buys already: a workbook can be opened, examined and handed back with a guarantee nothing was touched, and every part of the machinery that will carry an edit — the ZIP writer's byte-level fidelity, the package writer's part ordering, the content types — is exercised over the whole corpus from the first day.
What a shape looks like. A shape says its appearance in two places and they compose: `xdr:spPr` is what the shape itself states and has the last word, and `xdr:style` is what it takes from the theme — as *references* (`a:fillRef`, `a:lnRef`, `a:fontRef`) that carry a colour of their own. That colour is what the reference is for: the first fill styles of every theme are a plain solid fill of the placeholder colour, so the colour in the reference is the fill in the overwhelming majority of files.
A workbook sheet. Contents are loaded on demand.
Everything the sheet parser needs from the workbook around it.
Parsed sheet contents.
One sparkline: the range it draws, and the cell it draws in.
A chart the size of a cell, drawn inside it. The group holds the look and the list holds the cells: every sparkline of a group is drawn the same way, from a range of its own.
A table style the workbook defines itself. Excel's own sixty styles are a name and nothing else — their definitions live in the application — but a style a person built is written out in full, as parts pointing at differential formats.
The colour scheme of `theme1.xml`, in the order the theme writes it.
Type aliases
A cell value after parsing.
A merged block. Named for what it means; the shape is an ordinary range.
Values
The BIFF version, as the number Excel writes into BOF. Only the boundaries matter to a reader. 0x0500 is where strings became sixteen bit and the shared string table appeared; 0x0600 is where a row index became sixteen bits everywhere and the record set settled. Everything below 0x0500 is a different format wearing the same extension, and the two differences that bite are that its strings are bytes in a code page and that it has no substreams at all.
The password Excel uses when the author gave none. A workbook saved as read-only-recommended is encrypted with this, and every Excel opens it without a prompt. It has been public since 1997 and is a marker rather than a secret.
Cell error codes, as an error cell stores them.
The function table. A formula does not store the name of a function; it stores a number, and the number is an index into a table Excel has carried since 1987. `SUM` is four and always has been. New functions were appended, so the table also dates itself: an index above 367 means the file was written by Excel 2007 or later saving into the old format. Two of the entries are not functions at all. 255 is how a formula calls an add-in: the "function" takes the name as its first argument, and rendering it literally produces `<name>(args)` — which is what the formula means. 148 is the same trick for a macro sheet call. Names are the invariant, English ones. Excel displays a formula in the running application's language and stores it in this table's, which is the whole reason the table exists.
The legacy indexed colour palette. Before themes, a workbook referred to colours by index into a 56-entry table that lived in the file (`<indexedColors>`) and, when it did not, was assumed to be this one. Files still arrive with `indexed="10"` on a font, and number format codes still say `[Color 10]`, so the default table has to be here even though nothing has written it deliberately in twenty years. Indices 0-7 repeat as 8-15: the first eight are the "system" colours and the second eight are the same colours in the user-editable part of the palette. Index 64 is "automatic" — the window text colour — and 65 the window background; both are resolved by the renderer rather than by a table.
The last column and row a worksheet can have: XFD1048576.
Record numbers, and the handful of enumerations that go with them. A BIFF stream is a flat sequence of records: two bytes of type, two bytes of length, then that many bytes of payload. There is no nesting and no schema — the meaning of a record depends on which substream it is in and, for a dispiriting number of them, on which version of Excel wrote it. The same concept is often three different numbers: a `NUMBER` cell is 0x0003 in BIFF2, 0x0203 from BIFF3 on; `FORMULA` is 0x0006, 0x0206 or 0x0406. Only the records this parser acts on are named here. An unnamed record is not an error — a workbook is full of records about window positions, print queues and calculation settings that nothing on a screen depends on.
`BOUNDSHEET`'s type nibble.
BOF's `dt` field: which kind of substream is starting.
The stream inside the compound file that holds the workbook. Excel 97 and later call it `Workbook`; Excel 5.0 and 95 call it `Book`. A file saved by Excel 97 "for compatibility" contains *both*, and the modern one is the one to read — the other is a lossy copy kept for Excel 5.
Excel workbooks, both generations, as one module. Recognition is stated as data so the registry can pick this module out of six without loading any of them: an OPC package announces its main part's content type, and that is the only thing that tells the three OOXML formats apart. The compound file is the case rules cannot settle — a `.doc`, an `.xls` and a `.ppt` share one signature — so it shortlists itself and `canOpen` reads the directory to be sure. One module rather than two because the binary reader produces the same model: nothing above here has to know which generation it was handed.
@genomdev/xlsx/view
Classes
One axis of the grid. Ranges must be sorted and must not overlap; the constructor enforces neither because both callers build them in order and the check would be the most expensive thing here.
The geometry of one sheet at one zoom level.
Functions
The button Excel puts in a cell that opens something. Two of them look alike and mean different things. A filter button sits in the header row of an `autoFilter` range and is always there; the arrow of a list validation belongs to any cell the list covers. Both are what a reader looks for to know a sheet can be filtered or chosen from. A column that is currently filtering is drawn apart from one that merely could, because in Excel it is: the funnel replaces the arrow, and it is the only thing on screen saying rows are missing.
A column width in characters converted to pixels. `px = trunc(width * digitWidth) + 5` (MS-OI29500). The truncation is not a rounding choice — it is what makes 8.43 characters come out as exactly 64 pixels, and dropping it puts every column a pixel out.
Draws one sparkline into an SVG element. `values` are the numbers the range held, with `undefined` where a cell was empty — which is a hole in the line rather than a zero, unless the group says otherwise.
How a cell turns its text, `xf/alignment/@textRotation`. Excel states one number for two directions and a special case, and the encoding is not an angle: 0 to 90 turn the text anticlockwise, 91 to 180 turn it *clockwise* by what is left after ninety — so 180 is a quarter turn down to the right, not half a turn — and 255 stacks the letters one under another without turning them at all. A turn is about the corner the line starts from: rising to the right it starts at the bottom left of the cell, falling to the right at the top left. Turning about the middle instead swings half the line out through the edge, and the cell clips it — a header reading `tated` where the file said `Rotated`. The turn also carries the line *left* by its own height, since the box pivots about a corner it no longer occupies, so the line is pushed back by that much first: without it a quarter turn leaves the text entirely outside its cell, and at forty-five degrees it eats the first letter.
The declarations that draw a shape. The geometry is honoured as far as CSS can take it: a rectangle is a rectangle, rounded corners are a radius, an ellipse is a radius of half. Everything else is drawn as its bounding rectangle — which the corpus says is a fair trade, since of the 2 862 shapes in it 2 827 are plain rectangles and 34 are rounded ones.
Interfaces
Everything a cell's class has to encode, beyond its format.
What the rules decided about one cell.
A run of consecutive indices that share a size.
One sparkline ready to draw: the look it belongs to, and its numbers.
Type aliases
Values
Excel's default column width, in pixels. The file says "8.43 characters" and means the width of the digit zero in the workbook's Normal font, plus five pixels of padding. For Calibri 11 that digit is seven pixels wide, which is where the familiar 64 comes from.
The renderer's own stylesheet. Only the chrome and the structure are here. Everything that comes from the workbook — fonts, fills, borders, alignment — is emitted as generated classes at render time, because it differs per file and there are a few dozen of them per sheet rather than a fixed set.
Excel workbooks with its renderer attached. What `@genomdev/xlsx/view` exports and what an application passes to the viewer. The universal half is the same object — the renderer is a field on it, not a second registration — so a format either brings a way to draw itself or it does not, and nothing has to pair them up by hand.