@office-kit/xlsx 0.15.0 → 0.17.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.
Files changed (66) hide show
  1. package/README.md +29 -8
  2. package/dist/cell/cell.d.ts +12 -19
  3. package/dist/{cell-Df3cKRIl.mjs → cell-B5OUUdfI.mjs} +13 -7
  4. package/dist/cell-B5OUUdfI.mjs.map +1 -0
  5. package/dist/{cell-style-CNsET6WU.mjs → cell-style-DjKKPWDL.mjs} +2 -2
  6. package/dist/{cell-style-CNsET6WU.mjs.map → cell-style-DjKKPWDL.mjs.map} +1 -1
  7. package/dist/cell.mjs +1 -1
  8. package/dist/{coordinate-Bt5Gni2I.mjs → coordinate-CxC88uBk.mjs} +31 -2
  9. package/dist/coordinate-CxC88uBk.mjs.map +1 -0
  10. package/dist/{defined-names-Ctu3F6ls.mjs → defined-names-Cmp8rbvI.mjs} +2 -2
  11. package/dist/{defined-names-Ctu3F6ls.mjs.map → defined-names-Cmp8rbvI.mjs.map} +1 -1
  12. package/dist/{differential-BPKa7wXi.mjs → differential-7KGzbliB.mjs} +2 -2
  13. package/dist/{differential-BPKa7wXi.mjs.map → differential-7KGzbliB.mjs.map} +1 -1
  14. package/dist/{drawing-ZJ3h4VHD.mjs → drawing-Cv6UtInY.mjs} +2 -2
  15. package/dist/{drawing-ZJ3h4VHD.mjs.map → drawing-Cv6UtInY.mjs.map} +1 -1
  16. package/dist/drawing.mjs +1 -1
  17. package/dist/{escape-rnWSPpWY.mjs → escape-rA5UpibT.mjs} +26 -3
  18. package/dist/escape-rA5UpibT.mjs.map +1 -0
  19. package/dist/io/load.d.ts +28 -0
  20. package/dist/io.mjs +2 -2
  21. package/dist/{load-CspGc399.mjs → load-DIdQ_zum.mjs} +66 -39
  22. package/dist/{load-CspGc399.mjs.map → load-DIdQ_zum.mjs.map} +1 -1
  23. package/dist/{manifest-pLmx7KYh.mjs → manifest-BHUYpjUG.mjs} +3 -3
  24. package/dist/{manifest-pLmx7KYh.mjs.map → manifest-BHUYpjUG.mjs.map} +1 -1
  25. package/dist/node.mjs +1 -1
  26. package/dist/packaging.mjs +1 -1
  27. package/dist/{reader-DHMxLBQV.mjs → reader-pC7bxYAs.mjs} +30 -7
  28. package/dist/reader-pC7bxYAs.mjs.map +1 -0
  29. package/dist/{save-DgPf1DyF.mjs → save-ax11Gsw0.mjs} +7 -7
  30. package/dist/{save-DgPf1DyF.mjs.map → save-ax11Gsw0.mjs.map} +1 -1
  31. package/dist/schema.mjs +1 -1
  32. package/dist/{serialize-BC2Wu3bR.mjs → serialize-Dmb6Vtt9.mjs} +23 -6
  33. package/dist/serialize-Dmb6Vtt9.mjs.map +1 -0
  34. package/dist/{serializer-C6nm0mRJ.mjs → serializer-dhv07Z6F.mjs} +2 -2
  35. package/dist/{serializer-C6nm0mRJ.mjs.map → serializer-dhv07Z6F.mjs.map} +1 -1
  36. package/dist/streaming/string-table.d.ts +7 -0
  37. package/dist/streaming.mjs +189 -83
  38. package/dist/streaming.mjs.map +1 -1
  39. package/dist/styles.mjs +3 -3
  40. package/dist/{stylesheet-writer-TxsinwbO.mjs → stylesheet-writer-BBYjNSMA.mjs} +201 -118
  41. package/dist/stylesheet-writer-BBYjNSMA.mjs.map +1 -0
  42. package/dist/{table-BrP0RwUd.mjs → table-BFi2WmgG.mjs} +2 -2
  43. package/dist/{table-BrP0RwUd.mjs.map → table-BFi2WmgG.mjs.map} +1 -1
  44. package/dist/utils/cell-number.d.ts +10 -0
  45. package/dist/utils/coordinate.d.ts +11 -0
  46. package/dist/utils/escape.d.ts +17 -0
  47. package/dist/utils/xsd-boolean.d.ts +9 -0
  48. package/dist/utils.mjs +2 -2
  49. package/dist/workbook/shared-strings.d.ts +2 -0
  50. package/dist/{workbook-DQCslHzY.mjs → workbook-CMel6UcI.mjs} +13 -12
  51. package/dist/workbook-CMel6UcI.mjs.map +1 -0
  52. package/dist/workbook.mjs +2 -2
  53. package/dist/worksheet/writer.d.ts +9 -5
  54. package/dist/{worksheet-B2NOQ6dM.mjs → worksheet-Cxn2c-jz.mjs} +3 -3
  55. package/dist/{worksheet-B2NOQ6dM.mjs.map → worksheet-Cxn2c-jz.mjs.map} +1 -1
  56. package/dist/worksheet.mjs +2 -2
  57. package/dist/xml.mjs +2 -2
  58. package/dist/zip.mjs +1 -1
  59. package/package.json +1 -1
  60. package/dist/cell-Df3cKRIl.mjs.map +0 -1
  61. package/dist/coordinate-Bt5Gni2I.mjs.map +0 -1
  62. package/dist/escape-rnWSPpWY.mjs.map +0 -1
  63. package/dist/reader-DHMxLBQV.mjs.map +0 -1
  64. package/dist/serialize-BC2Wu3bR.mjs.map +0 -1
  65. package/dist/stylesheet-writer-TxsinwbO.mjs.map +0 -1
  66. package/dist/workbook-DQCslHzY.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { $ as iterCells, $t as makeFreezePane, A as getCellsInRange, At as setFreezePanes, B as getPopulatedColumnIndices, Bt as ungroupColumns, C as findCells, Ct as setColumnDimension, Dt as setComments, E as getCellByCoord, Et as setComment, F as getMaxCol, Ft as setRowHeight, G as groupColumns, Gt as unhideRows, H as getRangeValues, Ht as unhideColumn, I as getMaxRow, It as setRowHeights, J as hideColumns, Jt as writeRange, K as groupRows, Kt as unmergeCells, L as getMergedCells, Lt as setSheetTabColor, M as getColumnDimension, Mt as setHyperlinks, N as getDataExtent, Nt as setRangeValues, Ot as setDefaultColumnWidth, P as getFreezePanes, Pt as setRowDimension, Q as isWorksheetEmpty, Qt as freezePaneRef, R as getMergedRangeAt, Rt as setSheetViewMode, S as expandRowGroup, St as setCellByCoord, T as getCell, Tt as setColumnWidths, U as getRowDimension, Ut as unhideColumns, V as getPopulatedRowIndices, Vt as ungroupRows, W as getTable, Wt as unhideRow, X as hideRows, Xt as makeSheetProperties, Y as hideRow, Yt as makeHyperlink, Z as isMergedCell, Zt as makeLegacyComment, _ as countCellsByKind, _n as unionRange, _t as removeHyperlink, a as addTable, an as isCellInRange, at as listTables, b as ensureCellByCoord, bt as setAutoFilter, c as applyToRange, ct as moveRange, d as clearAllCells, dn as rangeArea, dt as removeAllDataValidations, en as makeSheetView, et as iterRows, f as clearRange, fn as rangeContainsCell, ft as removeAllHyperlinks, g as countCells, gn as shiftRange, gt as removeDataValidations, h as copyRange, hn as rangesOverlap, ht as removeCellWatches, i as addIgnoredError, in as intersectionRange, it as listHyperlinks, j as getCellsInRow, jt as setHyperlink, k as getCellsInColumn, kt as setDefaultRowHeight, l as autofitColumns, lt as removeAllComments, m as collapseRowGroup, mt as removeAllTables, n as addConditionalFormatting, nn as makeRowDimension, nt as listComments, o as appendRow, on as isRangeInRange, ot as makeWorksheet, p as collapseColumnGroup, pn as rangeContainsRange, pt as removeAllMergedRanges, q as hideColumn, qt as unmergeCellsAt, r as addDataValidation, rn as expandRangeStr, rt as listDataValidations, s as appendRows, st as mergeCells, t as addCellWatch, tn as makeColumnDimension, tt as iterValues, ut as removeAllConditionalFormatting, v as deleteCell, vt as removeIgnoredErrors, w as getAutoFilter, wt as setColumnWidth, x as expandColumnGroup, xt as setCell, y as ensureCell, yt as removeTable, z as getNonEmptyCellCount, zt as setSheetZoom } from "./worksheet-B2NOQ6dM.mjs";
2
- import { a as makeCfRule, i as makeDataValidation, n as makeTableColumn, o as makeConditionalFormatting, r as makeTableDefinition, t as addExcelTable } from "./table-BrP0RwUd.mjs";
1
+ import { $ as iterCells, $t as makeFreezePane, A as getCellsInRange, At as setFreezePanes, B as getPopulatedColumnIndices, Bt as ungroupColumns, C as findCells, Ct as setColumnDimension, Dt as setComments, E as getCellByCoord, Et as setComment, F as getMaxCol, Ft as setRowHeight, G as groupColumns, Gt as unhideRows, H as getRangeValues, Ht as unhideColumn, I as getMaxRow, It as setRowHeights, J as hideColumns, Jt as writeRange, K as groupRows, Kt as unmergeCells, L as getMergedCells, Lt as setSheetTabColor, M as getColumnDimension, Mt as setHyperlinks, N as getDataExtent, Nt as setRangeValues, Ot as setDefaultColumnWidth, P as getFreezePanes, Pt as setRowDimension, Q as isWorksheetEmpty, Qt as freezePaneRef, R as getMergedRangeAt, Rt as setSheetViewMode, S as expandRowGroup, St as setCellByCoord, T as getCell, Tt as setColumnWidths, U as getRowDimension, Ut as unhideColumns, V as getPopulatedRowIndices, Vt as ungroupRows, W as getTable, Wt as unhideRow, X as hideRows, Xt as makeSheetProperties, Y as hideRow, Yt as makeHyperlink, Z as isMergedCell, Zt as makeLegacyComment, _ as countCellsByKind, _n as unionRange, _t as removeHyperlink, a as addTable, an as isCellInRange, at as listTables, b as ensureCellByCoord, bt as setAutoFilter, c as applyToRange, ct as moveRange, d as clearAllCells, dn as rangeArea, dt as removeAllDataValidations, en as makeSheetView, et as iterRows, f as clearRange, fn as rangeContainsCell, ft as removeAllHyperlinks, g as countCells, gn as shiftRange, gt as removeDataValidations, h as copyRange, hn as rangesOverlap, ht as removeCellWatches, i as addIgnoredError, in as intersectionRange, it as listHyperlinks, j as getCellsInRow, jt as setHyperlink, k as getCellsInColumn, kt as setDefaultRowHeight, l as autofitColumns, lt as removeAllComments, m as collapseRowGroup, mt as removeAllTables, n as addConditionalFormatting, nn as makeRowDimension, nt as listComments, o as appendRow, on as isRangeInRange, ot as makeWorksheet, p as collapseColumnGroup, pn as rangeContainsRange, pt as removeAllMergedRanges, q as hideColumn, qt as unmergeCellsAt, r as addDataValidation, rn as expandRangeStr, rt as listDataValidations, s as appendRows, st as mergeCells, t as addCellWatch, tn as makeColumnDimension, tt as iterValues, ut as removeAllConditionalFormatting, v as deleteCell, vt as removeIgnoredErrors, w as getAutoFilter, wt as setColumnWidth, x as expandColumnGroup, xt as setCell, y as ensureCell, yt as removeTable, z as getNonEmptyCellCount, zt as setSheetZoom } from "./worksheet-Cxn2c-jz.mjs";
2
+ import { a as makeCfRule, i as makeDataValidation, n as makeTableColumn, o as makeConditionalFormatting, r as makeTableDefinition, t as addExcelTable } from "./table-BFi2WmgG.mjs";
3
3
  //#region src/worksheet/auto-filter.ts
4
4
  function makeAutoFilter(opts) {
5
5
  return {
package/dist/xml.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { i as OpenXmlIoError } from "./exceptions-D-CFwxgm.mjs";
2
- import { n as escapeXmlAttr, r as escapeXmlText } from "./escape-rnWSPpWY.mjs";
2
+ import { n as escapeXmlAttr, r as escapeXmlText } from "./escape-rA5UpibT.mjs";
3
3
  import { $ as PACKAGE_PIVOT_CACHE, A as COREPROPS_NS, At as parseQName, B as DCTERMS_PREFIX, C as CHARTSHEET_TYPE, Ct as XLSM_TYPE, D as COMMENTS_NS, Dt as XML_NS, E as CHART_TYPE, Et as XLTX_TYPE, F as CUSTPROPS_NS, G as EXTERNAL_LINK_TYPE, H as DRAWING_NS, I as CX_NS, J as PACKAGE_CHARTS, K as IMAGE_NS, L as DCMITYPE_NS, M as CPROPS_TYPE, N as CTRLPROPS_TYPE, O as COMMENTS_TYPE, Ot as XPROPS_NS, P as CUSTOMUI_NS, Q as PACKAGE_IMAGES, R as DCORE_NS, S as CHARTSHAPE_TYPE, St as X16_NS, T as CHART_NS, Tt as XLTM_TYPE, U as DRAWING_TYPE, V as DEFAULT_PREFIXES, W as EXTERNAL_LINK_NS, X as PACKAGE_CHARTSHEETS_RELS, Y as PACKAGE_CHARTSHEETS, Z as PACKAGE_DRAWINGS, _ as ARC_WORKBOOK_RELS, _t as VML_NS, a as findChildren, at as PACKAGE_WORKSHEET_RELS, b as C16_NS, bt as X14_NS, c as ARC_CONTENT_TYPES, ct as PKG_REL_NS, d as ARC_CUSTOM_UI, dt as SHEET_DRAWING_NS, et as PACKAGE_PIVOT_TABLE, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, gt as VBA_TYPE, h as ARC_THEME, ht as THREADED_COMMENTS_NS, i as findChild, it as PACKAGE_WORKSHEETS, j as CPROPS_FMTID, jt as qname, k as CONTYPES_NS, kt as XSI_NS, l as ARC_CORE, lt as REL_NS, m as ARC_STYLE, mt as THEME_TYPE, n as el, nt as PACKAGE_RELS, o as ACTIVEX_TYPE, ot as PACKAGE_XL, p as ARC_SHARED_STRINGS, pt as STYLES_TYPE, q as MARKUP_COMPAT_NS, r as elNs, rt as PACKAGE_THEME, s as ARC_APP, st as PICTURE_NS, t as appendChild, tt as PACKAGE_PROPS, u as ARC_CUSTOM, ut as SHARED_STRINGS_TYPE, v as C14_NS, vt as VTYPES_NS, w as CHART_DRAWING_NS, wt as XLSX_TYPE, xt as X15_NS, y as C15_NS, yt as WORKSHEET_TYPE, z as DCTERMS_NS } from "./tree-BX-sRRVp.mjs";
4
4
  import { t as parseXml } from "./parser-By6RWZVW.mjs";
5
- import { t as serializeXml } from "./serializer-C6nm0mRJ.mjs";
5
+ import { t as serializeXml } from "./serializer-dhv07Z6F.mjs";
6
6
  import { t as utf8ByteLength } from "./utf8-OAkCDG5g.mjs";
7
7
  import { t as iterParse } from "./iterparse-DZE0zy2M.mjs";
8
8
  //#region src/xml/stream-writer.ts
package/dist/zip.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { n as DEFAULT_DECOMPRESSION_LIMITS, t as openZip } from "./reader-DHMxLBQV.mjs";
1
+ import { n as DEFAULT_DECOMPRESSION_LIMITS, t as openZip } from "./reader-pC7bxYAs.mjs";
2
2
  import { t as createZipWriter } from "./writer-C38RBZCy.mjs";
3
3
  export { DEFAULT_DECOMPRESSION_LIMITS, createZipWriter, openZip };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@office-kit/xlsx",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "TypeScript library for reading and writing Excel xlsx files in Node and the browser.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"file":"cell-Df3cKRIl.mjs","names":[],"sources":["../src/cell/rich-text.ts","../src/cell/cell.ts"],"sourcesContent":["// Inline rich-text runs.\n//\n// A rich-text cell value is `{ kind: 'rich-text', runs }`. Each run is\n// a string segment with an optional InlineFont describing the in-line\n// formatting (font name, size, bold / italic / underline, colour, …).\n// Run-level fields use OOXML's short attribute names (`sz`, `b`, `i`,\n// `u`) so the writer can splice them into `<rPr>` directly without\n// renaming.\n\nimport type { Color } from '../styles/colors.js';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\n\n/** Underline styles per openpyxl's cell-level NestedNoneSet. */\nexport type InlineUnderline = 'single' | 'double' | 'singleAccounting' | 'doubleAccounting';\n\nexport type InlineVertAlign = 'baseline' | 'superscript' | 'subscript';\n\nexport interface InlineFont {\n readonly name?: string;\n readonly sz?: number;\n readonly b?: boolean;\n readonly i?: boolean;\n readonly u?: InlineUnderline;\n readonly strike?: boolean;\n readonly outline?: boolean;\n readonly shadow?: boolean;\n readonly condense?: boolean;\n readonly extend?: boolean;\n readonly vertAlign?: InlineVertAlign;\n readonly color?: Color;\n readonly family?: number;\n readonly charset?: number;\n readonly scheme?: 'major' | 'minor';\n}\n\nexport interface TextRun {\n readonly text: string;\n readonly font?: InlineFont;\n}\n\n/** A frozen array of TextRuns. The shared cell value shape under `kind: 'rich-text'`. */\nexport type RichText = ReadonlyArray<TextRun>;\n\nexport function makeTextRun(text: string, font?: InlineFont): TextRun {\n if (typeof text !== 'string') {\n throw new OpenXmlSchemaError('makeTextRun: text must be a string');\n }\n return Object.freeze(font !== undefined ? { text, font } : { text });\n}\n\nexport function makeRichText(runs: ReadonlyArray<TextRun | { text: string; font?: InlineFont }>): RichText {\n const out = runs.map((r) => (Object.isFrozen(r) ? (r as TextRun) : makeTextRun(r.text, r.font)));\n return Object.freeze(out);\n}\n\n/**\n * Concatenate the plain-text content of a rich-text value (rich-text\n * read paths often want the raw text without formatting).\n */\nexport function richTextToString(rt: RichText): string {\n let out = '';\n for (const r of rt) out += r.text;\n return out;\n}\n","// Cell value model. Mirrors openpyxl/openpyxl/cell/cell.py.\n//\n// A Cell is a plain mutable object: the worksheet stores millions of these, so\n// per-cell freezes / spreads aren't viable on the hot path. The public surface\n// stays small — makeCell + getCoordinate + targeted setters — and uses\n// discriminated unions for the special CellValue shapes (formula, rich text,\n// duration, error).\n\nimport { columnLetterFromIndex, MAX_COL, MAX_ROW } from '../utils/coordinate.js';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport { normalizeFormulaText, startsWithEquals } from '../utils/formula-text.js';\nimport { ERROR_CODES } from '../utils/inference.js';\nimport { type RichText, richTextToString } from './rich-text.js';\n\n/** Excel error tokens. */\nexport type ExcelErrorCode = '#NULL!' | '#DIV/0!' | '#VALUE!' | '#REF!' | '#NAME?' | '#NUM!' | '#N/A' | '#GETTING_DATA';\n\n/** Formula sub-kind — drives the OOXML `<f t=\"…\">` attribute. */\nexport type FormulaKind = 'normal' | 'array' | 'shared' | 'dataTable';\n\nexport interface FormulaValue {\n readonly kind: 'formula';\n readonly formula: string;\n readonly t: FormulaKind;\n /** Cached value Excel last computed for the cell, used when `data_only` reads it back. */\n readonly cachedValue?: number | string | boolean;\n /** Range string (`\"A1:A10\"`) for array / shared / dataTable formulas. */\n readonly ref?: string;\n /** Shared-formula index. */\n readonly si?: number;\n /** Data-table specific fields (mirrors openpyxl DataTableFormula). */\n readonly r1?: string;\n readonly r2?: string;\n readonly dt2D?: boolean;\n readonly dtr?: boolean;\n readonly del1?: boolean;\n readonly del2?: boolean;\n readonly aca?: boolean;\n readonly ca?: boolean;\n}\n\nexport type CellValue =\n | number\n | string\n | boolean\n | Date\n | { kind: 'duration'; ms: number }\n | { kind: 'error'; code: ExcelErrorCode }\n | { kind: 'rich-text'; runs: RichText }\n | FormulaValue\n | null;\n\nexport interface Cell {\n /** 1-based row index. */\n row: number;\n /** 1-based column index. */\n col: number;\n /** Effective cell value. `null` represents an empty cell. */\n value: CellValue;\n /** Index into Workbook.styles.cellXfs. 0 = default. */\n styleId: number;\n /** Optional reference to a Hyperlink registered on the worksheet. */\n hyperlinkId?: number;\n /** Optional reference to a Comment registered on the worksheet. */\n commentId?: number;\n}\n\n/** Marker subtype for the placeholder cells inside a merged range (top-left holds the value). */\nexport interface MergedCell extends Cell {\n merged: true;\n}\n\nconst validateCoord = (row: number, col: number): void => {\n if (!Number.isInteger(row) || row < 1 || row > MAX_ROW) {\n throw new OpenXmlSchemaError(`Cell row ${row} out of range [1, ${MAX_ROW}]`);\n }\n if (!Number.isInteger(col) || col < 1 || col > MAX_COL) {\n throw new OpenXmlSchemaError(`Cell col ${col} out of range [1, ${MAX_COL}]`);\n }\n};\n\n/** Build a fresh Cell. Validates coordinates against the OOXML grid bounds. */\nexport function makeCell(row: number, col: number, value: CellValue = null, styleId = 0): Cell {\n validateCoord(row, col);\n return { row, col, value, styleId };\n}\n\n/** Format a Cell's coordinate as the canonical \"A1\" string. */\nexport function getCoordinate(c: Cell): string {\n return `${columnLetterFromIndex(c.col)}${c.row}`;\n}\n\n/**\n * Direct value setter. No type inference, no validation beyond the union — the\n * caller is in charge. Use {@link bindValue} for the \"do what I mean\" path.\n */\nexport function setCellValue(c: Cell, value: CellValue): void {\n c.value = value;\n}\n\n/**\n * \"Smart\" setter: infers the cell value from a JS runtime value.\n * - `string` starting with `=` → formula\n * - `string` matching an Excel error token → error variant\n * - other primitives / Date / null pass through verbatim\n *\n * Intentionally not the default — explicit is clearer for typed code, and\n * inferring on every write costs measurable time on the worksheet write hot\n * path.\n */\nexport function bindValue(c: Cell, value: number | string | boolean | Date | null): void {\n if (typeof value === 'string') {\n if (startsWithEquals(value)) {\n setFormula(c, value);\n return;\n }\n if (ERROR_CODES.has(value)) {\n c.value = { kind: 'error', code: value as ExcelErrorCode };\n return;\n }\n c.value = value;\n return;\n }\n c.value = value;\n}\n\n// ---- formula values --------------------------------------------------------\n\n// A `<f>` with no text is the shape Excel reports as damaged, and `'='` on its\n// own normalises to exactly that, so the kinds whose text is required reject it\n// here rather than at save time.\nconst requireFormulaText = (fn: string, formula: string): string => {\n const text = normalizeFormulaText(formula);\n if (text.length === 0) {\n throw new OpenXmlSchemaError(`${fn}: formula text must not be empty (got ${JSON.stringify(formula)})`);\n }\n return text;\n};\n\n/**\n * Build a formula cell value. Hand it to `setCell` when the write already\n * carries a style id, so placing a formatted formula stays a single call;\n * {@link setFormula} is the same thing applied to a cell you already hold.\n *\n * A leading `=` is stripped, so `'=SUM(A1:A3)'` and `'SUM(A1:A3)'` are\n * interchangeable.\n *\n * A cached value is optional. Without one, Excel, LibreOffice and Google\n * Sheets compute the result on open, but viewers that never calculate (Quick\n * Look, Outlook and SharePoint previews, most thumbnailers) render the cell\n * empty. Supply one whenever the producer can compute it.\n */\nexport function makeFormula(\n formula: string,\n opts?: { cachedValue?: FormulaValue['cachedValue'] },\n): FormulaValue {\n return Object.freeze({\n kind: 'formula',\n t: 'normal',\n formula: requireFormulaText('makeFormula', formula),\n ...(opts?.cachedValue !== undefined ? { cachedValue: opts.cachedValue } : {}),\n });\n}\n\n/**\n * Build an array (CSE) formula value spanning `ref`. Belongs on the top-left\n * cell of the range, since Excel reads `ref` to know how far the result\n * spreads.\n */\nexport function makeArrayFormula(\n ref: string,\n formula: string,\n opts?: { cachedValue?: FormulaValue['cachedValue'] },\n): FormulaValue {\n return Object.freeze({\n kind: 'formula',\n t: 'array',\n formula: requireFormulaText('makeArrayFormula', formula),\n ref,\n ...(opts?.cachedValue !== undefined ? { cachedValue: opts.cachedValue } : {}),\n });\n}\n\n/**\n * Build a shared-formula value. The first cell in the group carries the\n * formula text + `ref`; subsequent cells with the same `si` carry only the\n * index, and Excel reconstructs their text by shifting the references, so\n * their `formula` is legitimately empty.\n */\nexport function makeSharedFormula(\n si: number,\n formula?: string,\n ref?: string,\n opts?: { cachedValue?: FormulaValue['cachedValue'] },\n): FormulaValue {\n if (!Number.isInteger(si) || si < 0) {\n throw new OpenXmlSchemaError(`makeSharedFormula: si must be a non-negative integer; got ${si}`);\n }\n return Object.freeze({\n kind: 'formula',\n t: 'shared',\n formula: formula === undefined ? '' : normalizeFormulaText(formula),\n si,\n ...(ref !== undefined ? { ref } : {}),\n ...(opts?.cachedValue !== undefined ? { cachedValue: opts.cachedValue } : {}),\n });\n}\n\n/**\n * Plain `A1+B1` style formula, applied in place. Same value as\n * {@link makeFormula}, for a cell you already hold.\n */\nexport function setFormula(c: Cell, formula: string, opts?: { cachedValue?: FormulaValue['cachedValue'] }): void {\n c.value = makeFormula(formula, opts);\n}\n\n/** Array (CSE) formula spanning a `ref` range, applied in place. */\nexport function setArrayFormula(\n c: Cell,\n ref: string,\n formula: string,\n opts?: { cachedValue?: FormulaValue['cachedValue'] },\n): void {\n c.value = makeArrayFormula(ref, formula, opts);\n}\n\n/** Shared formula, applied in place. See {@link makeSharedFormula}. */\nexport function setSharedFormula(\n c: Cell,\n si: number,\n formula?: string,\n ref?: string,\n opts?: { cachedValue?: FormulaValue['cachedValue'] },\n): void {\n c.value = makeSharedFormula(si, formula, ref, opts);\n}\n\n/**\n * Excel data-table formula (`<f t=\"dataTable\">`). These appear as the \"What-if\n * Analysis → Data Table\" feature output: a 1- or 2-variable sensitivity grid\n * where the formula references one or two input cells. The wire format mirrors\n * openpyxl's `DataTableFormula`:\n *\n * - `ref` — inclusive cell range the formula spans.\n * - `r1`, `r2`— input cell coordinates (\"$A$1\" etc.).\n * - `dt2D` — true for two-variable tables (uses both r1 and r2).\n * - `dtr` — row-direction flag (true) vs column-direction (false).\n * - `del1`/`del2` — Excel marks one of these true when the\n * corresponding input cell has been deleted; the formula keeps round-tripping\n * so Excel can show the warning state.\n * - `aca`/`ca` — alwaysCalculate / calculate flags.\n */\nexport interface DataTableFormulaOpts {\n ref: string;\n r1?: string;\n r2?: string;\n dt2D?: boolean;\n dtr?: boolean;\n del1?: boolean;\n del2?: boolean;\n aca?: boolean;\n ca?: boolean;\n cachedValue?: FormulaValue['cachedValue'];\n}\n\n/**\n * Build a data-table formula value. Preserves all the dt-specific attributes\n * so the writer can re-emit `<f t=\"dataTable\" r1=\"...\" />` verbatim and Excel\n * keeps treating the cell as a Data Table cell. Excel writes these with no\n * formula text at all, so `formula` may be empty.\n */\nexport function makeDataTableFormula(formula: string, opts: DataTableFormulaOpts): FormulaValue {\n return Object.freeze({\n kind: 'formula',\n t: 'dataTable',\n formula: normalizeFormulaText(formula),\n ref: opts.ref,\n ...(opts.r1 !== undefined ? { r1: opts.r1 } : {}),\n ...(opts.r2 !== undefined ? { r2: opts.r2 } : {}),\n ...(opts.dt2D !== undefined ? { dt2D: opts.dt2D } : {}),\n ...(opts.dtr !== undefined ? { dtr: opts.dtr } : {}),\n ...(opts.del1 !== undefined ? { del1: opts.del1 } : {}),\n ...(opts.del2 !== undefined ? { del2: opts.del2 } : {}),\n ...(opts.aca !== undefined ? { aca: opts.aca } : {}),\n ...(opts.ca !== undefined ? { ca: opts.ca } : {}),\n ...(opts.cachedValue !== undefined ? { cachedValue: opts.cachedValue } : {}),\n });\n}\n\n/** Data-table formula, applied in place. See {@link makeDataTableFormula}. */\nexport function setDataTableFormula(c: Cell, formula: string, opts: DataTableFormulaOpts): void {\n c.value = makeDataTableFormula(formula, opts);\n}\n\n// ---- value-shape helpers ---------------------------------------------------\n\n/** Build a `{ kind: 'error', code }` cell value. */\nexport function makeErrorValue(code: ExcelErrorCode): { kind: 'error'; code: ExcelErrorCode } {\n if (!ERROR_CODES.has(code)) {\n throw new OpenXmlSchemaError(`makeErrorValue: unknown error code \"${code}\"`);\n }\n return Object.freeze({ kind: 'error', code });\n}\n\n/** Build a `{ kind: 'duration', ms }` cell value. */\nexport function makeDurationValue(ms: number): { kind: 'duration'; ms: number } {\n if (!Number.isFinite(ms)) {\n throw new OpenXmlSchemaError(`makeDurationValue: ms \"${ms}\" is not finite`);\n }\n return Object.freeze({ kind: 'duration', ms });\n}\n\n/** True iff `c.value` is the formula variant. */\nexport function isFormulaCell(c: Cell): boolean {\n return isFormulaValue(c.value);\n}\n\n/** True iff `c.value` is the rich-text variant. */\nexport function isRichTextCell(c: Cell): boolean {\n return isRichTextValue(c.value);\n}\n\n/** Returns true iff the cell has no content. */\nexport function isEmptyCell(c: Cell): boolean {\n return c.value === null;\n}\n\n/**\n * Type guard for `MergedCell` — true iff the cell is a placeholder for a\n * merged-range covered cell (the top-left of a merged range holds the value;\n * the rest are `MergedCell`). Use this to filter merge-placeholders out of\n * value-walking loops.\n */\nexport function isMergedCell(c: Cell): c is MergedCell {\n return (c as MergedCell).merged === true;\n}\n\n/** Returns true iff the cell holds an Excel error value (`#REF!`, `#NAME?`, …). */\nexport function isErrorCell(c: Cell): boolean {\n return isErrorValue(c.value);\n}\n\n/**\n * Get the formula text from a formula-bearing cell, or `undefined` for\n * non-formula cells. Equivalent to: isFormulaValue(c.value) ? c.value.formula :\n * undefined but spares callers the type-narrow + member access.\n */\nexport function getFormulaText(c: Cell): string | undefined {\n return isFormulaValue(c.value) ? c.value.formula : undefined;\n}\n\n/**\n * Get the cached value Excel last computed for a formula cell, or `undefined`\n * for non-formula / uncached cells. Useful for `data_only` read paths that want\n * the displayed result without re-evaluating.\n */\nexport function getCachedFormulaValue(c: Cell): number | string | boolean | undefined {\n return isFormulaValue(c.value) ? c.value.cachedValue : undefined;\n}\n\n// ---- value-level type guards + coercion ----------------------------------\n\n/** True iff `v` is the formula variant. */\nexport function isFormulaValue(v: CellValue): v is FormulaValue {\n return typeof v === 'object' && v !== null && (v as { kind?: string }).kind === 'formula';\n}\n\n/** True iff `v` is the rich-text variant. */\nexport function isRichTextValue(v: CellValue): v is { kind: 'rich-text'; runs: RichText } {\n return typeof v === 'object' && v !== null && (v as { kind?: string }).kind === 'rich-text';\n}\n\n/** True iff `v` is the error variant. */\nexport function isErrorValue(v: CellValue): v is { kind: 'error'; code: ExcelErrorCode } {\n return typeof v === 'object' && v !== null && (v as { kind?: string }).kind === 'error';\n}\n\n/** True iff `v` is the duration variant. */\nexport function isDurationValue(v: CellValue): v is { kind: 'duration'; ms: number } {\n return typeof v === 'object' && v !== null && (v as { kind?: string }).kind === 'duration';\n}\n\nexport interface CellValueAsStringOptions {\n /** Renderer for `Date` cells. Defaults to `d => d.toISOString()`. */\n dateFormat?: (value: Date) => string;\n /** Replacement for the `null` cell value. Defaults to `''`. */\n emptyText?: string;\n}\n\n/**\n * Coerce a CellValue to its plain-string display form. Numbers / booleans\n * convert via `String`; rich text concatenates run text; formulas yield the\n * cached value (or empty string when uncached); errors yield their Excel token;\n * durations yield `\"<ms> ms\"` with no formatting; Dates yield\n * `Date.toISOString()`; `null` yields `\"\"`.\n *\n * Pass `opts.dateFormat` to override the Date renderer (e.g. a locale-specific\n * format) and `opts.emptyText` to substitute a different placeholder for\n * `null` cells.\n */\nexport function cellValueAsString(v: CellValue, opts?: CellValueAsStringOptions): string {\n const emptyText = opts?.emptyText ?? '';\n if (v === null) return emptyText;\n if (typeof v === 'string') return v;\n if (typeof v === 'number' || typeof v === 'boolean') return String(v);\n if (v instanceof Date) return opts?.dateFormat ? opts.dateFormat(v) : v.toISOString();\n if (isRichTextValue(v)) return richTextToString(v.runs);\n if (isFormulaValue(v)) {\n if (v.cachedValue === undefined) return '';\n return typeof v.cachedValue === 'string' ? v.cachedValue : String(v.cachedValue);\n }\n if (isErrorValue(v)) return v.code;\n if (isDurationValue(v)) return `${v.ms} ms`;\n return '';\n}\n\n/**\n * Coerce a CellValue to `boolean | undefined`. Booleans pass through; `'TRUE'`\n * / `'true'` and `'FALSE'` / `'false'` (case-insensitive) parse to true /\n * false; numbers yield `false` for 0 and `true` for any other finite value\n * (matching Excel's truthy-number coercion); formula cells return their cached\n * boolean if any. Everything else (null, Date, error, duration, rich-text,\n * non-bool strings) yields `undefined`.\n */\nexport function cellValueAsBoolean(v: CellValue): boolean | undefined {\n if (typeof v === 'boolean') return v;\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) return undefined;\n return v !== 0;\n }\n if (typeof v === 'string') {\n const lc = v.toLowerCase();\n if (lc === 'true') return true;\n if (lc === 'false') return false;\n return undefined;\n }\n if (isFormulaValue(v) && typeof v.cachedValue === 'boolean') return v.cachedValue;\n return undefined;\n}\n\n/**\n * Coerce a CellValue to a `Date` when one is meaningful. Pass-through for\n * `Date`-typed values; ISO-8601 strings (anything `new Date(s)` parses to a\n * finite time) round-trip; durations are interpreted as `new Date(ms)`.\n * Numbers, booleans, formulas, errors, rich text, and null all return\n * `undefined` — this helper does **not** apply the Excel-serial-to-Date\n * conversion (use `excelToDate` for that).\n */\nexport function cellValueAsDate(v: CellValue): Date | undefined {\n if (v instanceof Date) return v;\n if (typeof v === 'string') {\n if (v === '') return undefined;\n const t = Date.parse(v);\n if (!Number.isFinite(t)) return undefined;\n return new Date(t);\n }\n if (isDurationValue(v)) return new Date(v.ms);\n return undefined;\n}\n\n/**\n * Coerce a CellValue to a number when one is meaningful. Booleans yield 0/1;\n * numeric strings parse via `Number(s)`; rich-text concats then parses;\n * formulas with a numeric cached value pass through. Returns `undefined` when\n * there's no sensible numeric reading (text strings, errors, dates, durations,\n * null, empty).\n */\nexport function cellValueAsNumber(v: CellValue): number | undefined {\n if (v === null) return undefined;\n if (typeof v === 'number') return Number.isFinite(v) ? v : undefined;\n if (typeof v === 'boolean') return v ? 1 : 0;\n if (typeof v === 'string') {\n if (v === '') return undefined;\n const n = Number(v);\n return Number.isFinite(n) ? n : undefined;\n }\n if (isFormulaValue(v) && typeof v.cachedValue === 'number') return v.cachedValue;\n return undefined;\n}\n\n/**\n * Map a CellValue to the most natural JS primitive for display / export.\n * Unlike `cellValueAsString`/`cellValueAsNumber`/etc., which each force a\n * single target type and return `undefined` when the value can't be coerced,\n * this returns whatever primitive best represents the union variant:\n *\n * - `null` → `null`\n * - `string` / `number` / `boolean` / `Date` → passthrough\n * - rich text → joined run text (`string`)\n * - formula → recursive on `cachedValue` (`null` when uncached)\n * - error → error code (`string`)\n * - duration → `ms` (`number`)\n */\nexport function cellValueAsPrimitive(v: CellValue): string | number | boolean | Date | null {\n if (v === null) return null;\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return v;\n if (v instanceof Date) return v;\n if (isRichTextValue(v)) return richTextToString(v.runs);\n if (isFormulaValue(v)) {\n return v.cachedValue === undefined ? null : v.cachedValue;\n }\n if (isErrorValue(v)) return v.code;\n if (isDurationValue(v)) return v.ms;\n return null;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"coordinate-Bt5Gni2I.mjs","names":[],"sources":["../src/utils/coordinate.ts"],"sourcesContent":["// Worksheet coordinate utilities. Mirrors openpyxl/openpyxl/utils/cell.py.\n//\n// These functions are on the worksheet read/write hot path (millions of calls\n// when streaming a sheet) so the implementations stay branch-light, regex-based\n// only at the entry points, with bounded Map caches for the bidirectional\n// column letter <-> index mapping.\n\nimport { OpenXmlSchemaError } from './exceptions.js';\n\n/** Maximum column index Excel accepts (XFD). */\nexport const MAX_COL = 16384;\n/** Maximum row index Excel accepts. */\nexport const MAX_ROW = 1048576;\n\n// ---- column letter <-> 1-based index ---------------------------------------\n\nconst indexByLetter = new Map<string, number>();\nconst letterByIndex = new Map<number, string>();\n\n/**\n * 1-based column index → spreadsheet column letter (\"A\", \"Z\", \"AA\", \"XFD\").\n * Throws OpenXmlSchemaError when out of range.\n */\nexport function columnLetterFromIndex(n: number): string {\n const cached = letterByIndex.get(n);\n if (cached !== undefined) return cached;\n if (!Number.isInteger(n) || n < 1 || n > MAX_COL) {\n throw new OpenXmlSchemaError(`column index ${n} is out of range [1, ${MAX_COL}]`);\n }\n let m = n;\n let out = '';\n while (m > 0) {\n m -= 1; // shift to 0-based for the modulo\n out = String.fromCharCode(65 + (m % 26)) + out;\n m = Math.floor(m / 26);\n }\n letterByIndex.set(n, out);\n return out;\n}\n\n/**\n * Column letter → 1-based column index. Case-insensitive but at most 3 letters\n * (the spec ceiling). Throws on empty / non-A-Z / over-range.\n */\nexport function columnIndexFromLetter(letter: string): number {\n const cached = indexByLetter.get(letter);\n if (cached !== undefined) return cached;\n if (letter.length === 0 || letter.length > 3) {\n throw new OpenXmlSchemaError(`column letter \"${letter}\" is empty or too long`);\n }\n let n = 0;\n for (let i = 0; i < letter.length; i++) {\n const c = letter.charCodeAt(i);\n let v: number;\n if (c >= 65 && c <= 90)\n v = c - 64; // 'A' = 65\n else if (c >= 97 && c <= 122)\n v = c - 96; // 'a' = 97\n else throw new OpenXmlSchemaError(`column letter \"${letter}\" contains non-letter char`);\n n = n * 26 + v;\n }\n if (n < 1 || n > MAX_COL) {\n throw new OpenXmlSchemaError(`column letter \"${letter}\" expands to out-of-range index ${n}`);\n }\n // Normalise the cache key to upper-case so 'a' / 'A' share a slot.\n const key = letter.toUpperCase();\n indexByLetter.set(key, n);\n if (key !== letter) indexByLetter.set(letter, n);\n return n;\n}\n\n// ---- coordinate parsing ----------------------------------------------------\n\n/** A single-cell coordinate split into its letter and 1-based row. */\nexport interface CellCoordinate {\n column: string;\n row: number;\n}\n\n/** A single-cell coordinate split into 1-based numeric (col, row). */\nexport interface CellCoordinateNumeric {\n col: number;\n row: number;\n}\n\n/** A 1-based rectangular boundary. */\nexport interface CellRangeBoundaries {\n minCol: number;\n minRow: number;\n maxCol: number;\n maxRow: number;\n}\n\n/**\n * What every range-taking helper accepts: an A1 expression (`\"A4:H20\"`,\n * `\"A:A\"`, `\"1:1\"`) or already-computed 1-based bounds. Code that walks rows\n * and columns as integers passes the bounds directly instead of formatting a\n * string for the callee to parse straight back.\n *\n * Two spellings of one parameter, deliberately, rather than a second function\n * per helper: each capability keeps a single canonical path through the API.\n */\nexport type RangeRef = string | CellRangeBoundaries;\n\nconst COORD_RE = /^[$]?([A-Za-z]{1,3})[$]?([1-9][0-9]*)$/;\nconst COL_RANGE_RE = /^[$]?([A-Za-z]{1,3}):[$]?([A-Za-z]{1,3})$/;\nconst ROW_RANGE_RE = /^[$]?([1-9][0-9]*):[$]?([1-9][0-9]*)$/;\nconst SHEET_RANGE_RE = /^(?:'((?:[^']|'')+)'|([^'!]+))!(.+)$/;\n\n/**\n * Parse a single-cell coordinate string (\"A1\", \"$XFD$1048576\") into its column\n * letter (always uppercased) and 1-based row.\n */\nexport function coordinateFromString(coord: string): CellCoordinate {\n const m = COORD_RE.exec(coord);\n if (m === null || m[1] === undefined || m[2] === undefined) {\n throw new OpenXmlSchemaError(`coordinateFromString: invalid coordinate \"${coord}\"`);\n }\n const column = m[1].toUpperCase();\n const row = Number.parseInt(m[2], 10);\n if (row < 1 || row > MAX_ROW) {\n throw new OpenXmlSchemaError(`coordinateFromString: row ${row} out of range`);\n }\n // Validate column upper bound through the cached helper.\n columnIndexFromLetter(column);\n return { column, row };\n}\n\n/**\n * Same as {@link coordinateFromString} but returning the column as its 1-based\n * numeric index. Thin convenience for the worksheet read path.\n */\nexport function coordinateToTuple(coord: string): CellCoordinateNumeric {\n const c = coordinateFromString(coord);\n return { col: columnIndexFromLetter(c.column), row: c.row };\n}\n\n/** `$` placement for a composed A1 reference. */\nexport interface AbsoluteRefOptions {\n /** Prefix the column letter with `$` so it survives a fill-across. */\n absoluteCol?: boolean;\n /** Prefix the row number with `$` so it survives a fill-down. */\n absoluteRow?: boolean;\n}\n\n/**\n * Compose `\"A1\"` from a 1-based (col, row). Set `absoluteCol` / `absoluteRow`\n * to get the `$` forms formula text needs (`\"$B$5\"`, `\"B$5\"`), so callers\n * tracking integer coordinates never have to concatenate a column letter\n * themselves.\n */\nexport function tupleToCoordinate(col: number, row: number, opts: AbsoluteRefOptions = {}): string {\n if (!Number.isInteger(row) || row < 1 || row > MAX_ROW) {\n throw new OpenXmlSchemaError(`tupleToCoordinate: row ${row} out of range`);\n }\n const colMarker = opts.absoluteCol === true ? '$' : '';\n const rowMarker = opts.absoluteRow === true ? '$' : '';\n return `${colMarker}${columnLetterFromIndex(col)}${rowMarker}${row}`;\n}\n\n/**\n * Predicate: true iff `s` is a valid single-cell A1 coordinate (`\"A1\"`,\n * `\"XFD1048576\"`). Strings with `$` absolute markers, surrounding whitespace,\n * ranges (`A1:B2`), or out-of-bound row / column return false. Useful for\n * sanitising user input before passing to {@link coordinateToTuple} or\n * `setCellByCoord`.\n */\nexport function isValidCellRef(s: unknown): s is string {\n if (typeof s !== 'string') return false;\n const m = /^([A-Za-z]{1,3})([1-9][0-9]*)$/.exec(s);\n if (!m) return false;\n const colStr = m[1];\n const rowStr = m[2];\n if (colStr === undefined || rowStr === undefined) return false;\n const col = columnIndexFromLetterUnchecked(colStr);\n if (col < 1 || col > MAX_COL) return false;\n const row = Number.parseInt(rowStr, 10);\n if (!Number.isFinite(row) || row < 1 || row > MAX_ROW) return false;\n return true;\n}\n\n/**\n * Predicate: true iff `s` is a valid A1-style range expression — single cell,\n * two-corner range, whole column (`A:A`), or whole row (`1:1`). `$` markers,\n * whitespace, and out-of-bound bounds fail. Sanity-check before {@link\n * rangeBoundaries} / `parseRange`.\n */\nexport function isValidRangeRef(s: unknown): s is string {\n if (typeof s !== 'string' || s.length === 0) return false;\n if (isValidCellRef(s)) return true;\n const ab = /^([A-Za-z]{1,3})([1-9][0-9]*):([A-Za-z]{1,3})([1-9][0-9]*)$/.exec(s);\n if (ab) {\n const [, c1, r1, c2, r2] = ab;\n if (!c1 || !r1 || !c2 || !r2) return false;\n const col1 = columnIndexFromLetterUnchecked(c1);\n const col2 = columnIndexFromLetterUnchecked(c2);\n if (col1 < 1 || col1 > MAX_COL || col2 < 1 || col2 > MAX_COL) return false;\n const row1 = Number.parseInt(r1, 10);\n const row2 = Number.parseInt(r2, 10);\n return row1 >= 1 && row1 <= MAX_ROW && row2 >= 1 && row2 <= MAX_ROW;\n }\n const cc = /^([A-Za-z]{1,3}):([A-Za-z]{1,3})$/.exec(s);\n if (cc) {\n const [, c1, c2] = cc;\n if (!c1 || !c2) return false;\n const col1 = columnIndexFromLetterUnchecked(c1);\n const col2 = columnIndexFromLetterUnchecked(c2);\n return col1 >= 1 && col1 <= MAX_COL && col2 >= 1 && col2 <= MAX_COL;\n }\n const rr = /^([1-9][0-9]*):([1-9][0-9]*)$/.exec(s);\n if (rr) {\n const [, r1, r2] = rr;\n if (!r1 || !r2) return false;\n const row1 = Number.parseInt(r1, 10);\n const row2 = Number.parseInt(r2, 10);\n return row1 >= 1 && row1 <= MAX_ROW && row2 >= 1 && row2 <= MAX_ROW;\n }\n return false;\n}\n\nconst columnIndexFromLetterUnchecked = (letters: string): number => {\n let n = 0;\n const upper = letters.toUpperCase();\n for (let i = 0; i < upper.length; i++) {\n const ch = upper.charCodeAt(i);\n if (ch < 65 || ch > 90) return -1;\n n = n * 26 + (ch - 64);\n }\n return n;\n};\n\n/**\n * Predicate: true iff `s` is a valid 1..3-char column letter (`\"A\"` through\n * `\"XFD\"`, case-insensitive). Empty / over-long / out-of-bound / non-string\n * fails.\n */\nexport function isValidColumnLetter(s: unknown): s is string {\n if (typeof s !== 'string' || s.length === 0 || s.length > 3) return false;\n if (!/^[A-Za-z]+$/.test(s)) return false;\n const col = columnIndexFromLetterUnchecked(s);\n return col >= 1 && col <= MAX_COL;\n}\n\n/**\n * Predicate: true iff `n` is a valid 1-based row index in `[1, 1048576]`.\n * Non-finite / non-integer / out-of-bound fails.\n */\nexport function isValidRowNumber(n: unknown): n is number {\n return typeof n === 'number' && Number.isInteger(n) && n >= 1 && n <= MAX_ROW;\n}\n\n/**\n * Predicate: true iff `n` is a valid 1-based column index in `[1, 16384]`.\n * Non-finite / non-integer / out-of-bound fails.\n */\nexport function isValidColumnNumber(n: unknown): n is number {\n return typeof n === 'number' && Number.isInteger(n) && n >= 1 && n <= MAX_COL;\n}\n\n/**\n * Parse \"A1:B5\" / \"A:A\" / \"1:1\" / single-cell into 1-based (minCol, minRow,\n * maxCol, maxRow). Whole-column ranges fill rows to [1, MAX_ROW]; whole-row\n * ranges fill cols to [1, MAX_COL].\n */\nexport function rangeBoundaries(range: string): CellRangeBoundaries {\n const trimmed = range.trim();\n if (trimmed.length === 0) throw new OpenXmlSchemaError('rangeBoundaries: empty range');\n\n const colon = trimmed.indexOf(':');\n if (colon < 0) {\n // Single-cell shorthand: \"A1\" → A1:A1.\n const c = coordinateFromString(trimmed);\n const col = columnIndexFromLetter(c.column);\n return { minCol: col, minRow: c.row, maxCol: col, maxRow: c.row };\n }\n\n const left = trimmed.slice(0, colon);\n const right = trimmed.slice(colon + 1);\n\n const colsOnly = COL_RANGE_RE.exec(trimmed);\n if (colsOnly !== null && colsOnly[1] !== undefined && colsOnly[2] !== undefined) {\n const minCol = columnIndexFromLetter(colsOnly[1]);\n const maxCol = columnIndexFromLetter(colsOnly[2]);\n return {\n minCol: Math.min(minCol, maxCol),\n minRow: 1,\n maxCol: Math.max(minCol, maxCol),\n maxRow: MAX_ROW,\n };\n }\n\n const rowsOnly = ROW_RANGE_RE.exec(trimmed);\n if (rowsOnly !== null && rowsOnly[1] !== undefined && rowsOnly[2] !== undefined) {\n const minRow = Number.parseInt(rowsOnly[1], 10);\n const maxRow = Number.parseInt(rowsOnly[2], 10);\n if (minRow < 1 || maxRow < 1 || minRow > MAX_ROW || maxRow > MAX_ROW) {\n throw new OpenXmlSchemaError(`rangeBoundaries: row out of range in \"${trimmed}\"`);\n }\n return {\n minCol: 1,\n minRow: Math.min(minRow, maxRow),\n maxCol: MAX_COL,\n maxRow: Math.max(minRow, maxRow),\n };\n }\n\n const a = coordinateFromString(left);\n const b = coordinateFromString(right);\n const ac = columnIndexFromLetter(a.column);\n const bc = columnIndexFromLetter(b.column);\n return {\n minCol: Math.min(ac, bc),\n minRow: Math.min(a.row, b.row),\n maxCol: Math.max(ac, bc),\n maxRow: Math.max(a.row, b.row),\n };\n}\n\n/**\n * Inverse of {@link rangeBoundaries} for the rectangular case. `opts` forwards\n * to {@link tupleToCoordinate}, so `{ absoluteCol: true, absoluteRow: true }`\n * yields the `\"$A$4:$H$20\"` form a formula or defined name wants.\n */\nexport function boundariesToRangeString(b: CellRangeBoundaries, opts: AbsoluteRefOptions = {}): string {\n const tl = tupleToCoordinate(b.minCol, b.minRow, opts);\n if (b.minCol === b.maxCol && b.minRow === b.maxRow) return tl;\n const br = tupleToCoordinate(b.maxCol, b.maxRow, opts);\n return `${tl}:${br}`;\n}\n\n/**\n * Parse a sheet-qualified range (\"Sheet1!A1:B5\" / \"'Quarter 1'!A1\"). Sheet\n * names with single quotes inside use SQL-style doubling (\"'Bob''s Sheet'!A1\")\n * — we unescape on the way out.\n */\nexport function parseSheetRange(input: string): {\n sheet: string;\n range: string;\n bounds: CellRangeBoundaries;\n} {\n const m = SHEET_RANGE_RE.exec(input);\n if (m === null) throw new OpenXmlSchemaError(`parseSheetRange: missing \"!\" delimiter in \"${input}\"`);\n const quoted = m[1];\n const bare = m[2];\n const range = m[3];\n if (range === undefined) throw new OpenXmlSchemaError(`parseSheetRange: empty range part in \"${input}\"`);\n const sheet = quoted !== undefined ? quoted.replace(/''/g, \"'\") : (bare ?? '');\n return { sheet, range, bounds: rangeBoundaries(range) };\n}\n\nconst BARE_SHEET_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Inverse of {@link parseSheetRange}: format a sheet title + range (or\n * single-cell ref) as `Sheet1!A1` or `'Quarter 1'!A1` per Excel's\n * sheet-qualified syntax. Single quotes inside the title are escaped by\n * doubling (`'Bob''s Sheet'`).\n *\n * Quoting rule: sheet titles consisting only of `[A-Za-z_][A-Za-z0-9_]*` are\n * emitted bare; everything else (spaces, digits-leading, hyphens, apostrophes,\n * punctuation…) gets wrapped in single quotes.\n */\nexport function formatSheetQualifiedRef(sheet: string, ref: string): string {\n if (BARE_SHEET_NAME.test(sheet)) return `${sheet}!${ref}`;\n return `'${sheet.replace(/'/g, \"''\")}'!${ref}`;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"escape-rnWSPpWY.mjs","names":[],"sources":["../src/utils/escape.ts"],"sourcesContent":["// XML 1.0 cell-string escaping. Mirrors openpyxl/openpyxl/utils/escape.py.\n//\n// Excel emits cell strings with control characters and other illegal\n// XML 1.0 codepoints encoded as `_xHHHH_` (uppercase hex). The\n// underscore itself is a literal in normal text but a sequence opener\n// in escape position; an existing `_xHHHH_` in the input is therefore\n// re-escaped to `_x005F_xHHHH_` so it round-trips losslessly.\n\n// Escape every C0 control character (U+0000 through U+001F) plus the\n// surrogate range U+D800–U+DFFF. NUL is invalid in XML 1.0 entirely.\n// This range deliberately covers `\\t` (U+0009), `\\n` (U+000A) and `\\r`\n// (U+000D) even though XML 1.0 considers them legal whitespace — XML\n// parsers normalise CRLF / lone CR to LF on read, so a cell string\n// containing `\\r` would silently lose its CR without the `_x000D_`\n// encoding. openpyxl escapes the same `\\x01-\\x19` range; we add NUL\n// to keep the writer well-formed when callers feed in binary data.\nconst ILLEGAL_RE =\n // biome-ignore lint/suspicious/noControlCharactersInRegex: by design — these are the codepoints we replace\n /[\\x00-\\x1F\\ud800-\\udfff]/g;\nconst ESCAPED_PATTERN_RE = /(_)(x[0-9A-Fa-f]{4}_)/g;\n\nconst toHex4 = (n: number): string => n.toString(16).toUpperCase().padStart(4, '0');\n\n/**\n * Escape a string for safe storage in an OOXML cell. Already-escaped\n * sequences (`_xHHHH_`) are protected by escaping their leading\n * underscore; illegal codepoints are replaced with their `_xHHHH_`\n * representation.\n */\nexport function escapeCellString(s: string): string {\n // Re-escape any existing `_xHHHH_` so it round-trips; the underscore\n // becomes `_x005F_` and the rest of the sequence is left as-is.\n const protectedString = s.replace(ESCAPED_PATTERN_RE, '_x005F_$2');\n return protectedString.replace(ILLEGAL_RE, (ch) => `_x${toHex4(ch.charCodeAt(0))}_`);\n}\n\nconst UNESCAPE_RE = /_x([0-9A-Fa-f]{4})_/g;\n\n/**\n * Inverse of {@link escapeCellString}. Looking from left to right\n * we replace any `_xHHHH_` sequence with the corresponding code unit;\n * the protected `_x005F_` becomes a literal underscore which the\n * subsequent replacements skip safely (replace's regex is non-overlapping).\n */\nexport function unescapeCellString(s: string): string {\n return s.replace(UNESCAPE_RE, (_full, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)));\n}\n\n// ---- XML escape helpers ----------------------------------------------------\n//\n// One canonical implementation for text-node and attribute escaping. The\n// previous codebase carried three near-identical copies (one each in save.ts,\n// xml/serializer.ts, xml/stream-writer.ts); they disagreed about `>`-in-attribute\n// handling and would have drifted further apart over time. Keeping them in a\n// single place also makes future fixes (e.g. surrogate-pair scrubbing) land\n// once rather than three times.\n\n/**\n * Escape a string for safe placement in an XML text node. Replaces the three\n * codepoints that would otherwise terminate the text region or open a markup\n * sequence (`&`, `<`, `>`); `\"` and `'` are not legal markup terminators\n * inside text and stay verbatim.\n */\nexport function escapeXmlText(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\n/**\n * Escape a string for safe placement inside a `\"`-quoted XML attribute.\n * Handles `&`, `<`, `>` and the `\"` that would otherwise close the value.\n *\n * Note: this deliberately does NOT escape `\\r` / `\\n` / `\\t` to numeric\n * character references. XML 1.0 attribute-value normalisation would\n * collapses them to spaces, but the DOM read path (fast-xml-parser) does not\n * apply that normalisation, so the literal bytes round-trip through\n * `loadWorkbook`. Leaving them literal also matches what Excel itself emits.\n */\nexport function escapeXmlAttr(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n"],"mappings":";AAgBA,MAAM,aAEJ;AACF,MAAM,qBAAqB;AAE3B,MAAM,UAAU,MAAsB,EAAE,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,GAAG,GAAG;;;;;;;AAQlF,SAAgB,iBAAiB,GAAmB;CAIlD,OADwB,EAAE,QAAQ,oBAAoB,WACjC,CAAC,CAAC,QAAQ,aAAa,OAAO,KAAK,OAAO,GAAG,WAAW,CAAC,CAAC,EAAE,EAAE;AACrF;AAEA,MAAM,cAAc;;;;;;;AAQpB,SAAgB,mBAAmB,GAAmB;CACpD,OAAO,EAAE,QAAQ,cAAc,OAAO,QAAgB,OAAO,aAAa,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;AACrG;;;;;;;AAiBA,SAAgB,cAAc,GAAmB;CAC/C,OAAO,EAAE,QAAQ,MAAM,OAAO,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM;AAC5E;;;;;;;;;;;AAYA,SAAgB,cAAc,GAAmB;CAC/C,OAAO,EACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,QAAQ;AAC3B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reader-DHMxLBQV.mjs","names":[],"sources":["../src/zip/decompression-guard.ts","../src/zip/inflate-cache.ts","../src/zip/random-access-reader.ts","../src/zip/reader.ts"],"sourcesContent":["// Decompression-bomb safeguards for the ZIP reader.\n//\n// Zip-bombs encode pathological compression ratios (e.g. 100 B → 1 GB) so that\n// a naive `inflate` exhausts memory before the caller can react. The guards\n// here apply three orthogonal bounds:\n//\n// 1. Per-entry uncompressed cap — hard ceiling on a single payload.\n// 2. Total-archive uncompressed cap — hard ceiling summed across the archive.\n// 3. Per-entry compression ratio — bounds the *amplification factor* so that\n// even small compressed entries can't decompress into hundreds of MB.\n//\n// Defaults are sized to admit any legitimate xlsx (xml compresses well but\n// rarely exceeds the per-entry cap; office templates with embedded media stay\n// well under the total cap) while rejecting plausible bombs. Trusted callers\n// (e.g. a backend that only ever loads xlsx it generated itself) can pass\n// `false` to disable the guard.\n\nimport { OpenXmlDecompressionBombError, OpenXmlError } from '../utils/exceptions.js';\n\n/** Per-archive limits enforced during {@link openZip}. */\nexport interface DecompressionLimits {\n /**\n * Maximum decompressed bytes for a single archive entry. Default 512 MiB.\n * Legitimate xlsx sheets stay well below this even with millions of cells.\n */\n maxEntryUncompressedBytes?: number;\n /**\n * Maximum decompressed bytes summed across every entry the caller reads.\n * Default 1 GiB.\n */\n maxTotalUncompressedBytes?: number;\n /**\n * Maximum allowed `uncompressed / compressed` ratio for a single entry.\n * Default 1000. xml usually compresses 5–20×, but highly repetitive payloads\n * (long runs of zeros, sparse worksheets) can hit several hundred ×\n * legitimately. Classic zip-bombs run 10 000× and up, so a 1000× ceiling\n * still catches them while admitting realistic content. The implementation\n * treats compressed sizes below 64 B as exempt — there's no amplification\n * budget worth policing on such small entries, and the absolute per-entry /\n * archive limits already cover them.\n */\n maxCompressionRatio?: number;\n}\n\n/** Resolved limits with no `undefined` fields. */\nexport interface ResolvedDecompressionLimits {\n readonly maxEntryUncompressedBytes: number;\n readonly maxTotalUncompressedBytes: number;\n readonly maxCompressionRatio: number;\n}\n\n/** Default safeguards applied when the caller doesn't override them. */\nexport const DEFAULT_DECOMPRESSION_LIMITS: ResolvedDecompressionLimits = {\n maxEntryUncompressedBytes: 512 * 1024 * 1024,\n maxTotalUncompressedBytes: 1024 * 1024 * 1024,\n maxCompressionRatio: 1000,\n};\n\n/** Below this compressed size, the ratio check is skipped — see {@link DecompressionLimits}. */\nconst RATIO_CHECK_MIN_COMPRESSED_BYTES = 64;\n\n/**\n * `DecompressionLimits` plus the sentinel `false` to disable the guard.\n * Anything else is treated as \"use defaults\".\n */\nexport type DecompressionLimitsInput = DecompressionLimits | false | undefined;\n\n// Each limit must be a positive finite number to be meaningful; NaN / Infinity\n// / 0 / negatives silently turn `emitted > cap` and `totalInflated > cap` into\n// no-ops and disable the guard. Reject those at the boundary so the only way\n// the guard is off is the explicit `false` sentinel.\nconst requirePositiveFinite = (field: string, value: number): void => {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n throw new OpenXmlError(\n `resolveDecompressionLimits: ${field} must be a positive finite number; got ${String(value)}`,\n );\n }\n};\n\n/** Returns null when the guard is disabled. */\nexport function resolveDecompressionLimits(\n input: DecompressionLimitsInput,\n): ResolvedDecompressionLimits | null {\n if (input === false) return null;\n if (!input) return DEFAULT_DECOMPRESSION_LIMITS;\n const resolved: ResolvedDecompressionLimits = {\n maxEntryUncompressedBytes:\n input.maxEntryUncompressedBytes ?? DEFAULT_DECOMPRESSION_LIMITS.maxEntryUncompressedBytes,\n maxTotalUncompressedBytes:\n input.maxTotalUncompressedBytes ?? DEFAULT_DECOMPRESSION_LIMITS.maxTotalUncompressedBytes,\n maxCompressionRatio:\n input.maxCompressionRatio ?? DEFAULT_DECOMPRESSION_LIMITS.maxCompressionRatio,\n };\n requirePositiveFinite('maxEntryUncompressedBytes', resolved.maxEntryUncompressedBytes);\n requirePositiveFinite('maxTotalUncompressedBytes', resolved.maxTotalUncompressedBytes);\n requirePositiveFinite('maxCompressionRatio', resolved.maxCompressionRatio);\n return resolved;\n}\n\n/**\n * Per-archive byte accounting shared across every read of an archive opened\n * with the given limits. Tracks total inflated bytes so the cap is enforced\n * even when individual entries stay below the per-entry ceiling.\n */\nexport interface DecompressionBudget {\n readonly limits: ResolvedDecompressionLimits;\n totalInflated: number;\n /**\n * Largest number of bytes any single inflate of an entry has charged to the\n * archive total, keyed by path. {@link beginEntryInflate} keeps\n * {@link DecompressionBudget.totalInflated} equal to the sum of these.\n */\n readonly chargedByPath: Map<string, number>;\n}\n\nexport function createBudget(limits: ResolvedDecompressionLimits): DecompressionBudget {\n return { limits, totalInflated: 0, chargedByPath: new Map() };\n}\n\n/**\n * Start inflating `path`, returning the function every chunk of it must be\n * recorded through. The recorder throws {@link OpenXmlDecompressionBombError}\n * once the archive total is exceeded, naming `path`.\n *\n * The total bounds how much distinct payload an archive expands to, which is\n * what `checkDeclaredTotals` enforces from the central directory by counting\n * each entry once. An entry can be inflated more than once though: a\n * `readStream` and a `read` of the same part, or a re-read of one the inflate\n * cache turned away. So each path is charged its high-water mark rather than\n * the sum of its reads, and a second inflate only adds what it pushes that mark\n * past. Reads that overlap in any interleaving stay correct for the same\n * reason. The per-entry cap is independent of this and is re-evaluated from\n * scratch on every read.\n */\nexport function beginEntryInflate(\n budget: DecompressionBudget,\n path: string,\n): (bytes: number) => void {\n let inflated = 0;\n return (bytes: number): void => {\n inflated += bytes;\n const charged = budget.chargedByPath.get(path) ?? 0;\n if (inflated > charged) {\n budget.totalInflated += inflated - charged;\n budget.chargedByPath.set(path, inflated);\n }\n // A failed inflate also advances the high-water mark. Check the total\n // even when this read adds nothing, or retrying a rejected entry bypasses\n // the archive limit using the charge left by the first attempt.\n if (budget.totalInflated > budget.limits.maxTotalUncompressedBytes) {\n throw new OpenXmlDecompressionBombError(\n `openZip: archive-wide inflated size exceeded ${budget.limits.maxTotalUncompressedBytes} bytes` +\n ` while reading \"${path}\" (decompression-bomb guard).`,\n );\n }\n };\n}\n\n/**\n * Per-entry inflate cap, accounting for both the absolute per-entry bound and\n * the ratio-based bound. Returns the smaller of the two — whichever fires\n * first stops the inflate loop.\n */\nexport function entryInflateCap(budget: DecompressionBudget, compressedSize: number): number {\n const { maxEntryUncompressedBytes, maxCompressionRatio } = budget.limits;\n if (compressedSize < RATIO_CHECK_MIN_COMPRESSED_BYTES) {\n return maxEntryUncompressedBytes;\n }\n const ratioCap = compressedSize * maxCompressionRatio;\n return Math.min(maxEntryUncompressedBytes, ratioCap);\n}\n\n/** Throw if the declared central-directory totals already exceed the limits. */\nexport function checkDeclaredTotals(\n budget: DecompressionBudget,\n declaredEntries: ReadonlyArray<{ path: string; compSize: number; uncompSize: number }>,\n): void {\n let declaredTotal = 0;\n for (const entry of declaredEntries) {\n declaredTotal += entry.uncompSize;\n if (entry.uncompSize > budget.limits.maxEntryUncompressedBytes) {\n throw new OpenXmlDecompressionBombError(\n `openZip: entry \"${entry.path}\" declares ${entry.uncompSize} uncompressed bytes,` +\n ` exceeding the ${budget.limits.maxEntryUncompressedBytes}-byte per-entry limit` +\n ` (decompression-bomb guard).`,\n );\n }\n if (\n entry.compSize >= RATIO_CHECK_MIN_COMPRESSED_BYTES &&\n entry.uncompSize > entry.compSize * budget.limits.maxCompressionRatio\n ) {\n throw new OpenXmlDecompressionBombError(\n `openZip: entry \"${entry.path}\" declares ratio ${(entry.uncompSize / entry.compSize).toFixed(1)}x` +\n ` (${entry.uncompSize}/${entry.compSize}), exceeding the ${budget.limits.maxCompressionRatio}x` +\n ` per-entry limit (decompression-bomb guard).`,\n );\n }\n }\n if (declaredTotal > budget.limits.maxTotalUncompressedBytes) {\n throw new OpenXmlDecompressionBombError(\n `openZip: declared total uncompressed size ${declaredTotal} bytes exceeds the` +\n ` ${budget.limits.maxTotalUncompressedBytes}-byte archive limit (decompression-bomb guard).`,\n );\n }\n}\n\n/** Build the message thrown when a single entry exceeds its cap mid-inflate. */\nexport function entryOverflowError(path: string, cap: number): OpenXmlDecompressionBombError {\n return new OpenXmlDecompressionBombError(\n `openZip: inflated size of \"${path}\" exceeded ${cap} bytes (decompression-bomb guard).`,\n );\n}\n","// Bounded cache of inflated ZIP entries.\n//\n// A load comes back to a handful of parts more than once (the `.rels` files the\n// relationship walk revisits, above all), and inflating those again is waste.\n// Keeping every entry is worse than that waste: it puts the whole uncompressed\n// package back in memory, which is the cost the random-access reader exists to\n// avoid. So the cache takes small entries only, holds a few MB of them at most,\n// and drops the least recently used first. Anything it turns away or evicts\n// inflates again on the next read.\n\n/**\n * Largest entry the cache accepts. Sized for the parts a load revisits, which\n * are small; a sheet or a media blob is normally read once, so keeping one buys\n * nothing and costs its full inflated size for the life of the archive.\n */\nexport const CACHE_MAX_ENTRY_BYTES = 64 * 1024;\n\n/**\n * Ceiling on everything the cache holds, so an archive with thousands of small\n * parts cannot accumulate without limit.\n */\nexport const CACHE_MAX_TOTAL_BYTES = 4 * 1024 * 1024;\n\nexport interface InflateCache {\n /**\n * Cached bytes for `path`, or `undefined`. The caller owns the array it gets\n * back and may mutate it. A hit counts as a use for eviction order.\n */\n get(path: string): Uint8Array | undefined;\n /** Offer `bytes` for later re-reads. Entries above the size ceiling are ignored. */\n set(path: string, bytes: Uint8Array): void;\n clear(): void;\n}\n\nexport function createInflateCache(): InflateCache {\n // Insertion-ordered, so the first key `keys()` yields is the least recently\n // used for as long as every hit re-inserts.\n const held = new Map<string, Uint8Array>();\n let total = 0;\n\n return {\n get(path) {\n const hit = held.get(path);\n if (!hit) return undefined;\n // `set` on an existing key leaves it where it was, so a hit has to delete\n // first. Without this the entries being re-read are the first evicted.\n held.delete(path);\n held.set(path, hit);\n return hit.slice();\n },\n set(path, bytes) {\n if (bytes.byteLength > CACHE_MAX_ENTRY_BYTES) return;\n total += bytes.byteLength - (held.get(path)?.byteLength ?? 0);\n // Hold a copy, so the array the caller was handed stays theirs to mutate.\n held.set(path, bytes.slice());\n for (const [lru, entry] of held) {\n if (total <= CACHE_MAX_TOTAL_BYTES) break;\n held.delete(lru);\n total -= entry.byteLength;\n }\n },\n clear() {\n held.clear();\n total = 0;\n },\n };\n}\n","// Random-access ZIP reader.\n//\n// Handing every entry to `fflate.unzipSync` materialises every uncompressed\n// payload at once, so a 100 MB xlsx with 500 MB of decompressed sheet data\n// spikes the resident set accordingly. This reader keeps only the compressed\n// archive bytes resident, parses the central directory once (cheap, about 46 B\n// per entry plus the filename), and inflates each entry lazily on `read(path)`.\n//\n// Small inflated payloads are kept for re-reads (see ./inflate-cache.ts).\n// Keeping every one of them would put the whole uncompressed package back in\n// memory, which is the cost this reader exists to avoid, so that cache is\n// bounded in both entry size and total and anything outside those bounds\n// inflates again on the next read.\n//\n// Limitations:\n// - ZIP64 reads only when the standard ZIP32 fields fit. EOCD with\n// sentinel values (0xFFFF / 0xFFFFFFFF) falls back to fflate's `unzipSync` so\n// external ZIP64 archives still load (the writer side has its own ZIP32 cap\n// guard).\n// - Compression methods: STORE (0) and DEFLATE (8). Anything else\n// throws OpenXmlIoError.\n\nimport { Inflate, unzipSync } from 'fflate';\nimport { OpenXmlDecompressionBombError, OpenXmlIoError } from '../utils/exceptions.js';\nimport {\n beginEntryInflate,\n checkDeclaredTotals,\n createBudget,\n type DecompressionBudget,\n type DecompressionLimitsInput,\n entryInflateCap,\n entryOverflowError,\n resolveDecompressionLimits,\n} from './decompression-guard.js';\nimport { createInflateCache } from './inflate-cache.js';\nimport type { ZipArchive } from './reader.js';\n\n/**\n * Chunk size used when feeding compressed bytes into fflate's `Inflate` for\n * streaming reads. 64 KB matches the saxes/SAX consumer's typical batch and\n * keeps peak transient memory bounded.\n */\nconst INFLATE_CHUNK_BYTES = 64 * 1024;\n\n/**\n * `decode()` without `{ stream: true }` resets the decoder's state on every\n * call, so one instance is safe to share across entries and across archives.\n */\nconst CD_NAME_DECODER = new TextDecoder('utf-8');\n\nconst singleChunkStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n new ReadableStream<Uint8Array>({\n start(controller) {\n if (bytes.byteLength > 0) controller.enqueue(bytes);\n controller.close();\n },\n });\n\nconst SIG_EOCD = 0x06054b50;\nconst SIG_CD = 0x02014b50;\nconst SIG_LFH = 0x04034b50;\nconst SIG_ZIP64_EOCD = 0x06064b50;\nconst SIG_ZIP64_EOCD_LOCATOR = 0x07064b50;\nconst ZIP32_MAX_U16 = 0xffff;\nconst ZIP32_MAX_U32 = 0xffffffff;\nconst ZIP64_LOCATOR_SIZE = 20;\nconst ZIP64_EXTRA_HEADER_ID = 0x0001;\nconst COMP_STORE = 0;\nconst COMP_DEFLATE = 8;\n\ninterface CdEntry {\n path: string;\n lfhOffset: number;\n compMethod: number;\n compSize: number;\n uncompSize: number;\n /** General-purpose bit flag, used to detect bit-3 (data descriptor) and bit-11 (UTF-8). */\n gpFlag: number;\n}\n\nconst u16 = (b: Uint8Array, off: number): number => (b[off] ?? 0) | ((b[off + 1] ?? 0) << 8);\nconst u32 = (b: Uint8Array, off: number): number => {\n const v0 = b[off] ?? 0;\n const v1 = b[off + 1] ?? 0;\n const v2 = b[off + 2] ?? 0;\n const v3 = b[off + 3] ?? 0;\n return (v0 | (v1 << 8) | (v2 << 16) | (v3 << 24)) >>> 0;\n};\n\n// Read a little-endian 64-bit value as a JS Number. Safe for values up to\n// 2^53-1 (Number.MAX_SAFE_INTEGER ~ 9 PiB), which is well past any realistic\n// xlsx archive — we throw if a parsed value exceeds the safe-integer range.\nconst u64 = (b: Uint8Array, off: number): number => {\n const lo = u32(b, off);\n const hi = u32(b, off + 4);\n if (hi > 0x1fffff) {\n throw new OpenXmlIoError(\n `openZip: ZIP64 field at byte ${off} exceeds the safe-integer range (${hi}*2^32 + ${lo})`,\n );\n }\n return hi * 0x100000000 + lo;\n};\n\n/** Find the End-of-Central-Directory record by scanning backwards from EOF. */\nfunction findEocd(b: Uint8Array): number {\n const minStart = Math.max(0, b.length - 22 - 0xffff);\n for (let i = b.length - 22; i >= minStart; i--) {\n if (u32(b, i) === SIG_EOCD) return i;\n }\n throw new OpenXmlIoError('openZip: no End-of-Central-Directory signature found');\n}\n\ninterface CdSummary {\n totalEntries: number;\n cdSize: number;\n cdOffset: number;\n}\n\n/**\n * Resolve the central-directory totals. When the EOCD carries ZIP64 sentinel\n * values (0xFFFF / 0xFFFFFFFF) the real totals live in a ZIP64 EOCD record\n * located via the ZIP64 EOCD locator just before the regular EOCD. ECMA-376\n * xlsx archives stay in ZIP32 territory; the ZIP64 path exists so the\n * decompression-bomb guards still apply to large external archives instead of\n * falling all the way back to `unzipSync` (which produces every entry's bytes\n * before any cap can fire).\n */\nfunction readCdSummary(b: Uint8Array, eocdOff: number): CdSummary {\n let totalEntries = u16(b, eocdOff + 10);\n let cdSize = u32(b, eocdOff + 12);\n let cdOffset = u32(b, eocdOff + 16);\n\n const usesZip64Eocd =\n totalEntries === ZIP32_MAX_U16 || cdSize === ZIP32_MAX_U32 || cdOffset === ZIP32_MAX_U32;\n if (!usesZip64Eocd) {\n return { totalEntries, cdSize, cdOffset };\n }\n\n // Locate the ZIP64 EOCD locator. It sits immediately before the regular\n // EOCD when one is present.\n const locatorOff = eocdOff - ZIP64_LOCATOR_SIZE;\n if (locatorOff < 0 || u32(b, locatorOff) !== SIG_ZIP64_EOCD_LOCATOR) {\n throw new OpenXmlIoError('openZip: ZIP64 EOCD locator missing despite EOCD sentinel values');\n }\n const zip64EocdOff = u64(b, locatorOff + 8);\n if (zip64EocdOff < 0 || zip64EocdOff + 56 > b.length) {\n throw new OpenXmlIoError(`openZip: ZIP64 EOCD offset ${zip64EocdOff} out of bounds`);\n }\n if (u32(b, zip64EocdOff) !== SIG_ZIP64_EOCD) {\n throw new OpenXmlIoError(`openZip: ZIP64 EOCD signature missing at byte ${zip64EocdOff}`);\n }\n totalEntries = u64(b, zip64EocdOff + 32);\n cdSize = u64(b, zip64EocdOff + 40);\n cdOffset = u64(b, zip64EocdOff + 48);\n return { totalEntries, cdSize, cdOffset };\n}\n\n/** Walk the ZIP64 Extended Information extra field for sentinel-valued sizes/offset. */\nfunction readZip64Extra(\n b: Uint8Array,\n extraStart: number,\n extraLen: number,\n wantsUncompSize: boolean,\n wantsCompSize: boolean,\n wantsLfhOffset: boolean,\n entryPath: string,\n): { uncompSize?: number; compSize?: number; lfhOffset?: number } {\n let p = extraStart;\n const end = extraStart + extraLen;\n while (p + 4 <= end) {\n const id = u16(b, p);\n const size = u16(b, p + 2);\n const dataStart = p + 4;\n const next = dataStart + size;\n if (next > end) break;\n if (id === ZIP64_EXTRA_HEADER_ID) {\n // Sanity-check that the declared extra-field size actually covers every\n // u64 the CD asked us to read. A truncated / mis-sized ZIP64 extra\n // field would otherwise let u64() interpret bytes belonging to the\n // next extra record (or the next CD entry) as a size/offset, producing\n // bogus values that downstream bomb checks then trust. Fail closed.\n const needed = (wantsUncompSize ? 8 : 0) + (wantsCompSize ? 8 : 0) + (wantsLfhOffset ? 8 : 0);\n if (size < needed) {\n throw new OpenXmlIoError(\n `openZip: ZIP64 extended-info field for \"${entryPath}\" declares ${size} bytes` +\n ` but the central directory sentinels require ${needed}`,\n );\n }\n let q = dataStart;\n const result: { uncompSize?: number; compSize?: number; lfhOffset?: number } = {};\n if (wantsUncompSize) {\n result.uncompSize = u64(b, q);\n q += 8;\n }\n if (wantsCompSize) {\n result.compSize = u64(b, q);\n q += 8;\n }\n if (wantsLfhOffset) {\n result.lfhOffset = u64(b, q);\n q += 8;\n }\n return result;\n }\n p = next;\n }\n return {};\n}\n\n/** Parse the central directory into an array of entry descriptors. */\nfunction parseCentralDirectory(b: Uint8Array, cdOffset: number, expectedCount: number): CdEntry[] {\n const entries: CdEntry[] = [];\n let p = cdOffset;\n for (let i = 0; i < expectedCount; i++) {\n if (u32(b, p) !== SIG_CD) {\n throw new OpenXmlIoError(`openZip: malformed central directory at byte ${p}`);\n }\n const gpFlag = u16(b, p + 8);\n const compMethod = u16(b, p + 10);\n let compSize = u32(b, p + 20);\n let uncompSize = u32(b, p + 24);\n const nameLen = u16(b, p + 28);\n const extraLen = u16(b, p + 30);\n const commentLen = u16(b, p + 32);\n let lfhOffset = u32(b, p + 42);\n const nameBytes = b.subarray(p + 46, p + 46 + nameLen);\n // Bit 11 (0x0800) signals UTF-8 filename. xlsx archives are almost always\n // UTF-8 already; treat bit-0 as UTF-8 too since CP437 ⊃ ASCII and xlsx uses\n // ASCII paths.\n const path = CD_NAME_DECODER.decode(nameBytes);\n\n // ZIP64 Extended Information rewrites whichever of {uncompSize, compSize,\n // lfhOffset} are 0xFFFFFFFF sentinels in the canonical fields. The extra\n // field stores them in the order listed in the spec (and omits any that\n // weren't sentinels), so we have to track which slots to read.\n const wantsUncomp = uncompSize === ZIP32_MAX_U32;\n const wantsComp = compSize === ZIP32_MAX_U32;\n const wantsOffset = lfhOffset === ZIP32_MAX_U32;\n if (wantsUncomp || wantsComp || wantsOffset) {\n const extra = readZip64Extra(b, p + 46 + nameLen, extraLen, wantsUncomp, wantsComp, wantsOffset, path);\n if (extra.uncompSize !== undefined) uncompSize = extra.uncompSize;\n if (extra.compSize !== undefined) compSize = extra.compSize;\n if (extra.lfhOffset !== undefined) lfhOffset = extra.lfhOffset;\n }\n entries.push({ path, lfhOffset, compMethod, compSize, uncompSize, gpFlag });\n p += 46 + nameLen + extraLen + commentLen;\n }\n return entries;\n}\n\n/** Read the compressed bytes for a CD entry by walking its local file header. */\nfunction readCompressedBytes(b: Uint8Array, entry: CdEntry): Uint8Array {\n if (u32(b, entry.lfhOffset) !== SIG_LFH) {\n throw new OpenXmlIoError(`openZip: malformed local file header for \"${entry.path}\"`);\n }\n const nameLen = u16(b, entry.lfhOffset + 26);\n const extraLen = u16(b, entry.lfhOffset + 28);\n const dataStart = entry.lfhOffset + 30 + nameLen + extraLen;\n return b.subarray(dataStart, dataStart + entry.compSize);\n}\n\n/**\n * Open a buffered xlsx archive in random-access mode. The archive bytes stay\n * resident; entries inflate on demand inside `read(path)`.\n *\n * Falls back to `fflate.unzipSync` when the central directory uses ZIP64\n * sentinel values (entry count == 0xFFFF or any size field == 0xFFFFFFFF) so\n * external ZIP64 archives still load. xlsx files in the wild fit comfortably in\n * ZIP32; the fallback exists for safety.\n *\n * `decompressionLimits` opts the archive into the zip-bomb safeguards\n * documented on {@link DecompressionLimits}; pass `false` to disable. Defaults\n * fit any legitimate xlsx.\n */\nexport function openRandomAccessArchive(\n bytes: Uint8Array,\n decompressionLimits?: DecompressionLimitsInput,\n): ZipArchive {\n // Quick sanity on min archive size.\n if (bytes.length < 22) {\n throw new OpenXmlIoError('openZip: archive is shorter than the minimum EOCD size (22 bytes)');\n }\n\n let eocdOff: number;\n try {\n eocdOff = findEocd(bytes);\n } catch (cause) {\n throw new OpenXmlIoError('openZip: archive is not a valid zip', { cause });\n }\n\n const resolvedLimits = resolveDecompressionLimits(decompressionLimits);\n\n // Detect ZIP64 up front so a malformed ZIP64 record fails closed instead of\n // falling back to `unzipSync`. The fallback inflates every entry before any\n // bomb cap runs; the whole point of the ZIP64 path is to keep the streaming\n // caps in play, so we must not silently downgrade when ZIP64 is in use.\n const eocdTotalEntries = u16(bytes, eocdOff + 10);\n const eocdCdSize = u32(bytes, eocdOff + 12);\n const eocdCdOffset = u32(bytes, eocdOff + 16);\n const claimsZip64 =\n eocdTotalEntries === ZIP32_MAX_U16 ||\n eocdCdSize === ZIP32_MAX_U32 ||\n eocdCdOffset === ZIP32_MAX_U32;\n\n let summary: CdSummary;\n try {\n summary = readCdSummary(bytes, eocdOff);\n } catch (cause) {\n if (claimsZip64) {\n // ZIP64 sentinels are set but the matching record / locator is missing\n // or invalid. Fail closed — `unzipSync` would inflate every entry.\n throw cause instanceof OpenXmlIoError\n ? cause\n : new OpenXmlIoError('openZip: ZIP64 central directory is malformed', { cause });\n }\n // Plain ZIP32 with an unparseable EOCD — fflate's `unzipSync` is more\n // tolerant of quirky archives; the post-hoc bomb check still applies.\n return openViaUnzipSync(bytes, resolvedLimits);\n }\n\n let entries: CdEntry[];\n try {\n entries = parseCentralDirectory(bytes, summary.cdOffset, summary.totalEntries);\n } catch (cause) {\n if (claimsZip64) {\n throw cause instanceof OpenXmlIoError\n ? cause\n : new OpenXmlIoError('openZip: ZIP64 central directory is malformed', { cause });\n }\n // Malformed ZIP32 CD — fall back to fflate which is more tolerant.\n return openViaUnzipSync(bytes, resolvedLimits);\n }\n\n const byPath = new Map<string, CdEntry>();\n for (const e of entries) byPath.set(e.path, e);\n\n const budget: DecompressionBudget | null = resolvedLimits ? createBudget(resolvedLimits) : null;\n if (budget) {\n // Declared CD totals are cheap to inspect — reject obvious bombs before\n // wiring up the inflate state machine.\n checkDeclaredTotals(budget, entries);\n }\n\n // Re-read cache for small entries, so the `.rels` parts the passthrough walk\n // revisits do not inflate twice.\n const inflateCache = createInflateCache();\n let live = true;\n let archiveBytes: Uint8Array | undefined = bytes;\n\n const ensureLive = (): Uint8Array => {\n if (!live || !archiveBytes) {\n throw new OpenXmlIoError('openZip: archive is closed');\n }\n return archiveBytes;\n };\n\n const readEntry = (path: string): Uint8Array => {\n const buf = ensureLive();\n const cached = inflateCache.get(path);\n if (cached) return cached;\n const entry = byPath.get(path);\n if (!entry) {\n throw new OpenXmlIoError(`openZip: no entry at \"${path}\"`);\n }\n const compressed = readCompressedBytes(buf, entry);\n let out: Uint8Array;\n if (entry.compMethod === COMP_STORE) {\n // STORE has ratio 1, so the only thing left to check is the absolute\n // size cap (and the running archive total).\n if (budget) {\n if (compressed.byteLength > budget.limits.maxEntryUncompressedBytes) {\n throw entryOverflowError(path, budget.limits.maxEntryUncompressedBytes);\n }\n const record = beginEntryInflate(budget, path);\n record(compressed.byteLength);\n }\n // Copy so callers can safely mutate the returned bytes without perturbing\n // the underlying archive view.\n out = compressed.slice();\n } else if (entry.compMethod === COMP_DEFLATE) {\n out = inflateBounded(path, compressed, budget);\n } else {\n throw new OpenXmlIoError(`openZip: unsupported compression method ${entry.compMethod} for \"${path}\"`);\n }\n inflateCache.set(path, out);\n return out;\n };\n\n const readEntryStream = (path: string): ReadableStream<Uint8Array> => {\n const buf = ensureLive();\n const cached = inflateCache.get(path);\n if (cached) return singleChunkStream(cached);\n const entry = byPath.get(path);\n if (!entry) {\n throw new OpenXmlIoError(`openZip: no entry at \"${path}\"`);\n }\n const compressed = readCompressedBytes(buf, entry);\n if (entry.compMethod === COMP_STORE) {\n // STORE means the bytes on disk are the bytes the caller wants; no need\n // to involve the inflate state machine. Run the same caps the sync\n // `read()` STORE branch applies — otherwise an uncompressed bomb entry\n // reached via `readStream()` would bypass the per-entry / archive-total\n // accounting. Ratio is fixed at 1, so only the absolute bounds apply.\n if (budget) {\n if (compressed.byteLength > budget.limits.maxEntryUncompressedBytes) {\n throw entryOverflowError(path, budget.limits.maxEntryUncompressedBytes);\n }\n const record = beginEntryInflate(budget, path);\n record(compressed.byteLength);\n }\n // Copy because callers may mutate the returned bytes.\n return singleChunkStream(compressed.slice());\n }\n if (entry.compMethod !== COMP_DEFLATE) {\n throw new OpenXmlIoError(`openZip: unsupported compression method ${entry.compMethod} for \"${path}\"`);\n }\n // DEFLATE: drive fflate's `Inflate` from `pull()` so the consumer's\n // demand controls how much we inflate. Each `pull()` either emits one\n // already-buffered inflated chunk or pushes one more block of compressed\n // input — never both — so the ReadableStream internal queue stays at\n // depth 1 and the producer can't race ahead of the consumer.\n const entryCap = budget ? entryInflateCap(budget, compressed.byteLength) : Number.POSITIVE_INFINITY;\n const record = budget ? beginEntryInflate(budget, path) : null;\n let entryEmitted = 0;\n const pending: Uint8Array[] = [];\n let pushedOffset = 0;\n let inflaterFinal = false;\n let inflateError: Error | undefined;\n const inflater = new Inflate((chunk, final) => {\n if (inflateError) return;\n if (chunk.byteLength > 0) {\n entryEmitted += chunk.byteLength;\n if (entryEmitted > entryCap) {\n inflateError = entryOverflowError(path, entryCap);\n return;\n }\n if (record) {\n try {\n record(chunk.byteLength);\n } catch (err) {\n inflateError = err as Error;\n return;\n }\n }\n pending.push(chunk);\n }\n if (final) inflaterFinal = true;\n });\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n if (inflateError) {\n controller.error(inflateError);\n return;\n }\n // Emit at most one already-buffered inflated chunk per pull; subsequent\n // pulls drain the rest. This caps the stream's internal queue at one\n // chunk regardless of how many ondata callbacks fflate fired off the\n // most recent push.\n const buffered = pending.shift();\n if (buffered) {\n controller.enqueue(buffered);\n if (inflaterFinal && pending.length === 0 && pushedOffset >= compressed.byteLength) {\n controller.close();\n }\n return;\n }\n // No buffered output: push one block of compressed input and let\n // inflate's ondata fill `pending`. We stop pushing the moment we have\n // something to emit so the next pull can return it without racing\n // further inflation. `inflaterFinal` is set inside the ondata callback\n // during the same `push` call that sets `isLast`, so reaching\n // `pushedOffset >= compressed.byteLength` always ends the loop too.\n while (pending.length === 0 && pushedOffset < compressed.byteLength) {\n const end = Math.min(pushedOffset + INFLATE_CHUNK_BYTES, compressed.byteLength);\n const slice = compressed.subarray(pushedOffset, end);\n const isLast = end >= compressed.byteLength;\n try {\n inflater.push(slice, isLast);\n } catch (cause) {\n if (!inflateError) {\n inflateError = new OpenXmlIoError(`openZip: failed to inflate \"${path}\"`, { cause });\n }\n controller.error(inflateError);\n return;\n }\n // The ondata callback may have set inflateError (decompression-bomb\n // guard tripping mid-inflate). Surface it to the consumer before\n // continuing — otherwise we'd loop forever on a CD-lying bomb whose\n // chunks all land beyond the cap and never make it into `pending`.\n if (inflateError) {\n controller.error(inflateError);\n return;\n }\n pushedOffset = end;\n }\n const next = pending.shift();\n if (next) {\n controller.enqueue(next);\n }\n if (inflaterFinal && pending.length === 0 && pushedOffset >= compressed.byteLength) {\n controller.close();\n }\n },\n cancel() {\n // Consumer abandoned the stream early — drop buffered chunks and\n // advance the cursor past the end so the inflater and the compressed\n // slice are eligible for GC. fflate's `Inflate` has no terminate API\n // but losing the only reference is enough.\n pending.length = 0;\n pushedOffset = compressed.byteLength;\n inflaterFinal = true;\n },\n });\n };\n\n return {\n list(): string[] {\n ensureLive();\n return [...byPath.keys()].sort();\n },\n has(path: string): boolean {\n if (!live) return false;\n return byPath.has(path);\n },\n read(path: string): Uint8Array {\n return readEntry(path);\n },\n async readAsync(path: string): Promise<Uint8Array> {\n return readEntry(path);\n },\n readStream(path: string): ReadableStream<Uint8Array> {\n return readEntryStream(path);\n },\n close(): void {\n live = false;\n archiveBytes = undefined;\n inflateCache.clear();\n budget?.chargedByPath.clear();\n byPath.clear();\n },\n };\n}\n\n/**\n * Inflate `compressed` into a single `Uint8Array`, abort if it crosses the\n * configured per-entry cap or the archive-wide budget. Uses fflate's streaming\n * `Inflate` so the abort can fire on any internal block boundary rather than\n * after fflate has materialised the entire payload.\n */\nfunction inflateBounded(\n path: string,\n compressed: Uint8Array,\n budget: DecompressionBudget | null,\n): Uint8Array {\n const cap = budget ? entryInflateCap(budget, compressed.byteLength) : Number.POSITIVE_INFINITY;\n const record = budget ? beginEntryInflate(budget, path) : null;\n const acc: Uint8Array[] = [];\n let emitted = 0;\n let aborted: Error | undefined;\n const inflater = new Inflate((chunk) => {\n if (aborted) return;\n if (chunk.byteLength === 0) return;\n emitted += chunk.byteLength;\n if (emitted > cap) {\n aborted = entryOverflowError(path, cap);\n return;\n }\n if (record) {\n try {\n record(chunk.byteLength);\n } catch (err) {\n aborted = err as Error;\n return;\n }\n }\n acc.push(chunk);\n });\n let off = 0;\n while (off < compressed.byteLength) {\n const end = Math.min(off + INFLATE_CHUNK_BYTES, compressed.byteLength);\n const isLast = end >= compressed.byteLength;\n try {\n inflater.push(compressed.subarray(off, end), isLast);\n } catch (cause) {\n if (aborted) throw aborted;\n throw new OpenXmlIoError(`openZip: failed to inflate \"${path}\"`, { cause });\n }\n if (aborted) throw aborted;\n off = end;\n }\n const out = new Uint8Array(emitted);\n let cursor = 0;\n for (const chunk of acc) {\n out.set(chunk, cursor);\n cursor += chunk.byteLength;\n }\n return out;\n}\n\n/**\n * Fallback for archives we can't parse ourselves. The random-access reader\n * now understands ZIP64 EOCD + the Zip64 Extended Information extra field, so\n * the only way to reach this path is a malformed central directory. fflate's\n * `unzipSync` is more forgiving but inflates every entry up front; for\n * adversarial archives we still rely on a post-hoc cap check (the only one\n * available without our own streaming decoder).\n */\nfunction openViaUnzipSync(\n bytes: Uint8Array,\n limits: ReturnType<typeof resolveDecompressionLimits>,\n): ZipArchive {\n let entries: Record<string, Uint8Array> | undefined;\n try {\n entries = unzipSync(bytes);\n } catch (cause) {\n throw new OpenXmlIoError('openZip: archive is not a valid zip', { cause });\n }\n // fflate's `unzipSync` returns already-inflated bytes — we can't abort the\n // inflate mid-flight here, but a post-hoc check still rejects a malicious\n // archive *before* any caller-level code touches the bytes. The peak memory\n // spike is bounded by what fflate just produced; the random-access path\n // catches the common cases (ZIP64 + malformed-but-parseable CDs) up front.\n if (limits) {\n const budget = createBudget(limits);\n for (const [path, payload] of Object.entries(entries)) {\n if (payload.byteLength > limits.maxEntryUncompressedBytes) {\n throw new OpenXmlDecompressionBombError(\n `openZip: entry \"${path}\" inflated to ${payload.byteLength} bytes,` +\n ` exceeding the ${limits.maxEntryUncompressedBytes}-byte per-entry limit` +\n ` (decompression-bomb guard).`,\n );\n }\n const record = beginEntryInflate(budget, path);\n record(payload.byteLength);\n }\n }\n let live = true;\n return {\n list(): string[] {\n if (!live || !entries) throw new OpenXmlIoError('openZip: archive is closed');\n return Object.keys(entries).sort();\n },\n has(path: string): boolean {\n if (!live || !entries) return false;\n return Object.hasOwn(entries, path);\n },\n read(path: string): Uint8Array {\n if (!live || !entries) throw new OpenXmlIoError('openZip: archive is closed');\n const e = entries[path];\n if (!e) throw new OpenXmlIoError(`openZip: no entry at \"${path}\"`);\n // Copy, as the random-access path does: `read` hands the caller an array\n // of its own, and this one holds every entry for the archive's lifetime.\n return e.slice();\n },\n async readAsync(path: string): Promise<Uint8Array> {\n return this.read(path);\n },\n readStream(path: string): ReadableStream<Uint8Array> {\n // The unzipSync fallback path already has the entry fully inflated\n // (that's the price of dropping back from random-access). Hand it out as\n // a single-chunk stream so callers using the streaming reader don't have\n // to branch on the implementation.\n const inflated = this.read(path);\n return singleChunkStream(inflated);\n },\n close(): void {\n live = false;\n entries = undefined;\n },\n };\n}\n","// ZIP read layer.\n//\n// `openZip(source)` walks the central directory once and inflates each entry on\n// demand inside `read(path)` (see `./random-access-reader.ts`). That keeps peak\n// memory at compressed-archive size + per-entry inflate scratch + the bounded\n// cache of small re-read entries (`./inflate-cache.ts`), instead of holding\n// every uncompressed entry resident at once the way the old `unzipSync`\n// shortcut did. The fallback path through fflate's `unzipSync` is preserved for\n// ZIP64 / non-standard archives.\n\nimport type { XlsxSource } from '../io/source.js';\nimport { OpenXmlIoError, OpenXmlNotImplementedError } from '../utils/exceptions.js';\nimport type { DecompressionLimits } from './decompression-guard.js';\nimport { openRandomAccessArchive } from './random-access-reader.js';\n\nconst CFB_MAGIC = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];\n\nconst isCfbCompoundDocument = (bytes: Uint8Array): boolean => {\n if (bytes.length < CFB_MAGIC.length) return false;\n for (let i = 0; i < CFB_MAGIC.length; i++) {\n if (bytes[i] !== CFB_MAGIC[i]) return false;\n }\n return true;\n};\n\nexport interface ZipArchive {\n /** Sorted list of all entry paths in the archive. */\n list(): string[];\n /**\n * Synchronous read; throws OpenXmlIoError when the path is unknown. Each\n * call returns an array the caller owns: mutating it changes neither the\n * archive nor what a later read of the same path returns.\n */\n read(path: string): Uint8Array;\n /** Promise variant for symmetry with the future streaming reader. */\n readAsync(path: string): Promise<Uint8Array>;\n /**\n * Streaming read: returns the entry's inflated bytes as a Web\n * `ReadableStream<Uint8Array>` chunk-by-chunk. Lets callers (the streaming\n * worksheet iterator, in particular) push the inflated payload through a SAX\n * parser without first materialising it in full — peak memory for a sheet\n * walk drops to the inflate window + SAX state instead of the entire\n * uncompressed worksheet body. Throws OpenXmlIoError when the path is\n * unknown.\n */\n readStream(path: string): ReadableStream<Uint8Array>;\n /** Whether the archive holds an entry at the given path. */\n has(path: string): boolean;\n /** Release the in-memory entry table. Subsequent reads throw. */\n close(): void;\n}\n\n/** Options for {@link openZip}. */\nexport interface OpenZipOptions {\n /**\n * Decompression-bomb safeguards applied while inflating archive entries. The\n * default limits admit any legitimate xlsx and reject pathological archives\n * (extreme compression ratios, gigabyte-scale entries). Pass `false` to\n * disable the guard entirely — only safe when the source is fully trusted.\n * See {@link DecompressionLimits} for the individual knobs.\n */\n decompressionLimits?: DecompressionLimits | false;\n}\n\n/**\n * Open a zip archive from any {@link XlsxSource}. The source is fully\n * materialised in memory, the central directory is parsed once, and each\n * entry is inflated on demand by {@link openRandomAccessArchive}: peak memory\n * stays at compressed-archive size, plus per-entry inflate scratch, plus a few\n * MB at most of small entries kept for re-reads, rather than holding every\n * uncompressed entry resident. The fflate `unzipSync` fallback is preserved\n * internally for ZIP64 / non-standard archives the random-access reader\n * rejects, and it does hold every entry inflated.\n */\nexport async function openZip(source: XlsxSource, opts: OpenZipOptions = {}): Promise<ZipArchive> {\n let bytes: Uint8Array;\n try {\n bytes = await source.toBytes();\n } catch (cause) {\n throw new OpenXmlIoError('openZip: failed to read source bytes', { cause });\n }\n\n // Encrypted xlsx files (Excel 2007+ password protection) wrap the real\n // package inside an OLE Compound File Binary container with the magic\n // signature `D0 CF 11 E0 A1 B1 1A E1`. Detect that early and surface a clear\n // \"decrypt first\" error rather than letting fflate fail with a generic\n // invalid-zip message.\n if (isCfbCompoundDocument(bytes)) {\n throw new OpenXmlNotImplementedError(\n 'Encrypted xlsx is not supported. Decrypt with msoffcrypto-tool first.',\n );\n }\n\n return openRandomAccessArchive(bytes, opts.decompressionLimits);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"serialize-BC2Wu3bR.mjs","names":[],"sources":["../src/schema/core.ts","../src/schema/serialize.ts"],"sourcesContent":["// Schema layer types — pure data, zero classes.\n//\n// A Schema<T> describes how a plain `T` is mapped to and from an XmlNode. The\n// runtime serialiser (src/schema/serialize.ts) is a single switch-on-kind walk\n// that needs nothing beyond these tables; bundlers can drop schemas a build\n// path never imports because each lives in its own const.\n//\n// Lazy element schemas (`schema: () => SideSchema`) are how circular references\n// — Border has Sides, Side has its own Color, etc. — stay expressible without\n// hoisting hazards at module load.\n\n/** Primitive coercion kinds attribute values may declare. */\nexport type Primitive = 'string' | 'int' | 'float' | 'bool';\n\nexport interface AttrDef {\n kind: Primitive | 'enum';\n /** Allowed values when kind === 'enum'. */\n values?: readonly string[];\n /** Treat the attribute as optional; missing → undefined. */\n optional?: boolean;\n /**\n * Default applied during fromTree when the attribute is absent. Never\n * auto-stripped during toTree — round-trip equivalence is the primary\n * concern; explicit emitters decide when to omit.\n */\n default?: unknown;\n /** Numeric range bounds for kind in {'int', 'float'}. */\n min?: number;\n max?: number;\n /** XML attribute name when it differs from the property key on T. */\n xmlName?: string;\n /** Namespace URI for the XML attribute. Empty / undefined = no namespace. */\n xmlNs?: string;\n}\n\n/**\n * Discriminated union of element shapes a schema can declare. Names are\n * unprefixed (`xmlNs` carries the namespace); the serialiser pairs them into\n * Clark names internally.\n */\nexport type ElementDef =\n | {\n kind: 'text';\n /** Property key on T — the value lands at `T[key]`. */\n key: string;\n /** XML element local name. Defaults to `key` when omitted. */\n name?: string;\n xmlNs?: string;\n primitive: Primitive;\n optional?: boolean;\n default?: unknown;\n /**\n * Fixed attributes always emitted on this element. Keys in Clark\n * notation. On parse, attributes other than these are ignored; keep the\n * schema strict only for the value (text content).\n *\n * Used for the `xsi:type=\"dcterms:W3CDTF\"` marker that docProps/core.xml\n * emits on its <dcterms:created> / <dcterms:modified> children, and\n * similar fixed-marker patterns.\n */\n attrs?: Record<string, string>;\n }\n | {\n kind: 'object';\n key: string;\n name?: string;\n xmlNs?: string;\n // Schema is contravariant in T at the element boundary; named types\n // come back via T anyway. The `any` is intentional — `unknown` would\n // force a cast in every concrete schema definition with no extra safety.\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: () => Schema<any>;\n optional?: boolean;\n }\n | {\n kind: 'sequence';\n key: string;\n /** Local name of the repeated child element. */\n itemName: string;\n itemNs?: string;\n // See ElementDef.kind === 'object' for why `any` survives here.\n // oxlint-disable-next-line typescript/no-explicit-any\n itemSchema: () => Schema<any>;\n /** Optional wrapper element holding the items. */\n container?: { name: string; xmlNs?: string; count?: boolean };\n }\n | {\n /**\n * Empty marker element — `<key/>` whose presence sets `T[key] = true`,\n * absence leaves it `undefined`. Used by Font's `<b/>`, `<i/>`, etc.\n */\n kind: 'empty';\n key: string;\n name?: string;\n xmlNs?: string;\n }\n | {\n /**\n * `<key val=\"value\"/>` style child carrying a single primitive value via\n * an attribute (default `val`). This is openpyxl's NestedString /\n * NestedFloat / NestedInteger / NestedBool / NestedNoneSet pattern; it\n * shows up across Font, NumberFormat children and several chart\n * sub-elements.\n */\n kind: 'nested';\n key: string;\n name?: string;\n xmlNs?: string;\n primitive: Primitive | 'enum';\n /** Allowed values when primitive === 'enum'. */\n values?: readonly string[];\n /** Attribute name carrying the value. Defaults to \"val\". */\n valAttr?: string;\n /** Numeric range bounds (int / float). */\n min?: number;\n max?: number;\n optional?: boolean;\n default?: unknown;\n }\n | {\n /**\n * Opaque round-trip slot — fromTree stores the matched child as a raw\n * `XmlNode`, toTree splices it back verbatim. Used for subtrees we don't\n * want to model in detail (e.g. the vt:vector content under app.xml's\n * HeadingPairs / TitlesOfParts) but still need to preserve byte-for-byte\n * through edits.\n */\n kind: 'raw';\n key: string;\n name?: string;\n xmlNs?: string;\n optional?: boolean;\n };\n\nexport interface Schema<T> {\n /** Local element name. */\n tagname: string;\n /** Namespace URI of this element. */\n xmlNs?: string;\n /** Attribute table keyed by property name on T. */\n attrs: { readonly [K in keyof T & string]?: AttrDef };\n /** Child elements in declaration / emission order. */\n elements: ReadonlyArray<ElementDef>;\n /** Hook to normalise the parsed object (e.g., infer derived fields). */\n postParse?: (value: T, node: import('../xml/tree.js').XmlNode) => T;\n /** Hook to normalise the value before serialisation. */\n preSerialize?: (value: T) => T;\n}\n\n/**\n * Identity helper that pins inference to the supplied `T`. The runtime is just\n * `s => s`; the value matters only for the call-site type.\n */\nexport function defineSchema<T>(s: Schema<T>): Schema<T> {\n return s;\n}\n","// Schema-driven (de)serialisation. One switch-on-kind walk in each direction;\n// the schema does the work, no class-based descriptors.\n//\n// `toTree` and `fromTree` are pure functions over plain data. They run the same\n// on the schema layer's own types and on user types in the styles / chart /\n// drawing modules.\n\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport { qname } from '../xml/namespaces.js';\nimport { el, type XmlNode } from '../xml/tree.js';\nimport type { AttrDef, ElementDef, Primitive, Schema } from './core.js';\n\n// ---- coercion helpers -------------------------------------------------------\n\nconst coerceFromString = (raw: string, kind: Primitive | 'enum', def: AttrDef): unknown => {\n switch (kind) {\n case 'string':\n return raw;\n case 'int': {\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n)) throw new OpenXmlSchemaError(`expected an integer, got \"${raw}\"`);\n if (def.min !== undefined && n < def.min) throw new OpenXmlSchemaError(`int \"${n}\" is below min ${def.min}`);\n if (def.max !== undefined && n > def.max) throw new OpenXmlSchemaError(`int \"${n}\" is above max ${def.max}`);\n return n;\n }\n case 'float': {\n const n = Number.parseFloat(raw);\n if (!Number.isFinite(n)) throw new OpenXmlSchemaError(`expected a number, got \"${raw}\"`);\n if (def.min !== undefined && n < def.min) throw new OpenXmlSchemaError(`float \"${n}\" is below min ${def.min}`);\n if (def.max !== undefined && n > def.max) throw new OpenXmlSchemaError(`float \"${n}\" is above max ${def.max}`);\n return n;\n }\n case 'bool': {\n // openpyxl accepts 'true'/'t'/'1' or 'false'/'f'/'0' (case-insensitive).\n const v = raw.toLowerCase();\n if (v === 'true' || v === 't' || v === '1') return true;\n if (v === 'false' || v === 'f' || v === '0') return false;\n throw new OpenXmlSchemaError(`expected a boolean, got \"${raw}\"`);\n }\n case 'enum': {\n if (!def.values?.includes(raw)) {\n throw new OpenXmlSchemaError(`expected one of [${def.values?.join(', ') ?? ''}], got \"${raw}\"`);\n }\n return raw;\n }\n }\n};\n\nconst coerceToString = (value: unknown, kind: Primitive | 'enum', def: AttrDef): string => {\n switch (kind) {\n case 'string':\n if (typeof value !== 'string') throw new OpenXmlSchemaError(`expected a string, got ${typeof value}`);\n return value;\n case 'int': {\n if (typeof value !== 'number' || !Number.isFinite(value) || !Number.isInteger(value)) {\n throw new OpenXmlSchemaError(`expected an integer, got ${String(value)}`);\n }\n return String(value);\n }\n case 'float': {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new OpenXmlSchemaError(`expected a number, got ${String(value)}`);\n }\n return String(value);\n }\n case 'bool': {\n if (typeof value !== 'boolean') throw new OpenXmlSchemaError(`expected a boolean, got ${typeof value}`);\n // OOXML convention: '1' / '0'.\n return value ? '1' : '0';\n }\n case 'enum': {\n if (typeof value !== 'string' || !def.values || !def.values.includes(value)) {\n throw new OpenXmlSchemaError(`enum value out of range: ${String(value)}`);\n }\n return value;\n }\n }\n};\n\nconst attrXmlKey = (key: string, def: AttrDef): string => qname(def.xmlNs ?? '', def.xmlName ?? key);\nconst elementXmlName = (def: ElementDef, fallbackNs: string | undefined): string => {\n const local = def.kind === 'sequence' ? (def.container?.name ?? def.itemName) : (def.name ?? def.key);\n const ns = def.kind === 'sequence' ? (def.container?.xmlNs ?? def.itemNs ?? fallbackNs) : (def.xmlNs ?? fallbackNs);\n return qname(ns ?? '', local);\n};\n\n// ---- toTree -----------------------------------------------------------------\n\n/** Serialise a value of `T` into an {@link XmlNode} per the supplied schema. */\nexport function toTree<T>(value: T, schema: Schema<T>): XmlNode {\n const v = (schema.preSerialize ? schema.preSerialize(value) : value) as Record<string, unknown>;\n const node = el(qname(schema.xmlNs ?? '', schema.tagname));\n\n for (const [key, def] of Object.entries(schema.attrs) as Array<[string, AttrDef | undefined]>) {\n if (def === undefined) continue;\n const raw = v[key];\n if (raw === undefined) {\n if (def.optional) continue;\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required attribute \"${key}\" is missing`);\n }\n node.attrs[attrXmlKey(key, def)] = coerceToString(raw, def.kind, def);\n }\n\n for (const def of schema.elements) {\n const raw = v[def.key];\n if (raw === undefined) {\n // 'empty' has no `optional`; presence is binary on the value itself.\n // 'sequence' is implicitly optional (undefined / [] = no items). 'raw'\n // uses the same optional flag as text/object.\n if ((def.kind === 'text' || def.kind === 'object' || def.kind === 'raw') && !def.optional) {\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required element \"${def.key}\" is missing`);\n }\n continue;\n }\n switch (def.kind) {\n case 'text': {\n const text = coerceToString(raw, def.primitive, { kind: def.primitive });\n node.children.push(el(elementXmlName(def, schema.xmlNs), def.attrs ?? {}, [], text));\n break;\n }\n case 'empty': {\n if (raw === true) node.children.push(el(elementXmlName(def, schema.xmlNs), {}, []));\n break;\n }\n case 'nested': {\n const valAttr = def.valAttr ?? 'val';\n const fakeAttrDef: AttrDef = {\n kind: def.primitive,\n ...(def.values !== undefined ? { values: def.values } : {}),\n ...(def.min !== undefined ? { min: def.min } : {}),\n ...(def.max !== undefined ? { max: def.max } : {}),\n };\n const text = coerceToString(raw, def.primitive, fakeAttrDef);\n node.children.push(el(elementXmlName(def, schema.xmlNs), { [valAttr]: text }, []));\n break;\n }\n case 'object': {\n const sub = toTree(raw as Record<string, unknown>, def.schema());\n // Allow the schema to declare its own tagname OR be addressed by the\n // parent under a different element name. We honour the schema's tagname\n // (it's the canonical identity for that type).\n if (def.name !== undefined && sub.name !== qname(def.xmlNs ?? schema.xmlNs ?? '', def.name)) {\n // Re-tag the produced element to the parent-defined name.\n sub.name = qname(def.xmlNs ?? schema.xmlNs ?? '', def.name);\n } else if (def.name === undefined && def.key !== sub.name.replace(/^\\{[^}]*\\}/, '')) {\n // No explicit `name` — use the property key as the local name.\n sub.name = qname(def.xmlNs ?? schema.xmlNs ?? '', def.key);\n }\n node.children.push(sub);\n break;\n }\n case 'raw': {\n // Splice the stored XmlNode back verbatim, but realign its name to the\n // schema-declared position so the output is well-formed even when the\n // caller mutates `name`.\n const sub = raw as XmlNode;\n const expected = elementXmlName(def, schema.xmlNs);\n if (sub.name !== expected) {\n node.children.push({ ...sub, name: expected });\n } else {\n node.children.push(sub);\n }\n break;\n }\n case 'sequence': {\n const items = raw as ReadonlyArray<Record<string, unknown>>;\n const itemSchema = def.itemSchema();\n const built = items.map((item) => {\n const sub = toTree(item, itemSchema);\n // Re-tag to the declared itemName / itemNs.\n sub.name = qname(def.itemNs ?? itemSchema.xmlNs ?? '', def.itemName);\n return sub;\n });\n if (def.container !== undefined) {\n const containerAttrs: Record<string, string> = {};\n if (def.container.count) containerAttrs['count'] = String(items.length);\n node.children.push(el(elementXmlName(def, schema.xmlNs), containerAttrs, built));\n } else {\n for (const c of built) node.children.push(c);\n }\n break;\n }\n }\n }\n\n return node;\n}\n\n// ---- fromTree ---------------------------------------------------------------\n\nconst childByName = (node: XmlNode, fullName: string): XmlNode | undefined => {\n for (const c of node.children) if (c.name === fullName) return c;\n return undefined;\n};\n\nconst childrenByName = (node: XmlNode, fullName: string): XmlNode[] => {\n const out: XmlNode[] = [];\n for (const c of node.children) if (c.name === fullName) out.push(c);\n return out;\n};\n\n/** Materialise a value of `T` from an {@link XmlNode} per the supplied schema. */\nexport function fromTree<T>(node: XmlNode, schema: Schema<T>): T {\n const out: Record<string, unknown> = {};\n const expectedTag = qname(schema.xmlNs ?? '', schema.tagname);\n if (node.name !== expectedTag) {\n throw new OpenXmlSchemaError(`fromTree: expected <${schema.tagname}> (Clark \"${expectedTag}\"), got \"${node.name}\"`);\n }\n\n for (const [key, def] of Object.entries(schema.attrs) as Array<[string, AttrDef | undefined]>) {\n if (def === undefined) continue;\n const xmlKey = attrXmlKey(key, def);\n const raw = node.attrs[xmlKey];\n if (raw === undefined) {\n if (def.default !== undefined) out[key] = def.default;\n else if (!def.optional) {\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required attribute \"${key}\" is missing`);\n }\n continue;\n }\n out[key] = coerceFromString(raw, def.kind, def);\n }\n\n for (const def of schema.elements) {\n const fullName = elementXmlName(def, schema.xmlNs);\n switch (def.kind) {\n case 'text': {\n const child = childByName(node, fullName);\n if (child === undefined) {\n if (def.default !== undefined) out[def.key] = def.default;\n else if (!def.optional) {\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required text element \"${def.key}\" is missing`);\n }\n break;\n }\n out[def.key] = coerceFromString(child.text ?? '', def.primitive, { kind: def.primitive });\n break;\n }\n case 'empty': {\n const child = childByName(node, fullName);\n if (child !== undefined) out[def.key] = true;\n // Absent → leave key unset so the value compares equal to the\n // round-trip-emitting input that omitted it.\n break;\n }\n case 'nested': {\n const child = childByName(node, fullName);\n if (child === undefined) {\n // The element itself was omitted: leave the field unset for optional\n // nested elements, regardless of `default`. `default` here governs\n // the \"child present but @val omitted\" case (e.g. <u/> meaning\n // underline=single per ECMA-376 §18.4.13), not \"child entirely\n // absent\".\n if (!def.optional) {\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required nested element \"${def.key}\" is missing`);\n }\n break;\n }\n const valAttr = def.valAttr ?? 'val';\n const raw = child.attrs[valAttr];\n if (raw === undefined) {\n if (def.default !== undefined) {\n out[def.key] = def.default;\n break;\n }\n throw new OpenXmlSchemaError(\n `<${schema.tagname}>: nested element \"${def.key}\" missing attribute \"${valAttr}\"`,\n );\n }\n out[def.key] = coerceFromString(raw, def.primitive, {\n kind: def.primitive,\n ...(def.values !== undefined ? { values: def.values } : {}),\n ...(def.min !== undefined ? { min: def.min } : {}),\n ...(def.max !== undefined ? { max: def.max } : {}),\n });\n break;\n }\n case 'raw': {\n const child = childByName(node, fullName);\n if (child === undefined) {\n if (!def.optional) {\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required raw element \"${def.key}\" is missing`);\n }\n break;\n }\n out[def.key] = child;\n break;\n }\n case 'object': {\n const child = childByName(node, fullName);\n if (child === undefined) {\n if (!def.optional) {\n throw new OpenXmlSchemaError(`<${schema.tagname}>: required element \"${def.key}\" is missing`);\n }\n break;\n }\n const itemSchema = def.schema();\n // Address the child by the parent-declared name even if the child\n // schema names itself differently.\n const renamed: XmlNode =\n child.name === qname(itemSchema.xmlNs ?? '', itemSchema.tagname)\n ? child\n : { ...child, name: qname(itemSchema.xmlNs ?? '', itemSchema.tagname) };\n out[def.key] = fromTree(renamed, itemSchema);\n break;\n }\n case 'sequence': {\n const itemSchema = def.itemSchema();\n const itemFullName = qname(def.itemNs ?? itemSchema.xmlNs ?? '', def.itemName);\n const itemSchemaName = qname(itemSchema.xmlNs ?? '', itemSchema.tagname);\n const containerNode = def.container !== undefined ? childByName(node, fullName) : node;\n const items: unknown[] = [];\n if (containerNode !== undefined) {\n const matches = childrenByName(containerNode, itemFullName);\n for (const m of matches) {\n const renamed: XmlNode = m.name === itemSchemaName ? m : { ...m, name: itemSchemaName };\n items.push(fromTree(renamed, itemSchema));\n }\n }\n out[def.key] = items;\n break;\n }\n }\n }\n\n const built = out as unknown as T;\n return schema.postParse ? schema.postParse(built, node) : built;\n}\n"],"mappings":"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"}