@shbernal/ts-xlsx 3.1.0 → 3.2.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 (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -3,13 +3,15 @@ import { PassThrough } from 'node:stream';
3
3
  import { Zip, ZipDeflate } from 'fflate';
4
4
  import { concat } from '../../bytes.js';
5
5
  import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
6
+ import { formulaPlacement } from '../../core/array-formula-ranges.js';
7
+ import { formulaNamesInScope } from '../../core/formula.js';
6
8
  import { INTERNAL } from '../../core/internal.js';
7
9
  import { isSharedFormulaValue } from '../../core/value.js';
8
- import { Workbook } from '../../core/workbook.js';
10
+ import { Workbook, } from '../../core/workbook.js';
9
11
  import { AuthoringError, quoted } from '../../errors.js';
10
12
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
13
+ import { WorkbookMetadataTable } from './cell-metadata.js';
11
14
  import { collectNotes } from './comments.js';
12
- import { collectHyperlinks } from './hyperlinks.js';
13
15
  import { buildColumnDefaults, Extent, renderRow, } from './row-xml.js';
14
16
  import { buildPackageParts, createStyleRegistry } from './write.js';
15
17
  export class StreamedRow {
@@ -22,6 +24,11 @@ export class StreamedRow {
22
24
  this.#sheet = sheet;
23
25
  this.#number = number;
24
26
  }
27
+ static [INTERNAL] = {
28
+ create(cells, sheet, number) {
29
+ return new StreamedRow(cells, sheet, number);
30
+ },
31
+ };
25
32
  get cells() {
26
33
  return this.#cells;
27
34
  }
@@ -29,7 +36,7 @@ export class StreamedRow {
29
36
  if (this.#committed)
30
37
  return;
31
38
  this.#committed = true;
32
- this.#sheet?.[INTERNAL].flushRow(this.#number, this.#cells);
39
+ this.#sheet?.[INTERNAL].flushRow(this.#number);
33
40
  }
34
41
  }
35
42
  export class WorksheetStreamWriter {
@@ -43,18 +50,22 @@ export class WorksheetStreamWriter {
43
50
  #flushedNumbers = new Set();
44
51
  #extent = new Extent();
45
52
  #rowOutline = new Map();
46
- #hyperlinks = [];
47
53
  #notes = [];
54
+ #formulas = [];
48
55
  #dateEpoch;
49
- constructor(sheet, eager, styles, dateEpoch) {
56
+ #definedNames;
57
+ #cellMetadata;
58
+ constructor(sheet, eager, styles, cellMetadata, dateEpoch, definedNames) {
50
59
  this.#sheet = sheet;
51
60
  this.#eager = eager;
52
61
  this.#styles = styles;
62
+ this.#cellMetadata = cellMetadata;
53
63
  this.#dateEpoch = dateEpoch;
64
+ this.#definedNames = definedNames;
54
65
  }
55
66
  static [INTERNAL] = {
56
- create(sheet, eager, styles, dateEpoch) {
57
- return new WorksheetStreamWriter(sheet, eager, styles, dateEpoch);
67
+ create(sheet, eager, styles, cellMetadata, dateEpoch, definedNames) {
68
+ return new WorksheetStreamWriter(sheet, eager, styles, cellMetadata, dateEpoch, definedNames);
58
69
  },
59
70
  };
60
71
  get name() {
@@ -66,14 +77,14 @@ export class WorksheetStreamWriter {
66
77
  addRow(values) {
67
78
  this.#assertOpen();
68
79
  if (!this.#eager)
69
- return new StreamedRow(this.#sheet.addRow(values), null, 0);
80
+ return StreamedRow[INTERNAL].create(this.#sheet.addRow(values), null, 0);
70
81
  const number = this.#nextRowNumber();
71
- return new StreamedRow(this.#placeRow(number, values), this, number);
82
+ return StreamedRow[INTERNAL].create(this.#placeRow(number, values), this, number);
72
83
  }
73
84
  addRows(rows) {
74
85
  this.#assertOpen();
75
86
  if (!this.#eager)
76
- return this.#sheet.addRows(rows).map((cells) => new StreamedRow(cells, null, 0));
87
+ return this.#sheet.addRows(rows).map((cells) => StreamedRow[INTERNAL].create(cells, null, 0));
77
88
  return rows.map((values) => this.addRow(values));
78
89
  }
79
90
  #nextRowNumber() {
@@ -89,7 +100,9 @@ export class WorksheetStreamWriter {
89
100
  });
90
101
  return cells;
91
102
  }
92
- #flushRow(number, cells) {
103
+ #flushRow(number) {
104
+ const row = this.#sheet.getRow(number);
105
+ const { cells } = row;
93
106
  for (const cell of cells) {
94
107
  if (isSharedFormulaValue(cell.value)) {
95
108
  throw new AuthoringError(`row ${number} of streamed sheet ${quoted(this.#sheet.name)} carries a shared-formula cell; a ` +
@@ -98,13 +111,17 @@ export class WorksheetStreamWriter {
98
111
  }
99
112
  }
100
113
  this.#columnDefaults ??= buildColumnDefaults(this.#sheet);
101
- const properties = this.#sheet.getRow(number).properties;
114
+ const { properties } = row;
102
115
  this.#rowOutline.set(number, {
103
116
  outlineLevel: properties?.outlineLevel ?? 0,
104
117
  hidden: properties?.hidden ?? false,
105
118
  });
106
- this.#hyperlinks.push(...collectHyperlinks(cells));
107
119
  this.#notes.push(...collectNotes(cells));
120
+ for (const cell of cells) {
121
+ const placement = formulaPlacement(cell.address, cell.col, cell.row, cell.value);
122
+ if (placement !== undefined)
123
+ this.#formulas.push(placement);
124
+ }
108
125
  const { xml, attrs, minCol, maxCol } = renderRow({ number, cells, properties }, {
109
126
  columnDefaults: this.#columnDefaults,
110
127
  styles: this.#styles,
@@ -112,6 +129,8 @@ export class WorksheetStreamWriter {
112
129
  sharedRoles: new Map(),
113
130
  collapsedSummaries: new Set(),
114
131
  dateEpoch: this.#dateEpoch,
132
+ formulaNames: formulaNamesInScope(this.#definedNames, this.#sheet.name),
133
+ cellMetadata: this.#cellMetadata,
115
134
  });
116
135
  if (xml !== '') {
117
136
  this.#flushedRows.push({ number, xml, attrs });
@@ -127,8 +146,8 @@ export class WorksheetStreamWriter {
127
146
  rows: this.#flushedRows,
128
147
  extent: this.#extent,
129
148
  rowOutline: this.#rowOutline,
130
- hyperlinks: this.#hyperlinks,
131
149
  notes: this.#notes,
150
+ formulas: this.#formulas,
132
151
  };
133
152
  }
134
153
  getCell(reference) {
@@ -145,6 +164,10 @@ export class WorksheetStreamWriter {
145
164
  this.#assertOpen();
146
165
  this.#sheet.addDataValidation(sqref, rule, options);
147
166
  }
167
+ addHyperlink(link) {
168
+ this.#assertOpen();
169
+ this.#sheet.addHyperlink(link);
170
+ }
148
171
  addConditionalFormatting(formatting) {
149
172
  this.#assertOpen();
150
173
  this.#sheet.addConditionalFormatting(formatting);
@@ -174,8 +197,8 @@ export class WorksheetStreamWriter {
174
197
  return this.#sheet;
175
198
  }
176
199
  [INTERNAL] = {
177
- flushRow: (number, cells) => {
178
- this.#flushRow(number, cells);
200
+ flushRow: (number) => {
201
+ this.#flushRow(number);
179
202
  },
180
203
  flushedSheet: () => this.#flushedSheet(),
181
204
  };
@@ -191,6 +214,7 @@ export class WorkbookStreamWriter {
191
214
  #writeOptions;
192
215
  #sink;
193
216
  #styles;
217
+ #cellMetadata = new WorkbookMetadataTable();
194
218
  #eager;
195
219
  #stream;
196
220
  #committed = false;
@@ -220,7 +244,7 @@ export class WorkbookStreamWriter {
220
244
  }
221
245
  addWorksheet(name, options = {}) {
222
246
  this.#assertOpen('no more worksheets can be added');
223
- const sheet = WorksheetStreamWriter[INTERNAL].create(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles, this.#workbook.dateEpoch);
247
+ const sheet = WorksheetStreamWriter[INTERNAL].create(this.#workbook.addWorksheet(name, options), this.#eager, this.#styles, this.#cellMetadata, this.#workbook.dateEpoch, this.#workbook.definedNames);
224
248
  this.#sheets.push(sheet);
225
249
  return sheet;
226
250
  }
@@ -245,6 +269,7 @@ export class WorkbookStreamWriter {
245
269
  const parts = buildPackageParts(this.#workbook, {
246
270
  ...this.#writeOptions,
247
271
  styles: this.#styles,
272
+ cellMetadata: this.#cellMetadata,
248
273
  flushed,
249
274
  });
250
275
  const bytes = await streamZipPackage(parts, (chunk) => {
@@ -1,14 +1,15 @@
1
1
  import type { Workbook } from '../../core/workbook.ts';
2
2
  import type { Worksheet } from '../../core/worksheet.ts';
3
+ import { WorkbookMetadataTable } from './cell-metadata.ts';
3
4
  import type { FlushedSheet } from './row-xml.ts';
4
5
  import { StyleRegistry } from './styles.ts';
5
6
  /** Options controlling how {@link writeXlsx} serialises a workbook. */
6
7
  export interface WriteOptions {
7
8
  /**
8
- * Pool plain string cell values into a shared-strings table (`xl/sharedStrings.xml`) that cells
9
- * reference by index, rather than storing each string inline in its cell. Deduplicates repeated
10
- * text and matches Excel's own storage; off by default, which keeps strings inline and omits the
11
- * part. Rich-text values stay inline regardless, so their run formatting is unaffected.
9
+ * Pool string and rich-text cell values into a shared-strings table (`xl/sharedStrings.xml`) that
10
+ * cells reference by index, rather than storing each inline in its cell. Deduplicates repeated text
11
+ * and matches Excel's own storage; a rich value is pooled as a `<si>` of its runs, so its formatting
12
+ * survives. Off by default, which keeps strings inline and omits the part.
12
13
  */
13
14
  readonly useSharedStrings?: boolean;
14
15
  }
@@ -27,6 +28,12 @@ export interface InternalWriteOptions extends WriteOptions {
27
28
  * so its output is unchanged.
28
29
  */
29
30
  readonly styles?: StyleRegistry;
31
+ /**
32
+ * The workbook metadata to record into, in place of a fresh table, for the reason {@link styles} is
33
+ * shared: a row the streaming writer rendered eagerly may already point a dynamic-array formula or a
34
+ * rich-value error at the parts this table decides whether to emit.
35
+ */
36
+ readonly cellMetadata?: WorkbookMetadataTable;
30
37
  /**
31
38
  * Per-sheet rows already serialised and evicted from the model by the streaming writer, keyed by
32
39
  * the model worksheet. Their XML is emitted ahead of the sheet's remaining live rows and their
@@ -1,14 +1,17 @@
1
1
  import { strToU8, zip, zipSync } from 'fflate';
2
+ import { formulaNamesInScope } from '../../core/formula.js';
3
+ import { pictureProperties } from '../../core/image.js';
2
4
  import { AuthoringError, InternalError, quoted } from '../../errors.js';
3
5
  import { isRelType } from '../../rel-type.js';
4
- import { relativePartPath, relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
6
+ import { relsPathFor, THEME_PART_PATH } from '../opc/part-paths.js';
5
7
  import { relsPartXml } from '../opc/rels.js';
6
8
  import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
7
- import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
8
- import { collectHyperlinks, liveCells, planHyperlinks } from './hyperlinks.js';
9
- import { drawingRelsXml, drawingXml } from './images.js';
10
- import { planMedia, planPreservedParts, RelIdAllocator, } from './package-plan.js';
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';
9
+ import { RICH_VALUE_STRUCTURES_XML, WorkbookMetadataTable } from './cell-metadata.js';
10
+ import { collectComments, commentsXml, liveCells, vmlDrawingXml } from './comments.js';
11
+ import { planHyperlinks } from './hyperlinks.js';
12
+ import { drawingXml } from './images.js';
13
+ import { planMedia, planPreservedParts, RelationshipLedger, } from './package-plan.js';
14
+ import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, mediaPart, METADATA_PART, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, printerSettingsPart, RICH_VALUE_STRUCTURES_PART, RICH_VALUES_PART, SHARED_STRINGS_PART, STYLES_PART, tablePart, threadedCommentsPart, vmlDrawingPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
12
15
  import { pivotCacheDefinitionXml, pivotCacheRecordsXml, pivotTableXml } from './pivot.js';
13
16
  import { REL } from './relationships.js';
14
17
  import { SharedStringTable } from './shared-strings.js';
@@ -16,8 +19,8 @@ import { StyleRegistry } from './styles.js';
16
19
  import { tableXml } from './tables.js';
17
20
  import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
18
21
  import { personsXml, threadedCommentsXml } from './threaded-comments.js';
19
- import { appPropsXml, contentTypesXml, corePropsXml, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
20
- import { worksheetRelsXml, worksheetXml } from './worksheet-xml.js';
22
+ import { appPropsXml, contentTypesXml, corePropsXml, workbookXml, } from './workbook-xml.js';
23
+ import { worksheetXml } from './worksheet-xml.js';
21
24
  export function writeXlsx(workbook, options = {}) {
22
25
  return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
23
26
  }
@@ -48,52 +51,77 @@ export function createStyleRegistry(workbook) {
48
51
  styles.addTableStyle(style);
49
52
  return styles;
50
53
  }
54
+ function keptDrawingPath(sheet) {
55
+ return sheet.preservedReferences.find((reference) => reference.element === 'drawing')?.entryPath;
56
+ }
57
+ function drawingImages(sheet, media, drawingRels) {
58
+ return sheet.images.map((image) => {
59
+ const { number: mediaNumber, image: registered } = media.resolve(image.imageId);
60
+ const embedId = drawingRels.add(REL.image, mediaPart(mediaNumber, registered.extension));
61
+ const properties = pictureProperties(image);
62
+ const target = properties.hyperlink?.target;
63
+ if (target === undefined)
64
+ return { anchor: image.anchor, embedId, properties };
65
+ const hyperlinkId = target.startsWith('#')
66
+ ? drawingRels.addInDocument(REL.hyperlink, target)
67
+ : drawingRels.addExternal(REL.hyperlink, target);
68
+ return { anchor: image.anchor, embedId, properties, hyperlinkId };
69
+ });
70
+ }
51
71
  function planSheet(context) {
52
72
  const { sheet, index, media, preserved, numbering, flushed } = context;
53
- const rels = new RelIdAllocator();
54
- const tables = sheet.tables.map((table) => ({
55
- table,
56
- number: ++numbering.table,
57
- relId: rels.next(),
58
- }));
73
+ const rels = new RelationshipLedger(worksheetPart(index + 1));
74
+ const tables = sheet.tables.map((table) => {
75
+ const number = ++numbering.table;
76
+ return { table, number, relId: rels.add(REL.table, tablePart(number)) };
77
+ });
59
78
  let drawing = null;
60
- if (sheet.images.length > 0) {
61
- const images = sheet.images.map((image, j) => {
62
- const { number, image: registered } = media.resolve(image.imageId);
63
- return {
64
- anchor: image.anchor,
65
- embedId: `rId${j + 1}`,
66
- mediaNumber: number,
67
- extension: registered.extension,
68
- };
69
- });
70
- drawing = { number: ++numbering.drawing, relId: rels.next(), images };
79
+ if (sheet.images.length > 0 && keptDrawingPath(sheet) === undefined) {
80
+ const number = ++numbering.drawing;
81
+ const path = drawingPart(number);
82
+ const drawingRels = new RelationshipLedger(path);
83
+ drawing = {
84
+ number,
85
+ relId: rels.add(REL.drawing, path),
86
+ images: drawingImages(sheet, media, drawingRels),
87
+ relationships: drawingRels.relationships,
88
+ };
71
89
  }
72
90
  const threads = sheet.commentThreads.filter((thread) => thread.comments.length > 0);
73
91
  const sheetComments = collectComments(liveCells(sheet), threads, flushed?.notes ?? []);
74
- const comments = sheetComments.length === 0
92
+ let comments = null;
93
+ if (sheetComments.length > 0) {
94
+ const vmlRelId = rels.add(REL.vmlDrawing, vmlDrawingPart(index + 1));
95
+ rels.add(REL.comments, commentsPart(index + 1));
96
+ comments = { number: index + 1, comments: sheetComments, vmlRelId };
97
+ }
98
+ let threadedComments = null;
99
+ if (threads.length > 0) {
100
+ rels.add(REL.threadedComment, threadedCommentsPart(index + 1));
101
+ threadedComments = { number: index + 1, threads };
102
+ }
103
+ const printerData = sheet.pageSetup.printerSettings;
104
+ const printerSettings = printerData === undefined
75
105
  ? null
76
106
  : {
77
107
  number: index + 1,
78
- comments: sheetComments,
79
- vmlRelId: rels.next(),
80
- commentsRelId: rels.next(),
108
+ data: printerData,
109
+ relId: rels.add(REL.printerSettings, printerSettingsPart(index + 1)),
81
110
  };
82
- const threadedComments = threads.length === 0 ? null : { number: index + 1, threads, relId: rels.next() };
83
- const printerData = sheet.pageSetup.printerSettings;
84
- const printerSettings = printerData === undefined ? null : { number: index + 1, data: printerData, relId: rels.next() };
85
- const hyperlinks = planHyperlinks([...collectHyperlinks(liveCells(sheet)), ...(flushed?.hyperlinks ?? [])].sort((a, b) => a.row - b.row || a.col - b.col), rels);
111
+ const hyperlinks = planHyperlinks(sheet.hyperlinks, rels);
86
112
  let background = null;
87
113
  if (sheet.backgroundImageId !== undefined) {
88
114
  const { number, image } = media.resolve(sheet.backgroundImageId);
89
- background = { relId: rels.next(), mediaNumber: number, extension: image.extension };
115
+ background = { relId: rels.add(REL.image, mediaPart(number, image.extension)) };
90
116
  }
91
- const preservedRefs = (preserved.perSheet[index] ?? []).map((reference) => ({ ...reference, relId: rels.next() }));
117
+ const preservedRefs = (preserved.perSheet[index] ?? []).map((reference) => ({ ...reference, relId: rels.add(reference.relType, reference.entryPath) }));
92
118
  const pivots = sheet.pivotTables.map((table) => {
93
119
  const number = ++numbering.pivot;
94
- return { number, cacheId: String(number), table, sheetRelId: rels.next(), workbookRelId: '' };
120
+ rels.add(REL.pivotTable, pivotTablePart(number));
121
+ return { number, cacheId: String(number), table, workbookRelId: '' };
95
122
  });
96
123
  return {
124
+ relationships: rels.relationships,
97
125
  tables,
98
126
  drawing,
99
127
  comments,
@@ -130,31 +158,55 @@ function resolveSheetReferences(plan) {
130
158
  slicerRelIds,
131
159
  };
132
160
  }
133
- function assignWorkbookRelIds(context) {
134
- const { sheetCount, hasSharedStrings, hasPersons, preservedWorkbook, pivots } = context;
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
- };
161
+ function planWorkbookRelationships(context) {
162
+ const { sheetCount, hasSharedStrings, hasPersons, hasCellMetadata, hasRichValues, preservedWorkbook, pivots, } = context;
163
+ const rels = new RelationshipLedger(WORKBOOK_PART);
164
+ const sheetRelIds = Array.from({ length: sheetCount }, (_, i) => rels.add(REL.worksheet, worksheetPart(i + 1)));
165
+ rels.add(REL.styles, STYLES_PART);
166
+ rels.add(REL.theme, THEME_PART_PATH);
167
+ if (hasSharedStrings)
168
+ rels.add(REL.sharedStrings, SHARED_STRINGS_PART);
169
+ if (hasPersons)
170
+ rels.add(REL.person, PERSONS_PART);
171
+ if (hasCellMetadata)
172
+ rels.add(REL.sheetMetadata, METADATA_PART);
173
+ if (hasRichValues) {
174
+ rels.add(REL.rdRichValue, RICH_VALUES_PART);
175
+ rels.add(REL.rdRichValueStructure, RICH_VALUE_STRUCTURES_PART);
176
+ }
177
+ const preservedWorkbookRels = preservedWorkbook.map((ref) => ({
178
+ ...ref,
179
+ relId: rels.add(ref.relType, ref.entryPath),
180
+ }));
181
+ for (const pivot of pivots) {
182
+ pivot.workbookRelId = rels.add(REL.pivotCacheDefinition, pivotCacheDefinitionPart(pivot.number));
183
+ }
184
+ return { sheetRelIds, preservedWorkbookRels, relationships: rels.relationships };
185
+ }
186
+ function planRootRelationships(rootRefs) {
187
+ const rels = new RelationshipLedger('');
188
+ rels.add(REL.officeDocument, WORKBOOK_PART);
189
+ rels.add(REL.coreProps, CORE_PROPS_PART);
190
+ rels.add(REL.extProps, APP_PROPS_PART);
191
+ for (const ref of rootRefs)
192
+ rels.add(ref.relType, ref.entryPath);
193
+ return rels.relationships;
152
194
  }
153
195
  function planPackage(workbook, sheets, flushed) {
154
196
  const media = planMedia(workbook, sheets);
155
- const generatedDrawingCount = sheets.filter((sheet) => sheet.images.length > 0).length;
197
+ const pictures = new Map();
198
+ let generatedDrawingCount = 0;
199
+ for (const sheet of sheets) {
200
+ if (sheet.images.length === 0)
201
+ continue;
202
+ const kept = keptDrawingPath(sheet);
203
+ if (kept === undefined)
204
+ generatedDrawingCount += 1;
205
+ else
206
+ pictures.set(kept, (ledger) => drawingImages(sheet, media, ledger));
207
+ }
156
208
  const generatedPivotCount = sheets.reduce((total, sheet) => total + sheet.pivotTables.length, 0);
157
- const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length, generatedPivotCount);
209
+ const preserved = planPreservedParts(workbook, generatedDrawingCount, media.parts.length, generatedPivotCount, pictures);
158
210
  const numbering = { table: 0, drawing: 0, pivot: 0 };
159
211
  const perSheet = sheets.map((sheet, index) => planSheet({ sheet, index, media, preserved, numbering, flushed: flushed?.get(sheet) }));
160
212
  return {
@@ -166,7 +218,7 @@ function planPackage(workbook, sheets, flushed) {
166
218
  };
167
219
  }
168
220
  function serialiseSheets(context) {
169
- const { workbook, sheets, plan, styles, sharedStrings, flushed } = context;
221
+ const { workbook, sheets, plan, styles, sharedStrings, cellMetadata, flushed } = context;
170
222
  return sheets.map((sheet, i) => {
171
223
  const sheetPlan = plan.perSheet[i];
172
224
  return worksheetXml({
@@ -176,25 +228,31 @@ function serialiseSheets(context) {
176
228
  references: resolveSheetReferences(sheetPlan),
177
229
  hyperlinks: sheetPlan.hyperlinks,
178
230
  sharedStrings,
231
+ cellMetadata,
179
232
  dateEpoch: workbook.dateEpoch,
233
+ formulaNames: formulaNamesInScope(workbook.definedNames, sheet.name),
180
234
  active: i === workbook.activeTabIndex,
181
235
  flushed: flushed?.get(sheet),
182
236
  });
183
237
  });
184
238
  }
185
239
  function emitPackageParts(context) {
186
- const { workbook, sheets, plan, styles, sharedStrings, sheetXml } = context;
240
+ const { workbook, sheets, plan, styles, sharedStrings, cellMetadata, sheetXml } = context;
187
241
  const { media, preserved, perSheet, allTables, allPivots } = plan;
188
242
  const hasSharedStrings = sharedStrings !== null && !sharedStrings.isEmpty;
243
+ const hasCellMetadata = !cellMetadata.isEmpty;
244
+ const hasRichValues = cellMetadata.hasRichValues;
189
245
  const commentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.comments);
190
246
  const drawingNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.drawing);
191
247
  const printerSettingsNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.printerSettings);
192
248
  const threadedCommentNumbers = numbersOf(perSheet, (sheetPlan) => sheetPlan.threadedComments);
193
249
  const persons = threadedCommentNumbers.length === 0 ? [] : workbook.persons;
194
- const workbookRels = assignWorkbookRelIds({
250
+ const workbookRels = planWorkbookRelationships({
195
251
  sheetCount: sheets.length,
196
252
  hasSharedStrings,
197
253
  hasPersons: persons.length > 0,
254
+ hasCellMetadata,
255
+ hasRichValues,
198
256
  preservedWorkbook: preserved.workbook,
199
257
  pivots: allPivots,
200
258
  });
@@ -213,12 +271,14 @@ function emitPackageParts(context) {
213
271
  preservedWorkbookRefs: preservedWorkbookRels,
214
272
  threadedCommentNumbers,
215
273
  hasPersons: persons.length > 0,
274
+ hasCellMetadata,
275
+ hasRichValues,
216
276
  })));
217
- files.add('_rels/.rels', strToU8(rootRelsXml(preserved.root)));
277
+ files.add(relsPathFor(''), strToU8(relsPartXml(planRootRelationships(preserved.root))));
218
278
  files.add(CORE_PROPS_PART, strToU8(corePropsXml(workbook.properties)));
219
279
  files.add(APP_PROPS_PART, strToU8(appPropsXml(workbook.properties)));
220
280
  files.add(WORKBOOK_PART, strToU8(workbookXml(workbook, workbookRels, allPivots)));
221
- files.add(relsPathFor(WORKBOOK_PART), strToU8(workbookRelsXml(workbookRels, allPivots)));
281
+ files.add(relsPathFor(WORKBOOK_PART), strToU8(relsPartXml(workbookRels.relationships)));
222
282
  files.add(STYLES_PART, strToU8(styles.toXml()));
223
283
  if (!preserved.themeEmitted) {
224
284
  const overrides = workbook.themeOverrides;
@@ -231,13 +291,22 @@ function emitPackageParts(context) {
231
291
  }
232
292
  if (persons.length > 0)
233
293
  files.add(PERSONS_PART, strToU8(personsXml(persons)));
294
+ if (hasCellMetadata)
295
+ files.add(METADATA_PART, strToU8(cellMetadata.toXml()));
296
+ if (hasRichValues) {
297
+ files.add(RICH_VALUES_PART, strToU8(cellMetadata.richValuesXml()));
298
+ files.add(RICH_VALUE_STRUCTURES_PART, strToU8(RICH_VALUE_STRUCTURES_XML));
299
+ }
234
300
  for (const part of media.parts) {
235
301
  files.add(mediaPart(part.number, part.extension), part.data);
236
302
  }
237
- emitSheetParts(files, perSheet, sheetXml);
238
- for (const { table, number } of allTables) {
239
- files.add(tablePart(number), strToU8(tableXml(table, number)));
240
- }
303
+ emitSheetParts(files, perSheet, sheetXml, (id) => workbook.getPerson(id));
304
+ perSheet.forEach(({ tables }, index) => {
305
+ const formulaNames = formulaNamesInScope(workbook.definedNames, sheets[index]?.name);
306
+ for (const { table, number } of tables) {
307
+ files.add(tablePart(number), strToU8(tableXml(table, number, formulaNames)));
308
+ }
309
+ });
241
310
  emitPivotParts(files, allPivots);
242
311
  emitPreservedParts(files, preserved.parts);
243
312
  return files.toRecord();
@@ -249,6 +318,7 @@ export function buildPackageParts(workbook, options = {}) {
249
318
  }
250
319
  const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
251
320
  const styles = options.styles ?? createStyleRegistry(workbook);
321
+ const cellMetadata = options.cellMetadata ?? new WorkbookMetadataTable();
252
322
  const plan = planPackage(workbook, sheets, options.flushed);
253
323
  const sheetXml = serialiseSheets({
254
324
  workbook,
@@ -256,9 +326,10 @@ export function buildPackageParts(workbook, options = {}) {
256
326
  plan,
257
327
  styles,
258
328
  sharedStrings,
329
+ cellMetadata,
259
330
  flushed: options.flushed,
260
331
  });
261
- return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, sheetXml });
332
+ return emitPackageParts({ workbook, sheets, plan, styles, sharedStrings, cellMetadata, sheetXml });
262
333
  }
263
334
  class PackageFiles {
264
335
  #files = Object.create(null);
@@ -272,21 +343,12 @@ class PackageFiles {
272
343
  return this.#files;
273
344
  }
274
345
  }
275
- function emitSheetParts(files, perSheet, sheetXml) {
346
+ function emitSheetParts(files, perSheet, sheetXml, personById) {
276
347
  perSheet.forEach((plan, i) => {
277
- const { tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedRefs, pivots, } = plan;
278
- const hasExternalHyperlink = hyperlinks.some((link) => link.relId !== undefined);
348
+ const { relationships, drawing, comments, threadedComments, printerSettings } = plan;
279
349
  files.add(worksheetPart(i + 1), strToU8(sheetXml[i]));
280
- if (tables.length > 0 ||
281
- drawing !== null ||
282
- comments !== null ||
283
- threadedComments !== null ||
284
- printerSettings !== null ||
285
- background !== null ||
286
- hasExternalHyperlink ||
287
- preservedRefs.length > 0 ||
288
- pivots.length > 0) {
289
- files.add(relsPathFor(worksheetPart(i + 1)), strToU8(worksheetRelsXml(plan)));
350
+ if (relationships.length > 0) {
351
+ files.add(relsPathFor(worksheetPart(i + 1)), strToU8(relsPartXml(relationships)));
290
352
  }
291
353
  if (printerSettings !== null) {
292
354
  files.add(printerSettingsPart(printerSettings.number), printerSettings.data);
@@ -294,15 +356,14 @@ function emitSheetParts(files, perSheet, sheetXml) {
294
356
  if (drawing !== null) {
295
357
  const drawingPath = drawingPart(drawing.number);
296
358
  files.add(drawingPath, strToU8(drawingXml(drawing.images)));
297
- const targets = drawing.images.map((image) => relativePartPath(drawingPath, mediaPart(image.mediaNumber, image.extension)));
298
- files.add(relsPathFor(drawingPath), strToU8(drawingRelsXml(targets)));
359
+ files.add(relsPathFor(drawingPath), strToU8(relsPartXml(drawing.relationships)));
299
360
  }
300
361
  if (comments !== null) {
301
362
  files.add(commentsPart(comments.number), strToU8(commentsXml(comments.comments)));
302
363
  files.add(vmlDrawingPart(comments.number), strToU8(vmlDrawingXml(comments.comments)));
303
364
  }
304
365
  if (threadedComments !== null) {
305
- files.add(threadedCommentsPart(threadedComments.number), strToU8(threadedCommentsXml(threadedComments.threads)));
366
+ files.add(threadedCommentsPart(threadedComments.number), strToU8(threadedCommentsXml(threadedComments.threads, personById)));
306
367
  }
307
368
  });
308
369
  }
@@ -311,16 +372,18 @@ function emitPivotParts(files, allPivots) {
311
372
  const { number, cacheId, table } = pivot;
312
373
  const tablePath = pivotTablePart(number);
313
374
  const definitionPath = pivotCacheDefinitionPart(number);
375
+ const recordsPath = pivotCacheRecordsPart(number);
376
+ const tableRels = new RelationshipLedger(tablePath);
377
+ tableRels.add(REL.pivotCacheDefinition, definitionPath);
314
378
  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)));
379
+ files.add(relsPathFor(tablePath), strToU8(relsPartXml(tableRels.relationships)));
380
+ const definitionRels = new RelationshipLedger(definitionPath);
381
+ const recordsRelId = definitionRels.add(REL.pivotCacheRecords, recordsPath);
382
+ files.add(definitionPath, strToU8(pivotCacheDefinitionXml(table, recordsRelId)));
383
+ files.add(relsPathFor(definitionPath), strToU8(relsPartXml(definitionRels.relationships)));
384
+ files.add(recordsPath, strToU8(pivotCacheRecordsXml(table)));
319
385
  }
320
386
  }
321
- function addSingleRelPart(files, from, type, to) {
322
- files.add(relsPathFor(from), strToU8(relsPartXml([{ id: 'rId1', type, target: relativePartPath(from, to) }])));
323
- }
324
387
  function emitPreservedParts(files, parts) {
325
388
  for (const part of parts) {
326
389
  files.add(part.path, part.bytes);
@@ -1,3 +1,12 @@
1
+ /**
2
+ * The last segment of a relationship Type (`vbaProjectSignatureAgile` in
3
+ * `http://schemas.microsoft.com/office/2014/relationships/vbaProjectSignatureAgile`), or the whole Type
4
+ * when it has no `/`: for a caller that looks the segment up rather than testing for one class.
5
+ *
6
+ * {@link isRelType} is not built on it, because a class name may span segments: the ribbon's is
7
+ * `ui/extensibility`.
8
+ */
9
+ export declare function relTypeSegment(type: string): string;
1
10
  /**
2
11
  * Does a relationship Type name this class of part?
3
12
  *
package/dist/rel-type.js CHANGED
@@ -1,3 +1,6 @@
1
+ export function relTypeSegment(type) {
2
+ return type.slice(type.lastIndexOf('/') + 1);
3
+ }
1
4
  export function isRelType(type, name) {
2
5
  return type.endsWith(`/${name}`);
3
6
  }
@@ -9,6 +9,8 @@ export declare function readU32(buf: Uint8Array, at: number): number;
9
9
  * `0xffff` turns that into a `dir` stream declaring 65,535 modules.
10
10
  */
11
11
  export declare function writeU16(buf: Uint8Array, at: number, value: number): void;
12
+ /** Write a little-endian `uint32` over bytes already in `buf`, asserted rather than masked as {@link writeU16} is. */
13
+ export declare function writeU32(buf: Uint8Array, at: number, value: number): void;
12
14
  /**
13
15
  * Replace `src[start..end)` with `insert`, returning a new array.
14
16
  *
package/dist/vba/bytes.js CHANGED
@@ -29,6 +29,17 @@ export function writeU16(buf, at, value) {
29
29
  buf[at] = value & 0xff;
30
30
  buf[at + 1] = (value >> 8) & 0xff;
31
31
  }
32
+ export function writeU32(buf, at, value) {
33
+ if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {
34
+ throw new VbaParseError(`${value} does not fit the uint32 at offset ${at}`);
35
+ }
36
+ if (at + 3 >= buf.length)
37
+ throw truncated(at, 4, buf.length);
38
+ buf[at] = value & 0xff;
39
+ buf[at + 1] = (value >>> 8) & 0xff;
40
+ buf[at + 2] = (value >>> 16) & 0xff;
41
+ buf[at + 3] = (value >>> 24) & 0xff;
42
+ }
32
43
  export function spliceBytes(src, start, end, insert = EMPTY) {
33
44
  const out = new Uint8Array(src.length - (end - start) + insert.length);
34
45
  out.set(src.subarray(0, start), 0);