Skip to content
Genom
API reference

@genomdev/xlsx

Excel workbooks (.xlsx and .xls) — parser, model, content adapter and viewer

194 exported symbols across 2 entry points

@genomdev/xlsx

Classes

DrawingScan
class DrawingScan

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.

readObject
(payload: Uint8Array) => void
`OBJ`: a list of sub-records whose first is always the common one.
readText
(record: BiffRecord, biff8: boolean) => void
`TXO`: the text of the object declared last. The header record holds only lengths; the characters are in the first `CONTINUE` and the formatting runs in the second. The characters have their own flag byte, so they are read from that piece rather than from the joined bytes — the same rule the shared string table follows.
readArt
(payload: Uint8Array) => void
`MSODRAWING`: a piece of the sheet's Office Art tree.
text
(objectId: number) => string | undefined
The text of an object, for a `NOTE` that named it by number.
finish
(geometry: CellGeometry) => DrawingObject[]
The drawings, joined. A shape and an object are matched by *order* rather than by identifier: the drawing tree and the `OBJ` records are written in the same sequence, and the shape's own `spid` is a drawing-wide number that the `OBJ` record does not carry. Comments are left out — they are shown against their cell, not as a floating box, and `SheetData` files them separately.
PartReader
class PartReader

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.

atBoundary
boolean
True when the next byte is the first byte of a `CONTINUE` record.
eof
boolean
u8
() => number
u16
() => number
u32
() => number
skip
(count: number) => void
bytes
(count: number) => Uint8Array
Reads `count` bytes, which may span a join. Copies rather than returning a view: the bytes are not contiguous in the general case, and a caller that got a view for the common case and a copy for the rare one would work on every file until the first large one.
Rc4from @genomdev/core
class Rc4

RC4 as a keystream generator: the cipher is symmetric, so one direction.

next
() => number
The next keystream byte. XOR it with a byte to encipher or decipher.
RecordStream
class RecordStream

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.

offset
number
byteLength
number
seek
(offset: number) => void
eof
boolean
peekType
() => number
The type of the next record without consuming it, or -1 at the end.
read
() => BiffRecord | undefined
Reads the next record, absorbing the `CONTINUE` records that belong to it. `MSODRAWING` is the one record whose continuation is ambiguous: a drawing too large for one record is continued, but so is the *next* drawing record in some writers' output. Absorbing greedily is right for both, because the Office Art parser reads a flat stream of its own records either way.
skipSubstream
() => void
Skips forward to the end of the current substream. Used when a substream is of a kind we do not read — a chart sheet, a Visual Basic module — and has to be stepped over rather than interpreted. Nested BOFs are counted, because a worksheet holding an embedded chart contains a complete substream of its own.
Stylesheet
class Stylesheet
fonts
readonly Font[]
fills
readonly Fill[]
borders
readonly Border[]
differentialFormats
readonly DifferentialFormat[]
namedStyles
readonly NamedCellStyle[]
tableStyles
ReadonlyMap<string, TableStyleDefinition>
Table styles the workbook defines itself; Excel's own are not here.
theme
ThemeColors | undefined
size
number
How many distinct cell formats the workbook defines.
defaultFont
Font
The font of the Normal style, which decides the default row height and the width of a column "character".
font
(index: number | undefined) => Font
numberFormatCode
(id: number) => string
The format code behind a number format id, built-in or custom.
differentialFormat
(index: number | undefined) => DifferentialFormat | undefined
format
(styleIndex: number | undefined) => CellFormat
Resolves a cell's style index into a complete format. The cached result is returned by identity, which is what lets the renderer hold a `WeakMap` from format to CSS class instead of hashing declarations per cell.
css
(color: Color | undefined) => string | undefined
Turns a workbook colour into a CSS colour. `undefined` means "no colour was specified", which is not the same as black: a font with no colour follows the window text colour, and the renderer is the only layer that knows what that is.
themeColor
(slot: string) => string | undefined
A theme colour by its DrawingML slot name. Charts refer to the theme by name — `accent1`, `tx1`, `bg2` — while cells refer to it by index. Same table, two ways in, and the pairs are aliases of each other: `tx1` is `dk1` and `bg1` is `lt1`.
StyleTables
class StyleTables

The style tables as they are being filled in, before the resolver sees them.

numberFormats
Map<number, string>
fonts
Font[]
xfs
CellStyleRecord[]
namedStyles
NamedCellStyle[]
fills
Fill[]
Fills and borders are deduplicated as XFs are read; a cell needs an index.
borders
Border[]
indexedColors
string[]
fill
(value: Fill) => number
border
(value: Border) => number
parts
() => StylesheetParts
XlsMedia
class XlsMedia
size
number
part
(index: number) => MediaPart | undefined
The part a shape's one-based blip index stands for.
bytes
(partName: string) => Promise<Uint8Array | undefined>
The bytes behind a part name, decoded once. Asynchronous because a metafile is usually deflated, and translating one to SVG has to wait for that. The result is cached: a logo placed on twelve sheets is one decode.
contentType
(partName: string) => Promise<string | undefined>
The content type as decoded, which is not always the one stored.
url
(partName: string) => Promise<string | undefined>
A URL a browser can show the picture from. An object URL where the host has them and a data URL otherwise, so the same document renders in a tab and on a server. Every object URL handed out is revoked on dispose — a workbook of two hundred photographs otherwise holds them all until the page is left.
dispose
() => void

Functions

builtinFormatCodefrom @genomdev/office-core
function builtinFormatCode(id: number): string | undefined

The format code of a built-in id, or `undefined` when the id is not reserved.

columnFromLabel
function columnFromLabel(letters: string): number

`A` becomes 0, `AA` becomes 26.

columnLabel
function columnLabel(index: number): string

0 becomes `A`, 26 becomes `AA`.

compileFormatfrom @genomdev/office-core
function compileFormat(code: string): CompiledFormat

Compiles a format code, or returns the cached compilation.

createEvaluationContext
function createEvaluationContext(document: XlsxDocument, options?: EvaluationOptions): Promise<WorkbookContext>

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.

createEvaluator
function createEvaluator(document: XlsxDocument, options?: EvaluationOptions): Promise<Evaluator>

A ready-made evaluator over a workbook, for callers that want one call.

createPredicateEvaluator
function createPredicateEvaluator(document: XlsxDocument, options?: EvaluationOptions): Promise<(formula: string, sheet: number, row: number, column: number) => CellValue>

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.

dateToSerialfrom @genomdev/office-core
function dateToSerial(date: Date, date1904?: boolean): number

The inverse: a `Date` as the serial number Excel would store for it.

decodeRk
function decodeRk(raw: number): number

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.

decryptStream
function decryptStream(bytes: Uint8Array, key: Uint8Array): Uint8Array

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.

deriveKey
function deriveKey(password: string, salt: Uint8Array): Uint8Array

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.

emptyStylesheet
function emptyStylesheet(): Stylesheet

An empty stylesheet, for a workbook that has no `styles.xml` at all.

evaluateFormula
function evaluateFormula(evaluator: Evaluator, formula: string, position: CellPosition): CellValue

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>`.

extractXlsx
function extractXlsx(input: ByteSourceInput | XlsxDocument, options?: ExtractOptions): Promise<ContentDocument>

Содержимое книги Excel — без зонтика и без чужих форматов в графе. Для того, кто знает, какой у него файл. Принимает и уже открытый документ: вьюверу незачем разбирать файл второй раз, чтобы поискать в нём.

formatCellReference
function formatCellReference(address: CellAddress): string

`{ row: 0, column: 0 }` becomes `A1`.

formatGeneralfrom @genomdev/office-core
function formatGeneral(value: number, widthCharacters?: number): string

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.

formatRange
function formatRange(range: CellRange): string
formatValuefrom @genomdev/office-core
function formatValue(value: string | number | boolean | Date | null, code: string | undefined, options?: FormatOptions): FormattedValue

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.

functionName
function functionName(index: number): string

The name a function index stands for, or a placeholder that keeps it visible.

indexedColorfrom @genomdev/office-core
function indexedColor(index: number, palette?: readonly string[]): string | undefined

Looks up an indexed colour, falling back to the default palette.

isBof
function isBof(type: number): boolean
isDateFormatfrom @genomdev/office-core
function isDateFormat(code: string | undefined): boolean

Whether a format code shows a date or a time rather than a number.

isTextFormatfrom @genomdev/office-core
function isTextFormat(code: string | undefined): boolean

Whether the code contains a text section, which is what makes it apply to strings.

md5from @genomdev/core
function md5(message: Uint8Array): Uint8Array

MD5, sixteen bytes out.

normalizeRange
function normalizeRange(range: CellRange): CellRange

Puts the corners in order, so a range written backwards still works.

openXls
function openXls(source: ByteSource, options?: OpenXlsOptions): Promise<XlsxDocument>

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.

openXlsx
function openXlsx(source: ByteSource, options?: OpenOptions): Promise<XlsxDocument>

Opens an Excel workbook.

packAddress
function packAddress(row: number, column: number): number

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.

parseCellReference
function parseCellReference(reference: string): CellAddress | undefined

`A1` becomes `{ row: 0, column: 0 }`; anything else returns `undefined`.

parseComments
function parseComments(root: XmlElement): CellComment[]

`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.

parseDrawing
function parseDrawing(root: XmlElement, resolve: RelationshipResolver): DrawingObject[]
parseRange
function parseRange(reference: string): CellRange | undefined

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.

parseRangeList
function parseRangeList(reference: string): CellRange[]

Parses a space-separated list of ranges, as `sqref` attributes hold.

parseSharedStrings
function parseSharedStrings(parser: XmlPullParser): SharedString[]

Streams the shared string table.

parseSheet
function parseSheet(parser: XmlPullParser, context: SheetContext): ParsedSheet
parseStyles
function parseStyles(root: XmlElement | undefined, theme: ThemeColors | undefined): Stylesheet
parseTable
function parseTable(root: XmlElement): SheetTable | undefined

`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.

parseTheme
function parseTheme(root: XmlElement): ThemeColors
parseWorkbook
function parseWorkbook(root: XmlElement): WorkbookProperties
placeFont
function placeFont(tables: StyleTables, font: Font): void

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.

rangeContains
function rangeContains(range: CellRange, row: number, column: number): boolean
rangesIntersect
function rangesIntersect(a: CellRange, b: CellRange): boolean
readByteString
function readByteString(reader: ByteReader, lengthBytes: 1 | 2, codePage: number): BiffString

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.

readConditionalFormat
function readConditionalFormat(payload: Uint8Array, globals: WorkbookGlobals, priority: number): ConditionalRule | undefined

Reads one `CF` record.

readerOf
function readerOf(record: BiffRecord): ByteReader

A `ByteReader` over a record's payload. The convenience is worth the line.

readFilePass
function readFilePass(payload: Uint8Array): FilePass | undefined

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.

readFont
function readFont(reader: ByteReader, biff8: boolean, codePage: number): Font

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.

readFormat
function readFormat(reader: ByteReader, biff8: boolean, codePage: number): { id: number; code: string; }

Reads a `FORMAT` record: a number format code and the id cells refer to it by.

readGlobals
function readGlobals(stream: RecordStream, version: number): WorkbookGlobals

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.

readPalette
function readPalette(reader: ByteReader): string[]

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.

readRichText
function readRichText(parser: XmlPullParser): SharedString

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.

readSheet
function readSheet(stream: RecordStream, position: number, globals: WorkbookGlobals): SheetData

Reads one worksheet, starting at the `BOF` the sheet directory pointed at.

readSstString
function readSstString(reader: PartReader): BiffString

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.

readStyle
function readStyle(reader: ByteReader, biff8: boolean, codePage: number): NamedCellStyle | undefined

Reads a `STYLE` record: the name of one of the entries in the XF table.

readUnicodeBody
function readUnicodeBody(reader: ByteReader, count: number, rich?: boolean): BiffString

The body of an `XLUnicodeString` whose length was read elsewhere.

readUnicodeString
function readUnicodeString(reader: ByteReader, lengthBytes?: 1 | 2, rich?: boolean): BiffString

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.

readVersionedString
function readVersionedString(reader: ByteReader, lengthBytes: 1 | 2, biff8: boolean, codePage: number): BiffString

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.

readXf
function readXf(reader: ByteReader, tables: StyleTables): CellStyleRecord

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.

renderFormula
function renderFormula(tokens: Uint8Array, at: CellPosition, context: FormulaContext): string | undefined

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.

saveXlsx
function saveXlsx(document: SavableWorkbook, options?: SaveXlsxOptions): Promise<SaveXlsxResult>
serialToDatefrom @genomdev/office-core
function serialToDate(serial: number, date1904?: boolean): Date | undefined

Converts a serial number to a JavaScript `Date` in UTC.

serialToDatePartsfrom @genomdev/office-core
function serialToDateParts(serial: number, date1904?: boolean): DateParts | undefined

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.

sharedFormulaBase
function sharedFormulaBase(tokens: Uint8Array): CellPosition | undefined

True when the stream is the one-token "see that cell instead" formula. Its operand is the address of the shared-formula or array master, and the caller has to look the real tokens up there.

toRichText
function toRichText(value: BiffString, font: (index: number) => Partial<Font> | undefined): readonly RichTextRun[] | undefined

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.

translateFormula
function translateFormula(formula: string, rowDelta: number, columnDelta: number): string

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.

unpackAddress
function unpackAddress(packed: number): CellAddress
verifyPassword
function verifyPassword(password: string, filePass: FilePass): Uint8Array | undefined

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.

walkXlsx
function walkXlsx(document: XlsxDocument, hash: string, options: ResolvedOptions): Promise<{ blocks: Block[]; annotations: Annotation[]; }>

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

Alignment
interface Alignment
horizontal
HorizontalAlignment
vertical
VerticalAlignment
wrapText
boolean
textRotation
number
Rotation in degrees, 0..90 anticlockwise and 91..180 for clockwise 1..90. The value 255 is Excel's flag for stacked (vertical) text.
indent
number
Indent steps; each is three characters wide.
shrinkToFit
boolean
readingOrder
number
0 context, 1 left-to-right, 2 right-to-left.
justifyLastLine
boolean
AutoFilter
interface AutoFilter
range
CellRange
filteredColumns
readonly number[]
Columns that currently filter, by offset within the range.
BiffRecord
interface BiffRecord
type
number
data
Uint8Array<ArrayBufferLike>
The payload, `CONTINUE` records appended.
parts
readonly Uint8Array<ArrayBufferLike>[]
The payloads separately: the record's own first, then each `CONTINUE`.
offset
number
Offset of the record's header within the stream.
BiffString
interface BiffString

A string and the formatting runs it carried, if any.

text
string
runs
readonly { readonly at: number; readonly fontIndex: number; }[]
`(character index, font index)` pairs, as the file writes them.
Border
interface Border
left
BorderEdge
right
BorderEdge
top
BorderEdge
bottom
BorderEdge
diagonal
BorderEdge
diagonalUp
boolean
diagonalDown
boolean
vertical?
BorderEdge | undefined
The lines *between* the cells a rule covers, which only a `dxf` has. A cell has four sides and no inside; a table style element covers a band of them and rules the lines within it, which is how a banded table gets the hairlines between its columns without any cell asking for one.
horizontal?
BorderEdge | undefined
BorderEdge
interface BorderEdge
style
BorderStyle
color
Color | undefined
BoundSheet
interface BoundSheet

One entry of the sheet directory.

name
string
position
number
Absolute offset of the sheet's `BOF` within the workbook stream.
kind
number
state
"visible" | "hidden" | "veryHidden"
Cell
interface Cell

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.

row
number
column
number
value
CellValue
type
CellType
styleIndex
number
Index into the workbook's `cellXfs`.
formula
string | undefined
The formula without its leading equals sign, when the cell is computed.
formulaKind
"normal" | "array" | "shared" | "dataTable" | undefined
How the formula applies: a plain one, an array formula, a shared master.
formulaRange
CellRange | undefined
The range an array formula spills into.
richText
readonly RichTextRun[] | undefined
Formatting runs, for a shared string that has more than one.
CellAddress
interface CellAddress

Zero-based cell position.

row
number
column
number
CellComment
interface CellComment

A comment or a threaded note attached to a cell.

row
number
column
number
author
string | undefined
text
string
CellFormat
interface CellFormat

A fully resolved format: what a cell actually looks like.

numberFormatId
number
numberFormatCode
string
font
Font
fill
Fill
border
Border
alignment
Alignment
protection
Protection
quotePrefix
boolean
CellGeometry
interface CellGeometry

How big a cell is, which is what turns a fractional offset into a length.

columnWidthEmu
(column: number) => number
rowHeightEmu
(row: number) => number
CellPosition
interface CellPosition

Where a formula sits, which is what a relative token is relative to.

row
number
column
number
CellRange
interface CellRange

A rectangular range, inclusive on both ends and zero-based.

startRow
number
startColumn
number
endRow
number
endColumn
number
CellStyleRecord
interface CellStyleRecord

One `xf` as written, before its parent style is folded in.

numberFormatId
number
fontId
number
fillId
number
borderId
number
xfId
number | undefined
Index into `cellStyleXfs`: the named style this format is based on.
alignment
Partial<Alignment> | undefined
protection
Protection | undefined
applyNumberFormat
boolean | undefined
applyFont
boolean | undefined
applyFill
boolean | undefined
applyBorder
boolean | undefined
applyAlignment
boolean | undefined
applyProtection
boolean | undefined
quotePrefix
boolean
Text stored with a leading apostrophe: a number the author wanted as text.
Color
interface Color

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.

rgb?
string | undefined
`AARRGGBB` as stored, alpha first.
theme?
number | undefined
indexed?
number | undefined
auto?
boolean | undefined
tint?
number | undefined
-1..1: negative darkens, positive lightens.
ColorScale
interface ColorScale
values
readonly ConditionalValue[]
colors
readonly Color[]
Column
interface Column

A `<col>` element: formatting for a span of columns.

min
number
First column of the span, zero-based — one less than the file says.
max
number
Last column of the span, zero-based and inclusive.
width
number | undefined
Width as the file states it — characters plus the cell's padding — or `undefined` for the sheet default.
customWidth
boolean
hidden
boolean
bestFit
boolean
styleIndex
number | undefined
outlineLevel
number
collapsed
boolean
ConditionalFormatting
interface ConditionalFormatting
ranges
readonly CellRange[]
rules
readonly ConditionalRule[]
ConditionalRule
interface ConditionalRule
type
ConditionalRuleType
priority
number
stopIfTrue
boolean
dxfId
number | undefined
Index into `dxfs`: the formatting to apply when the rule matches.
format
DifferentialFormat | undefined
operator
string | undefined
formulas
readonly string[]
text
string | undefined
timePeriod
string | undefined
rank
number | undefined
bottom
boolean
percent
boolean
aboveAverage
boolean
equalAverage
boolean
standardDeviation
number | undefined
extensionId
string | undefined
`x14:id`: the name of the rule's other half, in the extension block.
colorScale
ColorScale | undefined
dataBar
DataBar | undefined
iconSet
IconSet | undefined
ConditionalValue
interface ConditionalValue

A conditional formatting threshold (`cfvo`).

type
"formula" | "percent" | "num" | "max" | "min" | "percentile" | "autoMin" | "autoMax"
value
string | undefined
greaterOrEqual
boolean
Whether the icon-set band includes its lower bound.
DataBar
interface DataBar
min
ConditionalValue
max
ConditionalValue
color
Color | undefined
showValue
boolean
gradient
boolean
Excel 2010 extensions: a solid bar, a border, a different colour for negatives.
borderColor
Color | undefined
negativeColor
Color | undefined
axisColor
Color | undefined
direction
"context" | "leftToRight" | "rightToLeft"
axisPosition
"none" | "automatic" | "midpoint"
Where zero sits: `automatic` puts it where the values put it.
DataValidation
interface DataValidation
ranges
readonly CellRange[]
type
string
operator
string | undefined
formula1
string | undefined
formula2
string | undefined
allowBlank
boolean
showDropDown
boolean
showInputMessage
boolean
showErrorMessage
boolean
promptTitle
string | undefined
prompt
string | undefined
errorTitle
string | undefined
error
string | undefined
DatePartsfrom @genomdev/office-core
interface DateParts

Whole and fractional parts of a serial number, in calendar terms.

year
number
month
number
1..12.
day
number
1..31.
hours
number
minutes
number
seconds
number
milliseconds
number
weekday
number
0 = Sunday.
DefinedName
interface DefinedName

A defined name: a range or a formula the workbook gave a name to.

name
string
formula
string
localSheetIndex
number | undefined
Sheet index when the name is local to one sheet.
hidden
boolean
comment
string | undefined
DifferentialFormat
interface DifferentialFormat

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.

font
Partial<Font> | undefined
fill
Fill | undefined
border
Partial<Border> | undefined
alignment
Partial<Alignment> | undefined
numberFormatCode
string | undefined
DrawingAnchorPoint
interface DrawingAnchorPoint

An image, chart or shape anchored to the grid.

column
number
columnOffsetEmu
number
row
number
rowOffsetEmu
number
DrawingObject
interface DrawingObject
kind
"image" | "chart" | "diagram" | "shape"
name
string | undefined
description
string | undefined
anchor
"twoCell" | "oneCell" | "absolute"
How the object behaves when rows and columns are resized.
from
DrawingAnchorPoint
to
DrawingAnchorPoint | undefined
widthEmu
number | undefined
Size in EMU, for one-cell and absolute anchors.
heightEmu
number | undefined
xEmu
number | undefined
Absolute position in EMU, for absolute anchors.
yEmu
number | undefined
mediaPartName
string | undefined
The package part holding the image, for an image.
contentType
string | undefined
mediaHref
string | undefined
Where the picture lives, when it does not live in the file. A drawing may point at a picture on the web instead of carrying it, and the two are not interchangeable: the bytes are simply not here, and fetching them is a request a document makes on the reader's behalf to a host the reader never chose. Kept apart from the part name so that nothing downstream can mistake a URL for something it can read out of the package.
crop
{ left: number; top: number; right: number; bottom: number; } | undefined
The part of the picture that is shown, as fractions trimmed off each side. A picture cropped in Excel keeps all of its pixels — the file still holds the whole photograph — and records how much of each edge to hide. Ignoring it shows the parts the author cut away, which on a cropped headshot or a screenshot trimmed to one panel is a different picture entirely.
flipHorizontal
boolean
Whether the picture is mirrored, from `a:xfrm/@flipH` and `@flipV`.
flipVertical
boolean
text
string | undefined
Text drawn inside a shape.
shape
ShapeAppearance | undefined
How a shape is painted, when the object is one.
chart
ChartDefinition | undefined
The chart, when the object is one. Parsed rather than referenced: `c:chartSpace` is the same part in a workbook as in a document, and the renderer that draws it is shared.
diagram
DiagramDrawing | undefined
The shapes Office laid out for a SmartArt diagram, when the object is one. A diagram is stored as a data model, a layout algorithm, a colour list and a style, and drawing it from those would mean writing the layout solvers. Office also writes the finished shapes for every other consumer, and those are what this holds.
diagramDataId
string | undefined
`dgm:relIds/@r:dm`, which is where the shapes are found from.
rotation
number
hidden
boolean
EvaluationOptions
interface EvaluationOptions
now?
(() => Date) | undefined
The clock `TODAY` and `NOW` read. Fixed in tests, the real one otherwise.
FilePass
interface FilePass

How a workbook is protected, as `FILEPASS` states it.

kind
"rc4" | "xor" | "rc4-cryptoapi"
salt
Uint8Array<ArrayBufferLike>
verifier
Uint8Array<ArrayBufferLike>
verifierHash
Uint8Array<ArrayBufferLike>
Fill
interface Fill
pattern
PatternType
foreground
Color | undefined
The pattern's ink. For a solid fill this is the whole story — and note that it is `fgColor` that carries it, not `bgColor`, which is the trap in this part of the format.
background
Color | undefined
gradient
GradientFill | undefined
Font
interface Font
name
string | undefined
size
number | undefined
Size in points.
bold
boolean
italic
boolean
strike
boolean
underline
UnderlineStyle
vertAlign
"baseline" | "superscript" | "subscript"
color
Color | undefined
family
number | undefined
scheme
string | undefined
`major` or `minor`: which theme font this one follows.
outline
boolean
shadow
boolean
condense
boolean
extend
boolean
FormatOptionsfrom @genomdev/office-core
interface FormatOptions
date1904?
boolean | undefined
The workbook's 1904 date system flag.
locale?
string | undefined
BCP 47 locale for month and weekday names; overridden by `[$-…]` in the code.
palette?
readonly string[] | undefined
The workbook's own indexed palette, when it overrides the default.
widthCharacters?
number | undefined
How many characters the cell has room for. Only `General` uses it, and it is the reason `General` is not one format but a family: Excel shows as much of the number as fits and rounds away the rest, so the same value reads `3.337809381` in a wide column and `3.3378` in a narrow one. A caller with no column — `TEXT(x, "General")`, a text extraction — leaves it out and gets the full precision.
FormattedValuefrom @genomdev/office-core
interface FormattedValue

The result of formatting a value with a format code.

text
string
The text as Excel would display it.
color
string | undefined
Colour requested by the format code (`[Red]`), as CSS.
fill
string | undefined
The character of a `*` fill token, when the code has one. Excel repeats it until the cell is full — the usual use is `_(* #,##0_)`, the accounting format, where it pushes the number to the right edge.
kind
"number" | "boolean" | "text" | "error" | "date" | "empty"
What the value turned out to be; the renderer aligns on it.
FormulaContext
interface FormulaContext

What a formula needs from the workbook around it to name things.

biff8
boolean
codePage
number
sheetPrefix
(ixti: number) => string
The sheet prefix an external-sheet index stands for, `Sheet2!` and friends.
definedName
(index: number) => string
A defined name by the one-based index a token carries.
externalName
(supbookIndex: number, nameIndex: number) => string
A name in another workbook, for `ptgNameX`.
GradientFill
interface GradientFill
kind
"path" | "linear"
degree
number
stops
readonly GradientStop[]
GradientStop
interface GradientStop
position
number
color
Color | undefined
Hyperlink
interface Hyperlink
range
CellRange
target
string | undefined
Resolved target for an external link.
location
string | undefined
In-workbook destination, as `Sheet2!A1` or a defined name.
tooltip
string | undefined
display
string | undefined
IconSet
interface IconSet
name
string
values
readonly ConditionalValue[]
showValue
boolean
reverse
boolean
MediaPart
interface MediaPart

What a shape's blip index resolves to.

partName
string
contentType
string
NamedCellStyle
interface NamedCellStyle

A named style, as the style gallery lists it.

name
string
xfId
number
builtinId
number | undefined
hidden
boolean
OpenXlsOptions
interface OpenXlsOptions extends OpenOptions
password?
string | undefined
The password, for a workbook that has one. Not needed for the common case: a workbook Excel opens without asking is encrypted with a password Excel knows, and this reader tries that one first.
PageSetup
interface PageSetup
orientation
"default" | "portrait" | "landscape"
paperSize
number | undefined
scale
number | undefined
fitToWidth
number | undefined
fitToHeight
number | undefined
marginsInches
{ left: number; right: number; top: number; bottom: number; header: number; footer: number; } | undefined
printGridLines
boolean
printHeadings
boolean
printTitleRows
string | undefined
Rows and columns repeated on every printed page.
printTitleColumns
string | undefined
printArea
string | undefined
Pane
interface Pane

A frozen or split pane.

frozenColumns
number
Columns frozen at the left.
frozenRows
number
Rows frozen at the top.
splitX
number
Split position in twentieths of a point, for a split rather than a freeze.
splitY
number
frozen
boolean
topLeftCell
string | undefined
ParsedSheet
interface ParsedSheet

What the sheet part yields, before the parts it points at are resolved.

rows
Row[]
columns
Column[]
merges
CellRange[]
hyperlinks
(Hyperlink & { relationshipId: string | undefined; })[]
conditionalFormats
ConditionalFormatting[]
dataValidations
DataValidation[]
sparklines
SparklineGroup[]
autoFilter
AutoFilter | undefined
view
SheetView
format
SheetFormat
pageSetup
PageSetup | undefined
rowBreaks
number[]
columnBreaks
number[]
dimensions
{ rowCount: number; columnCount: number; }
tabColor
Color | undefined
drawingId
string | undefined
Relationship ids of the parts that have to be read separately.
legacyDrawingId
string | undefined
tableIds
string[]
Protection
interface Protection
locked
boolean
hidden
boolean
RichTextRun
interface RichTextRun

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.

text
string
font
Partial<Font> | undefined
Row
interface Row
index
number
cells
readonly Cell[]
Cells in column order; absent columns are simply not there.
heightPoints
number | undefined
Height in points, when the row carries one.
customHeight
boolean
Set when the height was chosen by the author rather than by the content.
hidden
boolean
styleIndex
number | undefined
Style applied to the whole row; only meaningful with `customFormat`.
customFormat
boolean
outlineLevel
number
Grouping depth, 0..7.
collapsed
boolean
SavableWorkbook
interface SavableWorkbook extends XlsxDocument

What a save needs of a workbook, beyond the model.

package?
OpcPackage | undefined
The package it was opened from; a workbook built from nothing has none.
SaveXlsxOptions
interface SaveXlsxOptions

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.

compress?
boolean | undefined
Compress the parts this save produces. On by default; nothing is produced yet.
SaveXlsxResult
interface SaveXlsxResult
bytes
Uint8Array<ArrayBufferLike>
rewritten
boolean
Whether any part was rewritten rather than copied. `false` means the result is the original file. It is a field rather than a fact because it will not always be false, and a caller that checks it now keeps working when it stops being.
lost
ReadonlyMap<string, number>
What could not be carried, by name and by count. Empty here, and the shape is the promise: when a sheet can be written from the model, what the model could not express is reported rather than left for the caller to discover.
ShapeAppearance
interface ShapeAppearance

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.

geometry
DiagramGeometry | undefined
`a:prstGeom/@prst`: `rect`, `roundRect`, `ellipse`, an arrow, a callout.
fill
DiagramColor | undefined
filled
boolean
`a:noFill`: the author turned the fill off, which is not the same as none stated.
outline
DiagramOutline | undefined
textColor
DiagramColor | undefined
Colour of the text inside, from `a:fontRef` or the first run's own colour.
fontSize
number | undefined
Point size of the first run, `a:rPr/@sz` in hundredths.
bold
boolean
textBox
boolean
`xdr:cNvSpPr/@txBox`: the object is a text box rather than an autoshape. It decides what the text does when the file says nothing: a text box sets its text at the top left, an autoshape centres it both ways.
verticalAlignment
"center" | "top" | "bottom" | undefined
`a:bodyPr/@anchor`: where the text sits in a box taller than it needs.
insets
{ left: number; top: number; right: number; bottom: number; }
Text insets in EMU, `a:bodyPr/@lIns` and friends. Every shape has them whether or not it says so: DrawingML's defaults are a tenth of an inch left and right and half that above and below, and text set hard against the edge of its box is how a reader sees their absence.
wrap
boolean
`a:bodyPr/@wrap`: `none` keeps the text on one line.
horizontalAlignment
"left" | "center" | "right" | "justify" | undefined
`a:pPr/@algn` of the first paragraph.
SharedString
interface SharedString

A shared string: its plain text, and its runs when it has more than one.

text
string
runs
readonly RichTextRun[] | undefined
Sheet
interface Sheet

A workbook sheet. Contents are loaded on demand.

name
string
id
number
Sheet identifier within the workbook, `sheetId`.
index
number
state
"visible" | "hidden" | "veryHidden"
partName
string
Name of the package part holding the sheet contents.
tabColor
Color | undefined
data
SheetData | undefined
The parsed sheet, once it has been loaded.
load
() => Promise<SheetData>
Parses the sheet. The result is cached.
SheetContext
interface SheetContext

Everything the sheet parser needs from the workbook around it.

sharedStrings
readonly SharedString[]
date1904
boolean
SheetData
interface SheetData

Parsed sheet contents.

rows
readonly Row[]
Rows in index order; empty rows are absent.
columns
readonly Column[]
merges
readonly CellRange[]
hyperlinks
readonly Hyperlink[]
conditionalFormats
readonly ConditionalFormatting[]
dataValidations
readonly DataValidation[]
drawings
readonly DrawingObject[]
tables
readonly SheetTable[]
comments
readonly CellComment[]
sparklines
readonly SparklineGroup[]
autoFilter
AutoFilter | undefined
view
SheetView
format
SheetFormat
pageSetup
PageSetup | undefined
rowBreaks
readonly number[]
Manual page breaks, by row and column index.
columnBreaks
readonly number[]
dimensions
{ rowCount: number; columnCount: number; }
Bounds of the used range.
SheetFormat
interface SheetFormat
defaultRowHeight
number
Default row height in points.
defaultColWidth
number | undefined
Default column width in characters, when the sheet overrides the standard.
baseColWidth
number
customHeight
boolean
Set when every row carries an explicit height.
zeroHeight
boolean
outlineLevelRow
number
summaryBelow
boolean
`sheetPr/outlinePr/@summaryBelow`: which side of a group its total is on. True by default, and it decides where the fold button goes — the button belongs to the summary, not to the group.
summaryRight
boolean
outlineLevelCol
number
SheetTable
interface SheetTable
name
string
displayName
string
range
CellRange
headerRowCount
number
totalsRowCount
number
columns
readonly TableColumn[]
styleName
string | undefined
showRowStripes
boolean
showColumnStripes
boolean
showFirstColumn
boolean
showLastColumn
boolean
headerRowDxfId
number | undefined
Formatting the table as a whole adds on top of its style.
dataDxfId
number | undefined
totalsRowDxfId
number | undefined
autoFilter
AutoFilter | undefined
Present when the table has filter buttons in its header row.
SheetView
interface SheetView
showGridLines
boolean
showRowColHeaders
boolean
showZeros
boolean
rightToLeft
boolean
tabSelected
boolean
zoomScale
number
view
"normal" | "pageBreakPreview" | "pageLayout"
pane
Pane | undefined
activeCell
string | undefined
The cell the cursor was on when the file was saved.
selection
readonly CellRange[]
Sparkline
interface Sparkline

One sparkline: the range it draws, and the cell it draws in.

formula
string
row
number
column
number
SparklineGroup
interface SparklineGroup

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.

type
"column" | "line" | "stacked"
showHigh
boolean
showLow
boolean
showFirst
boolean
showLast
boolean
showNegative
boolean
showMarkers
boolean
lineWeight
number
displayEmptyCellsAs
string
`gap`, `zero` or `span`: what a missing value does to the line.
colors
Readonly<Record<string, Color | undefined>>
`series`, `negative`, `axis`, `markers`, `first`, `last`, `high`, `low`.
sparklines
readonly Sparkline[]
StylesheetParts
interface StylesheetParts
numberFormats
ReadonlyMap<number, string>
fonts
readonly Font[]
fills
readonly Fill[]
borders
readonly Border[]
cellXfs
readonly CellStyleRecord[]
cellStyleXfs
readonly CellStyleRecord[]
differentialFormats
readonly DifferentialFormat[]
namedStyles
readonly NamedCellStyle[]
indexedColors
readonly string[]
The workbook's own indexed palette, when it replaces the default one.
tableStyles
ReadonlyMap<string, TableStyleDefinition>
Table styles the workbook defines itself, by name.
theme
ThemeColors | undefined
TableColumn
interface TableColumn
id
number
name
string
totalsRowLabel
string | undefined
totalsRowFunction
string | undefined
headerRowDxfId
number | undefined
Formatting the column carries itself, over and above the table style. A column of dates in a table can be given its own look without any of its cells recording one, which is why a viewer that reads only the cells shows the column exactly like its neighbours.
dataDxfId
number | undefined
totalsRowDxfId
number | undefined
TableStyleDefinition
interface TableStyleDefinition

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.

name
string
pivot
boolean
Whether it styles a pivot table rather than an ordinary one.
elements
readonly TableStyleElementRecord[]
TableStyleElementRecord
interface TableStyleElementRecord
part
string
`wholeTable`, `headerRow`, `firstRowStripe` and the rest.
size
number | undefined
Rows or columns to a band, for the striping parts.
dxfId
number | undefined
ThemeColors
interface ThemeColors

The colour scheme of `theme1.xml`, in the order the theme writes it.

dark1
string | undefined
light1
string | undefined
dark2
string | undefined
light2
string | undefined
accents
readonly string[]
hyperlink
string | undefined
followedHyperlink
string | undefined
majorFont
string | undefined
Major and minor typefaces, which fonts with `scheme="minor"` follow.
minorFont
string | undefined
WorkbookGlobals
interface WorkbookGlobals
version
number
codePage
number
date1904
boolean
sheets
readonly BoundSheet[]
sharedStrings
readonly BiffString[]
styles
StyleTables
definedNames
readonly DefinedName[]
activeSheetIndex
number
formulaContext
FormulaContext
Resolves a formula's sheet index into the prefix it is written with.
media
XlsMedia
The workbook's picture store, read from `MSODRAWINGGROUP`.
mediaPart
(index: number) => MediaPart | undefined
The part a shape's one-based blip index names.
font
(index: number) => Partial<Font> | undefined
The font a rich-text run index stands for.
XlsxDocument
interface XlsxDocument extends GenomDocument
format
"xlsx" | "xls"
Which generation this workbook was read from. Two values and one model. `@genomdev/xlsx` reads the binary format into this same interface rather than converting it, so nothing below the registry needs to know which it has — but the registry is keyed by format, and a caller asking what can be opened deserves a truthful answer.
kind
"spreadsheet"
metadata
DocumentMetadata
sheets
readonly Sheet[]
styles
Stylesheet
definedNames
readonly DefinedName[]
fileName?
string | undefined
What the file is called, when the source knew. `CELL("filename")` is the only thing that asks, and a surprising number of workbooks use it: it is the one way a formula can learn the name of its own sheet, and `INDIRECT` over that name is how a template addresses "the sheet before this one".
date1904
boolean
The 1904 date system, chosen per workbook and changing every date in it.
activeSheetIndex
number
The sheet that was active when the file was saved.
pageCount
number
Sheet count: the cheap, meaningful equivalent of a page count.
sheet
(nameOrIndex: string | number) => Sheet | undefined
media
(partName: string) => Promise<Uint8Array | undefined>
Resolves an image part to bytes, for the renderer.
mediaUrl
(partName: string) => Promise<string | undefined>
A URL a browser can show an embedded picture from, metafiles translated.

Type aliases

BorderStyle
type BorderStyle = | 'none' | 'thin' | 'medium' | 'thick' | 'dashed' | 'dotted' | 'double' | 'hair' | 'mediumDashed' | 'dashDot' | 'mediumDashDot' | 'dashDotDot' | 'mediumDashDotDot' | 'slantDashDot'
CellType
type CellType = 'number' | 'text' | 'boolean' | 'error' | 'blank' | 'date'
CellValue
type CellValue = string | number | boolean | Date | null

A cell value after parsing.

ConditionalRuleType
type ConditionalRuleType = | 'expression' | 'cellIs' | 'colorScale' | 'dataBar' | 'iconSet' | 'top10' | 'uniqueValues' | 'duplicateValues' | 'containsText' | 'notContainsText' | 'beginsWith' | 'endsWith' | 'containsBlanks' | 'notContainsBlanks' | 'containsErrors' | 'notContainsErrors' | 'timePeriod' | 'aboveAverage' | 'dataBarExt'
HorizontalAlignment
type HorizontalAlignment = 'general' | 'left' | 'center' | 'right' | 'fill' | 'justify' | 'centerContinuous' | 'distributed'
MergedRange
type MergedRange = CellRange

A merged block. Named for what it means; the shape is an ordinary range.

PatternType
type PatternType = | 'none' | 'solid' | 'mediumGray' | 'darkGray' | 'lightGray' | 'darkHorizontal' | 'darkVertical' | 'darkDown' | 'darkUp' | 'darkGrid' | 'darkTrellis' | 'lightHorizontal' | 'lightVertical' | 'lightDown' | 'lightUp' | 'lightGrid' | 'lightTrellis' | 'gray125' | 'gray0625'
UnderlineStyle
type UnderlineStyle = 'none' | 'single' | 'double' | 'singleAccounting' | 'doubleAccounting'
VerticalAlignment
type VerticalAlignment = 'top' | 'center' | 'bottom' | 'justify' | 'distributed'

Values

BiffVersion
BiffVersion: { readonly BIFF2: 512; readonly BIFF3: 768; readonly BIFF4: 1024; readonly BIFF5: 1280; readonly BIFF8: 1536; }

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.

DEFAULT_ALIGNMENT
DEFAULT_ALIGNMENT: Alignment
DEFAULT_FONT
DEFAULT_FONT: Font
DEFAULT_PASSWORD
DEFAULT_PASSWORD: "VelvetSweatshop"

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.

DEFAULT_PROTECTION
DEFAULT_PROTECTION: Protection
ERROR_TEXT
ERROR_TEXT: Readonly<Record<number, string>>

Cell error codes, as an error cell stores them.

FUNCTIONS
FUNCTIONS: Readonly<Record<number, string>>

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.

INDEXED_COLORSfrom @genomdev/office-core
INDEXED_COLORS: readonly string[]

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.

MAX_COLUMNS
MAX_COLUMNS: 16384

The last column and row a worksheet can have: XFD1048576.

MAX_ROWS
MAX_ROWS: 1048576
NO_BORDER
NO_BORDER: Border
NO_EDGE
NO_EDGE: BorderEdge
NO_FILL
NO_FILL: Fill
Record
Record: { readonly BOF: 2057; readonly BOF_BIFF4: 1033; readonly BOF_BIFF3: 521; readonly BOF_BIFF2: 9; readonly EOF: 10; readonly CONTINUE: 60; readonly FILEPASS: 47; readonly CODEPAGE: 66; readonly DATEMODE: 34; readonly BOUNDSHEET: 133; readonly SST: 252; readonly EXTSST: 255; readonly FORMAT: 1054; readonly FORMAT_BIFF3: 30; readonly FONT: 49; readonly FONT_BIFF3: 561; readonly XF: 224; readonly XF_BIFF4: 1091; readonly XF_BIFF3: 579; readonly XF_BIFF2: 67; readonly STYLE: 659; readonly PALETTE: 146; readonly NAME: 24; readonly EXTERNSHEET: 23; readonly EXTERNNAME: 35; readonly EXTERNCOUNT: 22; readonly SUPBOOK: 430; readonly WINDOW1: 61; readonly WRITEACCESS: 92; readonly COUNTRY: 140; readonly BUILTINFMTCOUNT: 86; readonly THEME: 2198; readonly USESELFS: 352; readonly DIMENSIONS: 512; readonly DIMENSIONS_BIFF2: 0; readonly INDEX: 523; readonly ROW: 520; readonly ROW_BIFF2: 8; readonly COLINFO: 125; readonly DEFCOLWIDTH: 85; readonly STANDARDWIDTH: 153; readonly DEFAULTROWHEIGHT: 549; readonly DEFAULTROWHEIGHT_BIFF2: 37; readonly GUTS: 128; readonly WSBOOL: 129; readonly MERGEDCELLS: 229; readonly BLANK: 513; readonly BLANK_BIFF2: 1; readonly MULBLANK: 190; readonly INTEGER_BIFF2: 2; readonly NUMBER: 515; readonly NUMBER_BIFF2: 3; readonly LABEL: 516; readonly LABEL_BIFF2: 4; readonly LABELSST: 253; readonly RSTRING: 214; readonly RK: 638; readonly MULRK: 189; readonly BOOLERR: 517; readonly BOOLERR_BIFF2: 5; readonly FORMULA: 6; readonly FORMULA_BIFF3: 518; readonly FORMULA_BIFF4: 1030; readonly ARRAY: 545; readonly ARRAY_BIFF2: 33; readonly SHRFMLA: 1212; readonly TABLEOP: 566; readonly STRING: 519; readonly STRING_BIFF2: 7; readonly IXFE: 68; readonly WINDOW2: 574; readonly WINDOW2_BIFF2: 62; readonly PANE: 65; readonly SELECTION: 29; readonly SCL: 160; readonly HLINK: 440; readonly HLINK_TOOLTIP: 2048; readonly CONDFMT: 432; readonly CF: 433; readonly CONDFMT12: 2170; readonly CF12: 2171; readonly DVAL: 434; readonly DV: 446; readonly NOTE: 28; readonly TXO: 438; readonly OBJ: 93; readonly MSODRAWING: 236; readonly MSODRAWINGGROUP: 235; readonly MSODRAWINGSELECTION: 237; readonly SHEETPROTECTION: 2151; readonly PROTECT: 18; readonly AUTOFILTER: 158; readonly AUTOFILTERINFO: 157; readonly FEATHEADR: 2151; readonly LIST12: 2167; readonly TABLESTYLES: 2190; readonly SETUP: 161; readonly LEFTMARGIN: 38; readonly RIGHTMARGIN: 39; readonly TOPMARGIN: 40; readonly BOTTOMMARGIN: 41; readonly HEADER: 20; readonly FOOTER: 21; readonly PRINTGRIDLINES: 43; readonly PRINTHEADERS: 42; readonly HORIZONTALPAGEBREAKS: 27; readonly VERTICALPAGEBREAKS: 26; }

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.

SheetKind
SheetKind: { readonly WORKSHEET: 0; readonly MACRO_SHEET: 1; readonly CHART: 2; readonly VISUAL_BASIC: 6; }

`BOUNDSHEET`'s type nibble.

SubstreamKind
SubstreamKind: { readonly GLOBALS: 5; readonly VISUAL_BASIC: 6; readonly WORKSHEET: 16; readonly CHART: 32; readonly MACRO_SHEET: 64; readonly WORKSPACE: 256; }

BOF's `dt` field: which kind of substream is starting.

WORKBOOK_STREAMS
WORKBOOK_STREAMS: readonly ["Workbook", "Book"]

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.

xlsx
xlsx: FormatModule<XlsxDocument>

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

AxisMetrics
class AxisMetrics

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.

count
number
total
number
defaultSize
number
size
(index: number) => number
offset
(index: number) => number
Pixel offset of the start of `index`.
indexAt
(position: number) => number
The index whose band contains `position`; clamped to the axis.
CellCssBuilder
class CellCssBuilder
stylesheet
StyleSheetBuilder
appearance
(format: CellFormat) => CellAppearance
The class and the measurement facts for a resolved cell format.
differential
(format: DifferentialFormat) => string | undefined
A class for a differential format, as conditional formatting applies.
tableStyle
(format: DifferentialFormat) => string | undefined
A class for what a table paints under its cells. The same declarations as any differential format, but emitted into a cascade layer, because a table style is the one kind of formatting that *loses*: a cell that sets its own fill keeps it, and the band shows only where the cell asked for nothing.
baseFontPoints
() => number
The size the sheet is set in, at the zoom it is drawn at, in points. The grid needs it because a cell that states no size of its own emits no `font-size` at all — the declarations are a *diff* against the base format, which is what keeps the stylesheet to a few dozen rules instead of one per cell. Those cells then inherit, and what they inherit has to be the base size multiplied by the zoom. It used to be a literal `11pt` in the stylesheet, which is how a plain sheet came to keep its type at one size while its columns and rows scaled around it: the reader zoomed out and the words stayed put while the cells shrank away from under them.
ConditionalFormatter
class ConditionalFormatter
empty
boolean
needsEvaluator
boolean
Whether any rule needs a formula engine to decide. An `expression` rule always does. So does a comparison whose operand is not a number, a quoted string or a cell of this sheet — `Sheet2!$A$1` is the common case, and it is common because the schema cannot hold it: a rule that compares across sheets is written in an extension block, and its operand is a formula like any other.
useEvaluator
(predicate: PredicateEvaluator | undefined) => void
Gives the formatter a way to evaluate `expression` rules.
evaluate
(row: number, column: number, cell: Cell | undefined) => ConditionalResult | undefined
SheetGrid
class SheetGrid
element
HTMLElement
metrics
SheetMetrics
Where the rows and columns are, as the grid currently has them. Not the object handed in: folding an outline group rebuilds it, and the rebuilt one is what everything is drawn against.
selection
GridSelection | undefined
refresh
() => void
Rebuilds the visible window from nothing. Called when something behind the cells has changed and cannot be seen from the window: the container was resized, the formula engine finished and the conditional formats can finally be evaluated, a sparkline arrived. A plain re-render would do nothing in those cases — the window is the same, and the panes are skipped precisely because it is — so what is already drawn is thrown away first.
destroy
() => void
scrollTo
(row: number, column: number) => void
Scrolls until a cell is inside the scrolling pane.
select
(row: number, column: number, extend?: boolean) => void
cellAt
(row: number, column: number) => Cell | undefined
The cell behind a selection, for the formula bar.
setSparklines
(sparklines: ReadonlyMap<number, SparklineCell>) => void
The sparklines of this sheet, by the cell each draws in. Set from outside rather than read here, because the numbers behind a sparkline come from a range that may be on another sheet, and resolving one needs the workbook and the formula engine — neither of which the grid has.
search
(query: string) => number
Every cell of the sheet whose text contains the query. Searched against what is *shown*, which is the only thing a reader can have read: a search for `15 January 2024` finds the cell holding 45306, and a search for 45306 does not. Rows the reader folded or filtered away are skipped for the same reason — they are not on the screen being searched. The browser cannot do this itself. A grid draws the few hundred cells in its window and no more, so a page can only find what has been drawn: it reports nothing for text plainly in the document, which is worse than having no search at all, because it answers.
stepMatch
(direction: 1 | -1) => { at: number; of: number; } | undefined
Moves to the next match, or the previous one, and shows it. Wraps at either end: a reader stepping through a sheet expects to come back round rather than to stop at the bottom with no word about why.
SheetMetrics
class SheetMetrics

The geometry of one sheet at one zoom level.

columns
AxisMetrics
rows
AxisMetrics
zoom
number
digitWidth
number
Width of the digit zero, kept so a cell can say how many characters fit.
withRowVisibility
(rowVisibility: ReadonlyMap<number, boolean>) => SheetMetrics
The same geometry with a different set of rows folded away. Rebuilt rather than adjusted: the offsets are a prefix sum, so one row changing height moves every row after it and there is nothing to patch. A rebuild walks the exceptions, which are a few dozen entries.
withSizes
(sizes: { columnWidths?: ReadonlyMap<number, number>; rowHeights?: ReadonlyMap<number, number>; }) => SheetMetrics
The same geometry with a column or a row dragged to a new size.
XlsxView
class XlsxView extends BaseDocumentView<XlsxDocument>
activeSheetIndex
number
pageOfLocator
(locator: string) => number | undefined
Which sheet an address is on. A workbook shows one sheet at a time, so an address on another sheet is in the same position as a page that has not been mounted: nothing the reader does to the grid will ever bring it into view. The flow of the address names the sheet, and the workbook says where that sheet sits.
showSheet
(index: number) => Promise<void>
goToCell
(row: number, column: number) => void
Selects a cell, scrolling it into view.
renderContent
() => Promise<void>
Renders the content into {@link root}. Called on every update.
openFind
() => void
Opens the find bar, or puts the cursor back in it. Part of the viewer rather than of the host: the reason a search is needed at all is the virtualised grid, which is this component, and a host that did not know to build one would leave the reader with a Ctrl+F that lies.
closeFind
() => void
Closes the find bar and takes its marks off the sheet.
onContainerResize
() => void
Called when the container is resized. The default is a full re-render. Renderers that can reflow incrementally should override this — a full rebuild on every resize frame is exactly the behaviour that makes viewers feel slow.
setZoom
(zoom: number) => void
Changes the zoom level and re-renders.
update
() => void
Re-render after the container was resized or settings changed.
destroy
() => void
Tear the view down and release resources. The container is left empty.

Functions

cellButton
function cellButton(row: number, column: number, filters: readonly AutoFilter[], validations: readonly DataValidation[]): "filter" | "filtering" | "list" | undefined

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.

columnWidthPixels
function columnWidthPixels(widthCharacters: number, digitWidth: number): number

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.

renderSparkline
function renderSparkline(ownerDocument: Document, group: SparklineGroup, values: readonly (number | undefined)[], box: SparklineBox, color: ColorLookup): SVGElement | undefined

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.

rotationCss
function rotationCss(rotation: number, lineHeightPx: number): { text: Record<string, string>; cell: Record<string, string>; } | undefined

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.

shapeCssfrom @genomdev/office-core
function shapeCss(shape: ShapeLook, box: { width: number; height: number; }, themeColor: ThemeColorLookup): ShapeCss

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

CellAppearance
interface CellAppearance

Everything a cell's class has to encode, beyond its format.

className
string | undefined
The class name, or `undefined` when the format needs no rule at all.
inheritedClassName
string | undefined
The part of the look the cell inherited rather than asked for. Drawn in a layer below the table styles, so that a table paints over the default font and under everything the cell actually set.
wrap
boolean
Whether the format wraps, which changes how the text is laid out.
fontFamily
string
Font as the measurer wants it, for the fit and overflow decisions.
fontSizePx
number
bold
boolean
italic
boolean
horizontal
import("/repo/packages/xlsx/src/index").HorizontalAlignment
indentPx
number
rotation
number
ConditionalResult
interface ConditionalResult

What the rules decided about one cell.

className
string | undefined
Class of the differential format, when a rule matched.
bar
{ start: number; width: number; color: string; border: string | undefined; gradient: boolean; axis: { at: number; color: string; } | undefined; showValue: boolean; } | undefined
A data bar, as fractions of the cell's width. `start` and `width` rather than a length alone, because a bar does not always begin at the edge: where the values cross zero the bar grows out of an axis somewhere inside the cell, and the negative ones grow the other way.
scaleColor
string | undefined
Background from a colour scale.
icon
string | undefined
Icon glyph from an icon set.
hideValue
boolean
Whether the rule hides the value and shows only its decoration.
GridOptions
interface GridOptions
data
SheetData
metrics
SheetMetrics
styles
Stylesheet
css
CellCssBuilder
conditional
ConditionalFormatter
measurer
TextMetricsCache
date1904
boolean
showGridLines
boolean
showHeaders
boolean
zoom
number
imageUrl
(partName: string) => string | undefined
Resolves an image part to a URL the browser can load.
onSelect
(selection: GridSelection) => void
onZoom?
((zoom: number) => void) | undefined
Asked for a new zoom, when the reader turns the wheel with Ctrl held. The grid does not own the zoom — it is a property of the view, which has to rebuild the metrics and the styles for it — so the wheel asks rather than sets.
locatorPrefix?
string | undefined
Address prefix for this sheet, when the view is stamping them. A worksheet's cells have a native address — `Sheet!C14` — so the prefix is everything before it and the cell appends its own reference. Cheaper than building a locator per cell, and a grid of a million cells is exactly where that matters. Absent by default: a viewer that never highlights anything should pay nothing for the machinery.
GridSelection
interface GridSelection
active
{ row: number; column: number; }
range
CellRange
MetricsOptions
interface MetricsOptions
zoom
number
digitWidth
number
Width of the digit zero in the workbook's default font, in pixels.
minimumColumns
number
Lowest number of columns and rows to lay out, so a small sheet still fills the view.
minimumRows
number
rowVisibility?
ReadonlyMap<number, boolean> | undefined
Rows whose visibility the reader has taken over: true hidden, false shown. What folding an outline group amounts to — the rows keep their values and lose their height — and it has to say *shown* as well as *hidden*, because a group saved folded arrives with its rows already hidden by the file, and unfolding it has to overrule that. Kept out of the parsed data because it is the reader's doing and not the document's.
columnWidths?
ReadonlyMap<number, number> | undefined
Sizes the reader has dragged, in pixels, already scaled by the zoom. A column dragged wider is not a change to the document — nothing is written back — and it is the one thing a reader can do about a column of hashes or a heading cut off at the fold. Kept beside the folded rows because it is the same kind of thing: what the reader has done to the view.
rowHeights?
ReadonlyMap<number, number> | undefined
SizeRange
interface SizeRange

A run of consecutive indices that share a size.

from
number
to
number
size
number
Size in pixels; zero means hidden.
SparklineCell
interface SparklineCell

One sparkline ready to draw: the look it belongs to, and its numbers.

group
SparklineGroup
values
readonly (number | undefined)[]
XlsxViewOptions
interface XlsxViewOptions extends ViewOptions
formulaBar?
boolean | undefined
Show the formula bar above the grid. Defaults to true.
sheetTabs?
boolean | undefined
Show the sheet tabs. Defaults to true.
headers?
boolean | undefined
Show the row and column headers. Defaults to the sheet's own setting.
onSelectionChange?
((selection: GridSelection, sheetIndex: number) => void) | undefined
Called when the reader selects a different cell or range.
onSheetChange?
((index: number) => void) | undefined
locators?
boolean | { hash: string; } | undefined
Stamp every cell with the address extraction gave it, as `data-loc`. Off by default: it is one attribute per rendered cell, and a viewer that never highlights anything should pay nothing for it. On, it is what lets a passage found by a retrieval system be scrolled to and highlighted in the workbook it came from. The hash must be the one extraction used, or an address minted by one half is refused by the other.

Type aliases

ShapeCssfrom @genomdev/office-core
type ShapeCss = Partial<Record<string, string>>

Values

DEFAULT_COLUMN_WIDTH_CHARACTERS
DEFAULT_COLUMN_WIDTH_CHARACTERS: 8.43

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.

DEFAULT_DIGIT_WIDTH
DEFAULT_DIGIT_WIDTH: 7
XLSX_VIEW_CSS
XLSX_VIEW_CSS: "\n.jo-xl {\n --jo-xl-line: #d0d7de;\n --jo-xl-header-bg: #f5f5f5;\n --jo-xl-header-fg: #575757;\n --jo-xl-surface: #ffffff;\n /*\n * Black, and not a softer near-black.\n *\n * A cell whose font names no colour is drawn in the window text colour, and\n * in Excel that is pure black — every reference reading of every workbook\n * says #000000. A gentler grey reads better on its own but it is not what the\n * document looks like, and next to a cell that *does* set black the\n * difference shows as two shades of text in one column.\n */\n --jo-xl-text: #000000;\n --jo-xl-accent: #217346;\n --jo-xl-selection: rgba(33, 115, 70, 0.12);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--jo-xl-surface);\n color: var(--jo-xl-text);\n font-family: Calibri, 'Segoe UI', system-ui, sans-serif;\n font-size: 11pt;\n --jo-xl-row-header-width: 46px;\n}\n\n/* ------------------------------------------------------------- formula bar */\n\n.jo-xl__formula-bar {\n display: flex;\n align-items: stretch;\n gap: 0;\n flex: 0 0 auto;\n border-bottom: 1px solid var(--jo-xl-line);\n background: var(--jo-xl-surface);\n font-size: 12px;\n}\n\n.jo-xl__name-box {\n width: 140px;\n padding: 4px 8px;\n border-right: 1px solid var(--jo-xl-line);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.jo-xl__fx {\n padding: 4px 10px;\n color: var(--jo-xl-header-fg);\n font-style: italic;\n border-right: 1px solid var(--jo-xl-line);\n}\n\n.jo-xl__formula {\n flex: 1;\n padding: 4px 8px;\n white-space: pre;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: Consolas, 'SF Mono', monospace;\n}\n\n/* -------------------------------------------------------------------- grid */\n\n.jo-xl__host {\n flex: 1 1 auto;\n min-height: 0;\n position: relative;\n}\n\n/*\n * The grid does not select text; it selects cells.\n *\n * Dragging over a range is the sheet's own gesture, and while the browser was\n * also allowed to drag a text selection through it the two ran at once — and\n * the browser's ran through the *document* order, which on an absolutely\n * positioned grid has nothing to do with what is on the screen. The reader drew\n * a tidy green rectangle and got a ragged blue one over cells nowhere near it.\n *\n * Text selection is not given up, only asked for: double-clicking a cell opens\n * it, and the --reading rule below hands that one cell back to the browser so\n * the words in it can be selected and copied on their own. Escape, or a click\n * anywhere else, closes it again. It is what Excel does, and it is the only\n * arrangement in which both gestures can mean something.\n */\n.jo-xl__grid {\n user-select: none;\n -webkit-user-select: none;\n display: grid;\n width: 100%;\n height: 100%;\n overflow: hidden;\n position: relative;\n background: var(--jo-xl-surface);\n}\n\n/*\n * The grid is a tab stop, and its focus is shown by the selection.\n *\n * A ring around the whole sheet says only that the grid has focus, which is the\n * least useful thing a reader could be told; the selection rectangle says\n * *where* the focus is, which is what they need and what every spreadsheet\n * shows. So the ring is suppressed and the selection stands in for it — which\n * means the selection must always be drawn, and it is.\n */\n.jo-xl__grid:focus,\n.jo-xl__grid:focus-visible {\n outline: none;\n}\n\n.jo-xl__grid--rtl { direction: rtl; }\n\n.jo-xl__corner,\n.jo-xl__colhead,\n.jo-xl__rowhead,\n.jo-xl__pane {\n position: relative;\n overflow: hidden;\n min-width: 0;\n min-height: 0;\n}\n\n.jo-xl__scroller {\n overflow: auto;\n /* Scrolling repaints the whole pane; telling the compositor in advance keeps\n a large sheet at one frame per scroll instead of one per repaint. */\n will-change: scroll-position;\n}\n\n.jo-xl__sizer {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n}\n\n.jo-xl__canvas,\n.jo-xl__lines {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n}\n\n/*\n * The headers are part of the grid, so they scale with it.\n *\n * Relative to the grid's own size, which the view sets from the workbook's base\n * format multiplied by the zoom. A literal pixel size here was one of the\n * lengths that did not scale: the header boxes shrank with the columns and the\n * letters in them did not.\n */\n.jo-xl__corner,\n.jo-xl__colhead,\n.jo-xl__rowhead {\n background: var(--jo-xl-header-bg);\n color: var(--jo-xl-header-fg);\n font-size: 0.75em;\n user-select: none;\n}\n\n.jo-xl__corner {\n border-right: 1px solid var(--jo-xl-line);\n border-bottom: 1px solid var(--jo-xl-line);\n cursor: pointer;\n}\n\n.jo-xl__colhead { border-bottom: 1px solid var(--jo-xl-line); }\n.jo-xl__rowhead { border-right: 1px solid var(--jo-xl-line); }\n\n/* The freeze line, drawn on the panes that border it. */\n.jo-xl__pane--tl,\n.jo-xl__pane--tr { border-bottom: 1px solid #a0a0a0; }\n.jo-xl__pane--tl,\n.jo-xl__pane--bl { border-right: 1px solid #a0a0a0; }\n\n.jo-xl__header {\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n border-right: 1px solid var(--jo-xl-line);\n border-bottom: 1px solid var(--jo-xl-line);\n overflow: hidden;\n cursor: default;\n}\n\n.jo-xl__header--selected {\n background: var(--jo-xl-selection);\n color: var(--jo-xl-accent);\n font-weight: 600;\n}\n\n/* ------------------------------------------------------------------- cells */\n\n.jo-xl__cell {\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n align-items: flex-end;\n box-sizing: border-box;\n /*\n * Excel's own gap between a cell's edge and its text, and it is a length like\n * any other on the grid: it scales. Written as a property the view sets from\n * the zoom rather than as a literal, because a literal is what it was — and a\n * three-pixel constant left unscaled shifts every column of a zoomed sheet by\n * three pixels more than it should, which is small, systematic, and exactly\n * the kind of thing nobody finds by looking.\n */\n padding: 0 var(--jo-xl-cell-pad, 3px);\n overflow: hidden;\n white-space: pre;\n line-height: 1.15;\n /* Layout containment only. Paint containment would clip the text of a cell\n that overflows into its empty neighbours, which is behaviour Excel has and\n a grid has to keep. */\n contain: layout;\n}\n\n/* Text that spills into empty neighbours, as Excel lets it. */\n.jo-xl__cell--overflow {\n overflow: visible;\n z-index: 2;\n max-width: var(--jo-xl-spill);\n}\n\n.jo-xl__cell--merged { z-index: 1; }\n\n/*\n * The one cell the reader opened for its text.\n *\n * Raised above its neighbours so that a selection dragged across it cannot\n * wander into the cells drawn over it, and given a caret so that the browser's\n * own word and line selection work inside it.\n */\n.jo-xl__cell--reading {\n user-select: text;\n -webkit-user-select: text;\n cursor: text;\n z-index: 3;\n overflow: visible;\n}\n\n/*\n * A link is blue only when the workbook says so.\n *\n * Excel does not colour a cell because it has a hyperlink on it; it colours it\n * because the cell wears the Hyperlink style, which is written into the file\n * like any other. Files that libraries produce often carry the link without the\n * style, and Excel draws those in plain black — so painting every link blue\n * shows a document that does not exist. The pointer stays, and the underline\n * comes back under the cursor, where it costs the picture nothing.\n */\n.jo-xl__cell--link {\n cursor: pointer;\n}\n\n.jo-xl__cell--link:hover {\n text-decoration: underline;\n}\n\n/* A comment is marked the way Excel marks it: a triangle in the corner. */\n.jo-xl__cell--commented::after {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n border: 3px solid transparent;\n border-top-color: #d1343b;\n border-right-color: #d1343b;\n}\n\n.jo-xl__gridline {\n position: absolute;\n top: 0;\n left: 0;\n background: var(--jo-xl-line);\n pointer-events: none;\n}\n\n.jo-xl__gridline--h { height: 1px; }\n.jo-xl__gridline--v { width: 1px; }\n\n.jo-xl__selection {\n position: absolute;\n top: 0;\n left: 0;\n border: 2px solid var(--jo-xl-accent);\n background: var(--jo-xl-selection);\n pointer-events: none;\n z-index: 3;\n}\n\n/* --------------------------------------------------- conditional formatting */\n\n.jo-xl__bar {\n position: absolute;\n left: 0;\n top: 2px;\n bottom: 2px;\n border-radius: 1px;\n opacity: 0.75;\n pointer-events: none;\n}\n\n/* The value sits above its data bar, as it does in Excel. An absolutely\n positioned bar paints over ordinary flow content, so the text is given a\n position of its own rather than the bar a negative index — which would put\n it behind the cell's own background. */\n.jo-xl__value {\n position: relative;\n z-index: 1;\n min-width: 0;\n overflow: inherit;\n text-overflow: inherit;\n white-space: inherit;\n}\n\n.jo-xl__icon {\n font-size: 0.85em;\n margin-right: 3px;\n flex: 0 0 auto;\n}\n\n/* The button of a filter header or of a list validation. Excel draws it as\n part of the cell, at the right edge, over whatever the cell holds. */\n\n.jo-xl__button {\n position: absolute;\n right: 1px;\n top: 50%;\n transform: translateY(-50%);\n /* Above the cell's own text, which is drawn after it and would otherwise\n take the click meant for the button. */\n z-index: 4;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15px;\n height: 15px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 2px;\n background: var(--jo-xl-header-bg);\n color: var(--jo-xl-header-fg);\n font-size: 9px;\n line-height: 1;\n /* It used to be a picture of a button. It opens a menu now, so it takes the\n click that lands on it rather than passing it through to the cell. */\n cursor: pointer;\n}\n\n.jo-xl__button:hover {\n border-color: var(--jo-xl-accent);\n}\n\n.jo-xl__button--filtering {\n color: var(--jo-xl-accent);\n font-weight: 700;\n}\n\n/*\n * The note a commented cell shows.\n *\n * Excel's colours, because a reader recognises them: the pale yellow paper and\n * the thin grey rule are what a note has looked like for thirty years, and a\n * box in the viewer's own palette would read as part of the viewer rather than\n * as something written into the document.\n */\n.jo-xl__note {\n position: absolute;\n z-index: 22;\n max-width: 320px;\n max-height: 240px;\n overflow: auto;\n padding: 6px 8px;\n border: 1px solid #b0b0a0;\n background: #ffffe1;\n box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.25);\n color: #000000;\n font-size: 12px;\n line-height: 1.35;\n /* The pointer must be able to reach it: a note longer than its box is one\n the reader has to scroll. */\n pointer-events: auto;\n}\n\n.jo-xl__note-author {\n font-weight: 700;\n margin-bottom: 2px;\n}\n\n.jo-xl__note-text {\n white-space: pre-wrap;\n}\n\n/*\n * The find bar, and the marks it leaves on the sheet.\n *\n * Floated over the top-right of the grid rather than pushed into the layout:\n * opening a search should not move the rows the reader was looking at.\n */\n.jo-xl__find {\n position: absolute;\n z-index: 25;\n top: 6px;\n right: 18px;\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 6px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 4px;\n background: var(--jo-xl-surface);\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15);\n font-size: 12px;\n}\n\n.jo-xl__find-input {\n width: 180px;\n padding: 3px 6px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 3px;\n font: inherit;\n color: inherit;\n background: var(--jo-xl-surface);\n}\n\n.jo-xl__find-count {\n min-width: 72px;\n color: var(--jo-xl-header-fg);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.jo-xl__find-button {\n width: 22px;\n height: 22px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 3px;\n background: var(--jo-xl-header-bg);\n color: inherit;\n font: inherit;\n line-height: 1;\n cursor: pointer;\n}\n\n.jo-xl__find-button:hover {\n border-color: var(--jo-xl-accent);\n}\n\n/*\n * A cell a search matched, and the one being stood on.\n *\n * The mark has to survive whatever the cell already wears — a table band, a\n * conditional format, its own fill — so it is an outline and a wash rather than\n * a background, which any of those would win against.\n */\n.jo-xl__cell--match {\n box-shadow: inset 0 0 0 1px var(--jo-xl-accent);\n background-image: linear-gradient(\n color-mix(in srgb, var(--jo-xl-accent) 18%, transparent),\n color-mix(in srgb, var(--jo-xl-accent) 18%, transparent)\n );\n}\n\n.jo-xl__cell--match-current {\n box-shadow: inset 0 0 0 2px var(--jo-xl-accent);\n background-image: linear-gradient(\n color-mix(in srgb, var(--jo-xl-accent) 38%, transparent),\n color-mix(in srgb, var(--jo-xl-accent) 38%, transparent)\n );\n}\n\n/*\n * The strip on a header's trailing edge that resizes it.\n *\n * Wider than the line it sits on: a two-pixel target is one a reader misses,\n * and every spreadsheet gives this a few pixels of slack. It stays inside the\n * header, which clips what overflows it, and sits above the header's own label\n * so that a press on it is a resize rather than a selection.\n */\n.jo-xl__grip {\n position: absolute;\n z-index: 5;\n}\n\n.jo-xl__grip--column {\n top: 0;\n bottom: 0;\n right: 0;\n width: 6px;\n cursor: col-resize;\n}\n\n.jo-xl__grip--row {\n left: 0;\n right: 0;\n bottom: 0;\n height: 6px;\n cursor: row-resize;\n}\n\n/* Where the drag will land, drawn while it is held. */\n.jo-xl__guide {\n position: absolute;\n z-index: 30;\n background: var(--jo-xl-accent);\n pointer-events: none;\n}\n\n.jo-xl__guide--column {\n top: 0;\n bottom: 0;\n width: 1px;\n}\n\n.jo-xl__guide--row {\n left: 0;\n right: 0;\n height: 1px;\n}\n\n/*\n * The menu a filter button opens.\n *\n * Above the panes rather than inside one: a pane clips what overflows it, and a\n * menu that lists twenty values would be cut off at the first row boundary.\n */\n.jo-xl__filter-menu {\n position: absolute;\n z-index: 20;\n min-width: 160px;\n max-width: 280px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 4px;\n background: var(--jo-xl-surface);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);\n font-size: 12px;\n overflow: hidden;\n}\n\n.jo-xl__filter-list {\n max-height: 240px;\n overflow: auto;\n padding: 4px 0;\n}\n\n.jo-xl__filter-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 8px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.jo-xl__filter-item:hover {\n background: var(--jo-xl-header-bg);\n}\n\n.jo-xl__filter-item span {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.jo-xl__filter-actions {\n display: flex;\n gap: 4px;\n padding: 6px;\n border-top: 1px solid var(--jo-xl-line);\n}\n\n.jo-xl__filter-button {\n flex: 1;\n padding: 3px 6px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 3px;\n background: var(--jo-xl-header-bg);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.jo-xl__filter-button:hover {\n border-color: var(--jo-xl-accent);\n}\n\n/* A shape's own outline, drawn under whatever text it holds. */\n\n.jo-xl__outline {\n position: absolute;\n inset: 0;\n overflow: visible;\n pointer-events: none;\n}\n\n/* The outline gutter: a bracket per group, a button on its summary row. */\n\n.jo-xl__outline-bracket {\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n background: var(--jo-xl-header-fg);\n opacity: 0.55;\n}\n\n.jo-xl__outline-button {\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 11px;\n height: 11px;\n border: 1px solid var(--jo-xl-header-fg);\n background: var(--jo-xl-bg);\n color: var(--jo-xl-header-fg);\n font-size: 9px;\n line-height: 1;\n}\n\n/* The axis of a data bar: where zero falls, when it falls inside the cell. */\n\n.jo-xl__bar-axis {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n pointer-events: none;\n}\n\n/* A sparkline fills its cell and sits behind whatever the cell holds. */\n\n.jo-xl__sparkline {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* --------------------------------------------------------------- drawings */\n\n.jo-xl__drawing {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 4;\n pointer-events: none;\n overflow: hidden;\n}\n\n/*\n * A picture fills its box, and is distorted if the box says so.\n *\n * Excel stretches a picture into the rectangle it was given — a:stretch is what\n * the file says and stretching is what it means — so a picture whose box was\n * dragged out of proportion is drawn out of proportion. Fitting it inside\n * instead keeps the shape but leaves bands of empty sheet on two sides and puts\n * the picture somewhere the author did not, which is the more visible error of\n * the two and the harder one to explain.\n */\n.jo-xl__drawing img {\n width: 100%;\n height: 100%;\n object-fit: fill;\n}\n\n.jo-xl__drawing--placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px dashed var(--jo-xl-line);\n background: color-mix(in srgb, var(--jo-xl-header-bg) 70%, transparent);\n color: var(--jo-xl-header-fg);\n font-size: 11px;\n text-align: center;\n padding: 4px;\n}\n\n.jo-xl__drawing--shape {\n white-space: pre-wrap;\n font-size: 11px;\n}\n\n/* ------------------------------------------------------------------- tabs */\n\n.jo-xl__tabs {\n display: flex;\n gap: 1px;\n padding: 3px 6px 0;\n border-top: 1px solid var(--jo-xl-line);\n background: var(--jo-xl-header-bg);\n overflow-x: auto;\n flex: 0 0 auto;\n}\n\n.jo-xl__tab {\n appearance: none;\n border: 1px solid transparent;\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n background: transparent;\n color: inherit;\n padding: 4px 12px;\n cursor: pointer;\n white-space: nowrap;\n font: inherit;\n font-size: 12px;\n border-bottom: 3px solid var(--jo-xl-tab-color, transparent);\n}\n\n.jo-xl__tab:hover { background: rgba(0, 0, 0, 0.04); }\n\n.jo-xl__tab[aria-selected='true'] {\n background: var(--jo-xl-surface);\n border-color: var(--jo-xl-line);\n border-bottom-color: var(--jo-xl-tab-color, var(--jo-xl-surface));\n font-weight: 600;\n color: var(--jo-xl-accent);\n}\n\n.jo-xl__tab--hidden { opacity: 0.55; font-style: italic; }\n\n.jo-xl__empty {\n padding: 32px;\n text-align: center;\n color: var(--jo-xl-header-fg);\n font-size: 13px;\n}\n\n@media (prefers-color-scheme: dark) {\n .jo-xl {\n --jo-xl-line: #3d444d;\n --jo-xl-header-bg: #21262d;\n --jo-xl-header-fg: #9198a1;\n --jo-xl-surface: #0d1117;\n --jo-xl-text: #e6edf3;\n --jo-xl-accent: #3fb950;\n --jo-xl-selection: rgba(63, 185, 80, 0.16);\n }\n\n .jo-xl__tab:hover { background: rgba(255, 255, 255, 0.06); }\n}\n\n@media print {\n .jo-xl__formula-bar,\n .jo-xl__tabs { display: none; }\n\n .jo-xl__scroller { overflow: visible; }\n}\n"

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.

xlsxView
xlsxView: ViewModule<XlsxDocument>

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.