@office-kit/xlsx 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -4
- package/dist/cell/cell.d.ts +12 -19
- package/dist/{cell-Df3cKRIl.mjs → cell-B5OUUdfI.mjs} +13 -7
- package/dist/cell-B5OUUdfI.mjs.map +1 -0
- package/dist/{cell-style-CNsET6WU.mjs → cell-style-CbB4nXTY.mjs} +11 -4
- package/dist/cell-style-CbB4nXTY.mjs.map +1 -0
- package/dist/cell.mjs +1 -1
- package/dist/{coordinate-Bt5Gni2I.mjs → coordinate-CxC88uBk.mjs} +31 -2
- package/dist/coordinate-CxC88uBk.mjs.map +1 -0
- package/dist/{defined-names-Ctu3F6ls.mjs → defined-names-Cmp8rbvI.mjs} +2 -2
- package/dist/{defined-names-Ctu3F6ls.mjs.map → defined-names-Cmp8rbvI.mjs.map} +1 -1
- package/dist/{differential-BPKa7wXi.mjs → differential-QLaKEiBk.mjs} +2 -2
- package/dist/{differential-BPKa7wXi.mjs.map → differential-QLaKEiBk.mjs.map} +1 -1
- package/dist/{drawing-ZJ3h4VHD.mjs → drawing-Cv6UtInY.mjs} +2 -2
- package/dist/{drawing-ZJ3h4VHD.mjs.map → drawing-Cv6UtInY.mjs.map} +1 -1
- package/dist/drawing.mjs +1 -1
- package/dist/{escape-rnWSPpWY.mjs → escape-rA5UpibT.mjs} +26 -3
- package/dist/escape-rA5UpibT.mjs.map +1 -0
- package/dist/io/load.d.ts +30 -0
- package/dist/io.mjs +2 -2
- package/dist/{iterparse-DZE0zy2M.mjs → iterparse-CZoRXvZW.mjs} +2 -2
- package/dist/{iterparse-DZE0zy2M.mjs.map → iterparse-CZoRXvZW.mjs.map} +1 -1
- package/dist/{load-B1sK-LjT.mjs → load-BpnCPv1S.mjs} +84 -45
- package/dist/load-BpnCPv1S.mjs.map +1 -0
- package/dist/{manifest-pLmx7KYh.mjs → manifest-9p0ccqmR.mjs} +10 -11
- package/dist/manifest-9p0ccqmR.mjs.map +1 -0
- package/dist/node.mjs +1 -1
- package/dist/packaging.mjs +1 -1
- package/dist/{parser-By6RWZVW.mjs → parser-B1tB9YaE.mjs} +15 -5
- package/dist/parser-B1tB9YaE.mjs.map +1 -0
- package/dist/{reader-DHMxLBQV.mjs → reader-pC7bxYAs.mjs} +30 -7
- package/dist/reader-pC7bxYAs.mjs.map +1 -0
- package/dist/{save-DmdWwxmw.mjs → save-1Z3344nz.mjs} +8 -8
- package/dist/{save-DmdWwxmw.mjs.map → save-1Z3344nz.mjs.map} +1 -1
- package/dist/schema.mjs +1 -1
- package/dist/{serialize-BC2Wu3bR.mjs → serialize-BtxgssFK.mjs} +24 -7
- package/dist/serialize-BtxgssFK.mjs.map +1 -0
- package/dist/{serializer-C6nm0mRJ.mjs → serializer-N6XIwsgp.mjs} +3 -3
- package/dist/{serializer-C6nm0mRJ.mjs.map → serializer-N6XIwsgp.mjs.map} +1 -1
- package/dist/streaming.mjs +129 -77
- package/dist/streaming.mjs.map +1 -1
- package/dist/styles/display-text.d.ts +95 -0
- package/dist/styles/format-code.d.ts +125 -0
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/numbers.d.ts +7 -0
- package/dist/styles.mjs +1161 -4
- package/dist/styles.mjs.map +1 -0
- package/dist/{stylesheet-writer-oXSuGnJy.mjs → stylesheet-writer-BEn-0P0x.mjs} +480 -173
- package/dist/stylesheet-writer-BEn-0P0x.mjs.map +1 -0
- package/dist/{table-BrP0RwUd.mjs → table-BxOLUZs3.mjs} +2 -2
- package/dist/{table-BrP0RwUd.mjs.map → table-BxOLUZs3.mjs.map} +1 -1
- package/dist/{tree-BX-sRRVp.mjs → tree-CspNjuYl.mjs} +12 -2
- package/dist/tree-CspNjuYl.mjs.map +1 -0
- package/dist/utils/cell-number.d.ts +10 -0
- package/dist/utils/coordinate.d.ts +11 -0
- package/dist/utils/escape.d.ts +17 -0
- package/dist/utils/xsd-boolean.d.ts +9 -0
- package/dist/utils.mjs +2 -2
- package/dist/workbook/index.d.ts +1 -1
- package/dist/workbook/workbook.d.ts +9 -46
- package/dist/{workbook-DOTFzCpz.mjs → workbook-tG9oYtzU.mjs} +50 -13
- package/dist/workbook-tG9oYtzU.mjs.map +1 -0
- package/dist/workbook.mjs +3 -3
- package/dist/worksheet/index.d.ts +1 -1
- package/dist/worksheet/worksheet.d.ts +55 -14
- package/dist/{worksheet-B2NOQ6dM.mjs → worksheet-DLMyDD7W.mjs} +87 -22
- package/dist/worksheet-DLMyDD7W.mjs.map +1 -0
- package/dist/worksheet.mjs +3 -3
- package/dist/xml/namespaces.d.ts +7 -0
- package/dist/xml/parser.d.ts +12 -0
- package/dist/xml/strict-package.d.ts +14 -0
- package/dist/xml.mjs +5 -5
- package/dist/zip.mjs +1 -1
- package/package.json +1 -1
- package/dist/cell-Df3cKRIl.mjs.map +0 -1
- package/dist/cell-style-CNsET6WU.mjs.map +0 -1
- package/dist/coordinate-Bt5Gni2I.mjs.map +0 -1
- package/dist/escape-rnWSPpWY.mjs.map +0 -1
- package/dist/load-B1sK-LjT.mjs.map +0 -1
- package/dist/manifest-pLmx7KYh.mjs.map +0 -1
- package/dist/parser-By6RWZVW.mjs.map +0 -1
- package/dist/reader-DHMxLBQV.mjs.map +0 -1
- package/dist/serialize-BC2Wu3bR.mjs.map +0 -1
- package/dist/stylesheet-writer-oXSuGnJy.mjs.map +0 -1
- package/dist/tree-BX-sRRVp.mjs.map +0 -1
- package/dist/workbook-DOTFzCpz.mjs.map +0 -1
- package/dist/worksheet-B2NOQ6dM.mjs.map +0 -1
package/dist/streaming.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.mjs","names":[],"sources":["../src/streaming/read-only.ts","../src/streaming/string-table.ts","../src/streaming/write-only.ts"],"sourcesContent":["// Streaming read-only workbook.\n//\n// `loadWorkbookStream` opens the zip + parses workbook.xml / sharedStrings.xml\n// / styles.xml metadata up front (small even on million-row archives), then\n// exposes a lazy `openWorksheet(name)` that SAX-iterates the sheet body via\n// `iterParse`. The iterator streams rows without materialising the full sheet\n// in memory.\n\nimport { makeSharedStrings, parseSharedStringsXml, type SharedStringsTable } from '../workbook/shared-strings.js';\nimport { ARC_CONTENT_TYPES, ARC_ROOT_RELS, ARC_SHARED_STRINGS, ARC_STYLE, REL_NS, SHEET_MAIN_NS } from '../xml/namespaces.js';\nimport { findById, relsFromBytes } from '../packaging/relationships.js';\nimport { manifestFromBytes } from '../packaging/manifest.js';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport type { DecompressionLimits } from '../zip/decompression-guard.js';\nimport { type ZipArchive, openZip } from '../zip/reader.js';\nimport type { CellValue, ExcelErrorCode } from '../cell/cell.js';\nimport { unescapeCellString } from '../utils/escape.js';\nimport { ERROR_CODES } from '../utils/inference.js';\nimport { iterParse, type SaxEvent, type SaxInput } from '../xml/iterparse.js';\nimport { parseXml } from '../xml/parser.js';\nimport { findChild, findChildren, type XmlNode } from '../xml/tree.js';\nimport type { XlsxSource } from '../io/source.js';\nimport { coordinateToTuple } from '../utils/coordinate.js';\nimport { type Stylesheet, makeStylesheet } from '../styles/stylesheet.js';\nimport { parseStylesheetXml } from '../styles/stylesheet-reader.js';\nimport { parseDate1904, resolveRelTarget } from '../io/load.js';\n\nconst SHEET_TAG = `{${SHEET_MAIN_NS}}sheet`;\nconst SHEETS_TAG = `{${SHEET_MAIN_NS}}sheets`;\n\nexport interface IterRowsOptions {\n minRow?: number;\n maxRow?: number;\n minCol?: number;\n maxCol?: number;\n}\n\nexport interface ReadOnlyCell {\n readonly row: number;\n readonly col: number;\n readonly value: CellValue;\n readonly styleId: number;\n}\n\nexport interface ReadOnlyWorksheet {\n title: string;\n iterRows(opts?: IterRowsOptions): AsyncIterableIterator<ReadOnlyCell[]>;\n iterValues(opts?: IterRowsOptions): AsyncIterableIterator<CellValue[]>;\n}\n\nexport interface ReadOnlyWorkbook {\n sheetNames: string[];\n styles: Stylesheet;\n /** Date1904 mode toggles between Excel's two epoch systems. */\n date1904: boolean;\n openWorksheet(name: string): ReadOnlyWorksheet;\n close(): Promise<void>;\n}\n\ninterface SheetEntry {\n name: string;\n rId: string;\n partPath: string;\n}\n\nconst parseSheetList = (root: XmlNode, workbookPath: string, archive: ZipArchive): SheetEntry[] => {\n const sheetsEl = findChild(root, SHEETS_TAG);\n if (!sheetsEl) return [];\n const wbRelsPath = relsPathFor(workbookPath);\n const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : { rels: [] };\n const out: SheetEntry[] = [];\n for (const sheet of findChildren(sheetsEl, SHEET_TAG)) {\n const name = sheet.attrs['name'];\n const rId = sheet.attrs[`{${REL_NS}}id`];\n if (!name || !rId) continue;\n const rel = findById(wbRels, rId);\n if (!rel) continue;\n const partPath = resolveRelTarget(workbookPath, rel.target);\n out.push({ name, rId, partPath });\n }\n return out;\n};\n\nconst relsPathFor = (partPath: string): string => {\n const i = partPath.lastIndexOf('/');\n if (i < 0) return `_rels/${partPath}.rels`;\n return `${partPath.slice(0, i)}/_rels/${partPath.slice(i + 1)}.rels`;\n};\n\nconst localName = (qname: string): string => {\n const i = qname.lastIndexOf('}');\n return i < 0 ? qname : qname.slice(i + 1);\n};\n\nconst decodeCellValue = (\n t: string,\n vText: string | undefined,\n inlineText: string | undefined,\n sst: ReadonlyArray<string>,\n): CellValue => {\n switch (t) {\n case 'n':\n return vText !== undefined && vText !== '' ? Number.parseFloat(vText) : null;\n case 's': {\n if (vText === undefined) return null;\n const idx = Number.parseInt(vText, 10);\n if (!Number.isInteger(idx) || idx < 0 || idx >= sst.length) return null;\n return sst[idx] ?? null;\n }\n case 'b':\n return vText === '1';\n case 'e': {\n if (!vText || !ERROR_CODES.has(vText)) return null;\n return { kind: 'error', code: vText as ExcelErrorCode };\n }\n case 'str':\n return vText ?? '';\n case 'inlineStr':\n return inlineText ?? '';\n default:\n return vText !== undefined && vText !== '' ? Number.parseFloat(vText) : null;\n }\n};\n\n/**\n * SAX-iterate `<sheetData>/<row>/<c>` events out of the worksheet bytes (or a\n * stream that yields them), yielding one `ReadOnlyCell[]` per row that matches\n * `opts`.\n */\nasync function* iterSheetRows(\n sheetInput: SaxInput,\n sst: ReadonlyArray<string>,\n opts: IterRowsOptions,\n): AsyncIterableIterator<ReadOnlyCell[]> {\n const minRow = opts.minRow ?? 1;\n const maxRow = opts.maxRow ?? Number.POSITIVE_INFINITY;\n const minCol = opts.minCol ?? 1;\n const maxCol = opts.maxCol ?? Number.POSITIVE_INFINITY;\n\n let inSheetData = false;\n let currentRow = -1;\n let currentRowAttrs: Record<string, string> | null = null;\n let currentCells: ReadOnlyCell[] = [];\n\n // Per-cell state. Reset when each <c> starts.\n let cellOpen = false;\n let cellRow = 0;\n let cellCol = 0;\n let cellType = 'n';\n let cellStyleId = 0;\n let inV = false;\n let vText = '';\n let inIs = false;\n let inIsT = false;\n let isText = '';\n let isRunText = '';\n\n for await (const ev of iterParse(sheetInput)) {\n const e = ev as SaxEvent;\n if (e.kind === 'start') {\n const local = localName(e.name);\n if (!inSheetData) {\n if (local === 'sheetData') inSheetData = true;\n continue;\n }\n switch (local) {\n case 'row': {\n currentRowAttrs = e.attrs;\n const rRaw = e.attrs['r'];\n currentRow = rRaw ? Number.parseInt(rRaw, 10) : currentRow + 1;\n currentCells = [];\n break;\n }\n case 'c': {\n if (currentRow < 0) break;\n // Skip cell-attr parsing entirely when the row is outside the\n // requested band — saves the parseInt + coordinateToTuple hit on\n // every cell of every excluded row.\n if (currentRow < minRow || currentRow > maxRow) break;\n cellOpen = true;\n cellType = e.attrs['t'] ?? 'n';\n const sRaw = e.attrs['s'];\n cellStyleId = sRaw ? Number.parseInt(sRaw, 10) || 0 : 0;\n const ref = e.attrs['r'];\n if (ref) {\n const tup = coordinateToTuple(ref);\n cellRow = tup.row;\n cellCol = tup.col;\n } else {\n cellRow = currentRow;\n cellCol = (currentCells[currentCells.length - 1]?.col ?? 0) + 1;\n }\n vText = '';\n isText = '';\n break;\n }\n case 'v':\n if (cellOpen) inV = true;\n break;\n case 'is':\n if (cellOpen) inIs = true;\n break;\n case 't':\n if (inIs) {\n inIsT = true;\n isRunText = '';\n }\n break;\n default:\n break;\n }\n continue;\n }\n if (e.kind === 'text') {\n if (inV) vText += e.text;\n else if (inIsT) isRunText += e.text;\n continue;\n }\n // end\n const local = localName(e.name);\n if (!inSheetData) continue;\n switch (local) {\n case 'sheetData':\n inSheetData = false;\n return;\n case 'row': {\n if (currentRow >= minRow && currentRow <= maxRow && currentCells.length > 0) {\n yield currentCells;\n }\n // Once we've crossed maxRow there are no more rows to yield — every\n // subsequent <row> would just be parsed and dropped. Stop iterating\n // early. ECMA-376 emits rows in ascending order.\n if (currentRow > maxRow) {\n inSheetData = false;\n return;\n }\n currentRow = -1;\n currentRowAttrs = null;\n currentCells = [];\n break;\n }\n case 'c': {\n if (cellOpen && cellCol >= minCol && cellCol <= maxCol && cellRow >= minRow && cellRow <= maxRow) {\n const value = decodeCellValue(cellType, vText, isText, sst);\n currentCells.push({ row: cellRow, col: cellCol, value, styleId: cellStyleId });\n }\n cellOpen = false;\n break;\n }\n case 'v':\n inV = false;\n break;\n case 'is':\n inIs = false;\n break;\n case 't':\n if (inIsT) {\n // Decode each complete <t>, so SAX chunks can split an escape but\n // adjacent rich-text runs cannot accidentally create one.\n isText += unescapeCellString(isRunText);\n inIsT = false;\n }\n break;\n default:\n break;\n }\n }\n // Avoid unused-var lint when row attrs never touched.\n void currentRowAttrs;\n}\n\nconst isXmlSpace = (b: number | undefined): boolean =>\n b === 0x20 /* sp */ || b === 0x09 /* tab */ || b === 0x0a /* lf */ || b === 0x0d /* cr */;\n\n/**\n * Read the `r=\"N\"` row number out of a `<row …>` attribute region, scanning\n * bytes directly. Returns -1 when the attribute is absent or malformed.\n *\n * Decoding the region to a string and running a regex instead costs one\n * TextDecoder, one string and one match array per row, which on a million-row\n * sheet dominates the scan this index exists to keep cheap.\n */\nconst readRowAttr = (bytes: Uint8Array, from: number, to: number): number => {\n for (let p = from; p + 3 < to; p++) {\n if (!isXmlSpace(bytes[p])) continue;\n if (bytes[p + 1] !== 0x72 /* 'r' */ || bytes[p + 2] !== 0x3d /* '=' */ || bytes[p + 3] !== 0x22 /* '\"' */) {\n continue;\n }\n let value = 0;\n let digits = 0;\n let q = p + 4;\n while (q < to) {\n const d = bytes[q];\n if (d === undefined || d < 0x30 || d > 0x39) break;\n value = value * 10 + (d - 0x30);\n digits++;\n q++;\n }\n // A non-numeric or unterminated `r` is not the row ref; keep scanning the\n // rest of the region the way the equivalent regex would have.\n if (digits > 0 && q < to && bytes[q] === 0x22 /* '\"' */) return value;\n }\n return -1;\n};\n\nconst isQuote = (b: number | undefined): boolean => b === 0x22 /* dquote */ || b === 0x27 /* squote */;\n\n/**\n * Byte offset just past the worksheet's opening `sheetData` tag, or\n * -1 when there is none. Skips the XML declaration, comments and processing\n * instructions ahead of it, and ignores `>` inside attribute values.\n */\nconst findSheetDataTagEnd = (bytes: Uint8Array): number => {\n const td = new TextDecoder();\n let i = 0;\n while (i < bytes.length) {\n if (bytes[i] !== 0x3c /* '<' */) {\n i++;\n continue;\n }\n const kind = bytes[i + 1];\n if (kind === 0x3f /* '?' */ || kind === 0x21 /* '!' */) {\n // Comments and processing instructions may contain `>` in their body.\n const isComment = bytes[i + 2] === 0x2d /* '-' */ && bytes[i + 3] === 0x2d;\n i += 2;\n while (i < bytes.length) {\n if (bytes[i] === 0x3e /* '>' */ &&\n (isComment ? bytes[i - 1] === 0x2d && bytes[i - 2] === 0x2d\n : kind !== 0x3f || bytes[i - 1] === 0x3f)) break;\n i++;\n }\n i++;\n continue;\n }\n let nameEnd = i + 1;\n while (nameEnd < bytes.length && !isXmlSpace(bytes[nameEnd]) && bytes[nameEnd] !== 0x3e && bytes[nameEnd] !== 0x2f) nameEnd++;\n const isSheetData = td.decode(bytes.subarray(i + 1, nameEnd)) === 'sheetData';\n let quote = 0;\n let tagEnd = -1;\n for (let j = i + 1; j < bytes.length; j++) {\n const b = bytes[j];\n if (b === undefined) break;\n if (quote !== 0) {\n if (b === quote) quote = 0;\n continue;\n }\n if (isQuote(b)) {\n quote = b;\n continue;\n }\n if (b === 0x3e /* '>' */) {\n tagEnd = j + 1;\n break;\n }\n }\n if (tagEnd < 0 || isSheetData) return tagEnd;\n i = tagEnd;\n }\n return -1;\n};\n\n/**\n * Build a sorted `[rowNum, byteOffset]` index for every `<row r=\"N\">`\n * occurrence in a worksheet's bytes. Pure byte-level scan (no SAX), cheap\n * relative to the per-cell SAX walk.\n *\n * `sheetDataEnd` is the byte offset of `</sheetData>` so callers can clip the\n * region that gets handed to saxes. `sheetDataTagEnd` includes the original\n * namespace-bearing ancestor tags, reused by {@link replayFromRow}.\n */\nconst buildRowOffsetIndex = (\n bytes: Uint8Array,\n): { index: ReadonlyArray<{ row: number; offset: number }>; sheetDataEnd: number; sheetDataTagEnd: number } => {\n const out: Array<{ row: number; offset: number }> = [];\n let sheetDataEnd = -1;\n let i = 0;\n while (i < bytes.length) {\n if (bytes[i] !== 0x3c /* '<' */) {\n i++;\n continue;\n }\n // Detect `</sheetData>` once — used to clip the slice fed to saxes.\n if (\n sheetDataEnd < 0 &&\n bytes[i + 1] === 0x2f /* '/' */ &&\n bytes[i + 2] === 0x73 /* 's' */ &&\n bytes[i + 3] === 0x68 /* 'h' */ &&\n bytes[i + 4] === 0x65 /* 'e' */ &&\n bytes[i + 5] === 0x65 /* 'e' */ &&\n bytes[i + 6] === 0x74 /* 't' */ &&\n bytes[i + 7] === 0x44 /* 'D' */ &&\n bytes[i + 8] === 0x61 /* 'a' */ &&\n bytes[i + 9] === 0x74 /* 't' */ &&\n bytes[i + 10] === 0x61 /* 'a' */ &&\n bytes[i + 11] === 0x3e /* '>' */\n ) {\n sheetDataEnd = i;\n break;\n }\n // Match `<row` followed by ASCII whitespace or '>'.\n if (\n bytes[i + 1] !== 0x72 /* 'r' */ ||\n bytes[i + 2] !== 0x6f /* 'o' */ ||\n bytes[i + 3] !== 0x77 /* 'w' */\n ) {\n i++;\n continue;\n }\n const next = bytes[i + 4];\n if (\n next !== 0x20 /* sp */ &&\n next !== 0x09 /* tab */ &&\n next !== 0x0a /* lf */ &&\n next !== 0x0d /* cr */ &&\n next !== 0x3e /* > */ &&\n next !== 0x2f /* / */\n ) {\n i++;\n continue;\n }\n // Walk to the closing '>'; the attrs region carries `r=\"N\"`.\n const start = i;\n let j = i + 4;\n while (j < bytes.length && bytes[j] !== 0x3e) j++;\n if (j >= bytes.length) break;\n const row = readRowAttr(bytes, start + 4, j);\n // Rows are 1-based (ECMA-376 §18.3.1.73), so `r=\"0\"` is not an offset this\n // index can seek to; -1 means there was no usable `r` at all.\n if (row > 0) out.push({ row, offset: start });\n i = j + 1;\n }\n if (sheetDataEnd < 0) sheetDataEnd = bytes.length;\n return { index: out, sheetDataEnd, sheetDataTagEnd: findSheetDataTagEnd(bytes) };\n};\n\n/**\n * Binary-search the row index for the first entry with `row >= target`. Returns\n * -1 when every recorded row is below the target.\n */\nconst firstRowAtOrAfter = (\n index: ReadonlyArray<{ row: number; offset: number }>,\n target: number,\n): number => {\n let lo = 0;\n let hi = index.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n const entry = index[mid];\n if (!entry || entry.row < target) lo = mid + 1;\n else hi = mid;\n }\n return lo < index.length ? lo : -1;\n};\n\n/**\n * Replay a row band with the original prefix through the opening sheetData\n * tag. Namespace declarations can occur on either worksheet or sheetData;\n * synthesising either tag loses bindings required by the retained rows.\n * Views avoid copying the inflated sheet just to prepend its ancestor tags.\n */\nconst replayFromRow = (\n bytes: Uint8Array,\n sheetDataTagEnd: number,\n fromOffset: number,\n sheetDataEnd: number,\n): ReadableStream<Uint8Array> => {\n const parts = [\n bytes.subarray(0, sheetDataTagEnd),\n bytes.subarray(fromOffset, sheetDataEnd),\n bytes.subarray(sheetDataEnd),\n ];\n let next = 0;\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n const part = parts[next];\n if (part === undefined) {\n controller.close();\n return;\n }\n next++;\n controller.enqueue(part);\n },\n });\n};\n\ninterface RowIndexCache {\n byWorksheet: WeakMap<object, ReturnType<typeof buildRowOffsetIndex> & { bytes: Uint8Array }>;\n}\n\n/**\n * Factory: build a {@link ReadOnlyWorksheet} bound to a single worksheet part\n * inside an opened archive. SAX iteration runs lazily: a whole-sheet `iterRows`\n * streams the part again on every call, so nothing but the inflate window and\n * the SAX state is ever resident.\n *\n * `iterRows({ minRow > 1 })` needs random access instead, so the first band\n * query inflates the part and indexes its row offsets, and holds both for the\n * life of the worksheet handle or until the workbook closes. Subsequent band\n * queries jump straight to the first matching row without inflating or scanning again.\n */\nconst makeStreamingReadOnlyWorksheet = (\n title: string,\n archive: ZipArchive,\n partPath: string,\n sst: ReadonlyArray<string>,\n indexes: RowIndexCache,\n): ReadOnlyWorksheet => {\n // Lazy + cached, bytes included: the archive only keeps small entries, so a\n // second band query that went back to `read` would inflate the whole part\n // again. The index adds one small object per row; the part is held from the first\n // band query until the worksheet handle goes away or the workbook closes.\n // This avoids parsing or inflating the whole part on every query.\n const cacheKey = {};\n const ensureIndexed = () => {\n let cached = indexes.byWorksheet.get(cacheKey);\n if (!cached) {\n const bytes = archive.read(partPath);\n cached = { bytes, ...buildRowOffsetIndex(bytes) };\n indexes.byWorksheet.set(cacheKey, cached);\n }\n return cached;\n };\n\n const iterRows = (opts: IterRowsOptions = {}): AsyncIterableIterator<ReadOnlyCell[]> => {\n const minRow = opts.minRow ?? 1;\n if (minRow <= 1) {\n // Whole-sheet (or no-min) iter — feed the SAX parser directly off the\n // archive's streaming inflate path so the worksheet's inflated payload\n // is never fully resident. Peak memory for the walk drops to the\n // inflate window + SAX state instead of the entire `<sheetData>` body.\n return iterSheetRows(archive.readStream(partPath), sst, opts);\n }\n // Band query (minRow > 1): the row-offset index needs the full inflated\n // bytes so we can binary-search to the byte offset of the first matching\n // row. Materialise once and reuse via `ensureIndexed`.\n const { bytes, index, sheetDataEnd, sheetDataTagEnd } = ensureIndexed();\n if (index.length === 0 || sheetDataTagEnd < 0) return iterSheetRows(bytes, sst, opts);\n const pos = firstRowAtOrAfter(index, minRow);\n if (pos < 0) {\n // Every row is below minRow, so there is nothing to yield.\n return (async function* () {})();\n }\n const target = index[pos];\n if (!target) return iterSheetRows(bytes, sst, opts);\n return iterSheetRows(replayFromRow(bytes, sheetDataTagEnd, target.offset, sheetDataEnd), sst, opts);\n };\n const iterValues = async function* (opts: IterRowsOptions = {}): AsyncIterableIterator<CellValue[]> {\n for await (const row of iterRows(opts)) {\n yield row.map((c) => c.value);\n }\n };\n return { title, iterRows, iterValues };\n};\n\n/**\n * Factory: build a {@link ReadOnlyWorkbook} from an opened archive + pre-parsed\n * sheet list / styles / shared strings. Per the project-wide \"no classes\" rule\n * (CLAUDE.md), the workbook is a plain object closing over the archive handle.\n */\nconst makeStreamingReadOnlyWorkbook = (\n sheetNames: string[],\n styles: Stylesheet,\n date1904: boolean,\n archive: ZipArchive,\n entries: ReadonlyMap<string, SheetEntry>,\n sst: ReadonlyArray<string>,\n): ReadOnlyWorkbook => {\n // Weak keys let unused worksheet handles release their indexed bytes. The\n // indirection also lets close() release every index while handles remain live.\n const indexes: RowIndexCache = { byWorksheet: new WeakMap() };\n return {\n sheetNames,\n styles,\n date1904,\n openWorksheet(name) {\n const entry = entries.get(name);\n if (!entry) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: no worksheet named \"${name}\"`);\n }\n return makeStreamingReadOnlyWorksheet(name, archive, entry.partPath, sst, indexes);\n },\n async close() {\n archive.close();\n indexes.byWorksheet = new WeakMap();\n },\n };\n};\n\n/** Options for {@link loadWorkbookStream}. */\nexport interface LoadWorkbookStreamOptions {\n /**\n * Decompression-bomb safeguards applied while inflating zip entries.\n * Defaults to limits that fit any legitimate xlsx; pass `false` to disable\n * (only safe for fully trusted sources).\n */\n decompressionLimits?: DecompressionLimits | false;\n}\n\n/** Open an xlsx for read-only streaming access. */\nexport async function loadWorkbookStream(\n source: XlsxSource,\n opts: LoadWorkbookStreamOptions = {},\n): Promise<ReadOnlyWorkbook> {\n const archive = await openZip(\n source,\n opts.decompressionLimits === undefined ? {} : { decompressionLimits: opts.decompressionLimits },\n );\n if (!archive.has(ARC_CONTENT_TYPES)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: missing \"${ARC_CONTENT_TYPES}\"`);\n }\n // Manifest parse is intentionally cheap and discarded — we resolve sheets by\n // walking workbook.xml.rels directly.\n manifestFromBytes(archive.read(ARC_CONTENT_TYPES));\n\n if (!archive.has(ARC_ROOT_RELS)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: missing \"${ARC_ROOT_RELS}\"`);\n }\n const rootRels = relsFromBytes(archive.read(ARC_ROOT_RELS));\n const officeDocRel = rootRels.rels.find((r) => r.type === `${REL_NS}/officeDocument`);\n if (!officeDocRel) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: no officeDocument relationship in root rels`);\n }\n const workbookPath = resolveRelTarget('', officeDocRel.target);\n if (!archive.has(workbookPath)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: workbook part \"${workbookPath}\" missing`);\n }\n const workbookRoot = parseXml(archive.read(workbookPath));\n const sheetEntries = parseSheetList(workbookRoot, workbookPath, archive);\n const entryMap = new Map<string, SheetEntry>();\n for (const e of sheetEntries) entryMap.set(e.name, e);\n\n let sst: SharedStringsTable = makeSharedStrings();\n if (archive.has(ARC_SHARED_STRINGS)) {\n sst = parseSharedStringsXml(archive.read(ARC_SHARED_STRINGS));\n }\n let styles: Stylesheet = makeStylesheet();\n if (archive.has(ARC_STYLE)) {\n styles = parseStylesheetXml(archive.read(ARC_STYLE));\n }\n\n return makeStreamingReadOnlyWorkbook(\n sheetEntries.map((e) => e.name),\n styles,\n parseDate1904(workbookRoot),\n archive,\n entryMap,\n sst.entries.map((e) => (typeof e === 'string' ? e : e.runs.map((r) => r.text).join(''))),\n );\n}\n","import { serializeRichString } from '../workbook/shared-strings.js';\nimport type { CellStringWriter } from '../worksheet/writer.js';\nimport { SHEET_MAIN_NS } from '../xml/namespaces.js';\nimport type { StreamingEntryWriter } from '../zip/writer.js';\n\n// Entry count bounds Map/array overhead; payload bounds long strings and rich\n// text markup. UTF-16 accounting is deterministic, not an exact V8 heap metric.\nconst MAX_ENTRIES = 100_000;\nconst MAX_PAYLOAD_BYTES = 8 * 1024 * 1024;\nconst UTF16_BYTES_PER_UNIT = 2;\nconst XML_CHUNK_UNITS = 16 * 1024;\n\nexport const createWriteOnlyStringTable = () => {\n const entries: string[] = [];\n const plainIndex = new Map<string, number>();\n const richIndex = new Map<string, number>();\n let payloadBytes = 0;\n let full = false;\n\n const serialize: CellStringWriter = (value) => {\n const plain = typeof value === 'string';\n // Plain strings can hit the cache without escaping. Rich text is keyed by\n // its emitted XML, which snapshots caller-owned runs and font objects.\n const key = plain ? value : serializeRichString(value);\n const index = plain ? plainIndex : richIndex;\n const cached = index.get(key);\n if (cached !== undefined) return { type: 's', xml: `<v>${cached}</v>` };\n const body = plain ? serializeRichString(value) : key;\n // Count both keys and XML, conservatively even when they share storage.\n const bytes = (key.length + body.length) * UTF16_BYTES_PER_UNIT;\n if (!full && entries.length < MAX_ENTRIES && payloadBytes + bytes <= MAX_PAYLOAD_BYTES) {\n const id = entries.length;\n entries.push(body);\n index.set(key, id);\n payloadBytes += bytes;\n return { type: 's', xml: `<v>${id}</v>` };\n }\n // Never evict: earlier worksheet cells already reference these indices.\n // Once a new entry does not fit, all subsequent new values stay inline.\n full = true;\n return { type: 'inlineStr', xml: `<is>${body}</is>` };\n };\n\n const write = async (stream: StreamingEntryWriter): Promise<void> => {\n const encoder = new TextEncoder();\n let pending = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sst xmlns=\"${SHEET_MAIN_NS}\" count=\"${entries.length}\" uniqueCount=\"${entries.length}\">`;\n const append = (text: string): void => {\n pending += text;\n while (pending.length >= XML_CHUNK_UNITS) {\n // TextEncoder must see surrogate pairs together, including at a chunk\n // boundary. Otherwise an astral character becomes two replacements.\n let end = XML_CHUNK_UNITS;\n const last = pending.charCodeAt(end - 1);\n if (last >= 0xd800 && last <= 0xdbff) end--;\n stream.write(encoder.encode(pending.slice(0, end)));\n pending = pending.slice(end);\n }\n };\n for (const body of entries) append(`<si>${body}</si>`);\n append('</sst>');\n if (pending.length > 0) stream.write(encoder.encode(pending));\n await stream.end();\n };\n\n return { serialize, write, get size() { return entries.length; } };\n};\n","// Rows are flushed through a bounded text buffer into the ZIP stream. The\n// workbook-wide string table has entry and payload limits; new strings beyond\n// those limits are written inline. Styles and sheet metadata remain resident.\n\nimport type { Cell, CellValue } from '../cell/cell.js';\nimport type { XlsxSink } from '../io/sink.js';\nimport { addDefault, addOverride, makeManifest, manifestToBytes } from '../packaging/manifest.js';\nimport { makeRelationships, relsToBytes } from '../packaging/relationships.js';\nimport {\n addCellXf,\n buildXfPatch,\n type CellStyleSpec,\n defaultCellXf,\n makeStylesheet,\n type Stylesheet,\n} from '../styles/stylesheet.js';\nimport { stylesheetToBytes } from '../styles/stylesheet-writer.js';\nimport { escapeXmlAttr } from '../utils/escape.js';\nimport { OpenXmlIoError } from '../utils/exceptions.js';\nimport { utf8ByteLength } from '../utils/utf8.js';\nimport { makeSharedStrings } from '../workbook/shared-strings.js';\nimport { validateSheetTitle } from '../workbook/workbook.js';\nimport { serializeCell } from '../worksheet/writer.js';\nimport {\n ARC_CONTENT_TYPES,\n ARC_ROOT_RELS,\n ARC_SHARED_STRINGS,\n ARC_STYLE,\n ARC_WORKBOOK,\n ARC_WORKBOOK_RELS,\n PKG_REL_NS,\n REL_NS,\n SHARED_STRINGS_TYPE,\n SHEET_MAIN_NS,\n STYLES_TYPE,\n WORKSHEET_TYPE,\n XLSX_TYPE,\n} from '../xml/namespaces.js';\nimport { type CompressionLevel, createZipWriter, type ZipWriterOptions } from '../zip/writer.js';\n\nimport { createWriteOnlyStringTable } from './string-table.js';\n\nconst escapeAttr = escapeXmlAttr;\n\nexport interface WriteOnlyOptions {\n /** Reserved — currently ignored (the buffered backend doesn't honour it). */\n estimatedMaxRow?: number;\n /**\n * Last-modified timestamp for every ZIP entry. Same reproducibility story as\n * `SaveOptions.mtime`: unset, fflate stamps the wall clock per entry and two\n * runs over identical rows differ in bytes. Recorded as the date's UTC wall\n * time, to a two-second resolution, and the year has to fall in 1980-2099.\n */\n mtime?: Date;\n /** Deflate level, 0 (no compression) to 9 (smallest). Defaults to fflate's own 6. */\n compressionLevel?: CompressionLevel;\n}\n\n/**\n * A cell's look on the write-only path. Identical to the modelled writer's\n * {@link CellStyleSpec}: the two writers resolve a spec to an xf through the\n * same builder, so an axis added to one is honoured by both.\n */\nexport type WriteOnlyStyle = CellStyleSpec;\n\nexport type WriteOnlyRowItem = CellValue | { value: CellValue; style?: WriteOnlyStyle };\n\nexport interface WriteOnlyWorksheet {\n readonly title: string;\n appendRow(row: WriteOnlyRowItem[]): Promise<void>;\n setColumnWidth(col: number, width: number): void;\n close(): Promise<void>;\n}\n\nexport interface WriteOnlyWorkbook {\n /** Add a new worksheet. The previous worksheet must be `close()`d first. */\n addWorksheet(title: string): Promise<WriteOnlyWorksheet>;\n /** Finalise the archive: emits styles / sharedStrings / workbook / manifest / rels. */\n finalize(): Promise<void>;\n /**\n * Release the sink without finalising the archive. Call this from a\n * surrounding catch block when the producer pipeline throws before\n * `finalize()` — without it, streaming destinations (`toFile` /\n * `toWritable`) keep the file descriptor / Writable open and the partial\n * xlsx looks valid on disk.\n *\n * Idempotent. Subsequent `addWorksheet` / `finalize` calls throw.\n */\n abort(cause?: unknown): void;\n}\n\nconst validateTitle = (title: string, taken: Set<string>): void => {\n const reason = validateSheetTitle(title);\n if (reason) {\n throw new OpenXmlIoError(`Worksheet title \"${title}\": ${reason}`);\n }\n if (taken.has(title.toLowerCase())) {\n throw new OpenXmlIoError(`Worksheet title \"${title}\" is already in use`);\n }\n};\n\n/**\n * Allocate a CellXf id for a style spec. `defaultCellXf` omits `xfId`, which\n * skips the cellStyleXfs bounds check and matches what Excel emits when there\n * is no parent style.\n */\nconst allocateXfId = (ss: Stylesheet, style: WriteOnlyStyle): number =>\n addCellXf(ss, { ...defaultCellXf(), ...buildXfPatch(ss, style) });\n\ninterface WorkbookState {\n styles: Stylesheet;\n strings: ReturnType<typeof createWriteOnlyStringTable>;\n /** Sheet emit metadata, in addWorksheet order. */\n sheets: Array<{\n title: string;\n sheetId: number;\n }>;\n /** True once finalize() has been called (further mutations throw). */\n finalised: boolean;\n /** True while a worksheet is open (the next addWorksheet must wait). */\n hasOpenWorksheet: boolean;\n /** ZIP writer the workbook + each open worksheet stream chunks through. */\n writer: import('../zip/writer.js').ZipWriter;\n}\n\n/**\n * Flush threshold for the worksheet's pending-row text buffer. Smaller values\n * minimise heap; larger values amortise the TextEncoder + push overhead. 64 KB\n * is a sweet spot — heap stays low and per-row JS work is dominated by the\n * actual XML construction, not flushing.\n */\nconst FLUSH_THRESHOLD_BYTES = 64 * 1024;\n\n/**\n * Factory: build a {@link WriteOnlyWorksheet} that closes over the shared\n * {@link WorkbookState}. Per the project-wide \"no classes\" rule (CLAUDE.md) the\n * worksheet is a plain object holding the row buffer + column-width map in\n * closure state.\n *\n * The worksheet streams its `<sheetData>` body chunk-by-chunk through the ZIP\n * writer's `addStreamingEntry` API. Row buffering stays at ~64 KB plus the\n * current row and deflate scratch; strings have a separate workbook-wide cap.\n * The XML envelope (decl / worksheet open / cols / sheetData open) flushes on\n * the first `appendRow` (or `close()` if the sheet is empty);\n * column widths staged via `setColumnWidth` *must* land before the first row.\n */\nconst makeWriteOnlyWorksheet = (state: WorkbookState, title: string, sheetId: number): WriteOnlyWorksheet => {\n let nextRow = 1;\n let closed = false;\n let headerFlushed = false;\n const columnWidths = new Map<number, number>();\n // Strings use the bounded workbook-wide writer; other cells still need\n // the regular serialization context (notably the shared style pool).\n const dummyCtx = { sharedStrings: makeSharedStrings(), styles: state.styles };\n const encoder = new TextEncoder();\n const stream = state.writer.addStreamingEntry(`xl/worksheets/sheet${sheetId}.xml`);\n let pendingText = '';\n let pendingBytes = 0;\n\n const writeText = (text: string): void => {\n pendingText += text;\n // UTF-8 byte length, computed without a full encode: scan once and add the\n // exact codepoint cost. `text.length` would undercount CJK by ~3× and let\n // the buffer balloon past FLUSH_THRESHOLD_BYTES on Japanese / Chinese\n // workloads.\n pendingBytes += utf8ByteLength(text);\n if (pendingBytes >= FLUSH_THRESHOLD_BYTES) {\n stream.write(encoder.encode(pendingText));\n pendingText = '';\n pendingBytes = 0;\n }\n };\n\n const flushHeader = (): void => {\n if (headerFlushed) return;\n headerFlushed = true;\n let header = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>';\n header += `<worksheet xmlns=\"${SHEET_MAIN_NS}\" xmlns:r=\"${REL_NS}\">`;\n if (columnWidths.size > 0) {\n header += '<cols>';\n const sorted = [...columnWidths.entries()].sort((a, b) => a[0] - b[0]);\n for (const [col, width] of sorted) {\n header += `<col min=\"${col}\" max=\"${col}\" width=\"${width}\" customWidth=\"1\"/>`;\n }\n header += '</cols>';\n }\n header += '<sheetData>';\n writeText(header);\n };\n\n const appendRow = async (row: WriteOnlyRowItem[]): Promise<void> => {\n if (closed) throw new OpenXmlIoError('appendRow: worksheet already closed');\n flushHeader();\n const r = nextRow++;\n let xml = `<row r=\"${r}\">`;\n for (let i = 0; i < row.length; i++) {\n const item = row[i];\n if (item === undefined || item === null) continue;\n const col = i + 1;\n let value: CellValue;\n let style: WriteOnlyStyle | undefined;\n if (item !== null && typeof item === 'object' && 'value' in (item as object)) {\n const wrapped = item as { value: CellValue; style?: WriteOnlyStyle };\n value = wrapped.value;\n style = wrapped.style;\n } else {\n value = item as CellValue;\n }\n const styleId = style ? allocateXfId(state.styles, style) : 0;\n // Ephemeral cell-shaped object — discarded as soon as serializeCell\n // returns its `<c .../>` string. Keeps the heap footprint at the size of\n // the pending text buffer instead of a full Worksheet model.\n const cell: Cell = { row: r, col, value, styleId };\n xml += serializeCell(cell, dummyCtx, state.strings.serialize);\n }\n xml += '</row>';\n writeText(xml);\n };\n\n const setColumnWidth = (col: number, width: number): void => {\n if (closed) throw new OpenXmlIoError('setColumnWidth: worksheet already closed');\n if (headerFlushed) {\n throw new OpenXmlIoError(\n 'setColumnWidth: must be called before the first appendRow — column widths are emitted as part of the worksheet header',\n );\n }\n columnWidths.set(col, width);\n };\n\n const close = async (): Promise<void> => {\n if (closed) return;\n closed = true;\n flushHeader();\n writeText('</sheetData></worksheet>');\n if (pendingText.length > 0) {\n stream.write(encoder.encode(pendingText));\n pendingText = '';\n pendingBytes = 0;\n }\n await stream.end();\n state.sheets.push({ title, sheetId });\n state.hasOpenWorksheet = false;\n };\n\n return { title, appendRow, setColumnWidth, close };\n};\n\n/**\n * Factory: build a {@link WriteOnlyWorkbook} from a sink. State lives in a\n * closure rather than on a class instance per the project-wide \"no classes\"\n * rule (CLAUDE.md).\n */\nconst makeWriteOnlyWorkbook = (sink: XlsxSink, zipOpts: ZipWriterOptions): WriteOnlyWorkbook => {\n const styles = makeStylesheet();\n // Reserve cellXfs[0] for the default (no apply* flags). Unstyled cells point\n // at this slot via styleId=0; user-styled cells start at index 1 so the\n // writer emits an `s=\"N\"` attribute for them.\n addCellXf(styles, defaultCellXf());\n // The ZIP writer is created up front: each addWorksheet opens a streaming\n // entry on it and flushes row chunks through the deflate stream as they\n // arrive. Sheets emit before styles / sst / workbook / rels / content-types\n // so the writer can serialise them in order.\n const state: WorkbookState = {\n styles,\n strings: createWriteOnlyStringTable(),\n sheets: [],\n finalised: false,\n hasOpenWorksheet: false,\n writer: createZipWriter(sink, zipOpts),\n };\n\n const addWorksheet = async (title: string): Promise<WriteOnlyWorksheet> => {\n if (state.finalised) {\n throw new OpenXmlIoError('addWorksheet: workbook already finalised');\n }\n if (state.hasOpenWorksheet) {\n throw new OpenXmlIoError(\n 'addWorksheet: previous worksheet still open — call close() before opening the next one',\n );\n }\n const taken = new Set(state.sheets.map((s) => s.title.toLowerCase()));\n validateTitle(title, taken);\n state.hasOpenWorksheet = true;\n const sheetId = state.sheets.length + 1;\n return makeWriteOnlyWorksheet(state, title, sheetId);\n };\n\n const finalize = async (): Promise<void> => {\n if (state.finalised) {\n throw new OpenXmlIoError('finalize: already finalised');\n }\n if (state.hasOpenWorksheet) {\n throw new OpenXmlIoError('finalize: a worksheet is still open — call close() before finalising');\n }\n state.finalised = true;\n const writer = state.writer;\n try {\n await finalizeImpl(state, writer);\n } catch (err) {\n // Release the sink so the half-emitted archive doesn't linger as a\n // valid-looking file on disk. Awaited so `toFile` has finished removing\n // it by the time the caller sees the error. abort() is idempotent.\n await writer.abort(err);\n throw err;\n }\n };\n\n const abort = (cause?: unknown): void => {\n if (state.finalised) return;\n state.finalised = true;\n // Sync by contract, so a `toFile` sink's unlink can still be in flight when\n // this returns. The failure paths that have to guarantee cleanup before the\n // caller sees an error await the writer's abort themselves.\n void state.writer.abort(cause);\n };\n\n return { addWorksheet, finalize, abort };\n};\n\nasync function finalizeImpl(state: WorkbookState, writer: WorkbookState['writer']): Promise<void> {\n // 1. Worksheets — already streamed through writer.addStreamingEntry\n // during each WriteOnlyWorksheet's appendRow / close cycle.\n\n // 2. Stylesheet.\n await writer.addEntry(ARC_STYLE, stylesheetToBytes(state.styles));\n\n // 3. SharedStrings (only when non-empty).\n if (state.strings.size > 0) {\n await state.strings.write(writer.addStreamingEntry(ARC_SHARED_STRINGS));\n }\n\n // 4. workbook.xml.\n const workbookXml = serializeWorkbookXml(state.sheets);\n await writer.addEntry(ARC_WORKBOOK, new TextEncoder().encode(workbookXml));\n\n // 5. workbook.xml.rels.\n const wbRels = makeRelationships();\n state.sheets.forEach((s, i) => {\n wbRels.rels.push({\n id: `rId${i + 1}`,\n type: `${REL_NS}/worksheet`,\n target: `worksheets/sheet${s.sheetId}.xml`,\n });\n });\n if (state.strings.size > 0) {\n wbRels.rels.push({\n id: `rId${wbRels.rels.length + 1}`,\n type: `${REL_NS}/sharedStrings`,\n target: 'sharedStrings.xml',\n });\n }\n wbRels.rels.push({\n id: `rId${wbRels.rels.length + 1}`,\n type: `${REL_NS}/styles`,\n target: 'styles.xml',\n });\n await writer.addEntry(ARC_WORKBOOK_RELS, relsToBytes(wbRels));\n\n // 6. root rels.\n const rootRels = makeRelationships();\n rootRels.rels.push({\n id: 'rId1',\n type: `${REL_NS}/officeDocument`,\n target: 'xl/workbook.xml',\n });\n await writer.addEntry(ARC_ROOT_RELS, relsToBytes(rootRels));\n void PKG_REL_NS; // imported for future docProps support\n\n // 7. [Content_Types].xml.\n const manifest = makeManifest();\n // Excel rejects packages whose [Content_Types].xml is missing the Default\n // entries for `rels` / `xml` — without them the package relationships file\n // can't be classified and Excel refuses to open.\n addDefault(manifest, 'rels', 'application/vnd.openxmlformats-package.relationships+xml');\n addDefault(manifest, 'xml', 'application/xml');\n addOverride(manifest, `/${ARC_WORKBOOK}`, XLSX_TYPE);\n for (const s of state.sheets) {\n addOverride(manifest, `/xl/worksheets/sheet${s.sheetId}.xml`, WORKSHEET_TYPE);\n }\n addOverride(manifest, `/${ARC_STYLE}`, STYLES_TYPE);\n if (state.strings.size > 0) {\n addOverride(manifest, `/${ARC_SHARED_STRINGS}`, SHARED_STRINGS_TYPE);\n }\n await writer.addEntry(ARC_CONTENT_TYPES, manifestToBytes(manifest));\n\n await writer.finalize();\n}\n\nconst serializeWorkbookXml = (\n sheets: ReadonlyArray<{ title: string; sheetId: number }>,\n): string => {\n const parts: string[] = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>',\n `<workbook xmlns=\"${SHEET_MAIN_NS}\" xmlns:r=\"${REL_NS}\">`,\n '<sheets>',\n ];\n sheets.forEach((s, i) => {\n parts.push(\n `<sheet name=\"${escapeAttr(s.title)}\" sheetId=\"${s.sheetId}\" r:id=\"rId${i + 1}\"/>`,\n );\n });\n parts.push('</sheets></workbook>');\n return parts.join('');\n};\n\n/** Open a workbook for streaming write-only output. */\nexport async function createWriteOnlyWorkbook(\n sink: XlsxSink,\n opts: WriteOnlyOptions = {},\n): Promise<WriteOnlyWorkbook> {\n // The streaming-deflate ZIP writer is constructed eagerly here: each\n // addWorksheet opens an entry on it and flushes row chunks through fflate's\n // `Zip` + `ZipDeflate` immediately, so peak memory stays at one pending row\n // buffer plus deflate scratch, with bounded workbook-wide string retention.\n return makeWriteOnlyWorkbook(sink, opts);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"streaming.mjs","names":[],"sources":["../src/streaming/read-only.ts","../src/streaming/string-table.ts","../src/streaming/write-only.ts"],"sourcesContent":["// Streaming read-only workbook.\n//\n// `loadWorkbookStream` opens the zip + parses workbook.xml / sharedStrings.xml\n// / styles.xml metadata up front (small even on million-row archives), then\n// exposes a lazy `openWorksheet(name)` that SAX-iterates the sheet body via\n// `iterParse`. The iterator streams rows without materialising the full sheet\n// in memory.\n\nimport { makeSharedStrings, parseSharedStringsXml, type SharedStringsTable } from '../workbook/shared-strings.js';\nimport { ARC_CONTENT_TYPES, ARC_ROOT_RELS, localNameOf } from '../xml/namespaces.js';\nimport { findById, findByType, makeRelationships, relsFromBytes } from '../packaging/relationships.js';\nimport { manifestFromBytes } from '../packaging/manifest.js';\nimport { parseCellNumber } from '../utils/cell-number.js';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport type { DecompressionLimits } from '../zip/decompression-guard.js';\nimport { type ZipArchive, openZip } from '../zip/reader.js';\nimport type { CellValue, ExcelErrorCode } from '../cell/cell.js';\nimport { unescapeCellString } from '../utils/escape.js';\nimport { ERROR_CODES } from '../utils/inference.js';\nimport { parseXsdBoolean } from '../utils/xsd-boolean.js';\nimport { iterParse, type SaxEvent, type SaxInput } from '../xml/iterparse.js';\nimport { parseXml } from '../xml/parser.js';\nimport { assertNotStrictRelTypes, assertNotStrictRoot } from '../xml/strict-package.js';\nimport type { XlsxSource } from '../io/source.js';\nimport { coordinateToTuple, derivedRowNumber, MAX_ROW, rowNumberFromAttr } from '../utils/coordinate.js';\nimport { type Stylesheet, makeStylesheet } from '../styles/stylesheet.js';\nimport { parseStylesheetXml } from '../styles/stylesheet-reader.js';\nimport {\n OFFICE_DOC_REL_TYPE,\n parseDate1904,\n parseSheetEntries,\n readOptionalWorkbookPart,\n resolveRelTarget,\n SHARED_STRINGS_PART,\n STYLES_PART,\n WORKBOOK_TAG,\n} from '../io/load.js';\n\nexport interface IterRowsOptions {\n minRow?: number;\n maxRow?: number;\n minCol?: number;\n maxCol?: number;\n}\n\nexport interface ReadOnlyCell {\n readonly row: number;\n readonly col: number;\n readonly value: CellValue;\n readonly styleId: number;\n}\n\nexport interface ReadOnlyWorksheet {\n title: string;\n iterRows(opts?: IterRowsOptions): AsyncIterableIterator<ReadOnlyCell[]>;\n iterValues(opts?: IterRowsOptions): AsyncIterableIterator<CellValue[]>;\n}\n\nexport interface ReadOnlyWorkbook {\n sheetNames: string[];\n styles: Stylesheet;\n /** Date1904 mode toggles between Excel's two epoch systems. */\n date1904: boolean;\n openWorksheet(name: string): ReadOnlyWorksheet;\n close(): Promise<void>;\n}\n\nconst relsPathFor = (partPath: string): string => {\n const i = partPath.lastIndexOf('/');\n if (i < 0) return `_rels/${partPath}.rels`;\n return `${partPath.slice(0, i)}/_rels/${partPath.slice(i + 1)}.rels`;\n};\n\nconst decodeCellValue = (\n t: string,\n vText: string | undefined,\n inlineText: string | undefined,\n sst: ReadonlyArray<string>,\n sheet: string,\n col: number,\n row: number,\n): CellValue => {\n switch (t) {\n case 'n':\n // Throws on text or an exponent past the double range, as loadWorkbook\n // does: the two entry points have to answer the same bytes the same way,\n // and a null here would be indistinguishable from an empty cell.\n return parseCellNumber(vText, sheet, col, row);\n case 's': {\n if (vText === undefined) return null;\n const idx = Number.parseInt(vText, 10);\n if (!Number.isInteger(idx) || idx < 0 || idx >= sst.length) return null;\n return sst[idx] ?? null;\n }\n case 'b':\n // Invalid boolean values stay empty in this reader, like out-of-range\n // shared-string indexes. Numeric values have stricter validation.\n return parseXsdBoolean(vText) ?? null;\n case 'e': {\n if (!vText || !ERROR_CODES.has(vText)) return null;\n return { kind: 'error', code: vText as ExcelErrorCode };\n }\n case 'str':\n return vText ?? '';\n case 'inlineStr':\n return inlineText ?? '';\n default:\n // An unhandled `t` (`\"d\"`, or something not in ST_CellType at all). The\n // type says nothing about what the text holds, so there is no finiteness\n // rule to apply; loadWorkbook rejects the cell type outright instead.\n return vText !== undefined && vText !== '' ? Number.parseFloat(vText) : null;\n }\n};\n\n/**\n * SAX-iterate `<sheetData>/<row>/<c>` events out of the worksheet bytes (or a\n * stream that yields them), yielding one `ReadOnlyCell[]` per row that matches\n * `opts`.\n *\n * `readSheetData` in `../worksheet/reader.ts` walks the same element shapes for\n * `loadWorkbook`. The two stay separate because this one is an async generator\n * over a stream with a row band and an early exit, and that per-event cost is\n * what the other one exists to avoid. Where a row or cell with no `@r` lands\n * has to come out the same in both, and is asserted to: see \"agrees with\n * loadWorkbook on where every cell lands\" in\n * `tests/worksheet/row-without-r-attribute.test.ts`.\n */\nasync function* iterSheetRows(\n title: string,\n sheetInput: SaxInput,\n sst: ReadonlyArray<string>,\n opts: IterRowsOptions,\n): AsyncIterableIterator<ReadOnlyCell[]> {\n const minRow = opts.minRow ?? 1;\n const maxRow = opts.maxRow ?? Number.POSITIVE_INFINITY;\n const minCol = opts.minCol ?? 1;\n const maxCol = opts.maxCol ?? Number.POSITIVE_INFINITY;\n\n let inSheetData = false;\n // -1: outside a <row>. 0: inside one that carried no `@r`, whose number the\n // first located cell settles. `nextRow` is the high-water mark a row with\n // neither an `@r` nor a located cell falls back to.\n let currentRow = -1;\n let nextRow = 1;\n let currentCells: ReadOnlyCell[] = [];\n let nextCol = 1;\n let pendingCells: Array<{ col: number; type: string; text: string; inline: string; styleId: number }> = [];\n const settleRow = (row: number): void => {\n currentRow = row;\n nextRow = Math.max(nextRow, row + 1);\n if (row >= minRow && row <= maxRow) {\n for (const cell of pendingCells) {\n currentCells.push({ row, col: cell.col, value: decodeCellValue(cell.type, cell.text, cell.inline, sst, title, cell.col, row), styleId: cell.styleId });\n }\n }\n pendingCells = [];\n };\n\n // Per-cell state. Reset when each <c> starts.\n let cellOpen = false;\n let cellRow = 0;\n let cellCol = 0;\n let cellType = 'n';\n let cellStyleId = 0;\n let inV = false;\n let vText = '';\n let inIs = false;\n let inIsT = false;\n let isText = '';\n let isRunText = '';\n\n let checkedRoot = false;\n for await (const ev of iterParse(sheetInput)) {\n const e = ev as SaxEvent;\n if (e.kind === 'start') {\n if (!checkedRoot) {\n assertNotStrictRoot(e.name);\n checkedRoot = true;\n }\n const local = localNameOf(e.name);\n if (!inSheetData) {\n if (local === 'sheetData') inSheetData = true;\n continue;\n }\n switch (local) {\n case 'row': {\n // `@r` is optional on CT_Row (ECMA-376 §18.3.1.73); such a row waits\n // for its first cell to name it.\n const rRaw = e.attrs['r'];\n if (rRaw === undefined) {\n currentRow = 0;\n } else {\n currentRow = rowNumberFromAttr(rRaw, 'loadWorkbookStream');\n nextRow = Math.max(nextRow, currentRow + 1);\n }\n currentCells = [];\n pendingCells = [];\n nextCol = 1;\n break;\n }\n case 'c': {\n if (currentRow < 0) break;\n const ref = e.attrs['r'];\n if (currentRow === 0 && ref) settleRow(coordinateToTuple(ref).row);\n // Skip cell-attr parsing entirely when the row is outside the\n // requested band — saves the parseInt + coordinateToTuple hit on\n // every cell of every excluded row.\n if (currentRow !== 0 && (currentRow < minRow || currentRow > maxRow)) break;\n cellOpen = true;\n cellType = e.attrs['t'] ?? 'n';\n const sRaw = e.attrs['s'];\n cellStyleId = sRaw ? Number.parseInt(sRaw, 10) || 0 : 0;\n if (ref) {\n const tup = coordinateToTuple(ref);\n cellRow = tup.row;\n cellCol = tup.col;\n } else {\n cellRow = currentRow;\n cellCol = nextCol;\n }\n nextCol = cellCol + 1;\n vText = '';\n isText = '';\n break;\n }\n case 'v':\n if (cellOpen) inV = true;\n break;\n case 'is':\n if (cellOpen) inIs = true;\n break;\n case 't':\n if (inIs) {\n inIsT = true;\n isRunText = '';\n }\n break;\n default:\n break;\n }\n continue;\n }\n if (e.kind === 'text') {\n if (inV) vText += e.text;\n else if (inIsT) isRunText += e.text;\n continue;\n }\n // end\n const local = localNameOf(e.name);\n if (!inSheetData) continue;\n switch (local) {\n case 'sheetData':\n inSheetData = false;\n return;\n case 'row': {\n // A row that held no located cell still consumes a slot, so settle it\n // before moving the high-water mark past it.\n if (currentRow === 0) {\n settleRow(derivedRowNumber(nextRow, 'loadWorkbookStream'));\n }\n if (currentRow >= minRow && currentRow <= maxRow && currentCells.length > 0) {\n yield currentCells;\n }\n // Once we've crossed maxRow there are no more rows to yield — every\n // subsequent <row> would just be parsed and dropped. Stop iterating\n // early. ECMA-376 emits rows in ascending order.\n if (currentRow > maxRow) {\n inSheetData = false;\n return;\n }\n currentRow = -1;\n currentCells = [];\n break;\n }\n case 'c': {\n if (cellOpen && cellRow === 0 && cellCol >= minCol && cellCol <= maxCol) {\n pendingCells.push({ col: cellCol, type: cellType, text: vText, inline: isText, styleId: cellStyleId });\n } else if (cellOpen && cellCol >= minCol && cellCol <= maxCol && cellRow >= minRow && cellRow <= maxRow) {\n const value = decodeCellValue(cellType, vText, isText, sst, title, cellCol, cellRow);\n currentCells.push({ row: cellRow, col: cellCol, value, styleId: cellStyleId });\n }\n cellOpen = false;\n break;\n }\n case 'v':\n inV = false;\n break;\n case 'is':\n inIs = false;\n break;\n case 't':\n if (inIsT) {\n // Decode each complete <t>, so SAX chunks can split an escape but\n // adjacent rich-text runs cannot accidentally create one.\n isText += unescapeCellString(isRunText);\n inIsT = false;\n }\n break;\n default:\n break;\n }\n }\n}\n\nconst isXmlSpace = (b: number | undefined): boolean =>\n b === 0x20 /* sp */ || b === 0x09 /* tab */ || b === 0x0a /* lf */ || b === 0x0d /* cr */;\n\n/**\n * Read the `r=\"N\"` row number out of a `<row …>` attribute region, scanning\n * bytes directly. Returns -1 when the attribute is absent, or holds anything\n * `parseRowNumberAttr` rejects: the two accept the same shapes, so the index\n * can never seek by a number the SAX walk reads differently.\n *\n * Decoding the region to a string and running a regex instead costs one\n * TextDecoder, one string and one match array per row, which on a million-row\n * sheet dominates the scan this index exists to keep cheap.\n */\nconst readRowAttr = (bytes: Uint8Array, from: number, to: number): number => {\n for (let p = from; p + 3 < to; p++) {\n if (!isXmlSpace(bytes[p])) continue;\n if (bytes[p + 1] !== 0x72 /* 'r' */ || bytes[p + 2] !== 0x3d /* '=' */ || bytes[p + 3] !== 0x22 /* '\"' */) {\n continue;\n }\n let q = p + 4;\n while (q < to && isXmlSpace(bytes[q])) q++;\n if (bytes[q] === 0x2b /* + */) q++;\n let value = 0;\n let digits = 0;\n while (q < to) {\n const d = bytes[q];\n if (d === undefined || d < 0x30 || d > 0x39) break;\n value = value * 10 + (d - 0x30);\n digits++;\n q++;\n }\n while (q < to && isXmlSpace(bytes[q])) q++;\n // A non-numeric, out-of-range or unterminated `r` is not the row ref; keep\n // scanning the rest of the region the way the equivalent regex would have.\n if (digits > 0 && value >= 1 && value <= MAX_ROW && q < to && bytes[q] === 0x22 /* '\"' */) return value;\n }\n return -1;\n};\n\nconst isQuote = (b: number | undefined): boolean => b === 0x22 /* dquote */ || b === 0x27 /* squote */;\n\n/**\n * Byte offset just past the worksheet's opening `sheetData` tag, or\n * -1 when there is none. Skips the XML declaration, comments and processing\n * instructions ahead of it, and ignores `>` inside attribute values.\n */\nconst findSheetDataTagEnd = (bytes: Uint8Array): number => {\n const td = new TextDecoder();\n let i = 0;\n while (i < bytes.length) {\n if (bytes[i] !== 0x3c /* '<' */) {\n i++;\n continue;\n }\n const kind = bytes[i + 1];\n if (kind === 0x3f /* '?' */ || kind === 0x21 /* '!' */) {\n // Comments and processing instructions may contain `>` in their body.\n const isComment = bytes[i + 2] === 0x2d /* '-' */ && bytes[i + 3] === 0x2d;\n i += 2;\n while (i < bytes.length) {\n if (bytes[i] === 0x3e /* '>' */ &&\n (isComment ? bytes[i - 1] === 0x2d && bytes[i - 2] === 0x2d\n : kind !== 0x3f || bytes[i - 1] === 0x3f)) break;\n i++;\n }\n i++;\n continue;\n }\n let nameEnd = i + 1;\n while (nameEnd < bytes.length && !isXmlSpace(bytes[nameEnd]) && bytes[nameEnd] !== 0x3e && bytes[nameEnd] !== 0x2f) nameEnd++;\n const isSheetData = td.decode(bytes.subarray(i + 1, nameEnd)) === 'sheetData';\n let quote = 0;\n let tagEnd = -1;\n for (let j = i + 1; j < bytes.length; j++) {\n const b = bytes[j];\n if (b === undefined) break;\n if (quote !== 0) {\n if (b === quote) quote = 0;\n continue;\n }\n if (isQuote(b)) {\n quote = b;\n continue;\n }\n if (b === 0x3e /* '>' */) {\n tagEnd = j + 1;\n break;\n }\n }\n if (tagEnd < 0 || isSheetData) return tagEnd;\n i = tagEnd;\n }\n return -1;\n};\n\n/**\n * Build a sorted `[rowNum, byteOffset]` index for every `<row r=\"N\">`\n * occurrence in a worksheet's bytes. Pure byte-level scan (no SAX), cheap\n * relative to the per-cell SAX walk.\n *\n * `sheetDataEnd` is the byte offset of `</sheetData>` so callers can clip the\n * region that gets handed to saxes. `sheetDataTagEnd` includes the original\n * namespace-bearing ancestor tags, reused by {@link replayFromRow}.\n *\n * `hasUnnumberedRow` reports a `<row>` this scan cannot number, which makes the\n * whole sheet unseekable: a row without `@r` takes the row its first cell\n * names, and reading cell refs is the SAX walk's job.\n */\nconst buildRowOffsetIndex = (\n bytes: Uint8Array,\n): {\n index: ReadonlyArray<{ row: number; offset: number }>;\n sheetDataEnd: number;\n sheetDataTagEnd: number;\n hasUnnumberedRow: boolean;\n} => {\n const out: Array<{ row: number; offset: number }> = [];\n let hasUnnumberedRow = false;\n let sheetDataEnd = -1;\n let i = 0;\n while (i < bytes.length) {\n if (bytes[i] !== 0x3c /* '<' */) {\n i++;\n continue;\n }\n // Detect `</sheetData>` once — used to clip the slice fed to saxes.\n if (\n sheetDataEnd < 0 &&\n bytes[i + 1] === 0x2f /* '/' */ &&\n bytes[i + 2] === 0x73 /* 's' */ &&\n bytes[i + 3] === 0x68 /* 'h' */ &&\n bytes[i + 4] === 0x65 /* 'e' */ &&\n bytes[i + 5] === 0x65 /* 'e' */ &&\n bytes[i + 6] === 0x74 /* 't' */ &&\n bytes[i + 7] === 0x44 /* 'D' */ &&\n bytes[i + 8] === 0x61 /* 'a' */ &&\n bytes[i + 9] === 0x74 /* 't' */ &&\n bytes[i + 10] === 0x61 /* 'a' */ &&\n bytes[i + 11] === 0x3e /* '>' */\n ) {\n sheetDataEnd = i;\n break;\n }\n // Match `<row` followed by ASCII whitespace or '>'.\n if (\n bytes[i + 1] !== 0x72 /* 'r' */ ||\n bytes[i + 2] !== 0x6f /* 'o' */ ||\n bytes[i + 3] !== 0x77 /* 'w' */\n ) {\n i++;\n continue;\n }\n const next = bytes[i + 4];\n if (\n next !== 0x20 /* sp */ &&\n next !== 0x09 /* tab */ &&\n next !== 0x0a /* lf */ &&\n next !== 0x0d /* cr */ &&\n next !== 0x3e /* > */ &&\n next !== 0x2f /* / */\n ) {\n i++;\n continue;\n }\n // Walk to the closing '>'; the attrs region carries `r=\"N\"`.\n const start = i;\n let j = i + 4;\n while (j < bytes.length && bytes[j] !== 0x3e) j++;\n if (j >= bytes.length) break;\n const row = readRowAttr(bytes, start + 4, j);\n if (row > 0) out.push({ row, offset: start });\n else hasUnnumberedRow = true;\n i = j + 1;\n }\n if (sheetDataEnd < 0) sheetDataEnd = bytes.length;\n return { index: out, sheetDataEnd, sheetDataTagEnd: findSheetDataTagEnd(bytes), hasUnnumberedRow };\n};\n\n/**\n * Binary-search the row index for the first entry with `row >= target`. Returns\n * -1 when every recorded row is below the target.\n */\nconst firstRowAtOrAfter = (\n index: ReadonlyArray<{ row: number; offset: number }>,\n target: number,\n): number => {\n let lo = 0;\n let hi = index.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n const entry = index[mid];\n if (!entry || entry.row < target) lo = mid + 1;\n else hi = mid;\n }\n return lo < index.length ? lo : -1;\n};\n\n/**\n * Replay a row band with the original prefix through the opening sheetData\n * tag. Namespace declarations can occur on either worksheet or sheetData;\n * synthesising either tag loses bindings required by the retained rows.\n * Views avoid copying the inflated sheet just to prepend its ancestor tags.\n */\nconst replayFromRow = (\n bytes: Uint8Array,\n sheetDataTagEnd: number,\n fromOffset: number,\n sheetDataEnd: number,\n): ReadableStream<Uint8Array> => {\n const parts = [\n bytes.subarray(0, sheetDataTagEnd),\n bytes.subarray(fromOffset, sheetDataEnd),\n bytes.subarray(sheetDataEnd),\n ];\n let next = 0;\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n const part = parts[next];\n if (part === undefined) {\n controller.close();\n return;\n }\n next++;\n controller.enqueue(part);\n },\n });\n};\n\n/**\n * A worksheet is seekable when every one of its rows carries an `@r` the byte\n * scan can read. Otherwise the scan's own numbering would disagree with the SAX\n * walk's, so band queries stream the part instead, and nothing is retained.\n */\ntype SheetSeek =\n | { seekable: false }\n | ({ seekable: true; bytes: Uint8Array } & Omit<ReturnType<typeof buildRowOffsetIndex>, 'hasUnnumberedRow'>);\n\ninterface RowIndexCache {\n byWorksheet: WeakMap<object, SheetSeek>;\n}\n\n/**\n * Factory: build a {@link ReadOnlyWorksheet} bound to a single worksheet part\n * inside an opened archive. SAX iteration runs lazily: a whole-sheet `iterRows`\n * streams the part again on every call, so nothing but the inflate window and\n * the SAX state is ever resident.\n *\n * `iterRows({ minRow > 1 })` needs random access instead, so the first band\n * query inflates the part and indexes its row offsets, and holds both for the\n * life of the worksheet handle or until the workbook closes. Subsequent band\n * queries jump straight to the first matching row without inflating or scanning\n * again. A sheet whose rows omit `@r` cannot be indexed by row number, so its\n * band queries keep streaming and hold nothing.\n */\nconst makeStreamingReadOnlyWorksheet = (\n title: string,\n archive: ZipArchive,\n partPath: string,\n sst: ReadonlyArray<string>,\n indexes: RowIndexCache,\n): ReadOnlyWorksheet => {\n // Lazy + cached, bytes included: the archive only keeps small entries, so a\n // second band query that went back to `read` would inflate the whole part\n // again. The index adds one small object per row; the part is held from the first\n // band query until the worksheet handle goes away or the workbook closes.\n // This avoids parsing or inflating the whole part on every query.\n const cacheKey = {};\n const ensureIndexed = (): SheetSeek => {\n let cached = indexes.byWorksheet.get(cacheKey);\n if (!cached) {\n const bytes = archive.read(partPath);\n const { hasUnnumberedRow, index, sheetDataEnd, sheetDataTagEnd } = buildRowOffsetIndex(bytes);\n cached = hasUnnumberedRow\n ? { seekable: false }\n : { seekable: true, bytes, index, sheetDataEnd, sheetDataTagEnd };\n indexes.byWorksheet.set(cacheKey, cached);\n }\n return cached;\n };\n\n const iterRows = (opts: IterRowsOptions = {}): AsyncIterableIterator<ReadOnlyCell[]> => {\n const minRow = opts.minRow ?? 1;\n if (minRow <= 1) {\n // Whole-sheet (or no-min) iter — feed the SAX parser directly off the\n // archive's streaming inflate path so the worksheet's inflated payload\n // is never fully resident. Peak memory for the walk drops to the\n // inflate window + SAX state instead of the entire `<sheetData>` body.\n return iterSheetRows(title, archive.readStream(partPath), sst, opts);\n }\n // Band query (minRow > 1): the row-offset index needs the full inflated\n // bytes so we can binary-search to the byte offset of the first matching\n // row. Materialise once and reuse via `ensureIndexed`.\n const seek = ensureIndexed();\n // Rows the index cannot number: walk the part instead of seeking into it,\n // which is the only way the derived numbers stay the SAX walk's.\n if (!seek.seekable) return iterSheetRows(title, archive.readStream(partPath), sst, opts);\n const { bytes, index, sheetDataEnd, sheetDataTagEnd } = seek;\n if (index.length === 0 || sheetDataTagEnd < 0) return iterSheetRows(title, bytes, sst, opts);\n const pos = firstRowAtOrAfter(index, minRow);\n if (pos < 0) {\n // Every row is below minRow, so there is nothing to yield.\n return (async function* () {})();\n }\n const target = index[pos];\n if (!target) return iterSheetRows(title, bytes, sst, opts);\n return iterSheetRows(title, replayFromRow(bytes, sheetDataTagEnd, target.offset, sheetDataEnd), sst, opts);\n };\n const iterValues = async function* (opts: IterRowsOptions = {}): AsyncIterableIterator<CellValue[]> {\n for await (const row of iterRows(opts)) {\n yield row.map((c) => c.value);\n }\n };\n return { title, iterRows, iterValues };\n};\n\n/**\n * Factory: build a {@link ReadOnlyWorkbook} from an opened archive + pre-parsed\n * sheet list / styles / shared strings. Per the project-wide \"no classes\" rule\n * (CLAUDE.md), the workbook is a plain object closing over the archive handle.\n */\nconst makeStreamingReadOnlyWorkbook = (\n sheetNames: string[],\n styles: Stylesheet,\n date1904: boolean,\n archive: ZipArchive,\n partPathByName: ReadonlyMap<string, string>,\n sst: ReadonlyArray<string>,\n): ReadOnlyWorkbook => {\n // Weak keys let unused worksheet handles release their indexed bytes. The\n // indirection also lets close() release every index while handles remain live.\n const indexes: RowIndexCache = { byWorksheet: new WeakMap() };\n return {\n sheetNames,\n styles,\n date1904,\n openWorksheet(name) {\n const partPath = partPathByName.get(name);\n if (partPath === undefined) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: no worksheet named \"${name}\"`);\n }\n return makeStreamingReadOnlyWorksheet(name, archive, partPath, sst, indexes);\n },\n async close() {\n archive.close();\n indexes.byWorksheet = new WeakMap();\n },\n };\n};\n\n/** Options for {@link loadWorkbookStream}. */\nexport interface LoadWorkbookStreamOptions {\n /**\n * Decompression-bomb safeguards applied while inflating zip entries.\n * Defaults to limits that fit any legitimate xlsx; pass `false` to disable\n * (only safe for fully trusted sources).\n */\n decompressionLimits?: DecompressionLimits | false;\n}\n\n/** Open an xlsx for read-only streaming access. */\nexport async function loadWorkbookStream(\n source: XlsxSource,\n opts: LoadWorkbookStreamOptions = {},\n): Promise<ReadOnlyWorkbook> {\n const archive = await openZip(\n source,\n opts.decompressionLimits === undefined ? {} : { decompressionLimits: opts.decompressionLimits },\n );\n if (!archive.has(ARC_CONTENT_TYPES)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: missing \"${ARC_CONTENT_TYPES}\"`);\n }\n // Manifest parse is intentionally cheap and discarded — we resolve sheets by\n // walking workbook.xml.rels directly.\n manifestFromBytes(archive.read(ARC_CONTENT_TYPES));\n\n if (!archive.has(ARC_ROOT_RELS)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: missing \"${ARC_ROOT_RELS}\"`);\n }\n const rootRels = relsFromBytes(archive.read(ARC_ROOT_RELS));\n const officeDocRel = findByType(rootRels, OFFICE_DOC_REL_TYPE);\n if (!officeDocRel) {\n assertNotStrictRelTypes(rootRels.rels.map((r) => r.type));\n throw new OpenXmlSchemaError(`loadWorkbookStream: no officeDocument relationship in root rels`);\n }\n const workbookPath = resolveRelTarget('', officeDocRel.target);\n if (!archive.has(workbookPath)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: workbook part \"${workbookPath}\" missing`);\n }\n const workbookRoot = parseXml(archive.read(workbookPath));\n if (workbookRoot.name !== WORKBOOK_TAG) {\n assertNotStrictRoot(workbookRoot.name);\n throw new OpenXmlSchemaError(\n `loadWorkbookStream: ${workbookPath} root is \"${workbookRoot.name}\", expected workbook`,\n );\n }\n // loadWorkbook's `<sheets>` parser, and its rejections below: a declaration\n // that loader refuses must not read here as a workbook without that sheet.\n const declaredSheets = parseSheetEntries(workbookRoot);\n const wbRelsPath = relsPathFor(workbookPath);\n if (declaredSheets.length > 0 && !archive.has(wbRelsPath)) {\n throw new OpenXmlSchemaError(\n `loadWorkbookStream: workbook has sheets but rels part \"${wbRelsPath}\" is missing`,\n );\n }\n const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : makeRelationships();\n const partPathByName = new Map<string, string>();\n for (const declared of declaredSheets) {\n if (partPathByName.has(declared.name)) {\n throw new OpenXmlSchemaError(`loadWorkbookStream: duplicate sheet name \"${declared.name}\"`);\n }\n const rel = findById(wbRels, declared.rId);\n if (!rel) {\n throw new OpenXmlSchemaError(\n `loadWorkbookStream: sheet \"${declared.name}\" rId \"${declared.rId}\" has no matching rels entry`,\n );\n }\n partPathByName.set(declared.name, resolveRelTarget(workbookPath, rel.target));\n }\n\n const sstBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, SHARED_STRINGS_PART);\n const sst: SharedStringsTable = sstBytes === undefined ? makeSharedStrings() : parseSharedStringsXml(sstBytes);\n const stylesBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, STYLES_PART);\n const styles: Stylesheet = stylesBytes === undefined ? makeStylesheet() : parseStylesheetXml(stylesBytes);\n\n return makeStreamingReadOnlyWorkbook(\n declaredSheets.map((e) => e.name),\n styles,\n parseDate1904(workbookRoot),\n archive,\n partPathByName,\n sst.entries.map((e) => (typeof e === 'string' ? e : e.runs.map((r) => r.text).join(''))),\n );\n}\n","import { serializeRichString } from '../workbook/shared-strings.js';\nimport type { CellStringWriter } from '../worksheet/writer.js';\nimport { SHEET_MAIN_NS } from '../xml/namespaces.js';\nimport type { StreamingEntryWriter } from '../zip/writer.js';\n\n// Entry count bounds Map/array overhead; payload bounds long strings and rich\n// text markup. UTF-16 accounting is deterministic, not an exact V8 heap metric.\nconst MAX_ENTRIES = 100_000;\nconst MAX_PAYLOAD_BYTES = 8 * 1024 * 1024;\nconst UTF16_BYTES_PER_UNIT = 2;\nconst XML_CHUNK_UNITS = 16 * 1024;\n\nexport const createWriteOnlyStringTable = () => {\n const entries: string[] = [];\n const plainIndex = new Map<string, number>();\n const richIndex = new Map<string, number>();\n let payloadBytes = 0;\n let full = false;\n\n const serialize: CellStringWriter = (value) => {\n const plain = typeof value === 'string';\n // Plain strings can hit the cache without escaping. Rich text is keyed by\n // its emitted XML, which snapshots caller-owned runs and font objects.\n const key = plain ? value : serializeRichString(value);\n const index = plain ? plainIndex : richIndex;\n const cached = index.get(key);\n if (cached !== undefined) return { type: 's', xml: `<v>${cached}</v>` };\n const body = plain ? serializeRichString(value) : key;\n // Count both keys and XML, conservatively even when they share storage.\n const bytes = (key.length + body.length) * UTF16_BYTES_PER_UNIT;\n if (!full && entries.length < MAX_ENTRIES && payloadBytes + bytes <= MAX_PAYLOAD_BYTES) {\n const id = entries.length;\n entries.push(body);\n index.set(key, id);\n payloadBytes += bytes;\n return { type: 's', xml: `<v>${id}</v>` };\n }\n // Never evict: earlier worksheet cells already reference these indices.\n // Once a new entry does not fit, all subsequent new values stay inline.\n full = true;\n return { type: 'inlineStr', xml: `<is>${body}</is>` };\n };\n\n const write = async (stream: StreamingEntryWriter): Promise<void> => {\n const encoder = new TextEncoder();\n let pending = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sst xmlns=\"${SHEET_MAIN_NS}\" count=\"${entries.length}\" uniqueCount=\"${entries.length}\">`;\n const append = (text: string): void => {\n pending += text;\n while (pending.length >= XML_CHUNK_UNITS) {\n // TextEncoder must see surrogate pairs together, including at a chunk\n // boundary. Otherwise an astral character becomes two replacements.\n let end = XML_CHUNK_UNITS;\n const last = pending.charCodeAt(end - 1);\n if (last >= 0xd800 && last <= 0xdbff) end--;\n stream.write(encoder.encode(pending.slice(0, end)));\n pending = pending.slice(end);\n }\n };\n for (const body of entries) append(`<si>${body}</si>`);\n append('</sst>');\n if (pending.length > 0) stream.write(encoder.encode(pending));\n await stream.end();\n };\n\n return { serialize, write, get size() { return entries.length; } };\n};\n","// Rows are flushed through a bounded text buffer into the ZIP stream. The\n// workbook-wide string table has entry and payload limits; new strings beyond\n// those limits are written inline. Styles and sheet metadata remain resident.\n\nimport type { Cell, CellValue } from '../cell/cell.js';\nimport type { XlsxSink } from '../io/sink.js';\nimport { addDefault, addOverride, makeManifest, manifestToBytes } from '../packaging/manifest.js';\nimport { makeRelationships, relsToBytes } from '../packaging/relationships.js';\nimport {\n addCellXf,\n buildXfPatch,\n type CellStyleSpec,\n defaultCellXf,\n makeStylesheet,\n type Stylesheet,\n} from '../styles/stylesheet.js';\nimport { stylesheetToBytes } from '../styles/stylesheet-writer.js';\nimport { escapeXmlAttr } from '../utils/escape.js';\nimport { OpenXmlIoError } from '../utils/exceptions.js';\nimport { utf8ByteLength } from '../utils/utf8.js';\nimport { makeSharedStrings } from '../workbook/shared-strings.js';\nimport { validateSheetTitle } from '../workbook/workbook.js';\nimport { serializeCell } from '../worksheet/writer.js';\nimport {\n ARC_CONTENT_TYPES,\n ARC_ROOT_RELS,\n ARC_SHARED_STRINGS,\n ARC_STYLE,\n ARC_WORKBOOK,\n ARC_WORKBOOK_RELS,\n PKG_REL_NS,\n REL_NS,\n SHARED_STRINGS_TYPE,\n SHEET_MAIN_NS,\n STYLES_TYPE,\n WORKSHEET_TYPE,\n XLSX_TYPE,\n} from '../xml/namespaces.js';\nimport { type CompressionLevel, createZipWriter, type ZipWriterOptions } from '../zip/writer.js';\n\nimport { createWriteOnlyStringTable } from './string-table.js';\n\nconst escapeAttr = escapeXmlAttr;\n\nexport interface WriteOnlyOptions {\n /** Reserved — currently ignored (the buffered backend doesn't honour it). */\n estimatedMaxRow?: number;\n /**\n * Last-modified timestamp for every ZIP entry. Same reproducibility story as\n * `SaveOptions.mtime`: unset, fflate stamps the wall clock per entry and two\n * runs over identical rows differ in bytes. Recorded as the date's UTC wall\n * time, to a two-second resolution, and the year has to fall in 1980-2099.\n */\n mtime?: Date;\n /** Deflate level, 0 (no compression) to 9 (smallest). Defaults to fflate's own 6. */\n compressionLevel?: CompressionLevel;\n}\n\n/**\n * A cell's look on the write-only path. Identical to the modelled writer's\n * {@link CellStyleSpec}: the two writers resolve a spec to an xf through the\n * same builder, so an axis added to one is honoured by both.\n */\nexport type WriteOnlyStyle = CellStyleSpec;\n\nexport type WriteOnlyRowItem = CellValue | { value: CellValue; style?: WriteOnlyStyle };\n\nexport interface WriteOnlyWorksheet {\n readonly title: string;\n appendRow(row: WriteOnlyRowItem[]): Promise<void>;\n setColumnWidth(col: number, width: number): void;\n close(): Promise<void>;\n}\n\nexport interface WriteOnlyWorkbook {\n /** Add a new worksheet. The previous worksheet must be `close()`d first. */\n addWorksheet(title: string): Promise<WriteOnlyWorksheet>;\n /** Finalise the archive: emits styles / sharedStrings / workbook / manifest / rels. */\n finalize(): Promise<void>;\n /**\n * Release the sink without finalising the archive. Call this from a\n * surrounding catch block when the producer pipeline throws before\n * `finalize()` — without it, streaming destinations (`toFile` /\n * `toWritable`) keep the file descriptor / Writable open and the partial\n * xlsx looks valid on disk.\n *\n * Idempotent. Subsequent `addWorksheet` / `finalize` calls throw.\n */\n abort(cause?: unknown): void;\n}\n\nconst validateTitle = (title: string, taken: Set<string>): void => {\n const reason = validateSheetTitle(title);\n if (reason) {\n throw new OpenXmlIoError(`Worksheet title \"${title}\": ${reason}`);\n }\n if (taken.has(title.toLowerCase())) {\n throw new OpenXmlIoError(`Worksheet title \"${title}\" is already in use`);\n }\n};\n\n/**\n * Allocate a CellXf id for a style spec. `defaultCellXf` omits `xfId`, which\n * skips the cellStyleXfs bounds check and matches what Excel emits when there\n * is no parent style.\n */\nconst allocateXfId = (ss: Stylesheet, style: WriteOnlyStyle): number =>\n addCellXf(ss, { ...defaultCellXf(), ...buildXfPatch(ss, style) });\n\ninterface WorkbookState {\n styles: Stylesheet;\n strings: ReturnType<typeof createWriteOnlyStringTable>;\n /** Sheet emit metadata, in addWorksheet order. */\n sheets: Array<{\n title: string;\n sheetId: number;\n }>;\n /** True once finalize() has been called (further mutations throw). */\n finalised: boolean;\n /** True while a worksheet is open (the next addWorksheet must wait). */\n hasOpenWorksheet: boolean;\n /** ZIP writer the workbook + each open worksheet stream chunks through. */\n writer: import('../zip/writer.js').ZipWriter;\n}\n\n/**\n * Flush threshold for the worksheet's pending-row text buffer. Smaller values\n * minimise heap; larger values amortise the TextEncoder + push overhead. 64 KB\n * is a sweet spot — heap stays low and per-row JS work is dominated by the\n * actual XML construction, not flushing.\n */\nconst FLUSH_THRESHOLD_BYTES = 64 * 1024;\n\n/**\n * Factory: build a {@link WriteOnlyWorksheet} that closes over the shared\n * {@link WorkbookState}. Per the project-wide \"no classes\" rule (CLAUDE.md) the\n * worksheet is a plain object holding the row buffer + column-width map in\n * closure state.\n *\n * The worksheet streams its `<sheetData>` body chunk-by-chunk through the ZIP\n * writer's `addStreamingEntry` API. Row buffering stays at ~64 KB plus the\n * current row and deflate scratch; strings have a separate workbook-wide cap.\n * The XML envelope (decl / worksheet open / cols / sheetData open) flushes on\n * the first `appendRow` (or `close()` if the sheet is empty);\n * column widths staged via `setColumnWidth` *must* land before the first row.\n */\nconst makeWriteOnlyWorksheet = (state: WorkbookState, title: string, sheetId: number): WriteOnlyWorksheet => {\n let nextRow = 1;\n let closed = false;\n let headerFlushed = false;\n const columnWidths = new Map<number, number>();\n // Strings use the bounded workbook-wide writer; other cells still need\n // the regular serialization context (notably the shared style pool).\n const dummyCtx = { sharedStrings: makeSharedStrings(), styles: state.styles };\n const encoder = new TextEncoder();\n const stream = state.writer.addStreamingEntry(`xl/worksheets/sheet${sheetId}.xml`);\n let pendingText = '';\n let pendingBytes = 0;\n\n const writeText = (text: string): void => {\n pendingText += text;\n // UTF-8 byte length, computed without a full encode: scan once and add the\n // exact codepoint cost. `text.length` would undercount CJK by ~3× and let\n // the buffer balloon past FLUSH_THRESHOLD_BYTES on Japanese / Chinese\n // workloads.\n pendingBytes += utf8ByteLength(text);\n if (pendingBytes >= FLUSH_THRESHOLD_BYTES) {\n stream.write(encoder.encode(pendingText));\n pendingText = '';\n pendingBytes = 0;\n }\n };\n\n const flushHeader = (): void => {\n if (headerFlushed) return;\n headerFlushed = true;\n let header = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>';\n header += `<worksheet xmlns=\"${SHEET_MAIN_NS}\" xmlns:r=\"${REL_NS}\">`;\n if (columnWidths.size > 0) {\n header += '<cols>';\n const sorted = [...columnWidths.entries()].sort((a, b) => a[0] - b[0]);\n for (const [col, width] of sorted) {\n header += `<col min=\"${col}\" max=\"${col}\" width=\"${width}\" customWidth=\"1\"/>`;\n }\n header += '</cols>';\n }\n header += '<sheetData>';\n writeText(header);\n };\n\n const appendRow = async (row: WriteOnlyRowItem[]): Promise<void> => {\n if (closed) throw new OpenXmlIoError('appendRow: worksheet already closed');\n flushHeader();\n const r = nextRow++;\n let xml = `<row r=\"${r}\">`;\n for (let i = 0; i < row.length; i++) {\n const item = row[i];\n if (item === undefined || item === null) continue;\n const col = i + 1;\n let value: CellValue;\n let style: WriteOnlyStyle | undefined;\n if (item !== null && typeof item === 'object' && 'value' in (item as object)) {\n const wrapped = item as { value: CellValue; style?: WriteOnlyStyle };\n value = wrapped.value;\n style = wrapped.style;\n } else {\n value = item as CellValue;\n }\n const styleId = style ? allocateXfId(state.styles, style) : 0;\n // Ephemeral cell-shaped object — discarded as soon as serializeCell\n // returns its `<c .../>` string. Keeps the heap footprint at the size of\n // the pending text buffer instead of a full Worksheet model.\n const cell: Cell = { row: r, col, value, styleId };\n xml += serializeCell(cell, dummyCtx, state.strings.serialize);\n }\n xml += '</row>';\n writeText(xml);\n };\n\n const setColumnWidth = (col: number, width: number): void => {\n if (closed) throw new OpenXmlIoError('setColumnWidth: worksheet already closed');\n if (headerFlushed) {\n throw new OpenXmlIoError(\n 'setColumnWidth: must be called before the first appendRow — column widths are emitted as part of the worksheet header',\n );\n }\n columnWidths.set(col, width);\n };\n\n const close = async (): Promise<void> => {\n if (closed) return;\n closed = true;\n flushHeader();\n writeText('</sheetData></worksheet>');\n if (pendingText.length > 0) {\n stream.write(encoder.encode(pendingText));\n pendingText = '';\n pendingBytes = 0;\n }\n await stream.end();\n state.sheets.push({ title, sheetId });\n state.hasOpenWorksheet = false;\n };\n\n return { title, appendRow, setColumnWidth, close };\n};\n\n/**\n * Factory: build a {@link WriteOnlyWorkbook} from a sink. State lives in a\n * closure rather than on a class instance per the project-wide \"no classes\"\n * rule (CLAUDE.md).\n */\nconst makeWriteOnlyWorkbook = (sink: XlsxSink, zipOpts: ZipWriterOptions): WriteOnlyWorkbook => {\n const styles = makeStylesheet();\n // Reserve cellXfs[0] for the default (no apply* flags). Unstyled cells point\n // at this slot via styleId=0; user-styled cells start at index 1 so the\n // writer emits an `s=\"N\"` attribute for them.\n addCellXf(styles, defaultCellXf());\n // The ZIP writer is created up front: each addWorksheet opens a streaming\n // entry on it and flushes row chunks through the deflate stream as they\n // arrive. Sheets emit before styles / sst / workbook / rels / content-types\n // so the writer can serialise them in order.\n const state: WorkbookState = {\n styles,\n strings: createWriteOnlyStringTable(),\n sheets: [],\n finalised: false,\n hasOpenWorksheet: false,\n writer: createZipWriter(sink, zipOpts),\n };\n\n const addWorksheet = async (title: string): Promise<WriteOnlyWorksheet> => {\n if (state.finalised) {\n throw new OpenXmlIoError('addWorksheet: workbook already finalised');\n }\n if (state.hasOpenWorksheet) {\n throw new OpenXmlIoError(\n 'addWorksheet: previous worksheet still open — call close() before opening the next one',\n );\n }\n const taken = new Set(state.sheets.map((s) => s.title.toLowerCase()));\n validateTitle(title, taken);\n state.hasOpenWorksheet = true;\n const sheetId = state.sheets.length + 1;\n return makeWriteOnlyWorksheet(state, title, sheetId);\n };\n\n const finalize = async (): Promise<void> => {\n if (state.finalised) {\n throw new OpenXmlIoError('finalize: already finalised');\n }\n if (state.hasOpenWorksheet) {\n throw new OpenXmlIoError('finalize: a worksheet is still open — call close() before finalising');\n }\n state.finalised = true;\n const writer = state.writer;\n try {\n await finalizeImpl(state, writer);\n } catch (err) {\n // Release the sink so the half-emitted archive doesn't linger as a\n // valid-looking file on disk. Awaited so `toFile` has finished removing\n // it by the time the caller sees the error. abort() is idempotent.\n await writer.abort(err);\n throw err;\n }\n };\n\n const abort = (cause?: unknown): void => {\n if (state.finalised) return;\n state.finalised = true;\n // Sync by contract, so a `toFile` sink's unlink can still be in flight when\n // this returns. The failure paths that have to guarantee cleanup before the\n // caller sees an error await the writer's abort themselves.\n void state.writer.abort(cause);\n };\n\n return { addWorksheet, finalize, abort };\n};\n\nasync function finalizeImpl(state: WorkbookState, writer: WorkbookState['writer']): Promise<void> {\n // 1. Worksheets — already streamed through writer.addStreamingEntry\n // during each WriteOnlyWorksheet's appendRow / close cycle.\n\n // 2. Stylesheet.\n await writer.addEntry(ARC_STYLE, stylesheetToBytes(state.styles));\n\n // 3. SharedStrings (only when non-empty).\n if (state.strings.size > 0) {\n await state.strings.write(writer.addStreamingEntry(ARC_SHARED_STRINGS));\n }\n\n // 4. workbook.xml.\n const workbookXml = serializeWorkbookXml(state.sheets);\n await writer.addEntry(ARC_WORKBOOK, new TextEncoder().encode(workbookXml));\n\n // 5. workbook.xml.rels.\n const wbRels = makeRelationships();\n state.sheets.forEach((s, i) => {\n wbRels.rels.push({\n id: `rId${i + 1}`,\n type: `${REL_NS}/worksheet`,\n target: `worksheets/sheet${s.sheetId}.xml`,\n });\n });\n if (state.strings.size > 0) {\n wbRels.rels.push({\n id: `rId${wbRels.rels.length + 1}`,\n type: `${REL_NS}/sharedStrings`,\n target: 'sharedStrings.xml',\n });\n }\n wbRels.rels.push({\n id: `rId${wbRels.rels.length + 1}`,\n type: `${REL_NS}/styles`,\n target: 'styles.xml',\n });\n await writer.addEntry(ARC_WORKBOOK_RELS, relsToBytes(wbRels));\n\n // 6. root rels.\n const rootRels = makeRelationships();\n rootRels.rels.push({\n id: 'rId1',\n type: `${REL_NS}/officeDocument`,\n target: 'xl/workbook.xml',\n });\n await writer.addEntry(ARC_ROOT_RELS, relsToBytes(rootRels));\n void PKG_REL_NS; // imported for future docProps support\n\n // 7. [Content_Types].xml.\n const manifest = makeManifest();\n // Excel rejects packages whose [Content_Types].xml is missing the Default\n // entries for `rels` / `xml` — without them the package relationships file\n // can't be classified and Excel refuses to open.\n addDefault(manifest, 'rels', 'application/vnd.openxmlformats-package.relationships+xml');\n addDefault(manifest, 'xml', 'application/xml');\n addOverride(manifest, `/${ARC_WORKBOOK}`, XLSX_TYPE);\n for (const s of state.sheets) {\n addOverride(manifest, `/xl/worksheets/sheet${s.sheetId}.xml`, WORKSHEET_TYPE);\n }\n addOverride(manifest, `/${ARC_STYLE}`, STYLES_TYPE);\n if (state.strings.size > 0) {\n addOverride(manifest, `/${ARC_SHARED_STRINGS}`, SHARED_STRINGS_TYPE);\n }\n await writer.addEntry(ARC_CONTENT_TYPES, manifestToBytes(manifest));\n\n await writer.finalize();\n}\n\nconst serializeWorkbookXml = (\n sheets: ReadonlyArray<{ title: string; sheetId: number }>,\n): string => {\n const parts: string[] = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>',\n `<workbook xmlns=\"${SHEET_MAIN_NS}\" xmlns:r=\"${REL_NS}\">`,\n '<sheets>',\n ];\n sheets.forEach((s, i) => {\n parts.push(\n `<sheet name=\"${escapeAttr(s.title)}\" sheetId=\"${s.sheetId}\" r:id=\"rId${i + 1}\"/>`,\n );\n });\n parts.push('</sheets></workbook>');\n return parts.join('');\n};\n\n/** Open a workbook for streaming write-only output. */\nexport async function createWriteOnlyWorkbook(\n sink: XlsxSink,\n opts: WriteOnlyOptions = {},\n): Promise<WriteOnlyWorkbook> {\n // The streaming-deflate ZIP writer is constructed eagerly here: each\n // addWorksheet opens an entry on it and flushes row chunks through fflate's\n // `Zip` + `ZipDeflate` immediately, so peak memory stays at one pending row\n // buffer plus deflate scratch, with bounded workbook-wide string retention.\n return makeWriteOnlyWorkbook(sink, opts);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Cell } from '../cell/cell.js';
|
|
2
|
+
import type { Workbook } from '../workbook/workbook.js';
|
|
3
|
+
/**
|
|
4
|
+
* The text Excel prints in `c`: the cell's value put through the number format
|
|
5
|
+
* its style points at.
|
|
6
|
+
*
|
|
7
|
+
* This is not a shorter `cellValueAsString`. That one takes a `CellValue` and
|
|
8
|
+
* answers "what is this value in JavaScript terms", so it never sees the
|
|
9
|
+
* stylesheet and cannot know that `0.5` is a percentage or that `45365` is a
|
|
10
|
+
* date. This one takes the workbook and the cell, resolves the cell's `numFmt`,
|
|
11
|
+
* and answers "what would somebody looking at this file in Excel read". Pick
|
|
12
|
+
* `cellValueAsString` when you want the value and this when you want the text:
|
|
13
|
+
* writing a sheet out as CSV, rendering it as HTML, diffing two workbooks by
|
|
14
|
+
* what they show, or logging a cell for a human. Choosing wrong fails quietly,
|
|
15
|
+
* which is why both say so.
|
|
16
|
+
*
|
|
17
|
+
* Covered:
|
|
18
|
+
*
|
|
19
|
+
* - `General`, at the 15 significant digits Excel stores. This is what turns
|
|
20
|
+
* `=1.1+2.2` into `3.3` rather than `3.3000000000000003`.
|
|
21
|
+
* - Every code in `BUILTIN_FORMATS`, including the accounting codes,
|
|
22
|
+
* scientific notation and the two fraction codes. The one exception is that
|
|
23
|
+
* catalogue's entry for numFmtId 44, whose stored form is missing the `;`
|
|
24
|
+
* section separators Excel's own accounting code carries, so it reads as a
|
|
25
|
+
* text layout.
|
|
26
|
+
* - Custom numeric codes: `0` `#` `?` digit placeholders, thousands grouping,
|
|
27
|
+
* trailing commas that divide by a thousand, `%`, `E+`/`E-` (including the
|
|
28
|
+
* engineering step `##0.0E+0`), `?/?` fractions with a placeholder or a
|
|
29
|
+
* spelled-out denominator, quoted and `\`-escaped literals, `_x` width
|
|
30
|
+
* reservations, currency symbols, and colour codes (which change nothing
|
|
31
|
+
* about the text).
|
|
32
|
+
* - The positive / negative / zero / text sections of a multi-section code. A
|
|
33
|
+
* lone section takes the minus sign in front of it; a value that rounds to
|
|
34
|
+
* zero prints without one.
|
|
35
|
+
* - Dates and times: `y` `m` `d` `h` `s` runs, `mmm` / `mmmm` month names,
|
|
36
|
+
* `ddd` / `dddd` weekday names, `AM/PM` and `A/P`, fractional seconds, and
|
|
37
|
+
* the elapsed forms `[h]` `[mm]` `[ss]`.
|
|
38
|
+
* - Booleans as `TRUE` / `FALSE`, error cells as their token, rich text as its
|
|
39
|
+
* runs joined, a formula cell from the value Excel cached for it, and an
|
|
40
|
+
* empty cell as `''`.
|
|
41
|
+
*
|
|
42
|
+
* Not covered, and the cell falls back to `cellValueAsString` so the reading is
|
|
43
|
+
* a plain coercion rather than a guess:
|
|
44
|
+
*
|
|
45
|
+
* - Comparison sections (`[>=100]"over";[<0]"under";0`).
|
|
46
|
+
* - A section splicing two numeric layouts together (`0.00" ("0.00")"`).
|
|
47
|
+
* - Calendar and numbering modifiers: era tokens (`g`, `e`, `b`) and bracket
|
|
48
|
+
* groups such as `[DBNum1]` that replace the digits themselves.
|
|
49
|
+
* - More than four sections, or an unterminated `"` or `[`.
|
|
50
|
+
*
|
|
51
|
+
* Three more places where the text is a judgement call, because Excel's own
|
|
52
|
+
* answer is not recoverable from the cell:
|
|
53
|
+
*
|
|
54
|
+
* - `*x` repeats `x` until the column is full, so nothing is printed for it.
|
|
55
|
+
* - Month and weekday names come out in English; a `[$-409]`-style locale tag
|
|
56
|
+
* is read for its currency symbol and otherwise ignored.
|
|
57
|
+
* - How wide the column is decides how many digits `General` shows and when it
|
|
58
|
+
* gives up and switches to scientific notation. Every stored digit is kept.
|
|
59
|
+
*
|
|
60
|
+
* A `Date` cell value is converted to its serial and run through the format, so
|
|
61
|
+
* it reads as a date only if the cell carries a date format. That is the same
|
|
62
|
+
* rule the file itself follows: the worksheet stores a serial and the style
|
|
63
|
+
* decides what it means. Use `setCellAsDate` on the write side.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getCellDisplayText(wb: Workbook, c: Cell): string;
|
|
66
|
+
/**
|
|
67
|
+
* The `Date` a date-formatted cell stands for, or `undefined` when the cell has
|
|
68
|
+
* no date reading.
|
|
69
|
+
*
|
|
70
|
+
* Excel stores a date as a plain number of days since the workbook epoch, so
|
|
71
|
+
* nothing in a loaded cell's value distinguishes `45365` the date from `45365`
|
|
72
|
+
* the count: the cell's number format is the only evidence, and
|
|
73
|
+
* `cellValueAsDate` deliberately does not consult it. This composes the three
|
|
74
|
+
* steps that reading one otherwise takes, `getCellNumberFormat` then the
|
|
75
|
+
* format's own reading then `excelToDate` with the epoch from `wb.date1904`,
|
|
76
|
+
* into the call that reading a foreign workbook actually needs.
|
|
77
|
+
*
|
|
78
|
+
* - A `Date` value passes through, formatted or not.
|
|
79
|
+
* - A number needs a format that names a day: a year, month or day part.
|
|
80
|
+
* Without one it is a count, and the answer is `undefined`.
|
|
81
|
+
* - A formula cell is read from the value Excel cached for it.
|
|
82
|
+
* - A time-of-day format (`h:mm`) names a moment inside a day but not which
|
|
83
|
+
* day, and an elapsed-time format (`[h]:mm`) measures a span rather than
|
|
84
|
+
* naming a moment, so neither has a date reading. Neither does a `duration`
|
|
85
|
+
* value: its `ms` is already the span.
|
|
86
|
+
* - The format is read by the parser {@link getCellDisplayText} renders with,
|
|
87
|
+
* so the two agree on what any one cell is. A code outside the subset that
|
|
88
|
+
* one documents has no date reading here either.
|
|
89
|
+
*
|
|
90
|
+
* The returned `Date` is built in UTC, which is how `excelToDate` and
|
|
91
|
+
* `dateToExcel` read every serial in this library. Use `getUTCFullYear` and
|
|
92
|
+
* friends, or the local-time accessors will shift the day for anybody east or
|
|
93
|
+
* west of UTC.
|
|
94
|
+
*/
|
|
95
|
+
export declare function getCellDate(wb: Workbook, c: Cell): Date | undefined;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { type ExcelEpoch } from '../utils/datetime.js';
|
|
2
|
+
type DigitPlaceholder = '0' | '#' | '?';
|
|
3
|
+
type DatePart = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second' | 'monthOrMinute';
|
|
4
|
+
type ElapsedPart = 'hour' | 'minute' | 'second';
|
|
5
|
+
interface DatePartToken {
|
|
6
|
+
readonly kind: 'datePart';
|
|
7
|
+
readonly part: DatePart;
|
|
8
|
+
readonly width: number;
|
|
9
|
+
}
|
|
10
|
+
interface ElapsedToken {
|
|
11
|
+
readonly kind: 'elapsed';
|
|
12
|
+
readonly part: ElapsedPart;
|
|
13
|
+
readonly width: number;
|
|
14
|
+
}
|
|
15
|
+
type Token = {
|
|
16
|
+
readonly kind: 'literal';
|
|
17
|
+
readonly text: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: 'general';
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'digit';
|
|
22
|
+
readonly placeholder: DigitPlaceholder;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'point';
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'comma';
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'percent';
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'exponent';
|
|
31
|
+
readonly explicitSign: boolean;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'slash';
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: 'fixedDenominator';
|
|
36
|
+
readonly text: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'textPlaceholder';
|
|
39
|
+
}
|
|
40
|
+
/** Excel prints the meridiem in the case the code carries, so `am/pm` stays lowercase. */
|
|
41
|
+
| {
|
|
42
|
+
readonly kind: 'meridiem';
|
|
43
|
+
readonly am: string;
|
|
44
|
+
readonly pm: string;
|
|
45
|
+
} | {
|
|
46
|
+
readonly kind: 'subsecond';
|
|
47
|
+
readonly digits: number;
|
|
48
|
+
} | DatePartToken | ElapsedToken;
|
|
49
|
+
interface NumberFormatSection {
|
|
50
|
+
readonly kind: 'number';
|
|
51
|
+
readonly tokens: readonly Token[];
|
|
52
|
+
readonly grouped: boolean;
|
|
53
|
+
/** Trailing commas; each one divides the value by a thousand. */
|
|
54
|
+
readonly scale: number;
|
|
55
|
+
/** `%` occurrences; each one multiplies the value by a hundred. */
|
|
56
|
+
readonly percents: number;
|
|
57
|
+
/** Token index where the integer region ends (exclusive). */
|
|
58
|
+
readonly intEnd: number;
|
|
59
|
+
readonly intPlaceholders: readonly DigitPlaceholder[];
|
|
60
|
+
readonly fracPlaceholders: readonly DigitPlaceholder[];
|
|
61
|
+
/** Token index of the `E+` / `E-` marker, or -1 outside scientific codes. */
|
|
62
|
+
readonly exponentIndex: number;
|
|
63
|
+
readonly exponentDigits: number;
|
|
64
|
+
readonly exponentExplicitSign: boolean;
|
|
65
|
+
}
|
|
66
|
+
interface DateFormatSection {
|
|
67
|
+
readonly kind: 'date';
|
|
68
|
+
readonly tokens: readonly Token[];
|
|
69
|
+
readonly elapsed: boolean;
|
|
70
|
+
/** A year, month or day part: the section names a day, not just a time within one. */
|
|
71
|
+
readonly calendar: boolean;
|
|
72
|
+
readonly hasMeridiem: boolean;
|
|
73
|
+
/** Widest fractional-second group in the section; the serial is rounded once at this precision. */
|
|
74
|
+
readonly subsecondDigits: number;
|
|
75
|
+
}
|
|
76
|
+
interface FractionFormatSection {
|
|
77
|
+
readonly kind: 'fraction';
|
|
78
|
+
readonly tokens: readonly Token[];
|
|
79
|
+
readonly slashIndex: number;
|
|
80
|
+
/** Token range holding the whole-number run, or -1 when the code has none. */
|
|
81
|
+
readonly wholeStart: number;
|
|
82
|
+
readonly wholeEnd: number;
|
|
83
|
+
readonly numeratorStart: number;
|
|
84
|
+
readonly denominatorEnd: number;
|
|
85
|
+
/** Placeholder count in the denominator run; caps the denominator at 10^n - 1. */
|
|
86
|
+
readonly denominatorDigits: number;
|
|
87
|
+
/** Non-zero when the code spells the denominator out, as in `# ?/16`. */
|
|
88
|
+
readonly fixedDenominator: number;
|
|
89
|
+
}
|
|
90
|
+
interface TextFormatSection {
|
|
91
|
+
readonly kind: 'text';
|
|
92
|
+
readonly tokens: readonly Token[];
|
|
93
|
+
}
|
|
94
|
+
type FormatSection = {
|
|
95
|
+
readonly kind: 'general';
|
|
96
|
+
} | {
|
|
97
|
+
readonly kind: 'blank';
|
|
98
|
+
} | {
|
|
99
|
+
readonly kind: 'literal';
|
|
100
|
+
readonly text: string;
|
|
101
|
+
} | NumberFormatSection | DateFormatSection | FractionFormatSection | TextFormatSection;
|
|
102
|
+
export interface ParsedFormat {
|
|
103
|
+
readonly sections: readonly FormatSection[];
|
|
104
|
+
}
|
|
105
|
+
/** Parse a `numFmt` format code, or `undefined` when it is out of scope. */
|
|
106
|
+
export declare function parseFormatCode(code: string): ParsedFormat | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* True when the format reads as a calendar date: a date section carrying a
|
|
109
|
+
* year, month or day, rather than a time of day (`h:mm`), an elapsed span
|
|
110
|
+
* (`[h]:mm`) or a numeric layout. The section selected for the value decides.
|
|
111
|
+
*/
|
|
112
|
+
export declare function hasCalendarDate(format: ParsedFormat, value: number): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Render a numeric cell value (a plain number, or a date / duration already
|
|
115
|
+
* converted to its Excel serial). `undefined` means the value has no reading
|
|
116
|
+
* under this format and the caller should degrade.
|
|
117
|
+
*/
|
|
118
|
+
export declare function renderNumericValue(format: ParsedFormat, value: number, epoch: ExcelEpoch): string | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Render a string cell value. Only the fourth section of a code applies to
|
|
121
|
+
* text, plus a lone section that is itself a text layout (`@" pcs"`); under any
|
|
122
|
+
* other code Excel prints the string unchanged.
|
|
123
|
+
*/
|
|
124
|
+
export declare function renderTextValue(format: ParsedFormat, text: string): string;
|
|
125
|
+
export {};
|
package/dist/styles/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { alignmentToCss, makeAlignment } from './alignment.js';
|
|
|
3
3
|
export type { Border, Side, SideStyle } from './borders.js';
|
|
4
4
|
export { borderToCss, makeBorder, makeSide } from './borders.js';
|
|
5
5
|
export { alignCellHorizontal, alignCellVertical, applyBuiltinStyle, applyNamedStyle, cellStyleToCss, centerCell, clearCellBackground, clearCellStyle, clearRangeStyle, cloneCellStyle, copyCellStyle, formatAsHeader, indentCell, patchCellFont, registerCellStyle, rotateCellText, setBold, setCellBackgroundColor, setFontColor, setFontName, setFontSize, setItalic, setStrikethrough, setUnderline, wrapCellText, getCellAlignment, getCellBorder, getCellFill, getCellFont, getCellNumberFormat, getCellProtection, setCellAlignment, setCellAsCurrency, setCellAsDate, setCellAsNumber, setCellAsPercent, setCellBorder, setCellBorderAll, setCellFill, setCellFont, setCellNumberFormat, setCellProtection, setCellStyle, setRangeBackgroundColor, setRangeBorderBox, setRangeFont, setRangeNumberFormat, setRangeAlignment, setRangeProtection, setRangeStyle, setRangeWrapText, } from './cell-style.js';
|
|
6
|
+
export { getCellDate, getCellDisplayText } from './display-text.js';
|
|
6
7
|
export type { DifferentialStyle } from './differential.js';
|
|
7
8
|
export { addDxf, makeDifferentialStyle } from './differential.js';
|
|
8
9
|
export type { NamedStyle, StylesheetNamedStyle } from './named-styles.js';
|
package/dist/styles/numbers.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ export declare function builtinFormatCode(id: number): string | undefined;
|
|
|
17
17
|
export declare function builtinFormatId(code: string): number | undefined;
|
|
18
18
|
/** True iff `code` is one of the OOXML built-in format strings. */
|
|
19
19
|
export declare function isBuiltinFormat(code: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The colour names Excel accepts in a bracket group (`[Red]`), as the regex
|
|
22
|
+
* alternation `STRIP_RE` needs them. The format-code parser needs the same
|
|
23
|
+
* names to tell a colour apart from a comparison or a calendar modifier, so
|
|
24
|
+
* they live here once.
|
|
25
|
+
*/
|
|
26
|
+
export declare const FORMAT_COLOR_NAMES = "BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW";
|
|
20
27
|
/**
|
|
21
28
|
* Heuristic: does the format string imply a date / time interpretation?
|
|
22
29
|
* Looks at only the first format section (positive-value branch); strips
|