@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
package/README.md CHANGED
@@ -149,12 +149,15 @@ const writer = new WorkbookStreamWriter();
149
149
  const out = writer.addWorksheet('Big');
150
150
  for (let i = 1; i <= 1_000_000; i++) out.addRow([i, i * i]).commit();
151
151
  out.commit();
152
- const packaged: Uint8Array = await writer.commit(); // also delivered via writer.stream
152
+ const packaged = await writer.commit(); // Uint8Array here; also delivered via writer.stream
153
153
  ```
154
154
 
155
155
  The streaming writer is asynchronous where the buffered path is synchronous: `commit()`
156
156
  resolves to the package bytes and simultaneously pipes them through `writer.stream` (a Node
157
157
  `Readable`), so `writer.stream.pipe(res)` streams a workbook straight to an HTTP response.
158
+ Hand the writer its own sink instead (`{stream}` or `{filename}`) and never touch
159
+ `writer.stream`, and `commit()` resolves with `undefined`: the archive goes to the sink and is
160
+ never assembled as one object, which is what passing a sink was for.
158
161
 
159
162
  ## Writing without blocking the event loop
160
163
 
package/dist/bytes.d.ts CHANGED
@@ -22,3 +22,10 @@ export declare function concat(chunks: readonly Uint8Array[], size?: number): Ui
22
22
  * hash, so the loop below is not on any path where its cost is measurable.
23
23
  */
24
24
  export declare function toBase64(bytes: Uint8Array): string;
25
+ /** A string's UTF-16LE bytes, code unit by code unit, with lone surrogates carried through. */
26
+ export declare function utf16leBytes(text: string): Uint8Array;
27
+ /**
28
+ * Decode UTF-16LE code units. A trailing odd byte is dropped: these fields are length-prefixed by
29
+ * their producer, and half a code unit carries nothing to decode.
30
+ */
31
+ export declare function decodeUtf16le(bytes: Uint8Array): string;
package/dist/bytes.js CHANGED
@@ -32,3 +32,25 @@ export function toBase64(bytes) {
32
32
  }
33
33
  return out;
34
34
  }
35
+ export function utf16leBytes(text) {
36
+ const bytes = new Uint8Array(text.length * 2);
37
+ for (let i = 0; i < text.length; i++) {
38
+ const unit = text.charCodeAt(i);
39
+ bytes[i * 2] = unit & 0xff;
40
+ bytes[i * 2 + 1] = unit >>> 8;
41
+ }
42
+ return bytes;
43
+ }
44
+ const CHARS_PER_BATCH = 4096;
45
+ export function decodeUtf16le(bytes) {
46
+ let text = '';
47
+ let batch = [];
48
+ for (let i = 0; i + 1 < bytes.length; i += 2) {
49
+ batch.push(bytes[i] | (bytes[i + 1] << 8));
50
+ if (batch.length === CHARS_PER_BATCH) {
51
+ text += String.fromCharCode(...batch);
52
+ batch = [];
53
+ }
54
+ }
55
+ return batch.length > 0 ? text + String.fromCharCode(...batch) : text;
56
+ }
@@ -2,6 +2,22 @@
2
2
  export declare const MAX_COLUMN = 16384;
3
3
  /** Excel's row bound: 1 through 1048576. The other axis of {@link MAX_COLUMN}. */
4
4
  export declare const MAX_ROW = 1048576;
5
+ /**
6
+ * The same two bounds, zero-based: the last valid *index* on each axis.
7
+ *
8
+ * BIFF12 counts from zero throughout, so its reader and its formula decoder both need these, and both
9
+ * were deriving them privately. Two derivations of one fact is one fewer than three, but it is still
10
+ * the shape where a change to a limit reaches some of its consequences. The subtraction belongs beside
11
+ * the number it subtracts from.
12
+ */
13
+ export declare const MAX_COLUMN_INDEX: number;
14
+ export declare const MAX_ROW_INDEX: number;
15
+ /**
16
+ * Refuse a coordinate that names no position on the grid.
17
+ *
18
+ * @throws {RangeError} unless `value` is an integer within the axis's bounds.
19
+ */
20
+ export declare function assertAxisInBounds(axis: 'row' | 'column', value: number): void;
5
21
  /** @throws {RangeError} unless `n` is an integer in `1..MAX_COLUMN`. */
6
22
  export declare function assertColumnInBounds(n: number): void;
7
23
  /** @throws {RangeError} unless `n` is an integer in `1..MAX_ROW`. */
@@ -56,7 +72,19 @@ export interface RangeAddress {
56
72
  *
57
73
  * @throws {RangeError} unless `n` is an integer in `1..MAX_COLUMN`. */
58
74
  export declare function numberToColumn(n: number): string;
59
- /** Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`). */
75
+ /**
76
+ * Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`), or `undefined` when they name
77
+ * no column that can exist: not letters at all, or past `XFD` (`"ZZZ"` is well-formed and is column
78
+ * 18278, which the grid does not have). The tolerant half of the pair the module header describes,
79
+ * for a caller reading a file rather than a caller's own argument.
80
+ */
81
+ export declare function tryColumnToNumber(letters: string): number | undefined;
82
+ /**
83
+ * Convert column letters to a 1-based number (`"A" → 1`, `"AA" → 27`).
84
+ *
85
+ * @throws {RangeError} if the letters are malformed or name a column past `XFD`. The two are
86
+ * separate messages because a caller fixes them differently.
87
+ */
60
88
  export declare function columnToNumber(letters: string): number;
61
89
  /**
62
90
  * One corner of a range, as its reference text. An axis the corner omits contributes nothing, so a
@@ -67,6 +95,17 @@ export declare function columnToNumber(letters: string): number;
67
95
  * The unbounded counterpart to {@link encodeAddress}, which names one cell and so requires both.
68
96
  */
69
97
  export declare function encodeCornerRef(col: number | undefined, row: number | undefined): string;
98
+ /**
99
+ * A bounded rectangle as its canonical `tl:br` A1 range, `"B2:D5"`. A one-cell rectangle still reads
100
+ * as `"B2:B2"`, which is the form Excel writes and the form every consumer of these refs parses.
101
+ *
102
+ * The rendering half of {@link boundedRect}, and it belongs beside it for the same reason: ten sites
103
+ * spelled `` `${encodeAddress(left, top)}:${encodeAddress(right, bottom)}` `` by hand, and the risk in
104
+ * that expression is not its length but its argument order. `encodeAddress` takes column first and a
105
+ * rectangle names its rows first, so the two orders are transposed with respect to each other at every
106
+ * one of those sites.
107
+ */
108
+ export declare function encodeRect(rect: GridRect): string;
70
109
  /**
71
110
  * Decode a single cell/row/column reference into `{address, col, row}`. Anchoring
72
111
  * `$` signs are accepted and dropped; an absent axis is `undefined`.
@@ -98,6 +137,19 @@ export declare function decodeCellRef(reference: string): CellPosition;
98
137
  * there is nothing here".
99
138
  */
100
139
  export declare function tryDecodeCellRef(reference: string): CellPosition | undefined;
140
+ /**
141
+ * Narrow a decoded range to a {@link GridRect}, or `undefined` when either axis is unbounded.
142
+ *
143
+ * A whole-column reference (`A:A`) and a whole-row one (`1:1`) are legal range references whose
144
+ * corners are partly absent, so every caller that needs a real rectangle has to test all four. Doing
145
+ * that in seven places is the other half of the convention `GridRect` already exists to state once:
146
+ * the shape is declared here, and so is the one narrowing that produces it.
147
+ *
148
+ * A caller wanting to say something *different* about each axis (a `Range` refuses `A:A` and `1:1`
149
+ * with two distinct messages) still tests them separately, and one that only cares about the axis it
150
+ * is splicing still tests only that one. Neither is this question.
151
+ */
152
+ export declare function boundedRect(range: RangeAddress): GridRect | undefined;
101
153
  /**
102
154
  * {@link decodeRange} for a reference that came out of a file: `undefined` for anything that does
103
155
  * not name a region that can exist. The sibling of {@link tryDecodeCellRef} on the other arity: a
@@ -114,5 +166,11 @@ export declare function tryDecodeRange(reference: string): RangeAddress | undefi
114
166
  * corners coincide.
115
167
  */
116
168
  export declare function decodeRange(reference: string): RangeAddress;
117
- /** Encode a 1-based `col`/`row` pair into its canonical A1 address (`"B2"`). */
169
+ /**
170
+ * Encode a 1-based `col`/`row` pair into its canonical A1 address (`"B2"`).
171
+ *
172
+ * Both axes go through the shared guard. The column already did, through `numberToColumn`; the row
173
+ * checked only its lower bound in a message of its own, so `encodeAddress(1, 1048577)` produced an
174
+ * address naming a row Excel has no reference for while `encodeAddress(16385, 1)` refused.
175
+ */
118
176
  export declare function encodeAddress(col: number, row: number): string;
@@ -1,14 +1,20 @@
1
+ import { quoted } from '../errors.js';
1
2
  export const MAX_COLUMN = 16384;
2
3
  export const MAX_ROW = 1048576;
3
- export function assertColumnInBounds(n) {
4
- if (!Number.isInteger(n) || n < 1 || n > MAX_COLUMN) {
5
- throw new RangeError(`column ${n} is out of bounds: Excel supports 1..${MAX_COLUMN}`);
4
+ export const MAX_COLUMN_INDEX = MAX_COLUMN - 1;
5
+ export const MAX_ROW_INDEX = MAX_ROW - 1;
6
+ const AXIS_BOUND = { row: MAX_ROW, column: MAX_COLUMN };
7
+ export function assertAxisInBounds(axis, value) {
8
+ const bound = AXIS_BOUND[axis];
9
+ if (!Number.isInteger(value) || value < 1 || value > bound) {
10
+ throw new RangeError(`${axis} ${value} is out of bounds: Excel supports 1..${bound}`);
6
11
  }
7
12
  }
13
+ export function assertColumnInBounds(n) {
14
+ assertAxisInBounds('column', n);
15
+ }
8
16
  export function assertRowInBounds(n) {
9
- if (!Number.isInteger(n) || n < 1 || n > MAX_ROW) {
10
- throw new RangeError(`row ${n} is out of bounds: Excel supports 1..${MAX_ROW}`);
11
- }
17
+ assertAxisInBounds('row', n);
12
18
  }
13
19
  export function rectsOverlap(a, b) {
14
20
  return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
@@ -26,38 +32,43 @@ export function numberToColumn(n) {
26
32
  }
27
33
  return letters;
28
34
  }
29
- export function columnToNumber(letters) {
30
- if (letters.length === 0 || letters.length > 3) {
31
- throw new RangeError(`invalid column letters: "${letters}"`);
32
- }
35
+ const COLUMN_LETTERS = /^[A-Z]{1,3}$/;
36
+ export function tryColumnToNumber(letters) {
37
+ if (!COLUMN_LETTERS.test(letters))
38
+ return undefined;
33
39
  let n = 0;
34
40
  for (let i = 0; i < letters.length; i++) {
35
- const code = letters.charCodeAt(i);
36
- if (code < 65 || code > 90) {
37
- throw new RangeError(`invalid column letters: "${letters}"`);
38
- }
39
- n = n * 26 + (code - 64);
41
+ n = n * 26 + (letters.charCodeAt(i) - 64);
40
42
  }
41
- if (n > MAX_COLUMN) {
42
- throw new RangeError(`column "${letters}" is out of bounds: Excel supports up to ${MAX_COLUMN} (XFD)`);
43
+ return n > MAX_COLUMN ? undefined : n;
44
+ }
45
+ export function columnToNumber(letters) {
46
+ const n = tryColumnToNumber(letters);
47
+ if (n !== undefined)
48
+ return n;
49
+ if (!COLUMN_LETTERS.test(letters)) {
50
+ throw new RangeError(`invalid column letters: ${quoted(letters)}`);
43
51
  }
44
- return n;
52
+ throw new RangeError(`column ${quoted(letters)} is out of bounds: Excel supports up to ${MAX_COLUMN} (XFD)`);
45
53
  }
46
54
  export function encodeCornerRef(col, row) {
47
55
  return `${col !== undefined ? numberToColumn(col) : ''}${row !== undefined ? row : ''}`;
48
56
  }
57
+ export function encodeRect(rect) {
58
+ return `${encodeAddress(rect.left, rect.top)}:${encodeAddress(rect.right, rect.bottom)}`;
59
+ }
49
60
  function makeCellAddress(col, row) {
50
61
  return { address: encodeCornerRef(col, row), col, row };
51
62
  }
52
63
  export function decodeAddress(reference) {
53
64
  const match = SINGLE_REF.exec(reference);
54
65
  if (!match) {
55
- throw new SyntaxError(`invalid cell reference: "${reference}"`);
66
+ throw new SyntaxError(`invalid cell reference: ${quoted(reference)}`);
56
67
  }
57
68
  const letters = match[1] ?? '';
58
69
  const digits = match[2] ?? '';
59
70
  if (letters.length === 0 && digits.length === 0) {
60
- throw new SyntaxError(`invalid cell reference: "${reference}"`);
71
+ throw new SyntaxError(`invalid cell reference: ${quoted(reference)}`);
61
72
  }
62
73
  const col = letters.length > 0 ? columnToNumber(letters) : undefined;
63
74
  const row = digits.length > 0 ? Number.parseInt(digits, 10) : undefined;
@@ -66,7 +77,7 @@ export function decodeAddress(reference) {
66
77
  export function decodeCellRef(reference) {
67
78
  const { col, row } = decodeAddress(reference);
68
79
  if (col === undefined || row === undefined) {
69
- throw new SyntaxError(`"${reference}" is not a single-cell reference: it omits a column or row`);
80
+ throw new SyntaxError(`${quoted(reference)} is not a single-cell reference: it omits a column or row`);
70
81
  }
71
82
  return { col, row };
72
83
  }
@@ -83,6 +94,13 @@ export function tryDecodeCellRef(reference) {
83
94
  }
84
95
  return rowCanExist(position.row) ? position : undefined;
85
96
  }
97
+ export function boundedRect(range) {
98
+ const { top, left, bottom, right } = range;
99
+ if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
100
+ return undefined;
101
+ }
102
+ return { top, left, bottom, right };
103
+ }
86
104
  export function tryDecodeRange(reference) {
87
105
  let range;
88
106
  try {
@@ -125,8 +143,6 @@ export function decodeRange(reference) {
125
143
  };
126
144
  }
127
145
  export function encodeAddress(col, row) {
128
- if (!Number.isInteger(row) || row < 1) {
129
- throw new RangeError(`row ${row} is out of bounds: rows start at 1`);
130
- }
146
+ assertRowInBounds(row);
131
147
  return `${numberToColumn(col)}${row}`;
132
148
  }
@@ -1,3 +1,4 @@
1
+ import { type AxisSplice } from './grid-shift.ts';
1
2
  /**
2
3
  * A worksheet's autofilter: the filtered region plus any per-column criteria narrowing it. A bare
3
4
  * range (no columns) is just the header-row dropdowns Excel draws; adding {@link FilterColumn}s
@@ -54,8 +55,8 @@ export declare const isCustomFilterOperator: (value: string) => value is CustomF
54
55
  */
55
56
  export declare function canonicalizeAutoFilter(input: string | AutoFilter): AutoFilter;
56
57
  /**
57
- * Re-anchor a filter through a splice of `count` lines at `start` on `axis`, or drop it (`undefined`)
58
- * when the splice deleted every line it covered.
58
+ * Re-anchor a filter through a splice, or drop it (`undefined`) when the splice deleted every line
59
+ * it covered.
59
60
  *
60
61
  * A row splice only moves the range. A column splice moves its left edge too, and a criterion is
61
62
  * addressed by its offset from that edge rather than by an absolute column, so every offset is
@@ -63,4 +64,4 @@ export declare function canonicalizeAutoFilter(input: string | AutoFilter): Auto
63
64
  * Left alone, those offsets would keep their old numbers and silently re-point each filter at a
64
65
  * neighbouring column.
65
66
  */
66
- export declare function shiftAutoFilter(filter: AutoFilter, axis: 'row' | 'col', start: number, count: number, delta: number): AutoFilter | undefined;
67
+ export declare function shiftAutoFilter(filter: AutoFilter, splice: AxisSplice): AutoFilter | undefined;
@@ -1,7 +1,7 @@
1
- import { AuthoringError } from '../errors.js';
1
+ import { AuthoringError, quoted } from '../errors.js';
2
2
  import { tokenSet } from '../token-set.js';
3
- import { decodeRange, encodeAddress } from './address.js';
4
- import { isDeletedSpan, shiftIndex } from './grid-shift.js';
3
+ import { boundedRect, decodeRange, encodeRect } from './address.js';
4
+ import { isDeletedSpan, shiftIndex, shiftRect } from './grid-shift.js';
5
5
  export const isCustomFilterOperator = tokenSet({
6
6
  equal: true,
7
7
  notEqual: true,
@@ -12,15 +12,16 @@ export const isCustomFilterOperator = tokenSet({
12
12
  });
13
13
  export function canonicalizeAutoFilter(input) {
14
14
  const ref = typeof input === 'string' ? input : input.ref;
15
- const { top, left, bottom, right, dimensions } = decodeRange(ref);
16
- if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
17
- throw new AuthoringError(`autofilter range "${ref}" must be a bounded rectangle`);
15
+ const decoded = decodeRange(ref);
16
+ const rect = boundedRect(decoded);
17
+ if (rect === undefined) {
18
+ throw new AuthoringError(`autofilter range ${quoted(ref)} must be a bounded rectangle`);
18
19
  }
19
20
  if (typeof input === 'string')
20
- return { ref: dimensions, columns: [] };
21
- const width = right - left + 1;
21
+ return { ref: decoded.dimensions, columns: [] };
22
+ const width = rect.right - rect.left + 1;
22
23
  return {
23
- ref: dimensions,
24
+ ref: decoded.dimensions,
24
25
  columns: input.columns.map((column) => canonicalizeColumn(column, width)),
25
26
  };
26
27
  }
@@ -28,35 +29,35 @@ function canonicalizeColumn(column, width) {
28
29
  if (!Number.isInteger(column.colId) || column.colId < 0 || column.colId >= width) {
29
30
  throw new AuthoringError(`autofilter colId ${column.colId} is outside the filter range`);
30
31
  }
31
- if (column.criteria.kind === 'custom') {
32
- const count = column.criteria.predicates.length;
33
- if (count < 1 || count > 2) {
34
- throw new AuthoringError(`a custom filter needs one or two predicates, got ${count}`);
35
- }
32
+ const criteria = column.criteria;
33
+ if (criteria.kind === 'values') {
34
+ return { colId: column.colId, criteria: { ...criteria, values: [...criteria.values] } };
36
35
  }
37
- return column;
36
+ const count = criteria.predicates.length;
37
+ if (count < 1 || count > 2) {
38
+ throw new AuthoringError(`a custom filter needs one or two predicates, got ${count}`);
39
+ }
40
+ return {
41
+ colId: column.colId,
42
+ criteria: { ...criteria, predicates: criteria.predicates.map((predicate) => ({ ...predicate })) },
43
+ };
38
44
  }
39
- export function shiftAutoFilter(filter, axis, start, count, delta) {
40
- const { top, left, bottom, right } = decodeRange(filter.ref);
41
- if (top === undefined || left === undefined || bottom === undefined || right === undefined) {
45
+ export function shiftAutoFilter(filter, splice) {
46
+ const rect = boundedRect(decodeRange(filter.ref));
47
+ if (rect === undefined)
42
48
  return filter;
43
- }
44
- const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
45
- if (isDeletedSpan(lo, hi, start, count))
49
+ const moved = shiftRect(rect, splice);
50
+ if (moved === undefined)
46
51
  return undefined;
47
- const movedLo = shiftIndex(lo, start, count, delta, axis);
48
- const movedHi = shiftIndex(hi, start, count, delta, axis);
49
- if (axis === 'row') {
50
- const ref = `${encodeAddress(left, movedLo)}:${encodeAddress(right, movedHi)}`;
52
+ const ref = encodeRect(moved);
53
+ if (splice.axis === 'row')
51
54
  return { ref, columns: filter.columns };
52
- }
53
- const ref = `${encodeAddress(movedLo, top)}:${encodeAddress(movedHi, bottom)}`;
54
55
  const columns = [];
55
56
  for (const column of filter.columns) {
56
- const absolute = left + column.colId;
57
- if (isDeletedSpan(absolute, absolute, start, count))
57
+ const absolute = rect.left + column.colId;
58
+ if (isDeletedSpan(absolute, absolute, splice))
58
59
  continue;
59
- columns.push({ ...column, colId: shiftIndex(absolute, start, count, delta, axis) - movedLo });
60
+ columns.push({ ...column, colId: shiftIndex(absolute, splice) - moved.left });
60
61
  }
61
62
  return { ref, columns };
62
63
  }
@@ -3,6 +3,8 @@ export declare abstract class AxisHandle<P extends object> {
3
3
  protected abstract propertiesOf(): P | undefined;
4
4
  /** This position's format record, created if it does not exist yet. */
5
5
  protected abstract ensureProperties(): P;
6
+ /** Remove this position's format record entirely, so nothing is left declaring the line. */
7
+ protected abstract dropProperties(): void;
6
8
  protected read<K extends keyof P>(key: K): P[K] | undefined;
7
9
  protected write<K extends keyof P>(key: K, value: P[K]): void;
8
10
  }
@@ -5,8 +5,11 @@ export class AxisHandle {
5
5
  write(key, value) {
6
6
  if (value === undefined) {
7
7
  const properties = this.propertiesOf();
8
- if (properties !== undefined)
9
- delete properties[key];
8
+ if (properties === undefined)
9
+ return;
10
+ delete properties[key];
11
+ if (Object.keys(properties).length === 0)
12
+ this.dropProperties();
10
13
  return;
11
14
  }
12
15
  this.ensureProperties()[key] = value;
@@ -139,9 +139,10 @@ export declare class Cell {
139
139
  set note(note: string | undefined);
140
140
  }
141
141
  /**
142
- * Whether a cell carries formatting of its own: one of the {@link CellStyle} facets, the quote-prefix
143
- * flag, or a link to a named style. Driven by {@link CELL_STYLE_FACETS}, so a facet added to the
144
- * tuple reaches every "is this cell blank" decision without anyone remembering to widen a literal.
142
+ * Whether a cell carries formatting of its own: any {@link CellContent} facet, which is the six shared
143
+ * style facets plus the quote-prefix flag and the link to a named style. Driven by
144
+ * {@link CELL_CONTENT_FACETS}, so a facet added to the tuple reaches every "is this cell blank"
145
+ * decision without anyone remembering to widen a literal.
145
146
  *
146
147
  * Row- and column-inherited formatting is not the cell's own and does not count. Neither does a
147
148
  * note: it lives in the comments part, not the cell's `<c>` element, so a writer deciding whether an
package/dist/core/cell.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { assertColumnInBounds, assertRowInBounds, encodeAddress } from './address.js';
2
2
  import { NAMED_STYLE_ID } from './internal.js';
3
- import { assignStyleFacets, CELL_STYLE_FACETS, } from './style.js';
3
+ import { assignContentFacets, assignStyleFacets, CELL_CONTENT_FACETS, } from './style.js';
4
4
  import { cellValueToText, coerceCellValue, detectValueType, } from './value.js';
5
5
  export class Cell {
6
6
  row;
@@ -107,9 +107,7 @@ export class Cell {
107
107
  }
108
108
  }
109
109
  export function cellHasOwnStyle(cell) {
110
- return (CELL_STYLE_FACETS.some((facet) => cell[facet] !== undefined) ||
111
- cell.quotePrefix === true ||
112
- cell[NAMED_STYLE_ID] !== undefined);
110
+ return CELL_CONTENT_FACETS.some((facet) => cell[facet] !== undefined);
113
111
  }
114
112
  export function cellCarriesContent(cell) {
115
113
  return cell.value !== null || cell.note !== undefined || cellHasOwnStyle(cell);
@@ -119,7 +117,7 @@ export function applyCellStyle(cell, style) {
119
117
  }
120
118
  export function copyCellContent(source, target) {
121
119
  target.value = source.value;
122
- applyCellStyle(target, source);
120
+ assignContentFacets(target, source);
123
121
  target.note = source.note;
124
122
  }
125
123
  export function cellToModel(cell) {
@@ -129,6 +127,6 @@ export function cellToModel(cell) {
129
127
  value: cell.value,
130
128
  note: cell.note,
131
129
  };
132
- assignStyleFacets(model, cell);
130
+ assignContentFacets(model, cell);
133
131
  return model;
134
132
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * How far a copy of one field goes.
3
+ *
4
+ * - `'value'`: assign it. Correct for a scalar, and for anything genuinely immutable.
5
+ * - `'record'`: `{...v}`. A flat object whose own fields are scalars.
6
+ * - `'values'`: `[...v]`. An array of scalars.
7
+ * - `'records'`: `v.map(x => ({...x}))`. An array of flat objects.
8
+ * - a function: the field's own clone, for a nested type that has a plan of its own.
9
+ */
10
+ export type CloneStrategy<V> = 'value' | 'record' | 'values' | 'records' | ((value: V) => V);
11
+ /**
12
+ * One strategy per field of `T`. The `Record` is what makes a missing field a compile error.
13
+ *
14
+ * A field's strategy sees the field's type without `undefined`, because {@link cloneWith} skips an
15
+ * absent field rather than handing it over. Under `exactOptionalPropertyTypes` a facet declared
16
+ * `font?: Font | undefined` keeps that `undefined` through `Required`, so without the exclusion a
17
+ * nested type's own clone function would not satisfy its own field's strategy.
18
+ */
19
+ export type ClonePlan<T> = {
20
+ readonly [K in keyof Required<T>]-?: CloneStrategy<Exclude<Required<T>[K], undefined>>;
21
+ };
22
+ /**
23
+ * Copy `source` according to `plan`, leaving a field absent when the source leaves it absent.
24
+ *
25
+ * Absent rather than `undefined`: every one of these types declares its optional fields under
26
+ * `exactOptionalPropertyTypes`, where a present-but-undefined key is a different shape from a missing
27
+ * one, and a round-trip that fabricated the first would report formatting a cell does not have.
28
+ */
29
+ export declare function cloneWith<T extends object>(source: T, plan: ClonePlan<T>): T;
@@ -0,0 +1,24 @@
1
+ export function cloneWith(source, plan) {
2
+ const out = {};
3
+ for (const key of Object.keys(plan)) {
4
+ const value = source[key];
5
+ if (value === undefined)
6
+ continue;
7
+ out[key] = applyStrategy(value, plan[key]);
8
+ }
9
+ return out;
10
+ }
11
+ function applyStrategy(value, strategy) {
12
+ if (typeof strategy === 'function')
13
+ return strategy(value);
14
+ switch (strategy) {
15
+ case 'value':
16
+ return value;
17
+ case 'record':
18
+ return { ...value };
19
+ case 'values':
20
+ return [...value];
21
+ case 'records':
22
+ return value.map((entry) => ({ ...entry }));
23
+ }
24
+ }
@@ -1,3 +1,4 @@
1
+ import { hex } from '../hex.js';
1
2
  import { parseArgb } from './style.js';
2
3
  import { DEFAULT_THEME_COLOR_SCHEME, THEME_COLOR_SLOTS } from './theme.js';
3
4
  export const DEFAULT_INDEXED_COLORS = [
@@ -102,8 +103,5 @@ function hueToChannel(p, q, offset) {
102
103
  return p;
103
104
  }
104
105
  function channelHex(value) {
105
- return Math.round(clamp01(value) * 255)
106
- .toString(16)
107
- .toUpperCase()
108
- .padStart(2, '0');
106
+ return hex(Math.round(clamp01(value) * 255), 2);
109
107
  }
@@ -21,6 +21,7 @@ export declare class Column extends AxisHandle<ColumnProperties> {
21
21
  get properties(): Readonly<ColumnProperties> | undefined;
22
22
  protected propertiesOf(): ColumnProperties | undefined;
23
23
  protected ensureProperties(): ColumnProperties;
24
+ protected dropProperties(): void;
24
25
  /**
25
26
  * Stable key naming this column so a keyed-object row (see {@link Worksheet.addRow}) can place a
26
27
  * value under it by name rather than position. In-memory only: never serialized to OOXML.
@@ -22,6 +22,9 @@ export class Column extends AxisHandle {
22
22
  ensureProperties() {
23
23
  return this.#sheet[INTERNAL].ensureColumnProperties(this.index);
24
24
  }
25
+ dropProperties() {
26
+ this.#sheet[INTERNAL].dropColumnProperties(this.index);
27
+ }
25
28
  get key() {
26
29
  return this.read('key');
27
30
  }
@@ -1,9 +1,10 @@
1
+ import { quoted } from '../errors.js';
1
2
  const GUID = /^\{?([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})\}?$/i;
2
3
  export function commentThreadGuid(value, what) {
3
4
  const match = GUID.exec(value.trim());
4
5
  if (match === null) {
5
6
  throw new SyntaxError(`${what} must be a GUID: Excel writes threaded-comment ids as ` +
6
- `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", but got "${value}"`);
7
+ `"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", but got ${quoted(value)}`);
7
8
  }
8
9
  return `{${match.slice(1).join('-').toUpperCase()}}`;
9
10
  }
@@ -1,4 +1,5 @@
1
1
  import { type ConditionalFormatting } from './conditional-formatting.ts';
2
+ import type { AxisSplice } from './grid-shift.ts';
2
3
  export declare class ConditionalFormattingOverlay {
3
4
  #private;
4
5
  /**
@@ -14,7 +15,7 @@ export declare class ConditionalFormattingOverlay {
14
15
  * Re-anchor every rule set through a row or column splice, so a highlight keeps covering the cells
15
16
  * it was written for. A rule set whose every target area fell inside a deleted span goes with them.
16
17
  */
17
- shift(axis: 'row' | 'col', start: number, count: number, delta: number): void;
18
+ shift(splice: AxisSplice): void;
18
19
  /** Drop every conditional formatting, leaving the overlay empty. */
19
20
  clear(): void;
20
21
  }
@@ -9,10 +9,10 @@ export class ConditionalFormattingOverlay {
9
9
  get entries() {
10
10
  return this.#entries;
11
11
  }
12
- shift(axis, start, count, delta) {
12
+ shift(splice) {
13
13
  const entries = [];
14
14
  for (const entry of this.#entries) {
15
- const ref = shiftSqref(entry.ref, axis, start, count, delta);
15
+ const ref = shiftSqref(entry.ref, splice);
16
16
  if (ref !== undefined)
17
17
  entries.push({ ...entry, ref });
18
18
  }
@@ -1,4 +1,7 @@
1
- import type { Color, DifferentialStyle } from './style.ts';
1
+ import { type ClonePlan } from './clone.ts';
2
+ import type { AssertNever } from './internal.ts';
3
+ import { type Color } from './style.ts';
4
+ import type { DifferentialStyle } from './workbook-styles.ts';
2
5
  /** How a {@link CfValueObject} reads its `value`: `ST_CfvoType` verbatim. */
3
6
  export type CfValueObjectType = 'num' | 'percent' | 'max' | 'min' | 'percentile' | 'formula';
4
7
  /** Narrow a raw `<cfvo type>` token to a known {@link CfValueObjectType}. */
@@ -108,3 +111,8 @@ export interface ConditionalFormatting {
108
111
  /** A defensive deep copy, so a stored conditional formatting never aliases the caller's object nor
109
112
  * any of its nested arrays (rules, formulae, cfvo, colours) or the differential style. */
110
113
  export declare function cloneConditionalFormatting(cf: ConditionalFormatting): ConditionalFormatting;
114
+ declare const RULE_CLONE: ClonePlan<ConditionalFormattingRule>;
115
+ export type EveryRuleFieldIsCloned = AssertNever<Exclude<keyof Required<ConditionalFormattingRule>, keyof typeof RULE_CLONE>>;
116
+ declare const STYLE_CLONE: ClonePlan<DifferentialStyle>;
117
+ export type EveryDifferentialStyleFieldIsCloned = AssertNever<Exclude<keyof Required<DifferentialStyle>, keyof typeof STYLE_CLONE>>;
118
+ export {};