@shbernal/ts-xlsx 2.0.0 → 3.0.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 (147) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +26 -4
  5. package/dist/core/address.js +21 -9
  6. package/dist/core/autofilter.d.ts +12 -1
  7. package/dist/core/autofilter.js +35 -12
  8. package/dist/core/axis-handle.d.ts +8 -0
  9. package/dist/core/axis-handle.js +14 -0
  10. package/dist/core/cell.d.ts +24 -0
  11. package/dist/core/cell.js +9 -1
  12. package/dist/core/color-resolution.d.ts +1 -1
  13. package/dist/core/color-resolution.js +7 -8
  14. package/dist/core/column.d.ts +4 -1
  15. package/dist/core/column.js +31 -35
  16. package/dist/core/conditional-formatting-overlay.d.ts +5 -0
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +45 -5
  19. package/dist/core/conditional-formatting.js +74 -0
  20. package/dist/core/data-validation-overlay.d.ts +11 -0
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +9 -2
  23. package/dist/core/data-validation.js +26 -0
  24. package/dist/core/grid-edits.d.ts +16 -0
  25. package/dist/core/grid-edits.js +26 -8
  26. package/dist/core/grid-shift.d.ts +19 -0
  27. package/dist/core/grid-shift.js +8 -0
  28. package/dist/core/image.d.ts +2 -0
  29. package/dist/core/image.js +2 -0
  30. package/dist/core/merge-index.d.ts +31 -0
  31. package/dist/core/merge-index.js +60 -0
  32. package/dist/core/merge.d.ts +12 -11
  33. package/dist/core/merge.js +36 -10
  34. package/dist/core/page-setup.d.ts +86 -2
  35. package/dist/core/page-setup.js +34 -1
  36. package/dist/core/pivot-table.d.ts +2 -0
  37. package/dist/core/pivot-table.js +23 -18
  38. package/dist/core/protection.d.ts +3 -0
  39. package/dist/core/protection.js +20 -14
  40. package/dist/core/row-input.d.ts +7 -0
  41. package/dist/core/row-input.js +10 -8
  42. package/dist/core/row.d.ts +4 -1
  43. package/dist/core/row.js +19 -23
  44. package/dist/core/style.d.ts +78 -7
  45. package/dist/core/style.js +90 -80
  46. package/dist/core/table.d.ts +26 -12
  47. package/dist/core/table.js +67 -33
  48. package/dist/core/theme.d.ts +3 -1
  49. package/dist/core/theme.js +4 -0
  50. package/dist/core/used-extent.d.ts +44 -0
  51. package/dist/core/used-extent.js +102 -0
  52. package/dist/core/workbook.d.ts +2 -2
  53. package/dist/core/worksheet-comments.d.ts +8 -0
  54. package/dist/core/worksheet-comments.js +18 -0
  55. package/dist/core/worksheet.d.ts +31 -7
  56. package/dist/core/worksheet.js +72 -86
  57. package/dist/customui/ribbon.js +23 -24
  58. package/dist/entries/core.d.ts +3 -3
  59. package/dist/entries/csv.d.ts +1 -1
  60. package/dist/entries/node-unavailable.d.ts +15 -0
  61. package/dist/entries/node-unavailable.js +20 -0
  62. package/dist/entries/node.d.ts +1 -0
  63. package/dist/entries/node.js +1 -0
  64. package/dist/entries/xlsx.d.ts +0 -1
  65. package/dist/entries/xlsx.js +0 -1
  66. package/dist/io/csv/read.js +1 -1
  67. package/dist/io/csv/write.d.ts +12 -2
  68. package/dist/io/csv/write.js +17 -2
  69. package/dist/io/opc/inflate.js +3 -13
  70. package/dist/io/opc/part-paths.d.ts +1 -0
  71. package/dist/io/opc/part-paths.js +15 -0
  72. package/dist/io/opc/read-opc.d.ts +22 -3
  73. package/dist/io/opc/read-opc.js +11 -39
  74. package/dist/io/opc/rels.d.ts +12 -6
  75. package/dist/io/opc/rels.js +3 -5
  76. package/dist/io/style/xf-style.d.ts +33 -4
  77. package/dist/io/style/xf-style.js +12 -1
  78. package/dist/io/xlsb/read-styles.js +6 -3
  79. package/dist/io/xlsb/read.d.ts +1 -1
  80. package/dist/io/xlsb/read.js +5 -8
  81. package/dist/io/xlsx/cell-accumulator.d.ts +2 -4
  82. package/dist/io/xlsx/cell-accumulator.js +20 -58
  83. package/dist/io/xlsx/cell-value.d.ts +1 -1
  84. package/dist/io/xlsx/cell-value.js +7 -4
  85. package/dist/io/xlsx/color-xml.d.ts +1 -1
  86. package/dist/io/xlsx/color-xml.js +8 -7
  87. package/dist/io/xlsx/comments.js +6 -3
  88. package/dist/io/xlsx/conditional-formatting.d.ts +4 -3
  89. package/dist/io/xlsx/conditional-formatting.js +81 -65
  90. package/dist/io/xlsx/data-validation.d.ts +10 -8
  91. package/dist/io/xlsx/data-validation.js +44 -41
  92. package/dist/io/xlsx/edit-vba.js +4 -11
  93. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  94. package/dist/io/xlsx/hyperlinks.js +26 -31
  95. package/dist/io/xlsx/images.js +33 -35
  96. package/dist/io/xlsx/package-plan.d.ts +9 -1
  97. package/dist/io/xlsx/package-plan.js +29 -16
  98. package/dist/io/xlsx/part-names.d.ts +41 -0
  99. package/dist/io/xlsx/part-names.js +46 -0
  100. package/dist/io/xlsx/read-pivot.js +4 -9
  101. package/dist/io/xlsx/read-rows.js +12 -13
  102. package/dist/io/xlsx/read-shared-strings.js +9 -47
  103. package/dist/io/xlsx/read-styles.d.ts +1 -1
  104. package/dist/io/xlsx/read-styles.js +65 -43
  105. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  106. package/dist/io/xlsx/read-worksheet.js +48 -63
  107. package/dist/io/xlsx/read.d.ts +3 -4
  108. package/dist/io/xlsx/read.js +124 -146
  109. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  110. package/dist/io/xlsx/rich-runs.js +95 -30
  111. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  112. package/dist/io/xlsx/sheet-properties.js +23 -26
  113. package/dist/io/xlsx/styles.d.ts +3 -15
  114. package/dist/io/xlsx/styles.js +83 -105
  115. package/dist/io/xlsx/tables.js +14 -13
  116. package/dist/io/xlsx/theme-xml.js +8 -5
  117. package/dist/io/xlsx/threaded-comments.js +20 -19
  118. package/dist/io/xlsx/workbook-xml.js +43 -36
  119. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  120. package/dist/io/xlsx/worksheet-xml.js +47 -52
  121. package/dist/io/xlsx/write-stream.d.ts +14 -1
  122. package/dist/io/xlsx/write-stream.js +40 -33
  123. package/dist/io/xlsx/write.d.ts +5 -0
  124. package/dist/io/xlsx/write.js +155 -131
  125. package/dist/sha512.d.ts +2 -0
  126. package/dist/sha512.js +146 -0
  127. package/dist/token-set.d.ts +15 -0
  128. package/dist/token-set.js +4 -0
  129. package/dist/vba/bytes.d.ts +1 -2
  130. package/dist/vba/bytes.js +1 -12
  131. package/dist/vba/cfb-format.d.ts +24 -0
  132. package/dist/vba/cfb-format.js +12 -0
  133. package/dist/vba/cfb-writer.js +17 -17
  134. package/dist/vba/cfb.js +1 -7
  135. package/dist/vba/dir-records.d.ts +53 -0
  136. package/dist/vba/dir-records.js +28 -0
  137. package/dist/vba/ms-ovba.js +32 -11
  138. package/dist/vba/project-editor.js +5 -34
  139. package/dist/vba/project.js +15 -31
  140. package/dist/vba/vba-encoding.js +1 -1
  141. package/dist/xml/xml-read.d.ts +117 -72
  142. package/dist/xml/xml-read.js +123 -169
  143. package/dist/xml/xml-scan.d.ts +100 -0
  144. package/dist/xml/xml-scan.js +208 -0
  145. package/dist/xml/xml.d.ts +44 -2
  146. package/dist/xml/xml.js +17 -5
  147. package/package.json +22 -6
@@ -1,10 +1,12 @@
1
1
  import { mangleFormula, quoteSheetName } from '../../core/formula.js';
2
2
  import { WORKBOOK_PROTECTION_CREDENTIAL_ATTRS } from '../../core/workbook-protection.js';
3
- import { escapeAttr, escapeText, XML_DECLARATION } from '../../xml/xml.js';
4
- import { extensionOf, relativePartPath, THEME_PART_PATH } from '../opc/part-paths.js';
3
+ import { isVisibility } from '../../core/worksheet.js';
4
+ import { checkedToken, escapeAttr, escapeText, numAttr, textAttr, XML_DECLARATION, } from '../../xml/xml.js';
5
+ import { extensionOf, THEME_PART_PATH } from '../opc/part-paths.js';
5
6
  import { relationship, relationshipsPart } from '../opc/rels.js';
6
7
  import { imageContentType } from './images.js';
7
8
  import { SLICER_CACHES_EXT_URI } from './namespaces.js';
9
+ import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, SHARED_STRINGS_PART, STYLES_PART, tablePart, targetFromWorkbook, threadedCommentsPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
8
10
  import { NS, REL } from './relationships.js';
9
11
  import { x14Ext } from './x14-ext.js';
10
12
  const CT = {
@@ -71,45 +73,47 @@ function contentTypeOverrides(sheetCount, tables, drawingNumbers, commentNumbers
71
73
  .filter((part) => part.path !== THEME_PART_PATH)
72
74
  .filter((part) => extensionDefaults.get(extensionOf(part.path).toLowerCase())?.contentType !==
73
75
  part.contentType)
74
- .map((part) => override(`/${part.path}`, part.contentType));
76
+ .map((part) => override(part.path, part.contentType));
75
77
  return [
76
- override('/xl/workbook.xml', isMacroEnabled(preservedWorkbookRefs) ? CT.macroEnabledWorkbook : CT.workbook),
77
- ...Array.from({ length: sheetCount }, (_, i) => override(`/xl/worksheets/sheet${i + 1}.xml`, CT.worksheet)),
78
- ...tables.map(({ number }) => override(`/xl/tables/table${number}.xml`, CT.table)),
79
- ...drawingNumbers.map((number) => override(`/xl/drawings/drawing${number}.xml`, CT.drawing)),
80
- ...commentNumbers.map((number) => override(`/xl/comments${number}.xml`, CT.comments)),
81
- ...threadedCommentNumbers.map((number) => override(`/xl/threadedComments/threadedComment${number}.xml`, CT.threadedComments)),
82
- ...pivots.map(({ number }) => override(`/xl/pivotTables/pivotTable${number}.xml`, CT.pivotTable)),
83
- ...pivots.map(({ number }) => override(`/xl/pivotCache/pivotCacheDefinition${number}.xml`, CT.pivotCacheDefinition)),
84
- ...pivots.map(({ number }) => override(`/xl/pivotCache/pivotCacheRecords${number}.xml`, CT.pivotCacheRecords)),
85
- override(`/${THEME_PART_PATH}`, CT.theme),
86
- override('/xl/styles.xml', CT.styles),
87
- ...(hasSharedStrings ? [override('/xl/sharedStrings.xml', CT.sharedStrings)] : []),
88
- ...(hasPersons ? [override('/xl/persons/person.xml', CT.person)] : []),
89
- override('/docProps/core.xml', CT.core),
90
- override('/docProps/app.xml', CT.app),
78
+ override(WORKBOOK_PART, isMacroEnabled(preservedWorkbookRefs) ? CT.macroEnabledWorkbook : CT.workbook),
79
+ ...Array.from({ length: sheetCount }, (_, i) => override(worksheetPart(i + 1), CT.worksheet)),
80
+ ...tables.map(({ number }) => override(tablePart(number), CT.table)),
81
+ ...drawingNumbers.map((number) => override(drawingPart(number), CT.drawing)),
82
+ ...commentNumbers.map((number) => override(commentsPart(number), CT.comments)),
83
+ ...threadedCommentNumbers.map((number) => override(threadedCommentsPart(number), CT.threadedComments)),
84
+ ...pivots.map(({ number }) => override(pivotTablePart(number), CT.pivotTable)),
85
+ ...pivots.map(({ number }) => override(pivotCacheDefinitionPart(number), CT.pivotCacheDefinition)),
86
+ ...pivots.map(({ number }) => override(pivotCacheRecordsPart(number), CT.pivotCacheRecords)),
87
+ override(THEME_PART_PATH, CT.theme),
88
+ override(STYLES_PART, CT.styles),
89
+ ...(hasSharedStrings ? [override(SHARED_STRINGS_PART, CT.sharedStrings)] : []),
90
+ ...(hasPersons ? [override(PERSONS_PART, CT.person)] : []),
91
+ override(CORE_PROPS_PART, CT.core),
92
+ override(APP_PROPS_PART, CT.app),
91
93
  ...preservedOverrides,
92
94
  ].join('');
93
95
  }
94
- function override(partName, contentType) {
95
- return `<Override PartName="${partName}" ContentType="${contentType}"/>`;
96
+ function override(partPath, contentType) {
97
+ return `<Override PartName="/${partPath}" ContentType="${contentType}"/>`;
96
98
  }
97
99
  function defaultType(extension, contentType) {
98
100
  return `<Default Extension="${extension}" ContentType="${contentType}"/>`;
99
101
  }
100
102
  export function rootRelsXml(rootRefs) {
101
103
  return relationshipsPart([
102
- relationship('rId1', REL.officeDocument, 'xl/workbook.xml'),
103
- relationship('rId2', REL.coreProps, 'docProps/core.xml'),
104
- relationship('rId3', REL.extProps, 'docProps/app.xml'),
105
- ...rootRefs.map((ref, i) => relationship(`rId${4 + i}`, ref.relType, escapeAttr(ref.entryPath))),
104
+ relationship('rId1', REL.officeDocument, WORKBOOK_PART),
105
+ relationship('rId2', REL.coreProps, CORE_PROPS_PART),
106
+ relationship('rId3', REL.extProps, APP_PROPS_PART),
107
+ ...rootRefs.map((ref, i) => relationship(`rId${4 + i}`, ref.relType, ref.entryPath)),
106
108
  ]);
107
109
  }
108
110
  export function workbookXml(workbook, preservedRels, pivots) {
109
111
  const sheets = workbook.worksheets;
110
112
  const entries = sheets
111
113
  .map((sheet, i) => {
112
- const state = sheet.state === 'visible' ? '' : ` state="${sheet.state}"`;
114
+ const state = sheet.state === 'visible'
115
+ ? ''
116
+ : ` state="${checkedToken(sheet.state, isVisibility, 'sheet visibility')}"`;
113
117
  return `<sheet name="${escapeAttr(sheet.name)}" sheetId="${sheet.id}"${state} r:id="rId${i + 1}"/>`;
114
118
  })
115
119
  .join('');
@@ -128,11 +132,14 @@ export function workbookXml(workbook, preservedRels, pivots) {
128
132
  function bookViewsXml(workbook) {
129
133
  const view = workbook.view;
130
134
  const activeTab = workbook.activeTabIndex;
131
- const attrs = (view.visibility !== undefined && view.visibility !== 'visible'
132
- ? ` visibility="${view.visibility}"`
133
- : '') +
135
+ const attrs = (view.visibility === undefined || view.visibility === 'visible'
136
+ ? ''
137
+ : ` visibility="${checkedToken(view.visibility, isVisibility, 'window visibility')}"`) +
134
138
  (view.minimized ? ' minimized="1"' : '') +
135
- ` xWindow="${view.x}" yWindow="${view.y}" windowWidth="${view.width}" windowHeight="${view.height}"` +
139
+ numAttr('xWindow', view.x) +
140
+ numAttr('yWindow', view.y) +
141
+ numAttr('windowWidth', view.width) +
142
+ numAttr('windowHeight', view.height) +
136
143
  (activeTab === 0 ? '' : ` activeTab="${activeTab}"`);
137
144
  return `<bookViews><workbookView${attrs}/></bookViews>`;
138
145
  }
@@ -192,7 +199,7 @@ function definedNamesXml(workbook) {
192
199
  const scopeAttr = name.scope === undefined
193
200
  ? ''
194
201
  : ` localSheetId="${sheets.findIndex((sheet) => sheet.name === name.scope)}"`;
195
- const commentAttr = name.comment === undefined ? '' : ` comment="${escapeAttr(name.comment)}"`;
202
+ const commentAttr = textAttr('comment', name.comment);
196
203
  const hiddenAttr = name.hidden ? ' hidden="1"' : '';
197
204
  return (`<definedName name="${escapeAttr(name.name)}"${scopeAttr}${commentAttr}${hiddenAttr}>` +
198
205
  `${escapeText(mangleFormula(name.refersTo))}</definedName>`);
@@ -215,19 +222,19 @@ function filterDatabaseRefersTo(sheetName, range) {
215
222
  export const FIXED_WORKBOOK_REL_COUNT = 2;
216
223
  export function workbookRelsXml(sheetCount, hasSharedStrings, personsRelId, preservedRels, pivots) {
217
224
  return relationshipsPart([
218
- ...Array.from({ length: sheetCount }, (_, i) => relationship(`rId${i + 1}`, REL.worksheet, `worksheets/sheet${i + 1}.xml`)),
225
+ ...Array.from({ length: sheetCount }, (_, i) => relationship(`rId${i + 1}`, REL.worksheet, targetFromWorkbook(worksheetPart(i + 1)))),
219
226
  relationship(`rId${sheetCount + 1}`, REL.styles, 'styles.xml'),
220
- relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT}`, REL.theme, relativePartPath('xl/workbook.xml', THEME_PART_PATH)),
227
+ relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT}`, REL.theme, targetFromWorkbook(THEME_PART_PATH)),
221
228
  ...(hasSharedStrings
222
229
  ? [
223
- relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT + 1}`, REL.sharedStrings, 'sharedStrings.xml'),
230
+ relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT + 1}`, REL.sharedStrings, targetFromWorkbook(SHARED_STRINGS_PART)),
224
231
  ]
225
232
  : []),
226
233
  ...(personsRelId === null
227
234
  ? []
228
- : [relationship(personsRelId, REL.person, 'persons/person.xml')]),
229
- ...preservedRels.map((ref) => relationship(ref.relId, ref.relType, escapeAttr(relativePartPath('xl/workbook.xml', ref.entryPath)))),
230
- ...pivots.map((pivot) => relationship(pivot.workbookRelId, REL.pivotCacheDefinition, `pivotCache/pivotCacheDefinition${pivot.number}.xml`)),
235
+ : [relationship(personsRelId, REL.person, targetFromWorkbook(PERSONS_PART))]),
236
+ ...preservedRels.map((ref) => relationship(ref.relId, ref.relType, escapeAttr(targetFromWorkbook(ref.entryPath)))),
237
+ ...pivots.map((pivot) => relationship(pivot.workbookRelId, REL.pivotCacheDefinition, targetFromWorkbook(pivotCacheDefinitionPart(pivot.number)))),
231
238
  ]);
232
239
  }
233
240
  export function corePropsXml(properties) {
@@ -1,4 +1,4 @@
1
- import type { Cell } from '../../core/cell.ts';
1
+ import { type Cell } from '../../core/cell.ts';
2
2
  import type { ColumnProperties, RowProperties, Worksheet } from '../../core/worksheet.ts';
3
3
  import { type HyperlinkPlan } from './hyperlinks.ts';
4
4
  import type { BackgroundPlan, CommentPlan, DrawingPlan, PivotPlan, PreservedReferencePlan, PrinterSettingsPlan, TablePlan, ThreadedCommentPlan } from './package-plan.ts';
@@ -1,16 +1,18 @@
1
1
  import { decodeRange, encodeAddress } from '../../core/address.js';
2
+ import { cellHasOwnStyle } from '../../core/cell.js';
2
3
  import { DEFAULT_DATE_NUMFMT, dateToSerial } from '../../core/date.js';
3
4
  import { mangleFormula } from '../../core/formula.js';
4
5
  import { NAMED_STYLE_ID } from '../../core/internal.js';
6
+ import { CELL_STYLE_FACETS, pickStyleFacets } from '../../core/style.js';
5
7
  import { detectValueType, isDataTableFormulaValue, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, } from '../../core/value.js';
6
8
  import { AuthoringError, InternalError } from '../../errors.js';
7
- import { escapeAttr, escapeSpreadsheetText, escapeText, numberText, textElement, XML_DECLARATION, } from '../../xml/xml.js';
8
- import { relativePartPath } from '../opc/part-paths.js';
9
+ import { assertWritableNumber, escapeAttr, escapeSpreadsheetText, escapeText, numberText, textAttr, textElement, XML_DECLARATION, } from '../../xml/xml.js';
9
10
  import { relationship, relationshipsPart } from '../opc/rels.js';
10
11
  import { conditionalFormattingsExtXml, conditionalFormattingsXml } from './conditional-formatting.js';
11
12
  import { dataValidationsExtXml, dataValidationsXml } from './data-validation.js';
12
13
  import { hyperlinksXml } from './hyperlinks.js';
13
14
  import { SLICER_LIST_EXT_URI } from './namespaces.js';
15
+ import { commentsPart, drawingPart, mediaPart, pivotTablePart, printerSettingsPart, tablePart, targetFromWorksheet, threadedCommentsPart, vmlDrawingPart, } from './part-names.js';
14
16
  import { NS, REL } from './relationships.js';
15
17
  import { richTextRunsXml } from './rich-text.js';
16
18
  import { planSharedFormulas } from './shared-formulas.js';
@@ -114,7 +116,7 @@ export function buildColumnDefaults(sheet) {
114
116
  }
115
117
  export function renderRow(entry, ctx) {
116
118
  const { number, cells, properties } = entry;
117
- const rendered = cells.filter((cell) => cell.value !== null || hasOwnStyle(cell));
119
+ const rendered = cells.filter((cell) => cell.value !== null || cellHasOwnStyle(cell));
118
120
  const attrs = rowAttrs(properties, ctx.styles, ctx.collapsedSummaries.has(number));
119
121
  if (rendered.length === 0 && attrs === '')
120
122
  return { xml: '', minCol: Infinity, maxCol: -Infinity };
@@ -136,16 +138,20 @@ export function renderRow(entry, ctx) {
136
138
  return { xml: `<row r="${number}"${attrs}>${cellsXml}</row>`, minCol, maxCol };
137
139
  }
138
140
  function composeCellStyle(cell, rowFill, colDef) {
139
- return {
140
- fill: cell.fill ?? rowFill ?? colDef?.fill,
141
- numFmt: cell.numFmt ?? colDef?.numFmt ?? dateDefaultNumFmt(cell.value),
142
- font: cell.font ?? colDef?.font,
143
- border: cell.border ?? colDef?.border,
144
- alignment: cell.alignment ?? colDef?.alignment,
145
- protection: cell.protection ?? colDef?.protection,
146
- quotePrefix: cell.quotePrefix,
147
- xfId: cell[NAMED_STYLE_ID],
148
- };
141
+ const style = {};
142
+ for (const facet of CELL_STYLE_FACETS)
143
+ inheritFacet(style, facet, cell, colDef);
144
+ style.fill = cell.fill ?? rowFill ?? colDef?.fill;
145
+ style.numFmt = cell.numFmt ?? colDef?.numFmt ?? dateDefaultNumFmt(cell.value);
146
+ if (cell.quotePrefix !== undefined)
147
+ style.quotePrefix = cell.quotePrefix;
148
+ const xfId = cell[NAMED_STYLE_ID];
149
+ if (xfId !== undefined)
150
+ style.xfId = xfId;
151
+ return style;
152
+ }
153
+ function inheritFacet(target, key, cell, colDef) {
154
+ target[key] = cell[key] ?? colDef?.[key];
149
155
  }
150
156
  function worksheetExtLstXml(sheet, slicerRelIds) {
151
157
  const exts = [
@@ -199,36 +205,38 @@ function tablePartsXml(tables) {
199
205
  }
200
206
  export function worksheetRelsXml(tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedReferences, pivots) {
201
207
  const rels = [
202
- ...tables.map(({ relId, number }) => relationship(relId, REL.table, `../tables/table${number}.xml`)),
203
- ...pivots.map((pivot) => relationship(pivot.sheetRelId, REL.pivotTable, `../pivotTables/pivotTable${pivot.number}.xml`)),
208
+ ...tables.map(({ relId, number }) => relationship(relId, REL.table, targetFromWorksheet(tablePart(number)))),
209
+ ...pivots.map((pivot) => relationship(pivot.sheetRelId, REL.pivotTable, targetFromWorksheet(pivotTablePart(pivot.number)))),
204
210
  ...(drawing === null
205
211
  ? []
206
- : [relationship(drawing.relId, REL.drawing, `../drawings/drawing${drawing.number}.xml`)]),
212
+ : [
213
+ relationship(drawing.relId, REL.drawing, targetFromWorksheet(drawingPart(drawing.number))),
214
+ ]),
207
215
  ...(comments === null
208
216
  ? []
209
217
  : [
210
- relationship(comments.vmlRelId, REL.vmlDrawing, `../drawings/vmlDrawing${comments.number}.vml`),
211
- relationship(comments.commentsRelId, REL.comments, `../comments${comments.number}.xml`),
218
+ relationship(comments.vmlRelId, REL.vmlDrawing, targetFromWorksheet(vmlDrawingPart(comments.number))),
219
+ relationship(comments.commentsRelId, REL.comments, targetFromWorksheet(commentsPart(comments.number))),
212
220
  ]),
213
221
  ...(threadedComments === null
214
222
  ? []
215
223
  : [
216
- relationship(threadedComments.relId, REL.threadedComment, `../threadedComments/threadedComment${threadedComments.number}.xml`),
224
+ relationship(threadedComments.relId, REL.threadedComment, targetFromWorksheet(threadedCommentsPart(threadedComments.number))),
217
225
  ]),
218
226
  ...(printerSettings === null
219
227
  ? []
220
228
  : [
221
- relationship(printerSettings.relId, REL.printerSettings, `../printerSettings/printerSettings${printerSettings.number}.bin`),
229
+ relationship(printerSettings.relId, REL.printerSettings, targetFromWorksheet(printerSettingsPart(printerSettings.number))),
222
230
  ]),
223
231
  ...(background === null
224
232
  ? []
225
233
  : [
226
- relationship(background.relId, REL.image, `../media/image${background.mediaNumber}.${background.extension}`),
234
+ relationship(background.relId, REL.image, targetFromWorksheet(mediaPart(background.mediaNumber, background.extension))),
227
235
  ]),
228
- ...preservedReferences.map((reference) => relationship(reference.relId, reference.relType, escapeAttr(relativePartPath('xl/worksheets/sheet1.xml', reference.entryPath)))),
236
+ ...preservedReferences.map((reference) => relationship(reference.relId, reference.relType, escapeAttr(targetFromWorksheet(reference.entryPath)))),
229
237
  ...hyperlinks
230
238
  .filter((link) => link.relId !== undefined && link.target !== undefined)
231
- .map((link) => relationship(link.relId, REL.hyperlink, escapeAttr(link.target), {
239
+ .map((link) => relationship(link.relId, REL.hyperlink, link.target, {
232
240
  external: true,
233
241
  })),
234
242
  ];
@@ -243,12 +251,16 @@ function sheetFormatPr(properties, outlineLevel) {
243
251
  }
244
252
  if (properties.defaultRowHeight !== undefined)
245
253
  attrs += ' customHeight="1"';
246
- if (outlineLevel.col > 0)
247
- attrs += ` outlineLevelCol="${outlineLevel.col}"`;
248
- if (outlineLevel.row > 0)
249
- attrs += ` outlineLevelRow="${outlineLevel.row}"`;
254
+ attrs += outlineAttr('outlineLevelCol', outlineLevel.col);
255
+ attrs += outlineAttr('outlineLevelRow', outlineLevel.row);
250
256
  return `<sheetFormatPr${attrs}/>`;
251
257
  }
258
+ function outlineAttr(name, level) {
259
+ if (level === undefined)
260
+ return '';
261
+ const text = numberText(level);
262
+ return level > 0 ? ` ${name}="${text}"` : '';
263
+ }
252
264
  function maxColumnOutlineLevel(sheet) {
253
265
  let max = 0;
254
266
  for (const column of sheet.columns())
@@ -285,22 +297,16 @@ function colBody(properties, styles) {
285
297
  attrs += ' hidden="1"';
286
298
  meaningful = true;
287
299
  }
288
- if (properties.outlineLevel !== undefined && properties.outlineLevel > 0) {
289
- attrs += ` outlineLevel="${properties.outlineLevel}"`;
300
+ const outline = outlineAttr('outlineLevel', properties.outlineLevel);
301
+ if (outline !== '') {
302
+ attrs += outline;
290
303
  meaningful = true;
291
304
  }
292
305
  if (properties.collapsed) {
293
306
  attrs += ' collapsed="1"';
294
307
  meaningful = true;
295
308
  }
296
- const style = styles.styleId({
297
- fill: properties.fill,
298
- numFmt: properties.numFmt,
299
- font: properties.font,
300
- border: properties.border,
301
- alignment: properties.alignment,
302
- protection: properties.protection,
303
- });
309
+ const style = styles.styleId(pickStyleFacets(properties));
304
310
  if (style !== 0) {
305
311
  attrs += ` style="${style}"`;
306
312
  meaningful = true;
@@ -315,9 +321,7 @@ function rowAttrs(properties, styles, collapsedSummary) {
315
321
  attrs += ` ht="${numberText(properties.height)}" customHeight="1"`;
316
322
  if (properties.hidden)
317
323
  attrs += ' hidden="1"';
318
- if (properties.outlineLevel !== undefined && properties.outlineLevel > 0) {
319
- attrs += ` outlineLevel="${properties.outlineLevel}"`;
320
- }
324
+ attrs += outlineAttr('outlineLevel', properties.outlineLevel);
321
325
  if (properties.collapsed || collapsedSummary)
322
326
  attrs += ' collapsed="1"';
323
327
  const style = styles.styleId({ fill: properties.fill });
@@ -331,6 +335,7 @@ function scanRowOutline(sheet) {
331
335
  let maxLevel = 0;
332
336
  for (const { number, properties } of sheet.rows()) {
333
337
  const rowLevel = properties?.outlineLevel ?? 0;
338
+ assertWritableNumber(rowLevel);
334
339
  level.set(number, rowLevel);
335
340
  hidden.set(number, properties?.hidden ?? false);
336
341
  if (rowLevel > maxLevel)
@@ -407,16 +412,6 @@ function cellXml(cell, style, shared, sharedStrings) {
407
412
  return `<c r="${ref}"${s}/>`;
408
413
  throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm: every CellValue kind is handled above`);
409
414
  }
410
- function hasOwnStyle(cell) {
411
- return (cell.fill !== undefined ||
412
- cell.numFmt !== undefined ||
413
- cell.font !== undefined ||
414
- cell.border !== undefined ||
415
- cell.alignment !== undefined ||
416
- cell.protection !== undefined ||
417
- cell.quotePrefix === true ||
418
- cell[NAMED_STYLE_ID] !== undefined);
419
- }
420
415
  function cellFormulaXml(ref, s, value, shared) {
421
416
  if (shared !== undefined) {
422
417
  if (shared.ref !== undefined && isFormulaValue(value)) {
@@ -430,8 +425,8 @@ function cellFormulaXml(ref, s, value, shared) {
430
425
  const attrs = `ref="${escapeAttr(value.ref)}"` +
431
426
  ` dt2D="${value.dataTable2D ? 1 : 0}"` +
432
427
  ` dtr="${value.dataTableRow ? 1 : 0}"` +
433
- (value.r1 !== undefined ? ` r1="${escapeAttr(value.r1)}"` : '') +
434
- (value.r2 !== undefined ? ` r2="${escapeAttr(value.r2)}"` : '');
428
+ textAttr('r1', value.r1) +
429
+ textAttr('r2', value.r2);
435
430
  return formulaBodyXml(ref, s, `<f t="dataTable" ${attrs}/>`, value.result);
436
431
  }
437
432
  if (isFormulaValue(value)) {
@@ -88,7 +88,14 @@ export declare class WorksheetStreamWriter {
88
88
  */
89
89
  flushRow(number: number, cells: readonly Cell[]): void;
90
90
  flushedSheet(): FlushedSheet | undefined;
91
- /** Address a cell by its A1 reference to read or style it before the sheet is committed. */
91
+ /**
92
+ * Address a cell by its A1 reference to read or style it before the sheet is committed. The row it
93
+ * names must still be live: a row whose {@link StreamedRow.commit} has run is finished.
94
+ *
95
+ * @throws {AuthoringError} if the reference names an already-committed row. That row's `<row>` is
96
+ * rendered and its cells are released, so the cell this would materialise could only be written
97
+ * as a second row carrying the same number.
98
+ */
92
99
  getCell(reference: string): Cell;
93
100
  /**
94
101
  * Attach a data validation to a range before the sheet is committed. Delegates to the model, so the
@@ -165,6 +172,12 @@ export declare class WorkbookStreamWriter {
165
172
  * Assemble the workbook into its package, stream the bytes through {@link stream}, and resolve with
166
173
  * the same bytes. Every sheet is frozen first, so a row added after this rejects legibly. Idempotent
167
174
  * only in that a second call throws rather than re-emitting.
175
+ *
176
+ * If assembling or zipping the package fails, the returned promise rejects *and* every stream this
177
+ * writer was given or handed out is destroyed with that error. A caller piping {@link stream}, or
178
+ * awaiting its own sink, therefore sees an `error` rather than waiting on a stream that will never
179
+ * end; and it is an `error` rather than a clean `end` because the bytes written so far are a
180
+ * truncated package that must not be read as a whole one.
168
181
  */
169
182
  commit(): Promise<Uint8Array>;
170
183
  }
@@ -1,7 +1,8 @@
1
1
  import { createWriteStream } from 'node:fs';
2
2
  import { PassThrough } from 'node:stream';
3
3
  import { Zip, ZipDeflate } from 'fflate';
4
- import { encodeAddress } from '../../core/address.js';
4
+ import { concat } from '../../bytes.js';
5
+ import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
5
6
  import { INTERNAL } from '../../core/internal.js';
6
7
  import { isSharedFormulaValue } from '../../core/value.js';
7
8
  import { Workbook } from '../../core/workbook.js';
@@ -36,6 +37,7 @@ export class WorksheetStreamWriter {
36
37
  #lastRow = 0;
37
38
  #columnDefaults;
38
39
  #flushedRows = [];
40
+ #flushedNumbers = new Set();
39
41
  #extent = new Extent();
40
42
  #maxRowOutlineLevel = 0;
41
43
  constructor(sheet, eager, styles) {
@@ -97,6 +99,7 @@ export class WorksheetStreamWriter {
97
99
  this.#flushedRows.push({ number, xml });
98
100
  this.#extent.add(number, minCol, maxCol);
99
101
  }
102
+ this.#flushedNumbers.add(number);
100
103
  this.#sheet[INTERNAL].evictRow(number);
101
104
  }
102
105
  flushedSheet() {
@@ -110,6 +113,12 @@ export class WorksheetStreamWriter {
110
113
  }
111
114
  getCell(reference) {
112
115
  this.#assertOpen();
116
+ const target = tryDecodeCellRef(reference);
117
+ if (target !== undefined && this.#flushedNumbers.has(target.row)) {
118
+ throw new AuthoringError(`cell ${reference} of streamed sheet "${this.#sheet.name}" is in row ${target.row}, which is ` +
119
+ 'already committed: its bytes are written and its cells released, so this value could only ' +
120
+ 'be emitted as a second row with that number. Style a row before committing it.');
121
+ }
113
122
  return this.#sheet.getCell(reference);
114
123
  }
115
124
  addDataValidation(sqref, rule, options = {}) {
@@ -178,23 +187,17 @@ export class WorkbookStreamWriter {
178
187
  return this.#stream;
179
188
  }
180
189
  addImage(options) {
181
- if (this.#committed) {
182
- throw new AuthoringError('the workbook is already committed: no more images can be registered');
183
- }
190
+ this.#assertOpen('no more images can be registered');
184
191
  return this.#workbook.addImage(options);
185
192
  }
186
193
  addWorksheet(name, options = {}) {
187
- if (this.#committed) {
188
- throw new AuthoringError('the workbook is already committed: no more worksheets can be added');
189
- }
194
+ this.#assertOpen('no more worksheets can be added');
190
195
  const sheet = new WorksheetStreamWriter(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles);
191
196
  this.#sheets.push(sheet);
192
197
  return sheet;
193
198
  }
194
199
  async commit() {
195
- if (this.#committed) {
196
- throw new AuthoringError('the workbook is already committed');
197
- }
200
+ this.#assertOpen('its package is already assembled and cannot be re-emitted');
198
201
  this.#committed = true;
199
202
  for (const sheet of this.#sheets)
200
203
  sheet.commit();
@@ -206,22 +209,36 @@ export class WorkbookStreamWriter {
206
209
  if (sheetFlushed)
207
210
  flushed.set(sheet.model, sheetFlushed);
208
211
  }
209
- const parts = buildPackageParts(this.#workbook, {
210
- ...this.#writeOptions,
211
- styles: this.#styles,
212
- flushed,
213
- });
214
212
  const owned = this.#stream;
215
213
  const sink = this.#sink;
216
214
  const sinkSettled = sink ? settleOnFinish(sink) : undefined;
217
- const bytes = await streamZipPackage(parts, (chunk) => {
218
- owned?.write(chunk);
219
- sink?.write(chunk);
220
- });
221
- owned?.end();
222
- sink?.end();
223
- await sinkSettled;
224
- return bytes;
215
+ try {
216
+ const parts = buildPackageParts(this.#workbook, {
217
+ ...this.#writeOptions,
218
+ styles: this.#styles,
219
+ flushed,
220
+ });
221
+ const bytes = await streamZipPackage(parts, (chunk) => {
222
+ owned?.write(chunk);
223
+ sink?.write(chunk);
224
+ });
225
+ owned?.end();
226
+ sink?.end();
227
+ await sinkSettled;
228
+ return bytes;
229
+ }
230
+ catch (error) {
231
+ const failure = error instanceof Error ? error : new Error(String(error));
232
+ sinkSettled?.catch(() => { });
233
+ owned?.destroy(failure);
234
+ sink?.destroy(failure);
235
+ throw error;
236
+ }
237
+ }
238
+ #assertOpen(what) {
239
+ if (this.#committed) {
240
+ throw new AuthoringError(`the workbook is already committed: ${what}`);
241
+ }
225
242
  }
226
243
  }
227
244
  function settleOnFinish(sink) {
@@ -252,13 +269,3 @@ function streamZipPackage(parts, onChunk) {
252
269
  zip.end();
253
270
  });
254
271
  }
255
- function concat(chunks) {
256
- const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
257
- const out = new Uint8Array(total);
258
- let offset = 0;
259
- for (const chunk of chunks) {
260
- out.set(chunk, offset);
261
- offset += chunk.length;
262
- }
263
- return out;
264
- }
@@ -81,6 +81,11 @@ export declare function createStyleRegistry(workbook: Workbook): StyleRegistry;
81
81
  * streaming writer can drive the identical parts through a streamed zip container rather than
82
82
  * `zipSync`. Neither writer duplicates a byte of serialisation.
83
83
  *
84
+ * Three steps, and their order is load-bearing. The package is planned in full before anything is
85
+ * serialised, so a drawing's embed and a pivot's cache can target a part number that already exists.
86
+ * The sheets are serialised before the parts are emitted, because interning a style is a side effect
87
+ * of that pass and `xl/styles.xml` is one of the parts emitted in the third step.
88
+ *
84
89
  * @throws {AuthoringError} if the workbook has no worksheets, or holds a value the writer cannot represent.
85
90
  */
86
91
  export declare function buildPackageParts(workbook: Workbook, options?: InternalWriteOptions): Record<string, Uint8Array>;