@shbernal/ts-xlsx 3.0.0 → 3.1.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 (213) hide show
  1. package/README.md +4 -1
  2. package/dist/bytes.d.ts +7 -0
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +60 -2
  5. package/dist/core/address.js +40 -24
  6. package/dist/core/autofilter.d.ts +4 -3
  7. package/dist/core/autofilter.js +31 -30
  8. package/dist/core/axis-handle.d.ts +2 -0
  9. package/dist/core/axis-handle.js +5 -2
  10. package/dist/core/cell.d.ts +4 -3
  11. package/dist/core/cell.js +4 -6
  12. package/dist/core/clone.d.ts +29 -0
  13. package/dist/core/clone.js +24 -0
  14. package/dist/core/color-resolution.js +2 -4
  15. package/dist/core/column.d.ts +1 -0
  16. package/dist/core/column.js +3 -0
  17. package/dist/core/comment-thread.js +2 -1
  18. package/dist/core/conditional-formatting-overlay.d.ts +2 -1
  19. package/dist/core/conditional-formatting-overlay.js +2 -2
  20. package/dist/core/conditional-formatting.d.ts +9 -1
  21. package/dist/core/conditional-formatting.js +34 -18
  22. package/dist/core/containers.d.ts +12 -0
  23. package/dist/core/containers.js +5 -0
  24. package/dist/core/data-validation-overlay.d.ts +2 -1
  25. package/dist/core/data-validation-overlay.js +4 -4
  26. package/dist/core/data-validation.d.ts +6 -0
  27. package/dist/core/data-validation.js +15 -4
  28. package/dist/core/date-format.d.ts +20 -0
  29. package/dist/core/date-format.js +160 -0
  30. package/dist/core/date.d.ts +43 -8
  31. package/dist/core/date.js +18 -2
  32. package/dist/core/formula.d.ts +26 -0
  33. package/dist/core/formula.js +20 -4
  34. package/dist/core/grid-edits.d.ts +2 -3
  35. package/dist/core/grid-edits.js +69 -70
  36. package/dist/core/grid-shift.d.ts +58 -9
  37. package/dist/core/grid-shift.js +26 -4
  38. package/dist/core/image.d.ts +13 -9
  39. package/dist/core/image.js +7 -5
  40. package/dist/core/internal.d.ts +8 -2
  41. package/dist/core/limits.d.ts +23 -0
  42. package/dist/core/limits.js +4 -0
  43. package/dist/core/merge.d.ts +5 -4
  44. package/dist/core/merge.js +18 -19
  45. package/dist/core/pivot-table.js +63 -45
  46. package/dist/core/protection.js +2 -11
  47. package/dist/core/range.js +34 -30
  48. package/dist/core/row-input.js +2 -2
  49. package/dist/core/row.d.ts +1 -0
  50. package/dist/core/row.js +3 -0
  51. package/dist/core/style.d.ts +85 -70
  52. package/dist/core/style.js +92 -28
  53. package/dist/core/table-style.d.ts +1 -1
  54. package/dist/core/table-style.js +4 -4
  55. package/dist/core/table.d.ts +27 -9
  56. package/dist/core/table.js +51 -34
  57. package/dist/core/theme.js +6 -5
  58. package/dist/core/value.d.ts +9 -0
  59. package/dist/core/value.js +36 -34
  60. package/dist/core/workbook-media.d.ts +34 -0
  61. package/dist/core/workbook-media.js +44 -0
  62. package/dist/core/workbook-styles.d.ts +67 -1
  63. package/dist/core/workbook-vba.js +3 -2
  64. package/dist/core/workbook.d.ts +23 -2
  65. package/dist/core/workbook.js +24 -40
  66. package/dist/core/worksheet-comments.d.ts +2 -1
  67. package/dist/core/worksheet-comments.js +20 -13
  68. package/dist/core/worksheet-merges.d.ts +44 -0
  69. package/dist/core/worksheet-merges.js +59 -0
  70. package/dist/core/worksheet-model.d.ts +9 -1
  71. package/dist/core/worksheet-model.js +24 -9
  72. package/dist/core/worksheet.d.ts +55 -6
  73. package/dist/core/worksheet.js +36 -44
  74. package/dist/customui/ribbon.js +2 -1
  75. package/dist/entries/core.d.ts +18 -13
  76. package/dist/entries/core.js +12 -6
  77. package/dist/entries/xlsx.d.ts +1 -1
  78. package/dist/errors.d.ts +64 -0
  79. package/dist/errors.js +22 -0
  80. package/dist/hex.d.ts +8 -0
  81. package/dist/hex.js +3 -0
  82. package/dist/io/csv/delimiter.d.ts +9 -0
  83. package/dist/io/csv/delimiter.js +6 -0
  84. package/dist/io/csv/read.js +11 -4
  85. package/dist/io/csv/write.d.ts +9 -2
  86. package/dist/io/csv/write.js +9 -38
  87. package/dist/io/opc/inflate.js +14 -0
  88. package/dist/io/opc/namespaces.d.ts +16 -0
  89. package/dist/io/opc/namespaces.js +4 -0
  90. package/dist/io/opc/part-paths.d.ts +9 -0
  91. package/dist/io/opc/part-paths.js +1 -1
  92. package/dist/io/opc/read-opc.d.ts +54 -14
  93. package/dist/io/opc/read-opc.js +42 -20
  94. package/dist/io/opc/read-options.d.ts +5 -1
  95. package/dist/io/opc/sniff-format.d.ts +3 -3
  96. package/dist/io/opc/sniff-format.js +2 -2
  97. package/dist/io/style/xf-style.d.ts +23 -1
  98. package/dist/io/style/xf-style.js +8 -0
  99. package/dist/io/xlsb/formula.d.ts +2 -2
  100. package/dist/io/xlsb/formula.js +49 -34
  101. package/dist/io/xlsb/primitives.js +8 -14
  102. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  103. package/dist/io/xlsb/ptg-functions.js +385 -596
  104. package/dist/io/xlsb/read-styles.js +45 -81
  105. package/dist/io/xlsb/read-worksheet.d.ts +20 -1
  106. package/dist/io/xlsb/read-worksheet.js +73 -89
  107. package/dist/io/xlsb/read.d.ts +6 -3
  108. package/dist/io/xlsb/read.js +44 -23
  109. package/dist/io/xlsb/record-stream.d.ts +30 -0
  110. package/dist/io/xlsb/record-stream.js +20 -0
  111. package/dist/io/xlsb/record-types.d.ts +1 -5
  112. package/dist/io/xlsb/record-types.js +1 -5
  113. package/dist/io/xlsx/cell-accumulator.d.ts +8 -1
  114. package/dist/io/xlsx/cell-accumulator.js +32 -9
  115. package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
  116. package/dist/io/xlsx/cell-style-resolution.js +29 -0
  117. package/dist/io/xlsx/cell-value.d.ts +5 -3
  118. package/dist/io/xlsx/cell-value.js +13 -15
  119. package/dist/io/xlsx/color-xml.js +3 -2
  120. package/dist/io/xlsx/column-budget.d.ts +26 -0
  121. package/dist/io/xlsx/column-budget.js +22 -0
  122. package/dist/io/xlsx/comments.d.ts +6 -2
  123. package/dist/io/xlsx/comments.js +36 -32
  124. package/dist/io/xlsx/conditional-formatting.d.ts +6 -3
  125. package/dist/io/xlsx/conditional-formatting.js +30 -26
  126. package/dist/io/xlsx/data-validation.js +29 -27
  127. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  128. package/dist/io/xlsx/edit-vba.js +14 -21
  129. package/dist/io/xlsx/font-xml.d.ts +7 -0
  130. package/dist/io/xlsx/font-xml.js +104 -0
  131. package/dist/io/xlsx/hyperlinks.d.ts +37 -5
  132. package/dist/io/xlsx/hyperlinks.js +22 -13
  133. package/dist/io/xlsx/images.js +11 -13
  134. package/dist/io/xlsx/namespaces.d.ts +16 -0
  135. package/dist/io/xlsx/namespaces.js +7 -0
  136. package/dist/io/xlsx/package-plan.d.ts +14 -2
  137. package/dist/io/xlsx/package-plan.js +39 -11
  138. package/dist/io/xlsx/pivot.js +8 -7
  139. package/dist/io/xlsx/read-parts.d.ts +59 -0
  140. package/dist/io/xlsx/read-parts.js +252 -0
  141. package/dist/io/xlsx/read-pivot.js +2 -1
  142. package/dist/io/xlsx/read-repair.d.ts +26 -0
  143. package/dist/io/xlsx/read-repair.js +39 -0
  144. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +6 -3
  145. package/dist/io/xlsx/read-rows.d.ts +18 -5
  146. package/dist/io/xlsx/read-rows.js +45 -31
  147. package/dist/io/xlsx/read-shared-strings.js +3 -2
  148. package/dist/io/xlsx/read-styles.d.ts +1 -6
  149. package/dist/io/xlsx/read-styles.js +6 -71
  150. package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
  151. package/dist/io/xlsx/read-workbook-xml.js +170 -0
  152. package/dist/io/xlsx/read-worksheet.d.ts +2 -1
  153. package/dist/io/xlsx/read-worksheet.js +35 -164
  154. package/dist/io/xlsx/read.d.ts +5 -12
  155. package/dist/io/xlsx/read.js +73 -406
  156. package/dist/io/xlsx/rich-text.js +1 -1
  157. package/dist/io/xlsx/row-position.d.ts +18 -0
  158. package/dist/io/xlsx/row-position.js +11 -0
  159. package/dist/io/xlsx/row-xml.d.ts +128 -0
  160. package/dist/io/xlsx/row-xml.js +203 -0
  161. package/dist/io/xlsx/shared-formulas.js +2 -2
  162. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  163. package/dist/io/xlsx/sheet-properties.js +145 -9
  164. package/dist/io/xlsx/style-elements.d.ts +43 -0
  165. package/dist/io/xlsx/style-elements.js +156 -0
  166. package/dist/io/xlsx/styles.d.ts +12 -3
  167. package/dist/io/xlsx/styles.js +5 -207
  168. package/dist/io/xlsx/tables.js +10 -14
  169. package/dist/io/xlsx/theme-xml.d.ts +26 -4
  170. package/dist/io/xlsx/theme-xml.js +107 -69
  171. package/dist/io/xlsx/threaded-comments.d.ts +8 -10
  172. package/dist/io/xlsx/threaded-comments.js +2 -1
  173. package/dist/io/xlsx/workbook-xml.d.ts +54 -4
  174. package/dist/io/xlsx/workbook-xml.js +60 -34
  175. package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
  176. package/dist/io/xlsx/worksheet-xml.js +48 -229
  177. package/dist/io/xlsx/write-stream.d.ts +65 -16
  178. package/dist/io/xlsx/write-stream.js +50 -20
  179. package/dist/io/xlsx/write.d.ts +1 -2
  180. package/dist/io/xlsx/write.js +114 -74
  181. package/dist/rel-type.d.ts +16 -0
  182. package/dist/rel-type.js +6 -0
  183. package/dist/token-set.d.ts +13 -0
  184. package/dist/token-set.js +4 -1
  185. package/dist/vba/bytes.d.ts +14 -5
  186. package/dist/vba/bytes.js +16 -6
  187. package/dist/vba/cfb-format.d.ts +8 -0
  188. package/dist/vba/cfb-format.js +1 -0
  189. package/dist/vba/cfb-writer.d.ts +9 -0
  190. package/dist/vba/cfb-writer.js +12 -9
  191. package/dist/vba/cfb.d.ts +13 -1
  192. package/dist/vba/cfb.js +73 -51
  193. package/dist/vba/codepage.js +2 -1
  194. package/dist/vba/errors.d.ts +7 -0
  195. package/dist/vba/ms-ovba.js +51 -16
  196. package/dist/vba/project-editor.js +35 -40
  197. package/dist/vba/project.d.ts +11 -1
  198. package/dist/vba/project.js +27 -8
  199. package/dist/vba/vba-encoding.js +4 -5
  200. package/dist/xml/xml-attrs.d.ts +37 -0
  201. package/dist/xml/xml-attrs.js +30 -0
  202. package/dist/xml/xml-chars.d.ts +40 -0
  203. package/dist/xml/xml-chars.js +12 -0
  204. package/dist/xml/xml-namespaces.d.ts +31 -0
  205. package/dist/xml/xml-namespaces.js +50 -0
  206. package/dist/xml/xml-read.d.ts +72 -10
  207. package/dist/xml/xml-read.js +94 -18
  208. package/dist/xml/xml-scan.d.ts +15 -37
  209. package/dist/xml/xml-scan.js +12 -40
  210. package/dist/xml/xml.d.ts +28 -27
  211. package/dist/xml/xml.js +24 -24
  212. package/package.json +12 -7
  213. /package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +0 -0
@@ -6,9 +6,12 @@ import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
6
6
  import { INTERNAL } from '../../core/internal.js';
7
7
  import { isSharedFormulaValue } from '../../core/value.js';
8
8
  import { Workbook } from '../../core/workbook.js';
9
- import { AuthoringError } from '../../errors.js';
9
+ import { AuthoringError, quoted } from '../../errors.js';
10
10
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
11
- import { buildColumnDefaults, buildPackageParts, createStyleRegistry, Extent, renderRow, } from './write.js';
11
+ import { collectNotes } from './comments.js';
12
+ import { collectHyperlinks } from './hyperlinks.js';
13
+ import { buildColumnDefaults, Extent, renderRow, } from './row-xml.js';
14
+ import { buildPackageParts, createStyleRegistry } from './write.js';
12
15
  export class StreamedRow {
13
16
  #cells;
14
17
  #sheet;
@@ -26,7 +29,7 @@ export class StreamedRow {
26
29
  if (this.#committed)
27
30
  return;
28
31
  this.#committed = true;
29
- this.#sheet?.flushRow(this.#number, this.#cells);
32
+ this.#sheet?.[INTERNAL].flushRow(this.#number, this.#cells);
30
33
  }
31
34
  }
32
35
  export class WorksheetStreamWriter {
@@ -39,12 +42,21 @@ export class WorksheetStreamWriter {
39
42
  #flushedRows = [];
40
43
  #flushedNumbers = new Set();
41
44
  #extent = new Extent();
42
- #maxRowOutlineLevel = 0;
43
- constructor(sheet, eager, styles) {
45
+ #rowOutline = new Map();
46
+ #hyperlinks = [];
47
+ #notes = [];
48
+ #dateEpoch;
49
+ constructor(sheet, eager, styles, dateEpoch) {
44
50
  this.#sheet = sheet;
45
51
  this.#eager = eager;
46
52
  this.#styles = styles;
53
+ this.#dateEpoch = dateEpoch;
47
54
  }
55
+ static [INTERNAL] = {
56
+ create(sheet, eager, styles, dateEpoch) {
57
+ return new WorksheetStreamWriter(sheet, eager, styles, dateEpoch);
58
+ },
59
+ };
48
60
  get name() {
49
61
  return this.#sheet.name;
50
62
  }
@@ -77,45 +89,53 @@ export class WorksheetStreamWriter {
77
89
  });
78
90
  return cells;
79
91
  }
80
- flushRow(number, cells) {
92
+ #flushRow(number, cells) {
81
93
  for (const cell of cells) {
82
94
  if (isSharedFormulaValue(cell.value)) {
83
- throw new AuthoringError(`row ${number} of streamed sheet "${this.#sheet.name}" carries a shared-formula cell; a ` +
95
+ throw new AuthoringError(`row ${number} of streamed sheet ${quoted(this.#sheet.name)} carries a shared-formula cell; a ` +
84
96
  'committed row is finalised before the sheet is planned, so author shared formulas through ' +
85
97
  'getCell (leaving the row uncommitted) instead');
86
98
  }
87
99
  }
88
100
  this.#columnDefaults ??= buildColumnDefaults(this.#sheet);
89
101
  const properties = this.#sheet.getRow(number).properties;
90
- this.#maxRowOutlineLevel = Math.max(this.#maxRowOutlineLevel, properties?.outlineLevel ?? 0);
91
- const { xml, minCol, maxCol } = renderRow({ number, cells, properties }, {
102
+ this.#rowOutline.set(number, {
103
+ outlineLevel: properties?.outlineLevel ?? 0,
104
+ hidden: properties?.hidden ?? false,
105
+ });
106
+ this.#hyperlinks.push(...collectHyperlinks(cells));
107
+ this.#notes.push(...collectNotes(cells));
108
+ const { xml, attrs, minCol, maxCol } = renderRow({ number, cells, properties }, {
92
109
  columnDefaults: this.#columnDefaults,
93
110
  styles: this.#styles,
94
111
  sharedStrings: null,
95
112
  sharedRoles: new Map(),
96
113
  collapsedSummaries: new Set(),
114
+ dateEpoch: this.#dateEpoch,
97
115
  });
98
116
  if (xml !== '') {
99
- this.#flushedRows.push({ number, xml });
117
+ this.#flushedRows.push({ number, xml, attrs });
100
118
  this.#extent.add(number, minCol, maxCol);
101
119
  }
102
120
  this.#flushedNumbers.add(number);
103
121
  this.#sheet[INTERNAL].evictRow(number);
104
122
  }
105
- flushedSheet() {
123
+ #flushedSheet() {
106
124
  if (this.#flushedRows.length === 0)
107
125
  return undefined;
108
126
  return {
109
127
  rows: this.#flushedRows,
110
128
  extent: this.#extent,
111
- maxRowOutlineLevel: this.#maxRowOutlineLevel,
129
+ rowOutline: this.#rowOutline,
130
+ hyperlinks: this.#hyperlinks,
131
+ notes: this.#notes,
112
132
  };
113
133
  }
114
134
  getCell(reference) {
115
135
  this.#assertOpen();
116
136
  const target = tryDecodeCellRef(reference);
117
137
  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 ` +
138
+ throw new AuthoringError(`cell ${reference} of streamed sheet ${quoted(this.#sheet.name)} is in row ${target.row}, which is ` +
119
139
  'already committed: its bytes are written and its cells released, so this value could only ' +
120
140
  'be emitted as a second row with that number. Style a row before committing it.');
121
141
  }
@@ -153,9 +173,15 @@ export class WorksheetStreamWriter {
153
173
  get model() {
154
174
  return this.#sheet;
155
175
  }
176
+ [INTERNAL] = {
177
+ flushRow: (number, cells) => {
178
+ this.#flushRow(number, cells);
179
+ },
180
+ flushedSheet: () => this.#flushedSheet(),
181
+ };
156
182
  #assertOpen() {
157
183
  if (this.#committed) {
158
- throw new AuthoringError(`worksheet "${this.#sheet.name}" is already committed: its rows are finalised and no more can be added`);
184
+ throw new AuthoringError(`worksheet ${quoted(this.#sheet.name)} is already committed: its rows are finalised and no more can be added`);
159
185
  }
160
186
  }
161
187
  }
@@ -172,6 +198,8 @@ export class WorkbookStreamWriter {
172
198
  constructor(options = {}) {
173
199
  this.#writeOptions = { useSharedStrings: options.useSharedStrings ?? false };
174
200
  this.#eager = !this.#writeOptions.useSharedStrings;
201
+ if (options.dateEpoch !== undefined)
202
+ this.#workbook.dateEpoch = options.dateEpoch;
175
203
  this.#styles = createStyleRegistry(this.#workbook);
176
204
  if (options.stream && options.filename) {
177
205
  throw new AuthoringError('provide either a stream or a filename to the streaming writer, not both');
@@ -192,7 +220,7 @@ export class WorkbookStreamWriter {
192
220
  }
193
221
  addWorksheet(name, options = {}) {
194
222
  this.#assertOpen('no more worksheets can be added');
195
- const sheet = new WorksheetStreamWriter(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles);
223
+ const sheet = WorksheetStreamWriter[INTERNAL].create(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles, this.#workbook.dateEpoch);
196
224
  this.#sheets.push(sheet);
197
225
  return sheet;
198
226
  }
@@ -205,12 +233,13 @@ export class WorkbookStreamWriter {
205
233
  this.#workbook.fullCalcOnLoad = true;
206
234
  const flushed = new Map();
207
235
  for (const sheet of this.#sheets) {
208
- const sheetFlushed = sheet.flushedSheet();
236
+ const sheetFlushed = sheet[INTERNAL].flushedSheet();
209
237
  if (sheetFlushed)
210
238
  flushed.set(sheet.model, sheetFlushed);
211
239
  }
212
240
  const owned = this.#stream;
213
241
  const sink = this.#sink;
242
+ const collect = sink === undefined || owned !== undefined;
214
243
  const sinkSettled = sink ? settleOnFinish(sink) : undefined;
215
244
  try {
216
245
  const parts = buildPackageParts(this.#workbook, {
@@ -221,7 +250,7 @@ export class WorkbookStreamWriter {
221
250
  const bytes = await streamZipPackage(parts, (chunk) => {
222
251
  owned?.write(chunk);
223
252
  sink?.write(chunk);
224
- });
253
+ }, collect);
225
254
  owned?.end();
226
255
  sink?.end();
227
256
  await sinkSettled;
@@ -247,7 +276,7 @@ function settleOnFinish(sink) {
247
276
  sink.once('error', reject);
248
277
  });
249
278
  }
250
- function streamZipPackage(parts, onChunk) {
279
+ function streamZipPackage(parts, onChunk, collect) {
251
280
  return new Promise((resolve, reject) => {
252
281
  const collected = [];
253
282
  const zip = new Zip((err, chunk, final) => {
@@ -255,10 +284,11 @@ function streamZipPackage(parts, onChunk) {
255
284
  reject(err);
256
285
  return;
257
286
  }
258
- collected.push(chunk);
287
+ if (collect)
288
+ collected.push(chunk);
259
289
  onChunk(chunk);
260
290
  if (final)
261
- resolve(concat(collected));
291
+ resolve(collect ? concat(collected) : undefined);
262
292
  });
263
293
  for (const [name, data] of Object.entries(parts)) {
264
294
  const entry = new ZipDeflate(name, { level: 6 });
@@ -1,8 +1,7 @@
1
1
  import type { Workbook } from '../../core/workbook.ts';
2
2
  import type { Worksheet } from '../../core/worksheet.ts';
3
+ import type { FlushedSheet } from './row-xml.ts';
3
4
  import { StyleRegistry } from './styles.ts';
4
- import { type FlushedSheet } from './worksheet-xml.ts';
5
- export { buildColumnDefaults, Extent, type FlushedSheet, type RowRenderContext, renderRow, } from './worksheet-xml.ts';
6
5
  /** Options controlling how {@link writeXlsx} serialises a workbook. */
7
6
  export interface WriteOptions {
8
7
  /**
@@ -1,12 +1,13 @@
1
1
  import { strToU8, zip, zipSync } from 'fflate';
2
- import { AuthoringError } from '../../errors.js';
2
+ import { AuthoringError, InternalError, quoted } from '../../errors.js';
3
+ import { isRelType } from '../../rel-type.js';
3
4
  import { relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
4
5
  import { relsPartXml } from '../opc/rels.js';
5
6
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
6
7
  import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
7
- import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
8
+ import { collectHyperlinks, liveCells, planHyperlinks } from './hyperlinks.js';
8
9
  import { drawingRelsXml, drawingXml } from './images.js';
9
- import { planMedia, planPreservedParts, SheetRelIds, } from './package-plan.js';
10
+ import { planMedia, planPreservedParts, RelIdAllocator, } from './package-plan.js';
10
11
  import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, mediaPart, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, printerSettingsPart, SHARED_STRINGS_PART, STYLES_PART, tablePart, threadedCommentsPart, vmlDrawingPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
11
12
  import { pivotCacheDefinitionXml, pivotCacheRecordsXml, pivotTableXml } from './pivot.js';
12
13
  import { REL } from './relationships.js';
@@ -15,9 +16,8 @@ import { StyleRegistry } from './styles.js';
15
16
  import { tableXml } from './tables.js';
16
17
  import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
17
18
  import { personsXml, threadedCommentsXml } from './threaded-comments.js';
18
- import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
19
- import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
20
- export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
19
+ import { appPropsXml, contentTypesXml, corePropsXml, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
20
+ import { worksheetRelsXml, worksheetXml } from './worksheet-xml.js';
21
21
  export function writeXlsx(workbook, options = {}) {
22
22
  return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
23
23
  }
@@ -49,8 +49,8 @@ export function createStyleRegistry(workbook) {
49
49
  return styles;
50
50
  }
51
51
  function planSheet(context) {
52
- const { sheet, index, media, preserved, numbering } = context;
53
- const rels = new SheetRelIds();
52
+ const { sheet, index, media, preserved, numbering, flushed } = context;
53
+ const rels = new RelIdAllocator();
54
54
  const tables = sheet.tables.map((table) => ({
55
55
  table,
56
56
  number: ++numbering.table,
@@ -70,7 +70,7 @@ function planSheet(context) {
70
70
  drawing = { number: ++numbering.drawing, relId: rels.next(), images };
71
71
  }
72
72
  const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
73
- const sheetComments = collectComments(sheet, threads);
73
+ const sheetComments = collectComments(liveCells(sheet), threads, flushed?.notes ?? []);
74
74
  const comments = sheetComments.length === 0
75
75
  ? null
76
76
  : {
@@ -82,7 +82,7 @@ function planSheet(context) {
82
82
  const threadedComments = threads.length === 0 ? null : { number: index + 1, threads, relId: rels.next() };
83
83
  const printerData = sheet.pageSetup.printerSettings;
84
84
  const printerSettings = printerData === undefined ? null : { number: index + 1, data: printerData, relId: rels.next() };
85
- const hyperlinks = planHyperlinks(collectHyperlinks(sheet), rels);
85
+ const hyperlinks = planHyperlinks([...collectHyperlinks(liveCells(sheet)), ...(flushed?.hyperlinks ?? [])].sort((a, b) => a.row - b.row || a.col - b.col), rels);
86
86
  let background = null;
87
87
  if (sheet.backgroundImageId !== undefined) {
88
88
  const { number, image } = media.resolve(sheet.backgroundImageId);
@@ -119,7 +119,7 @@ function resolveSheetReferences(plan) {
119
119
  const preservedDrawingRelId = refs.find((ref) => ref.element === 'drawing')?.relId ?? null;
120
120
  const legacyDrawingHFRelId = refs.find((ref) => ref.element === 'legacyDrawingHF')?.relId ?? null;
121
121
  const slicerRelIds = refs
122
- .filter((ref) => ref.relType.endsWith('/slicer'))
122
+ .filter((ref) => isRelType(ref.relType, 'slicer'))
123
123
  .map((ref) => ref.relId);
124
124
  return {
125
125
  drawingRelId: plan.drawing?.relId ?? preservedDrawingRelId,
@@ -132,25 +132,31 @@ function resolveSheetReferences(plan) {
132
132
  }
133
133
  function assignWorkbookRelIds(context) {
134
134
  const { sheetCount, hasSharedStrings, hasPersons, preservedWorkbook, pivots } = context;
135
- const modeledCount = sheetCount + FIXED_WORKBOOK_REL_COUNT + (hasSharedStrings ? 1 : 0);
136
- const personsRelId = hasPersons ? `rId${modeledCount + 1}` : null;
137
- const preservedBase = modeledCount + (personsRelId === null ? 0 : 1);
138
- const preservedWorkbookRels = preservedWorkbook.map((ref, i) => ({
139
- ...ref,
140
- relId: `rId${preservedBase + 1 + i}`,
141
- }));
142
- const pivotBase = preservedBase + preservedWorkbook.length;
143
- pivots.forEach((pivot, i) => {
144
- pivot.workbookRelId = `rId${pivotBase + 1 + i}`;
145
- });
146
- return { personsRelId, preservedWorkbookRels };
135
+ const ids = new RelIdAllocator();
136
+ const sheetRelIds = Array.from({ length: sheetCount }, () => ids.next());
137
+ const stylesRelId = ids.next();
138
+ const themeRelId = ids.next();
139
+ const sharedStringsRelId = hasSharedStrings ? ids.next() : null;
140
+ const personsRelId = hasPersons ? ids.next() : null;
141
+ const preservedWorkbookRels = preservedWorkbook.map((ref) => ({ ...ref, relId: ids.next() }));
142
+ for (const pivot of pivots)
143
+ pivot.workbookRelId = ids.next();
144
+ return {
145
+ sheetRelIds,
146
+ stylesRelId,
147
+ themeRelId,
148
+ sharedStringsRelId,
149
+ personsRelId,
150
+ preservedWorkbookRels,
151
+ };
147
152
  }
148
- function planPackage(workbook, sheets) {
153
+ function planPackage(workbook, sheets, flushed) {
149
154
  const media = planMedia(workbook, sheets);
150
155
  const generatedDrawingCount = sheets.filter((sheet) => sheet.images.length > 0).length;
151
- const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length);
156
+ const generatedPivotCount = sheets.reduce((total, sheet) => total + sheet.pivotTables.length, 0);
157
+ const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length, generatedPivotCount);
152
158
  const numbering = { table: 0, drawing: 0, pivot: 0 };
153
- const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering }));
159
+ const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering, flushed: flushed?.get(sheet) }));
154
160
  return {
155
161
  media,
156
162
  preserved,
@@ -159,10 +165,21 @@ function planPackage(workbook, sheets) {
159
165
  allPivots: perSheet.flatMap((plan) => plan.pivots),
160
166
  };
161
167
  }
162
- function serialiseSheets(workbook, sheets, plan, styles, sharedStrings, flushed) {
168
+ function serialiseSheets(context) {
169
+ const { workbook, sheets, plan, styles, sharedStrings, flushed } = context;
163
170
  return sheets.map((sheet, i) => {
164
171
  const sheetPlan = plan.perSheet[i];
165
- return worksheetXml(sheet, sheetPlan.tables, styles, resolveSheetReferences(sheetPlan), sheetPlan.hyperlinks, sharedStrings, i === workbook.activeTabIndex, flushed?.get(sheet));
172
+ return worksheetXml({
173
+ sheet,
174
+ tables: sheetPlan.tables,
175
+ styles,
176
+ references: resolveSheetReferences(sheetPlan),
177
+ hyperlinks: sheetPlan.hyperlinks,
178
+ sharedStrings,
179
+ dateEpoch: workbook.dateEpoch,
180
+ active: i === workbook.activeTabIndex,
181
+ flushed: flushed?.get(sheet),
182
+ });
166
183
  });
167
184
  }
168
185
  function emitPackageParts(context) {
@@ -174,43 +191,56 @@ function emitPackageParts(context) {
174
191
  const printerSettingsNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.printerSettings);
175
192
  const threadedCommentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.threadedComments);
176
193
  const persons = threadedCommentNumbers.length === 0 ? [] : workbook.persons;
177
- const { personsRelId, preservedWorkbookRels } = assignWorkbookRelIds({
194
+ const workbookRels = assignWorkbookRelIds({
178
195
  sheetCount: sheets.length,
179
196
  hasSharedStrings,
180
197
  hasPersons: persons.length > 0,
181
198
  preservedWorkbook: preserved.workbook,
182
199
  pivots: allPivots,
183
200
  });
184
- const files = {
185
- '[Content_Types].xml': strToU8(contentTypesXml(sheets.length, allTables, commentNumbers, drawingNumbers, printerSettingsNumbers, media.extensions, hasSharedStrings, preserved.parts, allPivots, preservedWorkbookRels, threadedCommentNumbers, persons.length > 0)),
186
- '_rels/.rels': strToU8(rootRelsXml(preserved.root)),
187
- [CORE_PROPS_PART]: strToU8(corePropsXml(workbook.properties)),
188
- [APP_PROPS_PART]: strToU8(appPropsXml(workbook.properties)),
189
- [WORKBOOK_PART]: strToU8(workbookXml(workbook, preservedWorkbookRels, allPivots)),
190
- [relsPathFor(WORKBOOK_PART)]: strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
191
- [STYLES_PART]: strToU8(styles.toXml()),
192
- };
201
+ const { preservedWorkbookRels } = workbookRels;
202
+ const files = new PackageFiles();
203
+ files.add('[Content_Types].xml', strToU8(contentTypesXml({
204
+ sheetCount: sheets.length,
205
+ tables: allTables,
206
+ commentNumbers,
207
+ drawingNumbers,
208
+ printerSettingsNumbers,
209
+ mediaExtensions: media.extensions,
210
+ hasSharedStrings,
211
+ preservedParts: preserved.parts,
212
+ pivots: allPivots,
213
+ preservedWorkbookRefs: preservedWorkbookRels,
214
+ threadedCommentNumbers,
215
+ hasPersons: persons.length > 0,
216
+ })));
217
+ files.add('_rels/.rels', strToU8(rootRelsXml(preserved.root)));
218
+ files.add(CORE_PROPS_PART, strToU8(corePropsXml(workbook.properties)));
219
+ files.add(APP_PROPS_PART, strToU8(appPropsXml(workbook.properties)));
220
+ files.add(WORKBOOK_PART, strToU8(workbookXml(workbook, workbookRels, allPivots)));
221
+ files.add(relsPathFor(WORKBOOK_PART), strToU8(workbookRelsXml(workbookRels, allPivots)));
222
+ files.add(STYLES_PART, strToU8(styles.toXml()));
193
223
  if (!preserved.themeEmitted) {
194
224
  const overrides = workbook.themeOverrides;
195
- files[THEME_PART_PATH] = strToU8(overrides === undefined
225
+ files.add(THEME_PART_PATH, strToU8(overrides === undefined
196
226
  ? DEFAULT_THEME_XML
197
- : applyThemeOverrides(DEFAULT_THEME_XML, overrides));
227
+ : applyThemeOverrides(DEFAULT_THEME_XML, overrides)));
198
228
  }
199
229
  if (hasSharedStrings) {
200
- files[SHARED_STRINGS_PART] = strToU8(sharedStrings.toXml());
230
+ files.add(SHARED_STRINGS_PART, strToU8(sharedStrings.toXml()));
201
231
  }
202
232
  if (persons.length > 0)
203
- files[PERSONS_PART] = strToU8(personsXml(persons));
233
+ files.add(PERSONS_PART, strToU8(personsXml(persons)));
204
234
  for (const part of media.parts) {
205
- files[mediaPart(part.number, part.extension)] = part.data;
235
+ files.add(mediaPart(part.number, part.extension), part.data);
206
236
  }
207
237
  emitSheetParts(files, perSheet, sheetXml);
208
238
  for (const { table, number } of allTables) {
209
- files[tablePart(number)] = strToU8(tableXml(table, number));
239
+ files.add(tablePart(number), strToU8(tableXml(table, number)));
210
240
  }
211
241
  emitPivotParts(files, allPivots);
212
242
  emitPreservedParts(files, preserved.parts);
213
- return files;
243
+ return files.toRecord();
214
244
  }
215
245
  export function buildPackageParts(workbook, options = {}) {
216
246
  const sheets = workbook.worksheets;
@@ -219,15 +249,34 @@ export function buildPackageParts(workbook, options = {}) {
219
249
  }
220
250
  const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
221
251
  const styles = options.styles ?? createStyleRegistry(workbook);
222
- const plan = planPackage(workbook, sheets);
223
- const sheetXml = serialiseSheets(workbook, sheets, plan, styles, sharedStrings, options.flushed);
252
+ const plan = planPackage(workbook, sheets, options.flushed);
253
+ const sheetXml = serialiseSheets({
254
+ workbook,
255
+ sheets,
256
+ plan,
257
+ styles,
258
+ sharedStrings,
259
+ flushed: options.flushed,
260
+ });
224
261
  return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, sheetXml });
225
262
  }
263
+ class PackageFiles {
264
+ #files = Object.create(null);
265
+ add(path, bytes) {
266
+ if (path in this.#files) {
267
+ throw new InternalError(`two package parts claim the path ${quoted(path)}`);
268
+ }
269
+ this.#files[path] = bytes;
270
+ }
271
+ toRecord() {
272
+ return this.#files;
273
+ }
274
+ }
226
275
  function emitSheetParts(files, perSheet, sheetXml) {
227
276
  perSheet.forEach((plan, i) => {
228
277
  const { tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots, } = plan;
229
278
  const hasExternalHyperlink = hyperlinks.some((link) => link.relId !== undefined);
230
- files[worksheetPart(i + 1)] = strToU8(sheetXml[i]);
279
+ files.add(worksheetPart(i + 1), strToU8(sheetXml[i]));
231
280
  if (tables.length > 0 ||
232
281
  drawing !== null ||
233
282
  comments !== null ||
@@ -237,23 +286,23 @@ function emitSheetParts(files, perSheet, sheetXml) {
237
286
  hasExternalHyperlink ||
238
287
  preservedRefs.length > 0 ||
239
288
  pivots.length > 0) {
240
- files[relsPathFor(worksheetPart(i + 1))] = strToU8(worksheetRelsXml(tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots));
289
+ files.add(relsPathFor(worksheetPart(i + 1)), strToU8(worksheetRelsXml(plan)));
241
290
  }
242
291
  if (printerSettings !== null) {
243
- files[printerSettingsPart(printerSettings.number)] = printerSettings.data;
292
+ files.add(printerSettingsPart(printerSettings.number), printerSettings.data);
244
293
  }
245
294
  if (drawing !== null) {
246
295
  const drawingPath = drawingPart(drawing.number);
247
- files[drawingPath] = strToU8(drawingXml(drawing.images));
296
+ files.add(drawingPath, strToU8(drawingXml(drawing.images)));
248
297
  const targets = drawing.images.map((image) => relativePartPath(drawingPath, mediaPart(image.mediaNumber, image.extension)));
249
- files[relsPathFor(drawingPath)] = strToU8(drawingRelsXml(targets));
298
+ files.add(relsPathFor(drawingPath), strToU8(drawingRelsXml(targets)));
250
299
  }
251
300
  if (comments !== null) {
252
- files[commentsPart(comments.number)] = strToU8(commentsXml(comments.comments));
253
- files[vmlDrawingPart(comments.number)] = strToU8(vmlDrawingXml(comments.comments));
301
+ files.add(commentsPart(comments.number), strToU8(commentsXml(comments.comments)));
302
+ files.add(vmlDrawingPart(comments.number), strToU8(vmlDrawingXml(comments.comments)));
254
303
  }
255
304
  if (threadedComments !== null) {
256
- files[threadedCommentsPart(threadedComments.number)] = strToU8(threadedCommentsXml(threadedComments.threads));
305
+ files.add(threadedCommentsPart(threadedComments.number), strToU8(threadedCommentsXml(threadedComments.threads)));
257
306
  }
258
307
  });
259
308
  }
@@ -262,30 +311,21 @@ function emitPivotParts(files, allPivots) {
262
311
  const { number, cacheId, table } = pivot;
263
312
  const tablePath = pivotTablePart(number);
264
313
  const definitionPath = pivotCacheDefinitionPart(number);
265
- files[tablePath] = strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId));
266
- files[relsPathFor(tablePath)] = strToU8(relsPartXml([
267
- {
268
- id: 'rId1',
269
- type: REL.pivotCacheDefinition,
270
- target: relativePartPath(tablePath, definitionPath),
271
- },
272
- ]));
273
- files[definitionPath] = strToU8(pivotCacheDefinitionXml(table));
274
- files[relsPathFor(definitionPath)] = strToU8(relsPartXml([
275
- {
276
- id: 'rId1',
277
- type: REL.pivotCacheRecords,
278
- target: relativePartPath(definitionPath, pivotCacheRecordsPart(number)),
279
- },
280
- ]));
281
- files[pivotCacheRecordsPart(number)] = strToU8(pivotCacheRecordsXml(table));
314
+ files.add(tablePath, strToU8(pivotTableXml(table, `PivotTable${number}`, cacheId)));
315
+ addSingleRelPart(files, tablePath, REL.pivotCacheDefinition, definitionPath);
316
+ files.add(definitionPath, strToU8(pivotCacheDefinitionXml(table)));
317
+ addSingleRelPart(files, definitionPath, REL.pivotCacheRecords, pivotCacheRecordsPart(number));
318
+ files.add(pivotCacheRecordsPart(number), strToU8(pivotCacheRecordsXml(table)));
282
319
  }
283
320
  }
321
+ function addSingleRelPart(files, from, type, to) {
322
+ files.add(relsPathFor(from), strToU8(relsPartXml([{ id: 'rId1', type, target: relativePartPath(from, to) }])));
323
+ }
284
324
  function emitPreservedParts(files, parts) {
285
325
  for (const part of parts) {
286
- files[part.path] = part.bytes;
326
+ files.add(part.path, part.bytes);
287
327
  if (part.relsPath !== null && part.relsXml !== null) {
288
- files[part.relsPath] = strToU8(part.relsXml);
328
+ files.add(part.relsPath, strToU8(part.relsXml));
289
329
  }
290
330
  }
291
331
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Does a relationship Type name this class of part?
3
+ *
4
+ * A Type is a URI whose last segment names the part class, and the namespace in front of it varies by
5
+ * relationship family and by the schema version a producer wrote against. So the match is on that
6
+ * last segment.
7
+ *
8
+ * **The separator is the load-bearing character.** `/slicer` and `slicer` are not the same test:
9
+ * without the slash, `slicer` matches `slicerCache`, `table` matches `pivotTable`, and a lookup
10
+ * quietly gathers a part class nobody asked for. Every caller used to spell the slash itself, ten
11
+ * times over, with nowhere to say why it was there.
12
+ */
13
+ export declare function isRelType(type: string, name: string): boolean;
14
+ /** {@link isRelType} against several part classes at once, for the predicates that partition
15
+ * relationships into "modeled", "preserved verbatim", and "dropped". */
16
+ export declare function isAnyRelType(type: string, ...names: readonly string[]): boolean;
@@ -0,0 +1,6 @@
1
+ export function isRelType(type, name) {
2
+ return type.endsWith(`/${name}`);
3
+ }
4
+ export function isAnyRelType(type, ...names) {
5
+ return names.some((name) => isRelType(type, name));
6
+ }
@@ -13,3 +13,16 @@
13
13
  * expression instead of as its members.
14
14
  */
15
15
  export declare function tokenSet<T extends string>(members: Record<T, true>): (value: string) => value is T;
16
+ /**
17
+ * {@link tokenSet} for a union whose members are also needed as an ordered *list*, because a binary
18
+ * serialisation indexes one.
19
+ *
20
+ * The record form's whole guarantee is that a member cannot be omitted, and a list gives that up: it
21
+ * can name only members of the union, but it can name fewer. So a caller here owes an explicit proof
22
+ * of set equality in both directions, which is what `FILL_PATTERNS_IN_SCHEMA_ORDER` carries beside
23
+ * itself. That is a worse guarantee than the record's, taken deliberately: the alternative is the
24
+ * arrangement it replaced, where the union, the guard's table and the binary codec's index list were
25
+ * three hand-maintained copies of one enumeration and only two of them were checked against each
26
+ * other.
27
+ */
28
+ export declare function tokenSetOf<T extends string>(members: readonly T[]): (value: string) => value is T;
package/dist/token-set.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export function tokenSet(members) {
2
- const set = new Set(Object.keys(members));
2
+ return tokenSetOf(Object.keys(members));
3
+ }
4
+ export function tokenSetOf(members) {
5
+ const set = new Set(members);
3
6
  return (value) => set.has(value);
4
7
  }
@@ -1,11 +1,20 @@
1
- export { concat } from '../bytes.ts';
1
+ export { concat, decodeUtf16le } from '../bytes.ts';
2
2
  /** Read a little-endian `uint16`. @throws {VbaParseError} if the two bytes are not both in `buf`. */
3
3
  export declare function readU16(buf: Uint8Array, at: number): number;
4
4
  /** Read a little-endian `uint32`. @throws {VbaParseError} if the four bytes are not all in `buf`. */
5
5
  export declare function readU32(buf: Uint8Array, at: number): number;
6
6
  /**
7
- * Decode UTF-16LE code units: the encoding [MS-CFB] uses for directory-entry names and [MS-OVBA] for
8
- * every "Unicode" name field. A trailing odd byte is dropped: these fields are length-prefixed by the
9
- * producer and a half code unit carries nothing to decode.
7
+ * Write a little-endian `uint16` over bytes already in `buf`. The value is asserted rather than
8
+ * masked: a caller that arrived at `-1` or `0x1_0000` has miscounted something, and silently storing
9
+ * `0xffff` turns that into a `dir` stream declaring 65,535 modules.
10
10
  */
11
- export declare function decodeUtf16le(bytes: Uint8Array): string;
11
+ export declare function writeU16(buf: Uint8Array, at: number, value: number): void;
12
+ /**
13
+ * Replace `src[start..end)` with `insert`, returning a new array.
14
+ *
15
+ * The three record edits the project editor makes are all this shape: splice a MODULE record block in,
16
+ * cut one out, cut a PROJECTwm entry out. Each was an allocate-and-copy-around written out longhand,
17
+ * and each had to get the same three offsets right; saying it once means the arithmetic is checked
18
+ * once. Passing no `insert` is a pure deletion, and `start === end` a pure insertion.
19
+ */
20
+ export declare function spliceBytes(src: Uint8Array, start: number, end: number, insert?: Uint8Array): Uint8Array;
package/dist/vba/bytes.js CHANGED
@@ -1,4 +1,4 @@
1
- export { concat } from '../bytes.js';
1
+ export { concat, decodeUtf16le } from '../bytes.js';
2
2
  import { VbaParseError } from './errors.js';
3
3
  function truncated(at, need, length) {
4
4
  return new VbaParseError(`read of ${need} bytes at offset ${at} runs past the end of a ${length}-byte buffer`);
@@ -20,10 +20,20 @@ export function readU32(buf, at) {
20
20
  }
21
21
  return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
22
22
  }
23
- export function decodeUtf16le(bytes) {
24
- let s = '';
25
- for (let i = 0; i + 1 < bytes.length; i += 2) {
26
- s += String.fromCharCode(readU16(bytes, i));
23
+ export function writeU16(buf, at, value) {
24
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff) {
25
+ throw new VbaParseError(`${value} does not fit the uint16 at offset ${at}`);
27
26
  }
28
- return s;
27
+ if (at + 1 >= buf.length)
28
+ throw truncated(at, 2, buf.length);
29
+ buf[at] = value & 0xff;
30
+ buf[at + 1] = (value >> 8) & 0xff;
31
+ }
32
+ export function spliceBytes(src, start, end, insert = EMPTY) {
33
+ const out = new Uint8Array(src.length - (end - start) + insert.length);
34
+ out.set(src.subarray(0, start), 0);
35
+ out.set(insert, start);
36
+ out.set(src.subarray(end), start + insert.length);
37
+ return out;
29
38
  }
39
+ const EMPTY = new Uint8Array(0);
@@ -17,6 +17,14 @@ export declare const TYPE_EMPTY = 0;
17
17
  export declare const TYPE_STORAGE = 1;
18
18
  export declare const TYPE_STREAM = 2;
19
19
  export declare const TYPE_ROOT = 5;
20
+ /**
21
+ * The stream size at or above which a stream lives in the regular FAT rather than the mini stream
22
+ * ([MS-CFB] 2.2, header offset 56). The spec fixes it at 4096, so the reader checks the header's value
23
+ * against this rather than believing it: a crafted 0 or 0xFFFFFFFF routes every stream through the
24
+ * wrong allocator, and since both destinations are bounds-checked the result is a module's source read
25
+ * back as something nobody wrote rather than a clean rejection.
26
+ */
27
+ export declare const MINI_STREAM_CUTOFF = 4096;
20
28
  /** A directory entry is a fixed 128 bytes ([MS-CFB] 2.6.1), whatever the sector size. */
21
29
  export declare const DIR_ENTRY_SIZE = 128;
22
30
  /** 32 UTF-16 code units including the NUL terminator. This is also why a VBA module name is capped