@shbernal/ts-xlsx 2.1.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 (225) 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 +5 -4
  7. package/dist/core/autofilter.js +34 -35
  8. package/dist/core/axis-handle.d.ts +10 -0
  9. package/dist/core/axis-handle.js +17 -0
  10. package/dist/core/cell.d.ts +25 -0
  11. package/dist/core/cell.js +9 -3
  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 +5 -1
  16. package/dist/core/column.js +34 -35
  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 +14 -6
  21. package/dist/core/conditional-formatting.js +45 -43
  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 +9 -3
  27. package/dist/core/data-validation.js +21 -18
  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 +14 -10
  39. package/dist/core/image.js +9 -9
  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-index.d.ts +31 -0
  44. package/dist/core/merge-index.js +60 -0
  45. package/dist/core/merge.d.ts +5 -14
  46. package/dist/core/merge.js +18 -27
  47. package/dist/core/page-setup.d.ts +72 -2
  48. package/dist/core/page-setup.js +34 -8
  49. package/dist/core/pivot-table.d.ts +1 -1
  50. package/dist/core/pivot-table.js +67 -53
  51. package/dist/core/protection.js +2 -11
  52. package/dist/core/range.js +34 -30
  53. package/dist/core/row-input.js +2 -2
  54. package/dist/core/row.d.ts +5 -1
  55. package/dist/core/row.js +22 -23
  56. package/dist/core/style.d.ts +134 -75
  57. package/dist/core/style.js +112 -53
  58. package/dist/core/table-style.d.ts +1 -1
  59. package/dist/core/table-style.js +4 -4
  60. package/dist/core/table.d.ts +28 -10
  61. package/dist/core/table.js +54 -39
  62. package/dist/core/theme.js +6 -5
  63. package/dist/core/used-extent.d.ts +44 -0
  64. package/dist/core/used-extent.js +102 -0
  65. package/dist/core/value.d.ts +9 -0
  66. package/dist/core/value.js +36 -34
  67. package/dist/core/workbook-media.d.ts +34 -0
  68. package/dist/core/workbook-media.js +44 -0
  69. package/dist/core/workbook-styles.d.ts +67 -1
  70. package/dist/core/workbook-vba.js +3 -2
  71. package/dist/core/workbook.d.ts +23 -2
  72. package/dist/core/workbook.js +24 -40
  73. package/dist/core/worksheet-comments.d.ts +2 -1
  74. package/dist/core/worksheet-comments.js +20 -13
  75. package/dist/core/worksheet-merges.d.ts +44 -0
  76. package/dist/core/worksheet-merges.js +59 -0
  77. package/dist/core/worksheet-model.d.ts +9 -1
  78. package/dist/core/worksheet-model.js +24 -9
  79. package/dist/core/worksheet.d.ts +72 -12
  80. package/dist/core/worksheet.js +68 -70
  81. package/dist/customui/ribbon.js +6 -7
  82. package/dist/entries/core.d.ts +18 -13
  83. package/dist/entries/core.js +12 -6
  84. package/dist/entries/xlsx.d.ts +1 -1
  85. package/dist/errors.d.ts +64 -0
  86. package/dist/errors.js +22 -0
  87. package/dist/hex.d.ts +8 -0
  88. package/dist/hex.js +3 -0
  89. package/dist/io/csv/delimiter.d.ts +9 -0
  90. package/dist/io/csv/delimiter.js +6 -0
  91. package/dist/io/csv/read.js +11 -4
  92. package/dist/io/csv/write.d.ts +9 -2
  93. package/dist/io/csv/write.js +9 -38
  94. package/dist/io/opc/inflate.js +15 -1
  95. package/dist/io/opc/namespaces.d.ts +16 -0
  96. package/dist/io/opc/namespaces.js +4 -0
  97. package/dist/io/opc/part-paths.d.ts +10 -0
  98. package/dist/io/opc/part-paths.js +16 -1
  99. package/dist/io/opc/read-opc.d.ts +69 -10
  100. package/dist/io/opc/read-opc.js +49 -55
  101. package/dist/io/opc/read-options.d.ts +5 -1
  102. package/dist/io/opc/rels.d.ts +1 -6
  103. package/dist/io/opc/rels.js +1 -4
  104. package/dist/io/opc/sniff-format.d.ts +3 -3
  105. package/dist/io/opc/sniff-format.js +2 -2
  106. package/dist/io/style/xf-style.d.ts +48 -2
  107. package/dist/io/style/xf-style.js +14 -0
  108. package/dist/io/xlsb/formula.d.ts +2 -2
  109. package/dist/io/xlsb/formula.js +49 -34
  110. package/dist/io/xlsb/primitives.js +8 -14
  111. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  112. package/dist/io/xlsb/ptg-functions.js +385 -596
  113. package/dist/io/xlsb/read-styles.js +50 -83
  114. package/dist/io/xlsb/read-worksheet.d.ts +20 -1
  115. package/dist/io/xlsb/read-worksheet.js +73 -89
  116. package/dist/io/xlsb/read.d.ts +6 -3
  117. package/dist/io/xlsb/read.js +47 -29
  118. package/dist/io/xlsb/record-stream.d.ts +30 -0
  119. package/dist/io/xlsb/record-stream.js +20 -0
  120. package/dist/io/xlsb/record-types.d.ts +1 -5
  121. package/dist/io/xlsb/record-types.js +1 -5
  122. package/dist/io/xlsx/cell-accumulator.d.ts +9 -2
  123. package/dist/io/xlsx/cell-accumulator.js +32 -9
  124. package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
  125. package/dist/io/xlsx/cell-style-resolution.js +29 -0
  126. package/dist/io/xlsx/cell-value.d.ts +5 -3
  127. package/dist/io/xlsx/cell-value.js +13 -15
  128. package/dist/io/xlsx/color-xml.js +3 -2
  129. package/dist/io/xlsx/column-budget.d.ts +26 -0
  130. package/dist/io/xlsx/column-budget.js +22 -0
  131. package/dist/io/xlsx/comments.d.ts +6 -2
  132. package/dist/io/xlsx/comments.js +36 -31
  133. package/dist/io/xlsx/conditional-formatting.d.ts +9 -5
  134. package/dist/io/xlsx/conditional-formatting.js +51 -46
  135. package/dist/io/xlsx/data-validation.d.ts +9 -7
  136. package/dist/io/xlsx/data-validation.js +38 -35
  137. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  138. package/dist/io/xlsx/edit-vba.js +15 -29
  139. package/dist/io/xlsx/font-xml.d.ts +7 -0
  140. package/dist/io/xlsx/font-xml.js +104 -0
  141. package/dist/io/xlsx/hyperlinks.d.ts +41 -7
  142. package/dist/io/xlsx/hyperlinks.js +40 -28
  143. package/dist/io/xlsx/images.js +24 -25
  144. package/dist/io/xlsx/namespaces.d.ts +16 -0
  145. package/dist/io/xlsx/namespaces.js +7 -0
  146. package/dist/io/xlsx/package-plan.d.ts +23 -3
  147. package/dist/io/xlsx/package-plan.js +66 -25
  148. package/dist/io/xlsx/part-names.d.ts +41 -0
  149. package/dist/io/xlsx/part-names.js +46 -0
  150. package/dist/io/xlsx/pivot.js +8 -7
  151. package/dist/io/xlsx/read-parts.d.ts +59 -0
  152. package/dist/io/xlsx/read-parts.js +252 -0
  153. package/dist/io/xlsx/read-pivot.js +3 -1
  154. package/dist/io/xlsx/read-repair.d.ts +26 -0
  155. package/dist/io/xlsx/read-repair.js +39 -0
  156. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +1 -1
  157. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +7 -3
  158. package/dist/io/xlsx/read-rows.d.ts +18 -5
  159. package/dist/io/xlsx/read-rows.js +50 -37
  160. package/dist/io/xlsx/read-shared-strings.js +5 -3
  161. package/dist/io/xlsx/read-styles.d.ts +1 -6
  162. package/dist/io/xlsx/read-styles.js +65 -109
  163. package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
  164. package/dist/io/xlsx/read-workbook-xml.js +170 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +11 -1
  166. package/dist/io/xlsx/read-worksheet.js +51 -193
  167. package/dist/io/xlsx/read.d.ts +5 -13
  168. package/dist/io/xlsx/read.js +111 -436
  169. package/dist/io/xlsx/rich-text.js +1 -1
  170. package/dist/io/xlsx/row-position.d.ts +18 -0
  171. package/dist/io/xlsx/row-position.js +11 -0
  172. package/dist/io/xlsx/row-xml.d.ts +128 -0
  173. package/dist/io/xlsx/row-xml.js +203 -0
  174. package/dist/io/xlsx/shared-formulas.js +2 -2
  175. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  176. package/dist/io/xlsx/sheet-properties.js +166 -37
  177. package/dist/io/xlsx/style-elements.d.ts +43 -0
  178. package/dist/io/xlsx/style-elements.js +156 -0
  179. package/dist/io/xlsx/styles.d.ts +12 -3
  180. package/dist/io/xlsx/styles.js +5 -204
  181. package/dist/io/xlsx/tables.js +11 -14
  182. package/dist/io/xlsx/theme-xml.d.ts +26 -4
  183. package/dist/io/xlsx/theme-xml.js +107 -69
  184. package/dist/io/xlsx/threaded-comments.d.ts +8 -10
  185. package/dist/io/xlsx/threaded-comments.js +3 -1
  186. package/dist/io/xlsx/workbook-xml.d.ts +54 -4
  187. package/dist/io/xlsx/workbook-xml.js +84 -57
  188. package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
  189. package/dist/io/xlsx/worksheet-xml.js +59 -242
  190. package/dist/io/xlsx/write-stream.d.ts +71 -16
  191. package/dist/io/xlsx/write-stream.js +79 -41
  192. package/dist/io/xlsx/write.d.ts +6 -2
  193. package/dist/io/xlsx/write.js +217 -153
  194. package/dist/rel-type.d.ts +16 -0
  195. package/dist/rel-type.js +6 -0
  196. package/dist/token-set.d.ts +28 -0
  197. package/dist/token-set.js +7 -0
  198. package/dist/vba/bytes.d.ts +14 -5
  199. package/dist/vba/bytes.js +16 -6
  200. package/dist/vba/cfb-format.d.ts +32 -0
  201. package/dist/vba/cfb-format.js +13 -0
  202. package/dist/vba/cfb-writer.d.ts +9 -0
  203. package/dist/vba/cfb-writer.js +28 -25
  204. package/dist/vba/cfb.d.ts +13 -1
  205. package/dist/vba/cfb.js +73 -57
  206. package/dist/vba/codepage.js +2 -1
  207. package/dist/vba/errors.d.ts +7 -0
  208. package/dist/vba/ms-ovba.js +79 -23
  209. package/dist/vba/project-editor.js +35 -40
  210. package/dist/vba/project.d.ts +11 -1
  211. package/dist/vba/project.js +40 -21
  212. package/dist/vba/vba-encoding.js +5 -6
  213. package/dist/xml/xml-attrs.d.ts +37 -0
  214. package/dist/xml/xml-attrs.js +30 -0
  215. package/dist/xml/xml-chars.d.ts +40 -0
  216. package/dist/xml/xml-chars.js +12 -0
  217. package/dist/xml/xml-namespaces.d.ts +31 -0
  218. package/dist/xml/xml-namespaces.js +50 -0
  219. package/dist/xml/xml-read.d.ts +128 -81
  220. package/dist/xml/xml-read.js +161 -178
  221. package/dist/xml/xml-scan.d.ts +78 -0
  222. package/dist/xml/xml-scan.js +180 -0
  223. package/dist/xml/xml.d.ts +29 -28
  224. package/dist/xml/xml.js +24 -24
  225. package/package.json +13 -9
@@ -1,18 +1,20 @@
1
- import { AuthoringError } from '../errors.js';
2
- import { decodeCellRef, encodeAddress } from './address.js';
1
+ import { AuthoringError, quoted } from '../errors.js';
2
+ import { tokenSet } from '../token-set.js';
3
+ import { decodeCellRef, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, numberToColumn, } from './address.js';
4
+ import { cloneWith } from './clone.js';
3
5
  import { isDeletedSpan, shiftIndex } from './grid-shift.js';
6
+ import { MAX_TABLE_NAME_LENGTH, TABLE_NAME_PATTERN } from './limits.js';
7
+ const STYLE_INFO_CLONE = {
8
+ name: (name) => (name === 'None' ? undefined : name),
9
+ showFirstColumn: 'value',
10
+ showLastColumn: 'value',
11
+ showRowStripes: 'value',
12
+ showColumnStripes: 'value',
13
+ };
4
14
  function cloneStyleInfo(style) {
5
- const clone = {};
6
- if (style.name !== undefined && style.name !== 'None')
7
- clone.name = style.name;
8
- if (style.showFirstColumn !== undefined)
9
- clone.showFirstColumn = style.showFirstColumn;
10
- if (style.showLastColumn !== undefined)
11
- clone.showLastColumn = style.showLastColumn;
12
- if (style.showRowStripes !== undefined)
13
- clone.showRowStripes = style.showRowStripes;
14
- if (style.showColumnStripes !== undefined)
15
- clone.showColumnStripes = style.showColumnStripes;
15
+ const clone = cloneWith(style, STYLE_INFO_CLONE);
16
+ if (clone.name === undefined)
17
+ delete clone.name;
16
18
  return clone;
17
19
  }
18
20
  export const TOTALS_ROW_SUBTOTAL_CODE = {
@@ -25,7 +27,7 @@ export const TOTALS_ROW_SUBTOTAL_CODE = {
25
27
  sum: 109,
26
28
  var: 110,
27
29
  };
28
- const TOTALS_ROW_FUNCTIONS = {
30
+ export const isTotalsRowFunction = tokenSet({
29
31
  average: true,
30
32
  countNums: true,
31
33
  count: true,
@@ -36,11 +38,7 @@ const TOTALS_ROW_FUNCTIONS = {
36
38
  var: true,
37
39
  custom: true,
38
40
  none: true,
39
- };
40
- export function isTotalsRowFunction(value) {
41
- return Object.hasOwn(TOTALS_ROW_FUNCTIONS, value);
42
- }
43
- const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
41
+ });
44
42
  function disambiguateColumnNames(columns) {
45
43
  const seen = new Set();
46
44
  return columns.map((column) => {
@@ -52,11 +50,11 @@ function disambiguateColumnNames(columns) {
52
50
  });
53
51
  }
54
52
  function validateTableName(name) {
55
- if (name.length === 0 || name.length > 255) {
56
- throw new RangeError(`table name ${JSON.stringify(name)} must be between 1 and 255 characters`);
53
+ if (name.length === 0 || name.length > MAX_TABLE_NAME_LENGTH) {
54
+ throw new RangeError(`table name ${quoted(name)} must be between 1 and ${MAX_TABLE_NAME_LENGTH} characters`);
57
55
  }
58
- if (!IDENTIFIER.test(name)) {
59
- throw new SyntaxError(`table name ${JSON.stringify(name)} is not a valid Excel identifier: it must start with a letter, ` +
56
+ if (!TABLE_NAME_PATTERN.test(name)) {
57
+ throw new SyntaxError(`table name ${quoted(name)} is not a valid Excel identifier: it must start with a letter, ` +
60
58
  'underscore, or backslash and contain only letters, digits, periods, and underscores');
61
59
  }
62
60
  }
@@ -76,17 +74,17 @@ export class Table {
76
74
  constructor(options, grid) {
77
75
  validateTableName(options.name);
78
76
  if (options.columns.length === 0) {
79
- throw new AuthoringError(`table "${options.name}" must declare at least one column`);
77
+ throw new AuthoringError(`table ${quoted(options.name)} must declare at least one column`);
80
78
  }
81
79
  if (!Number.isInteger(options.rowCount) || options.rowCount < 0) {
82
- throw new RangeError(`table "${options.name}" has an invalid data-row count (${options.rowCount})`);
80
+ throw new RangeError(`table ${quoted(options.name)} has an invalid data-row count (${options.rowCount})`);
83
81
  }
84
82
  let anchor;
85
83
  try {
86
84
  anchor = decodeCellRef(options.ref);
87
85
  }
88
86
  catch (cause) {
89
- throw new SyntaxError(`table ref "${options.ref}" must anchor at a single cell (e.g. "A1")`, {
87
+ throw new SyntaxError(`table ref ${quoted(options.ref)} must anchor at a single cell (e.g. "A1")`, {
90
88
  cause,
91
89
  });
92
90
  }
@@ -104,7 +102,15 @@ export class Table {
104
102
  this.#dataRowCount = options.rowCount;
105
103
  this.#grid = grid;
106
104
  if (this.#rowSpan < 1) {
107
- throw new AuthoringError(`table "${this.name}" has no rows: it needs a header row or at least one data row`);
105
+ throw new AuthoringError(`table ${quoted(this.name)} has no rows: it needs a header row or at least one data row`);
106
+ }
107
+ if (this.#right > MAX_COLUMN) {
108
+ throw new RangeError(`table ${quoted(this.name)} spans ${this.columns.length} columns from ` +
109
+ `${numberToColumn(this.#anchorCol)}, past the last column (${numberToColumn(MAX_COLUMN)})`);
110
+ }
111
+ if (this.#bottom > MAX_ROW) {
112
+ throw new RangeError(`table ${quoted(this.name)} spans ${this.#rowSpan} rows from ${this.#anchorRow}, ` +
113
+ `past the last row (${MAX_ROW})`);
108
114
  }
109
115
  if (grid !== undefined)
110
116
  this.#materializeFrame(grid);
@@ -117,13 +123,13 @@ export class Table {
117
123
  }
118
124
  addRow(values = []) {
119
125
  if (values.length > this.columnCount) {
120
- throw new RangeError(`row has ${values.length} values but table "${this.name}" has ${this.columnCount} columns`);
126
+ throw new RangeError(`row has ${values.length} values but table ${quoted(this.name)} has ${this.columnCount} columns`);
121
127
  }
122
128
  const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
123
129
  const grid = this.#grid;
124
130
  if (this.totalsRow) {
125
131
  if (grid === undefined) {
126
- throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot relocate its totals row to append a data row`);
132
+ throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot relocate its totals row to append a data row`);
127
133
  }
128
134
  grid.insertRow(target);
129
135
  this.#writeRow(grid, target, values);
@@ -131,7 +137,7 @@ export class Table {
131
137
  }
132
138
  if (values.length > 0) {
133
139
  if (grid === undefined) {
134
- throw new AuthoringError(`table "${this.name}" is not attached to a worksheet: cannot write appended row values`);
140
+ throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot write appended row values`);
135
141
  }
136
142
  this.#writeRow(grid, target, values);
137
143
  }
@@ -176,11 +182,11 @@ export class Table {
176
182
  });
177
183
  }
178
184
  }
179
- shiftRows(start, count, delta) {
180
- if (isDeletedSpan(this.#anchorRow, this.#bottom, start, count))
185
+ shiftRows(splice) {
186
+ if (isDeletedSpan(this.#anchorRow, this.#bottom, splice))
181
187
  return false;
182
- const top = shiftIndex(this.#anchorRow, start, count, delta, 'row');
183
- const bottom = shiftIndex(this.#bottom, start, count, delta, 'row');
188
+ const top = shiftIndex(this.#anchorRow, splice);
189
+ const bottom = shiftIndex(this.#bottom, splice);
184
190
  const span = bottom - top + 1;
185
191
  const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
186
192
  const dataRows = span - fixedRows;
@@ -190,9 +196,13 @@ export class Table {
190
196
  this.#dataRowCount = dataRows;
191
197
  return true;
192
198
  }
193
- shiftColumns(start, count, delta) {
194
- if (this.#anchorCol >= start + count)
195
- this.#anchorCol += delta;
199
+ shiftColumns(splice) {
200
+ if (isDeletedSpan(this.#anchorCol, this.#right, splice))
201
+ return false;
202
+ const anchor = shiftIndex(this.#anchorCol, splice);
203
+ if (anchor + this.columns.length - 1 > MAX_COLUMN)
204
+ return false;
205
+ this.#anchorCol = anchor;
196
206
  return true;
197
207
  }
198
208
  get options() {
@@ -213,13 +223,18 @@ export class Table {
213
223
  return options;
214
224
  }
215
225
  get range() {
216
- return `${encodeAddress(this.#anchorCol, this.#anchorRow)}:${encodeAddress(this.#right, this.#bottom)}`;
226
+ return encodeRect({
227
+ top: this.#anchorRow,
228
+ left: this.#anchorCol,
229
+ bottom: this.#bottom,
230
+ right: this.#right,
231
+ });
217
232
  }
218
233
  get autoFilterRef() {
219
234
  if (!this.autoFilter)
220
235
  return undefined;
221
236
  const bottom = this.#anchorRow + this.#dataRowCount;
222
- return `${encodeAddress(this.#anchorCol, this.#anchorRow)}:${encodeAddress(this.#right, bottom)}`;
237
+ return encodeRect({ top: this.#anchorRow, left: this.#anchorCol, bottom, right: this.#right });
223
238
  }
224
239
  get region() {
225
240
  return { top: this.#anchorRow, left: this.#anchorCol, bottom: this.#bottom, right: this.#right };
@@ -1,3 +1,5 @@
1
+ import { quoted } from '../errors.js';
2
+ import { parseArgb } from './style.js';
1
3
  export const THEME_COLOR_SLOTS = [
2
4
  'lt1',
3
5
  'dk1',
@@ -36,10 +38,9 @@ export const DEFAULT_THEME_FONTS = {
36
38
  minor: OFFICE_BODY_FACE,
37
39
  };
38
40
  export function normalizeThemeColor(value) {
39
- const hex = value.startsWith('#') ? value.slice(1) : value;
40
- const rgb = hex.length === 8 ? hex.slice(2) : hex;
41
- if (!/^[0-9a-fA-F]{6}$/.test(rgb)) {
42
- throw new SyntaxError(`Invalid theme colour ${JSON.stringify(value)}: expected 6 hexadecimal digits (RRGGBB)`);
41
+ const argb = parseArgb(value);
42
+ if (argb === undefined) {
43
+ throw new SyntaxError(`invalid theme colour ${quoted(value)}: expected 6 hexadecimal digits (RRGGBB)`);
43
44
  }
44
- return rgb.toUpperCase();
45
+ return argb.slice(2).toUpperCase();
45
46
  }
@@ -0,0 +1,44 @@
1
+ import { type Cell } from './cell.ts';
2
+ import type { MergeRect } from './merge.ts';
3
+ /**
4
+ * The sheet storage an extent reads. Held by reference and never reassigned, so the extent always
5
+ * sees the live grid; the value types are irrelevant to it, only which keys are present.
6
+ */
7
+ export interface ExtentStorage {
8
+ readonly rows: ReadonlyMap<number, ReadonlyMap<number, Cell>>;
9
+ readonly rowProperties: ReadonlyMap<number, unknown>;
10
+ readonly columns: ReadonlyMap<number, unknown>;
11
+ readonly mergeRects: readonly MergeRect[];
12
+ }
13
+ /**
14
+ * The last used row and column of a sheet, tracked incrementally. The sheet reports every edit that
15
+ * can move the grid's outer keys; this turns those reports into the two accessors behind
16
+ * `Worksheet.rowCount` and `Worksheet.columnCount`.
17
+ */
18
+ export declare class UsedExtent {
19
+ #private;
20
+ constructor(storage: ExtentStorage);
21
+ /** Note a cell materialised at `(row, col)`. Whether it goes on to carry anything is not this
22
+ * class's business: the bound is on where a used cell *could* be, and the read confirms it. */
23
+ noteCell(row: number, col: number): void;
24
+ /** Note a row that bounds the used range by declaration: one given properties of its own. */
25
+ noteDeclaredRow(row: number): void;
26
+ /** Note a column that bounds the used range by declaration: one given properties of its own. */
27
+ noteDeclaredColumn(col: number): void;
28
+ /** Note a merged region, which occupies its whole rectangle and so bounds both axes. */
29
+ noteMerge(rect: MergeRect): void;
30
+ /**
31
+ * Report an edit that can pull the grid's outer keys inward: an eviction, a splice, an unmerge, a
32
+ * row replaced wholesale. The bounds are recomputed once, on the next read, rather than adjusted
33
+ * here, because finding the *new* maximum after a deletion is the same scan as finding it from
34
+ * scratch. Appending never invalidates, which is the whole point.
35
+ */
36
+ invalidate(): void;
37
+ /** Report that the grid has been emptied, which puts the bounds at a known zero rather than
38
+ * stale: nothing is left to scan. */
39
+ reset(): void;
40
+ /** The 1-based index of the last row carrying anything, or 0 when the sheet holds nothing. */
41
+ get lastRow(): number;
42
+ /** The 1-based index of the last column carrying anything, or 0 when the sheet holds nothing. */
43
+ get lastColumn(): number;
44
+ }
@@ -0,0 +1,102 @@
1
+ import { cellCarriesContent } from './cell.js';
2
+ function rowIsUsed(cols) {
3
+ for (const cell of cols.values()) {
4
+ if (cellCarriesContent(cell))
5
+ return true;
6
+ }
7
+ return false;
8
+ }
9
+ export class UsedExtent {
10
+ #storage;
11
+ #topRowKey = 0;
12
+ #topColumnKey = 0;
13
+ #topDeclaredRow = 0;
14
+ #topDeclaredColumn = 0;
15
+ #stale = false;
16
+ constructor(storage) {
17
+ this.#storage = storage;
18
+ }
19
+ noteCell(row, col) {
20
+ if (row > this.#topRowKey)
21
+ this.#topRowKey = row;
22
+ if (col > this.#topColumnKey)
23
+ this.#topColumnKey = col;
24
+ }
25
+ noteDeclaredRow(row) {
26
+ if (row > this.#topDeclaredRow)
27
+ this.#topDeclaredRow = row;
28
+ }
29
+ noteDeclaredColumn(col) {
30
+ if (col > this.#topDeclaredColumn)
31
+ this.#topDeclaredColumn = col;
32
+ }
33
+ noteMerge(rect) {
34
+ this.noteDeclaredRow(rect.bottom);
35
+ this.noteDeclaredColumn(rect.right);
36
+ }
37
+ invalidate() {
38
+ this.#stale = true;
39
+ }
40
+ reset() {
41
+ this.#topRowKey = 0;
42
+ this.#topColumnKey = 0;
43
+ this.#topDeclaredRow = 0;
44
+ this.#topDeclaredColumn = 0;
45
+ this.#stale = false;
46
+ }
47
+ get lastRow() {
48
+ this.#refresh();
49
+ if (this.#topRowKey <= this.#topDeclaredRow)
50
+ return this.#topDeclaredRow;
51
+ const top = this.#storage.rows.get(this.#topRowKey);
52
+ if (top !== undefined && rowIsUsed(top))
53
+ return this.#topRowKey;
54
+ let last = this.#topDeclaredRow;
55
+ for (const [number, cols] of this.#storage.rows) {
56
+ if (number > last && rowIsUsed(cols))
57
+ last = number;
58
+ }
59
+ return last;
60
+ }
61
+ get lastColumn() {
62
+ this.#refresh();
63
+ if (this.#topColumnKey <= this.#topDeclaredColumn)
64
+ return this.#topDeclaredColumn;
65
+ for (const cols of this.#storage.rows.values()) {
66
+ const cell = cols.get(this.#topColumnKey);
67
+ if (cell !== undefined && cellCarriesContent(cell))
68
+ return this.#topColumnKey;
69
+ }
70
+ let last = this.#topDeclaredColumn;
71
+ for (const cols of this.#storage.rows.values()) {
72
+ for (const [col, cell] of cols) {
73
+ if (col > last && cellCarriesContent(cell))
74
+ last = col;
75
+ }
76
+ }
77
+ return last;
78
+ }
79
+ #refresh() {
80
+ if (!this.#stale)
81
+ return;
82
+ this.#stale = false;
83
+ this.#topRowKey = 0;
84
+ this.#topColumnKey = 0;
85
+ this.#topDeclaredRow = 0;
86
+ this.#topDeclaredColumn = 0;
87
+ for (const [number, cols] of this.#storage.rows) {
88
+ if (number > this.#topRowKey)
89
+ this.#topRowKey = number;
90
+ for (const col of cols.keys()) {
91
+ if (col > this.#topColumnKey)
92
+ this.#topColumnKey = col;
93
+ }
94
+ }
95
+ for (const number of this.#storage.rowProperties.keys())
96
+ this.noteDeclaredRow(number);
97
+ for (const index of this.#storage.columns.keys())
98
+ this.noteDeclaredColumn(index);
99
+ for (const rect of this.#storage.mergeRects)
100
+ this.noteMerge(rect);
101
+ }
102
+ }
@@ -15,6 +15,15 @@ export type ValueType = (typeof ValueType)[keyof typeof ValueType];
15
15
  /** The canonical Excel error literals a cell (or formula result) can carry. */
16
16
  export declare const ERROR_CODES: readonly ['#N/A', '#REF!', '#NAME?', '#DIV/0!', '#NULL!', '#VALUE!', '#NUM!', '#SPILL!', '#CALC!', '#GETTING_DATA'];
17
17
  export type ErrorCode = (typeof ERROR_CODES)[number];
18
+ /**
19
+ * The error a spreadsheet puts in place of a reference that has nowhere left to point.
20
+ *
21
+ * Named here rather than in the two formula codecs that produce it. Both of them -- the A1 renderer
22
+ * in `core/formula.ts` and the BIFF12 one in `io/xlsb/formula.ts` -- had a private literal of their
23
+ * own, spelled the same and named differently, for a value {@link ERROR_CODES} already publishes and
24
+ * `isErrorCode` already recognises.
25
+ */
26
+ export declare const REF_ERROR: ErrorCode;
18
27
  /** An in-cell error, e.g. `{error: '#REF!'}`. */
19
28
  export interface ErrorValue {
20
29
  readonly error: ErrorCode;
@@ -21,6 +21,7 @@ export const ERROR_CODES = [
21
21
  '#CALC!',
22
22
  '#GETTING_DATA',
23
23
  ];
24
+ export const REF_ERROR = '#REF!';
24
25
  const ERROR_SET = new Set(ERROR_CODES);
25
26
  function hasKey(value, key) {
26
27
  return typeof value === 'object' && value !== null && key in value;
@@ -47,58 +48,59 @@ export function richTextToPlain(value) {
47
48
  return value.richText.map((run) => run.text).join('');
48
49
  }
49
50
  export function cellValueToText(value) {
50
- if (value === null)
51
- return '';
52
- switch (typeof value) {
53
- case 'number':
54
- return String(value);
55
- case 'string':
56
- return value;
57
- case 'boolean':
58
- return value ? 'TRUE' : 'FALSE';
59
- default:
60
- break;
61
- }
62
- if (value instanceof Date)
63
- return Number.isNaN(value.getTime()) ? '' : value.toISOString();
64
- if (isHyperlinkValue(value)) {
65
- return typeof value.text === 'string' ? value.text : richTextToPlain(value.text);
66
- }
67
- if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
68
- return value.result === undefined ? '' : cellValueToText(value.result);
69
- }
70
- if (isRichTextValue(value))
71
- return richTextToPlain(value);
72
- if (isErrorValue(value))
73
- return value.error;
74
- return unsupportedValue(value);
51
+ return classify(value, TO_TEXT);
75
52
  }
76
53
  export function detectValueType(value) {
54
+ return classify(value, TO_TYPE);
55
+ }
56
+ function classify(value, visit) {
77
57
  if (value === null)
78
- return ValueType.Null;
58
+ return visit.null(value);
79
59
  switch (typeof value) {
80
60
  case 'number':
81
- return ValueType.Number;
61
+ return visit.number(value);
82
62
  case 'string':
83
- return ValueType.String;
63
+ return visit.string(value);
84
64
  case 'boolean':
85
- return ValueType.Boolean;
65
+ return visit.boolean(value);
86
66
  default:
87
67
  break;
88
68
  }
89
69
  if (value instanceof Date)
90
- return ValueType.Date;
70
+ return visit.date(value);
91
71
  if (isHyperlinkValue(value))
92
- return ValueType.Hyperlink;
72
+ return visit.hyperlink(value);
93
73
  if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
94
- return ValueType.Formula;
74
+ return visit.formula(value);
95
75
  }
96
76
  if (isRichTextValue(value))
97
- return ValueType.RichText;
77
+ return visit.richText(value);
98
78
  if (isErrorValue(value))
99
- return ValueType.Error;
79
+ return visit.error(value);
100
80
  return unsupportedValue(value);
101
81
  }
82
+ const TO_TYPE = {
83
+ null: () => ValueType.Null,
84
+ number: () => ValueType.Number,
85
+ string: () => ValueType.String,
86
+ boolean: () => ValueType.Boolean,
87
+ date: () => ValueType.Date,
88
+ error: () => ValueType.Error,
89
+ formula: () => ValueType.Formula,
90
+ richText: () => ValueType.RichText,
91
+ hyperlink: () => ValueType.Hyperlink,
92
+ };
93
+ const TO_TEXT = {
94
+ null: () => '',
95
+ number: (value) => String(value),
96
+ string: (value) => value,
97
+ boolean: (value) => (value ? 'TRUE' : 'FALSE'),
98
+ date: (value) => (Number.isNaN(value.getTime()) ? '' : value.toISOString()),
99
+ error: (value) => value.error,
100
+ formula: (value) => (value.result === undefined ? '' : cellValueToText(value.result)),
101
+ richText: richTextToPlain,
102
+ hyperlink: (value) => (typeof value.text === 'string' ? value.text : richTextToPlain(value.text)),
103
+ };
102
104
  function unsupportedValue(value) {
103
105
  throw new TypeError(`unsupported cell value: ${describe(value)}`);
104
106
  }
@@ -0,0 +1,34 @@
1
+ import { type WorkbookImage } from './image.ts';
2
+ export declare class WorkbookMedia {
3
+ #private;
4
+ /** The registered images, indexed by the id {@link register} returned. Live, not a copy. */
5
+ get all(): readonly WorkbookImage[];
6
+ /** Look up a registered image by its id, or `undefined` if no image carries that id. */
7
+ get(id: number): WorkbookImage | undefined;
8
+ /**
9
+ * {@link get} for a caller that has no answer for an absent id.
10
+ *
11
+ * An unregistered id is what a sheet belonging to *another* workbook looks like from here, which
12
+ * is why the message names the sheet rather than the id alone. Emitting a package with a drawing
13
+ * pointing at media nobody registered is the silently-broken-image failure this refuses to start.
14
+ *
15
+ * @throws {AuthoringError} if no image carries that id.
16
+ */
17
+ require(id: number, forSheetName: string): WorkbookImage;
18
+ /** Store a picture's bytes and return the id that names them. */
19
+ register(extension: string | undefined, buffer: Uint8Array): number;
20
+ /**
21
+ * Register a picture arriving from elsewhere, re-using an identical one already held.
22
+ *
23
+ * The extension is re-normalised rather than trusted: a hand-built {@link WorkbookImage} may carry
24
+ * `".PNG"` where the registry holds `"png"`, and two spellings of one kind must not read as two
25
+ * pictures.
26
+ *
27
+ * Through the content index rather than a scan. Comparing byte-by-byte against every held picture
28
+ * made importing n distinct images cost n squared byte comparisons: fifty 1 MB pictures carried
29
+ * between workbooks compared about 2.5 GB. The *rule* is unchanged, and `imageContentKey` states
30
+ * why identity here is content and never object identity. The index is built in reverse so the
31
+ * FIRST id registered under a key wins, which is the answer the scan gave.
32
+ */
33
+ registerExisting(image: WorkbookImage): number;
34
+ }
@@ -0,0 +1,44 @@
1
+ import { AuthoringError, quoted } from '../errors.js';
2
+ import { imageContentKey, normalizeImageExtension } from './image.js';
3
+ export class WorkbookMedia {
4
+ #images = [];
5
+ #byContent;
6
+ get all() {
7
+ return this.#images;
8
+ }
9
+ get(id) {
10
+ return this.#images[id];
11
+ }
12
+ require(id, forSheetName) {
13
+ const image = this.#images[id];
14
+ if (image === undefined) {
15
+ throw new AuthoringError(`worksheet ${quoted(forSheetName)} shows image id ${id}, which is not registered on this ` +
16
+ "workbook: a sheet's images can only be exported by the workbook that holds them");
17
+ }
18
+ return image;
19
+ }
20
+ register(extension, buffer) {
21
+ const image = {
22
+ extension: normalizeImageExtension(extension, buffer),
23
+ data: buffer,
24
+ };
25
+ this.#images.push(image);
26
+ const id = this.#images.length - 1;
27
+ const index = this.#byContent;
28
+ if (index !== undefined) {
29
+ const key = imageContentKey(image);
30
+ if (!index.has(key))
31
+ index.set(key, id);
32
+ }
33
+ return id;
34
+ }
35
+ registerExisting(image) {
36
+ const candidate = {
37
+ extension: normalizeImageExtension(image.extension, image.data),
38
+ data: image.data,
39
+ };
40
+ this.#byContent ??= new Map(this.#images.map((held, id) => [imageContentKey(held), id]).reverse());
41
+ return (this.#byContent.get(imageContentKey(candidate)) ??
42
+ this.register(candidate.extension, candidate.data));
43
+ }
44
+ }
@@ -1,5 +1,71 @@
1
- import type { NamedCellStyle, TableStyleTable } from './style.ts';
1
+ import type { CellStyle } from './style.ts';
2
2
  import type { TableStyle } from './table-style.ts';
3
+ /**
4
+ * A named cell style: the OOXML `cellStyleXfs`/`cellStyles` layer. A spreadsheet applies a built-in
5
+ * or custom style (e.g. "Normal", "Accent1") whose visual facets live in this shared, named layer
6
+ * rather than on each cell's direct format; a cell links to it and inherits any facet the direct
7
+ * format leaves unset. The facets are a cell's own (see {@link CellStyle}); `name` is the style's
8
+ * display name and `builtinId` its Excel gallery index when it is a built-in style.
9
+ */
10
+ export type NamedCellStyle = Readonly<CellStyle> & {
11
+ readonly name?: string;
12
+ readonly builtinId?: number;
13
+ };
14
+ /**
15
+ * A differential style (OOXML CT_Dxf): formatting laid *over* whatever a cell already carries. Only
16
+ * the facets present override; the rest of the cell's own style shows through. It carries the subset
17
+ * of the cell-style facets (see {@link CellStyle}) a `<dxf>` can express: font, number format, fill,
18
+ * and border.
19
+ *
20
+ * Differential styles live in one workbook-level table (`<dxfs>`) that several features index into:
21
+ * a conditional-formatting rule's highlight format, and a table style's per-element formatting
22
+ * (`<tableStyleElement dxfId="…">`). They are interned and shared, so two features asking for the
23
+ * same formatting land on one entry.
24
+ *
25
+ * Not every facet reaches every consumer. As a **table style element**, Excel applies only the font,
26
+ * fill, and border: its own object model exposes `Font`, `Interior`, and `Borders` on a table style
27
+ * element and nothing for a number format, so a `numFmt` set here is carried faithfully through a
28
+ * round-trip but has no visible effect. The type is left whole rather than split, because the same
29
+ * value is legitimately reused across both consumers and narrowing it would only move the surprise.
30
+ */
31
+ export type DifferentialStyle = Pick<CellStyle, 'font' | 'numFmt' | 'fill' | 'border'>;
32
+ /**
33
+ * The `<tableStyles>` block of a styles part: the custom table/pivot style definitions a file
34
+ * declares, and the two gallery names it nominates as the default for a new table and a new pivot.
35
+ *
36
+ * Each entry of {@link styles} is one `<tableStyle>…</tableStyle>` fragment kept verbatim, for the
37
+ * same reason a `<dxf>` is: a `tableStyleElement`'s `dxfId` indexes the differential-style table,
38
+ * which the writer re-emits **at its original indices**, so the references stay valid without
39
+ * reparsing anything. That index-stability is load-bearing: renumbering the dxf table would
40
+ * silently re-point every preserved table style at a different format.
41
+ *
42
+ * The two default names are ordinary strings, not fragments: they are re-escaped on write, so they
43
+ * are held decoded.
44
+ */
45
+ export interface TableStyleTable {
46
+ readonly styles: readonly string[];
47
+ readonly defaultTableStyle?: string | undefined;
48
+ readonly defaultPivotStyle?: string | undefined;
49
+ /**
50
+ * The namespace prefixes the verbatim {@link styles} fragments use, mapped to their URI and to
51
+ * whether the source marked the prefix ignorable (`mc:Ignorable`).
52
+ *
53
+ * Carrying a fragment verbatim carries its *prefixes* too. Excel stamps a revision id
54
+ * (`xr9:uid="{…}"`) on every `<tableStyle>` it writes, so a fragment re-emitted under a
55
+ * `<styleSheet>` that declares only the default namespace is not namespace-well-formed, and no
56
+ * consumer can parse the part at all, which is a far louder failure than the dropped table style
57
+ * this preservation exists to prevent. The writer re-declares each prefix on `<styleSheet>` and
58
+ * re-states the ignorable ones, exactly as the source did.
59
+ */
60
+ readonly namespaces?: readonly TableStyleNamespace[];
61
+ }
62
+ /** One namespace declaration a preserved `<tableStyle>` fragment depends on. */
63
+ export interface TableStyleNamespace {
64
+ readonly prefix: string;
65
+ readonly uri: string;
66
+ /** Whether the source listed this prefix in the stylesheet's `mc:Ignorable`. */
67
+ readonly ignorable: boolean;
68
+ }
3
69
  /**
4
70
  * The preserved style tables of a workbook: the `<dxfs>` fragments, the named cell styles, the two
5
71
  * colour lists, and the table-style definitions a file declared plus the ones a caller authored.
@@ -1,3 +1,4 @@
1
+ import { isRelType } from '../rel-type.js';
1
2
  import { addVbaReference, parseVbaProject, removeVbaModule, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, VbaAuthorError, vbaProjectSignatureKind, } from '../vba/index.js';
2
3
  import { replaceContents } from './containers.js';
3
4
  export class WorkbookVbaProject {
@@ -21,7 +22,7 @@ export class WorkbookVbaProject {
21
22
  set bytes(bytes) {
22
23
  if (bytes !== undefined)
23
24
  parseVbaProject(bytes);
24
- replaceContents(this.#references, this.#references.filter((r) => !r.relType.endsWith('/vbaProject')));
25
+ replaceContents(this.#references, this.#references.filter((r) => !isRelType(r.relType, 'vbaProject')));
25
26
  if (bytes !== undefined) {
26
27
  this.#references.push({
27
28
  relType: VBA_PROJECT_REL_TYPE,
@@ -70,7 +71,7 @@ export class WorkbookVbaProject {
70
71
  this.bytes = addVbaReference(bytes, ref);
71
72
  }
72
73
  #ref() {
73
- return this.#references.find((r) => r.relType.endsWith('/vbaProject'));
74
+ return this.#references.find((r) => isRelType(r.relType, 'vbaProject'));
74
75
  }
75
76
  #entry() {
76
77
  const ref = this.#ref();