@shbernal/ts-xlsx 1.3.1 → 2.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 (186) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Quote a sheet name for use in a reference exactly when Excel would: a name that is not a plain
3
- * identifier or that would read as a cell address is wrapped in single quotes with its internal
3
+ * identifier, or that would read as a cell address, is wrapped in single quotes with its internal
4
4
  * quotes doubled, and a simple name is left bare. Shared by everything that *builds* a qualified
5
5
  * reference: the `_FilterDatabase` name the writer derives from an autofilter, and the `.xlsb`
6
6
  * reader's Ptg decoder, which has only a sheet index to work from and must spell the prefix itself.
7
7
  *
8
8
  * `last` names the far end of a 3-D span (`Data:More!A1`). A span is quoted as a whole or not at all,
9
- * because the quotes delimit the sheet *reference* rather than either name so one awkward endpoint
9
+ * because the quotes delimit the sheet *reference* rather than either name, so one awkward endpoint
10
10
  * puts both inside the quotes.
11
11
  */
12
12
  export declare function quoteSheetName(name: string, last?: string): string;
@@ -14,7 +14,7 @@ export declare function quoteSheetName(name: string, last?: string): string;
14
14
  * Prefix every modern function called by its plain name with `_xlfn.` so Excel accepts the stored
15
15
  * formula. Names already prefixed are left alone (never doubled), unknown/legacy functions pass
16
16
  * through untouched, and opaque regions (string literals, sheet names, structured references) are
17
- * preserved verbatim. No other rewriting occurs in particular no `@` implicit-intersection operator
17
+ * preserved verbatim. No other rewriting occurs: in particular no `@` implicit-intersection operator
18
18
  * is ever introduced.
19
19
  */
20
20
  export declare function mangleFunctions(formula: string): string;
@@ -25,8 +25,8 @@ export declare function mangleFunctions(formula: string): string;
25
25
  */
26
26
  export declare function unmangleFunctions(formula: string): string;
27
27
  /**
28
- * Prefix every LET/LAMBDA parameter identifier with `_xlpm.` at its declaration and at each
29
- * reference within the binding call's parentheses so Excel accepts the stored formula. The prefix
28
+ * Prefix every LET/LAMBDA parameter identifier with `_xlpm.`, at its declaration and at each
29
+ * reference within the binding call's parentheses, so Excel accepts the stored formula. The prefix
30
30
  * is lexically scoped: a name is only rewritten inside the call that binds it, opaque regions are
31
31
  * copied verbatim, and a lambda-valued parameter used as a call (`f(…)`) is prefixed too. Formulas
32
32
  * with no LET/LAMBDA pass through unchanged.
@@ -34,7 +34,7 @@ export declare function unmangleFunctions(formula: string): string;
34
34
  export declare function mangleParams(formula: string): string;
35
35
  /**
36
36
  * Mangle a model formula into its on-disk form: LET/LAMBDA parameter names first (`_xlpm.`), then the
37
- * modern-function prefix (`_xlfn.`). Ordering matters parameter mangling reads the plain LET/LAMBDA
37
+ * modern-function prefix (`_xlfn.`). Ordering matters: parameter mangling reads the plain LET/LAMBDA
38
38
  * names before the function pass qualifies them. The inverse for both prefixes is unmangleFunctions.
39
39
  */
40
40
  export declare function mangleFormula(formula: string): string;
@@ -1,9 +1,21 @@
1
+ import { type AutoFilter } from './autofilter.ts';
1
2
  import { Cell } from './cell.ts';
3
+ import type { ConditionalFormattingOverlay } from './conditional-formatting-overlay.ts';
4
+ import type { DataValidationOverlay } from './data-validation-overlay.ts';
2
5
  import { type AnchoredImage } from './image.ts';
3
6
  import type { MergeRect } from './merge.ts';
4
7
  import type { Table } from './table.ts';
5
8
  import { type CellValue } from './value.ts';
9
+ import type { WorksheetComments } from './worksheet-comments.ts';
6
10
  import type { ColumnProperties, RowProperties } from './worksheet.ts';
11
+ /**
12
+ * The sheet's autofilter, reached as a slot rather than held by reference like the containers beside
13
+ * it: it is a single replaceable value, and a splice that deletes every filtered line clears it.
14
+ */
15
+ export interface AutoFilterSlot {
16
+ get(): AutoFilter | undefined;
17
+ set(next: AutoFilter | undefined): void;
18
+ }
7
19
  interface GridStorage {
8
20
  readonly rows: Map<number, Map<number, Cell>>;
9
21
  readonly rowProperties: Map<number, RowProperties>;
@@ -12,6 +24,10 @@ interface GridStorage {
12
24
  readonly mergeRects: MergeRect[];
13
25
  readonly tables: Table[];
14
26
  readonly images: AnchoredImage[];
27
+ readonly dataValidations: DataValidationOverlay;
28
+ readonly conditionalFormattings: ConditionalFormattingOverlay;
29
+ readonly comments: WorksheetComments;
30
+ readonly autoFilter: AutoFilterSlot;
15
31
  }
16
32
  export declare class GridEdits {
17
33
  #private;
@@ -1,11 +1,10 @@
1
- import { decodeAddress, decodeRange, encodeAddress } from './address.js';
1
+ import { decodeRange, encodeAddress, tryDecodeCellRef } from './address.js';
2
+ import { shiftAutoFilter } from './autofilter.js';
2
3
  import { Cell, copyCellContent } from './cell.js';
3
4
  import { replaceContents } from './containers.js';
5
+ import { isDeletedSpan, shiftIndex } from './grid-shift.js';
4
6
  import { isOneCellAnchor } from './image.js';
5
7
  import { isSharedFormulaValue } from './value.js';
6
- function shiftIndex(v, start, count, delta) {
7
- return v < start ? v : v >= start + count ? v + delta : start;
8
- }
9
8
  export class GridEdits {
10
9
  #rows;
11
10
  #rowProperties;
@@ -14,6 +13,10 @@ export class GridEdits {
14
13
  #mergeRects;
15
14
  #tables;
16
15
  #images;
16
+ #dataValidations;
17
+ #conditionalFormattings;
18
+ #comments;
19
+ #autoFilter;
17
20
  constructor(storage) {
18
21
  this.#rows = storage.rows;
19
22
  this.#rowProperties = storage.rowProperties;
@@ -22,6 +25,10 @@ export class GridEdits {
22
25
  this.#mergeRects = storage.mergeRects;
23
26
  this.#tables = storage.tables;
24
27
  this.#images = storage.images;
28
+ this.#dataValidations = storage.dataValidations;
29
+ this.#conditionalFormattings = storage.conditionalFormattings;
30
+ this.#comments = storage.comments;
31
+ this.#autoFilter = storage.autoFilter;
25
32
  }
26
33
  spliceRows(start, count, inserted) {
27
34
  const delta = inserted.length - count;
@@ -43,6 +50,7 @@ export class GridEdits {
43
50
  this.#shiftTables('row', start, count, delta);
44
51
  this.#shiftImages('row', start, count, delta);
45
52
  this.#reanchorSharedFormulas('row', start, count, delta);
53
+ this.#shiftRangeBoundOverlays('row', start, count, delta);
46
54
  }
47
55
  spliceColumns(start, count, inserts) {
48
56
  const delta = inserts.length - count;
@@ -74,6 +82,16 @@ export class GridEdits {
74
82
  this.#shiftTables('col', start, count, delta);
75
83
  this.#shiftImages('col', start, count, delta);
76
84
  this.#reanchorSharedFormulas('col', start, count, delta);
85
+ this.#shiftRangeBoundOverlays('col', start, count, delta);
86
+ }
87
+ #shiftRangeBoundOverlays(axis, start, count, delta) {
88
+ this.#dataValidations.shift(axis, start, count, delta);
89
+ this.#conditionalFormattings.shift(axis, start, count, delta);
90
+ this.#comments.shift(axis, start, count, delta);
91
+ const filter = this.#autoFilter.get();
92
+ if (filter !== undefined) {
93
+ this.#autoFilter.set(shiftAutoFilter(filter, axis, start, count, delta));
94
+ }
77
95
  }
78
96
  #relocateRow(cols, destRow) {
79
97
  const moved = new Map();
@@ -95,12 +113,12 @@ export class GridEdits {
95
113
  const value = cell.value;
96
114
  if (!isSharedFormulaValue(value))
97
115
  continue;
98
- const master = decodeAddress(value.sharedFormula);
99
- if (master.col === undefined || master.row === undefined)
116
+ const master = tryDecodeCellRef(value.sharedFormula);
117
+ if (master === undefined)
100
118
  continue;
101
119
  const anchored = axis === 'row'
102
- ? encodeAddress(master.col, shiftIndex(master.row, start, count, delta))
103
- : encodeAddress(shiftIndex(master.col, start, count, delta), master.row);
120
+ ? encodeAddress(master.col, shiftIndex(master.row, start, count, delta, 'row'))
121
+ : encodeAddress(shiftIndex(master.col, start, count, delta, 'col'), master.row);
104
122
  if (anchored === value.sharedFormula)
105
123
  continue;
106
124
  const reanchored = { ...value, sharedFormula: anchored };
@@ -121,7 +139,7 @@ export class GridEdits {
121
139
  map.set(index, value);
122
140
  }
123
141
  #shiftMerges(axis, start, count, delta) {
124
- const shift = (v) => shiftIndex(v, start, count, delta);
142
+ const shift = (v) => shiftIndex(v, start, count, delta, axis);
125
143
  const merges = [];
126
144
  const rects = [];
127
145
  for (const range of this.#merges) {
@@ -131,7 +149,7 @@ export class GridEdits {
131
149
  continue;
132
150
  }
133
151
  const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
134
- if (lo >= start && hi < start + count)
152
+ if (isDeletedSpan(lo, hi, start, count))
135
153
  continue;
136
154
  const rect = axis === 'row'
137
155
  ? { top: shift(top), left, bottom: shift(bottom), right }
@@ -151,7 +169,7 @@ export class GridEdits {
151
169
  #shiftImages(axis, start, count, delta) {
152
170
  const shiftPoint = (point) => {
153
171
  const zeroBased = axis === 'row' ? point.row : point.col;
154
- const shifted = shiftIndex(zeroBased + 1, start, count, delta) - 1;
172
+ const shifted = shiftIndex(zeroBased + 1, start, count, delta, axis) - 1;
155
173
  if (shifted === zeroBased)
156
174
  return point;
157
175
  return axis === 'row' ? { ...point, row: shifted } : { ...point, col: shifted };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Where a 1-based coordinate on `axis` lands after `count` lines are deleted at `start` and the
3
+ * replacement lines shift what follows by `delta`: before the edit it stays put, at or after the
4
+ * edited span it shifts, and inside a deleted span it clamps to the cut line. The clamp is the best
5
+ * effort for a geometry straddling the cut; a caller that must instead *drop* what a delete swallowed
6
+ * whole tests {@link isDeletedSpan} first.
7
+ *
8
+ * A shift never leaves the grid: the result is clamped to the axis's last line, so a region already
9
+ * touching the bottom (or the right edge) keeps its edge there instead of naming a line the format
10
+ * has no room for. That shrinks such a region by what it could not move, which is the lesser of the
11
+ * two evils and is what Excel does to the same region on the same edit.
12
+ */
13
+ export declare function shiftIndex(v: number, start: number, count: number, delta: number, axis: 'row' | 'col'): number;
14
+ /**
15
+ * Whether the inclusive span `lo..hi` lies entirely within the `count` lines deleted at `start`: the
16
+ * test that separates "this moved" from "this is gone". A single coordinate is the degenerate span
17
+ * `lo === hi`.
18
+ */
19
+ export declare function isDeletedSpan(lo: number, hi: number, start: number, count: number): boolean;
@@ -0,0 +1,8 @@
1
+ import { MAX_COLUMN, MAX_ROW } from './address.js';
2
+ export function shiftIndex(v, start, count, delta, axis) {
3
+ const moved = v < start ? v : v >= start + count ? v + delta : start;
4
+ return Math.min(moved, axis === 'row' ? MAX_ROW : MAX_COLUMN);
5
+ }
6
+ export function isDeletedSpan(lo, hi, start, count) {
7
+ return lo >= start && hi < start + count;
8
+ }
@@ -17,7 +17,9 @@ export declare const PX_TO_EMU = 9525;
17
17
  * them; `oneCell` moves but keeps its size; `absolute` is pinned to the page and does neither. Excel
18
18
  * defaults to `oneCell` when the attribute is omitted. */
19
19
  export type ImageEditAs = 'oneCell' | 'twoCell' | 'absolute';
20
- /** A fixed image size in EMUs the extent of a one-cell anchor, which pixel dimensions convert into
20
+ /** Narrow a raw `<xdr:twoCellAnchor editAs>` token to a known {@link ImageEditAs}. */
21
+ export declare function isImageEditAs(value: string): value is ImageEditAs;
22
+ /** A fixed image size in EMUs: the extent of a one-cell anchor, which pixel dimensions convert into
21
23
  * via {@link PX_TO_EMU}. */
22
24
  export interface Extent {
23
25
  readonly cx: number;
@@ -63,7 +65,7 @@ export interface AnchoredImage {
63
65
  }
64
66
  /** A picture's bytes and its file kind, as held in the workbook's media registry. */
65
67
  export interface WorkbookImage {
66
- /** Lower-case file extension without a dot `"png"`, `"jpeg"`, `"gif"`. Drives the media part's
68
+ /** Lower-case file extension without a dot: `"png"`, `"jpeg"`, `"gif"`. Drives the media part's
67
69
  * name and content type. */
68
70
  readonly extension: string;
69
71
  readonly data: Uint8Array;
@@ -74,3 +76,38 @@ export interface WorkbookImage {
74
76
  * leading run of alphanumerics; a missing or all-punctuation hint falls back to sniffing the bytes'
75
77
  * magic number, so the package is always well-formed. */
76
78
  export declare function normalizeImageExtension(extension: string | undefined, data: Uint8Array): string;
79
+ /**
80
+ * An anchored image in workbook-independent form: the picture's own bytes rather than a media id
81
+ * into one particular workbook's registry.
82
+ *
83
+ * An {@link AnchoredImage} means nothing away from that registry: its `imageId` is an index, and
84
+ * the same index names a different picture (or none) in the next workbook. Attaching the picture
85
+ * itself is what lets an anchor cross that boundary, which is why the transfer form carries bytes
86
+ * where the stored form carries an id.
87
+ */
88
+ export interface PortableImage {
89
+ readonly image: WorkbookImage;
90
+ readonly anchor: ImageAnchor;
91
+ }
92
+ /**
93
+ * Every picture a worksheet shows, in the workbook-independent form of {@link PortableImage}: the
94
+ * images anchored to the grid, in the order they were added, and the background tiled behind it.
95
+ * {@link Workbook.exportImages} produces one, {@link Workbook.importImages} applies one.
96
+ */
97
+ export interface WorksheetImages {
98
+ readonly anchored: readonly PortableImage[];
99
+ readonly background: WorkbookImage | undefined;
100
+ }
101
+ /**
102
+ * The id under which `image` is already registered in `media`, or `undefined` if it is not.
103
+ *
104
+ * Content-addressed rather than reference-addressed: two byte-identical pictures are one picture,
105
+ * however they reached the registry. This is what keeps repeated imports from growing the media
106
+ * list without bound (the same logo carried onto twenty sheets registers once), and it is why the
107
+ * comparison is over bytes rather than object identity, which a picture arriving from another
108
+ * workbook would never satisfy.
109
+ *
110
+ * The length check comes first and short-circuits, so pictures of different sizes never reach the
111
+ * byte loop; only same-extension, same-length candidates are compared in full.
112
+ */
113
+ export declare function findRegisteredImage(media: readonly WorkbookImage[], image: WorkbookImage): number | undefined;
@@ -1,4 +1,8 @@
1
1
  export const PX_TO_EMU = 9525;
2
+ const IMAGE_EDIT_AS = { oneCell: true, twoCell: true, absolute: true };
3
+ export function isImageEditAs(value) {
4
+ return Object.hasOwn(IMAGE_EDIT_AS, value);
5
+ }
2
6
  export function isOneCellAnchor(anchor) {
3
7
  return 'ext' in anchor;
4
8
  }
@@ -38,3 +42,9 @@ export function normalizeImageExtension(extension, data) {
38
42
  }
39
43
  return sniffImageExtension(data);
40
44
  }
45
+ export function findRegisteredImage(media, image) {
46
+ const index = media.findIndex((held) => held.extension === image.extension &&
47
+ held.data.length === image.data.length &&
48
+ held.data.every((byte, i) => byte === image.data[i]));
49
+ return index === -1 ? undefined : index;
50
+ }
@@ -4,8 +4,16 @@
4
4
  */
5
5
  export declare const INTERNAL: unique symbol;
6
6
  /**
7
- * Keys a `Cell`'s link to a named cell style its OOXML `xfId`. A single hidden property rather than
7
+ * Keys a `Cell`'s link to a named cell style, its OOXML `xfId`. A single hidden property rather than
8
8
  * a channel object, so it stays a prototype accessor: cells are the one model class allocated in the
9
9
  * millions, and a per-instance internals object would be a real cost for state most cells never carry.
10
10
  */
11
11
  export declare const NAMED_STYLE_ID: unique symbol;
12
+ /**
13
+ * The exhaustiveness proof the mirror types are built on: instantiate it with a `keyof` difference
14
+ * that should be empty. An unmirrored field leaves that difference non-`never`, which does not
15
+ * satisfy the constraint, so the build fails naming the field that was missed: a type error at the
16
+ * declaration rather than a silent hole discovered by a round trip. `Row`, `Column` and
17
+ * `WORKSHEET_MODEL_FACETS` each carry one of these proofs; this is the mechanism all three use.
18
+ */
19
+ export type AssertNever<T extends never> = T;
@@ -4,8 +4,8 @@
4
4
  * shown.
5
5
  *
6
6
  * A file may state more, and stating more loses the value rather than the file. Excel opens such a
7
- * package without complaint and silently clamps the row to 409.6, a tick *above* what it lets
8
- * you assign, being 8192 twentieths of a point and so the width of the field it is read into — and
7
+ * package without complaint and silently clamps the row to 409.6, a tick *above* what it lets you
8
+ * assign, being 8192 twentieths of a point and so the width of the field it is read into, then
9
9
  * writes 409.6 back on its next save. Check against this constant to keep a stated height from
10
10
  * quietly becoming a different one.
11
11
  */
@@ -16,12 +16,12 @@ export declare const MAX_ROW_HEIGHT = 409.5;
16
16
  *
17
17
  * It is also the weaker of the two ceilings: it binds assignment only, and not a file at all.
18
18
  * Excel honours a `width` of 1000 read from a package, renders the column at it, and round-trips
19
- * it verbatim through its own save where an over-limit row height is clamped away. So a width
19
+ * it verbatim through its own save, where an over-limit row height is clamped away. So a width
20
20
  * above this is a column no Excel user could have produced by dragging, not a value at risk.
21
21
  *
22
22
  * Character units, not points or pixels: a width is a count of digits of the default font's
23
23
  * *maximum digit width*, which is why there is no companion `DEFAULT_COLUMN_WIDTH` constant here.
24
- * The width a column takes when it states none is a function of that font the familiar 8.43 holds
24
+ * The width a column takes when it states none is a function of that font: the familiar 8.43 holds
25
25
  * for Calibri 11 and not for a workbook whose normal style says otherwise (Excel reports 8.09 for
26
26
  * Aptos Narrow 11). `sheet.properties.defaultColWidth` is what a file declares, and
27
27
  * docs/knowledge/specs/default-font-must-not-be-assumed-for-column-widths.md is why assuming a
@@ -1,17 +1,11 @@
1
+ import { type GridRect } from './address.ts';
1
2
  import type { Cell } from './cell.ts';
2
- /** A merged region as inclusive 1-based grid bounds. */
3
- export interface MergeRect {
4
- readonly top: number;
5
- readonly left: number;
6
- readonly bottom: number;
7
- readonly right: number;
8
- }
9
- /** Whether two inclusive grid rectangles share at least one cell. */
10
- export declare function rectsOverlap(a: MergeRect, b: MergeRect): boolean;
3
+ /** A merged region, as the {@link GridRect} every range-shaped thing in the library is. */
4
+ export type MergeRect = GridRect;
11
5
  /**
12
6
  * Resolve a position to the master (top-left) of the merged region covering it, or to itself when no
13
7
  * region does. First covering region wins; `Worksheet.mergeCells` rejects overlaps, so at most one
14
- * region ever applies. Only fully-bounded rects participate an unbounded whole-row/column merge
8
+ * region ever applies. Only fully-bounded rects participate: an unbounded whole-row/column merge
15
9
  * carries no rect and so resolves nothing.
16
10
  */
17
11
  export declare function masterOf(rects: readonly MergeRect[], row: number, col: number): {
@@ -20,12 +14,23 @@ export declare function masterOf(rects: readonly MergeRect[], row: number, col:
20
14
  };
21
15
  /**
22
16
  * Drop any value already sitting in a merge's covered non-anchor cells, keeping only the top-left
23
- * anchor the collapse Excel performs on merge. A leftover covered value would serialise as a
17
+ * anchor, the collapse Excel performs on merge. A leftover covered value would serialise as a
24
18
  * populated `<c>` under the range's `<mergeCell>` ref, the geometry that trips Excel's repair prompt.
25
19
  * Styles are untouched: a border spanning the merged region rides the covered cells.
26
20
  */
27
21
  export declare function clearCoveredValues(rows: Map<number, Map<number, Cell>>, rect: MergeRect): void;
28
22
  /** Decode an OOXML `sqref` (one or more space-separated ranges) into containment rectangles. A whole
29
23
  * column or row leaves one axis unbounded, so its missing edges open to `Infinity` rather than
30
- * clamping a cell anywhere down the column still resolves inside it. */
24
+ * clamping: a cell anywhere down the column still resolves inside it. An area that names no region
25
+ * that can exist contributes no rectangle, the reader's rule for every other foreign attribute. */
31
26
  export declare function decodeSqrefRects(sqref: string): MergeRect[];
27
+ /**
28
+ * Re-anchor an OOXML `sqref` through a splice of `count` lines at `start` on `axis`, the inverse of
29
+ * {@link decodeSqrefRects}. Returns `undefined` when the splice deleted every area the `sqref` named:
30
+ * an empty `sqref` is not writable, so the entry holding it goes too.
31
+ *
32
+ * Each space-separated area shifts on its own, and one the splice does not move is returned as the
33
+ * *original text*. That matters for a file the library did not author: `B:B` and `B1:B1048576` decode
34
+ * identically, so a re-encode would rewrite a foreign spelling and cost the byte-clean round trip.
35
+ */
36
+ export declare function shiftSqref(sqref: string, axis: 'row' | 'col', start: number, count: number, delta: number): string | undefined;
@@ -1,7 +1,5 @@
1
- import { decodeRange } from './address.js';
2
- export function rectsOverlap(a, b) {
3
- return a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom;
4
- }
1
+ import { encodeCornerRef, tryDecodeRange } from './address.js';
2
+ import { isDeletedSpan, shiftIndex } from './grid-shift.js';
5
3
  export function masterOf(rects, row, col) {
6
4
  for (const rect of rects) {
7
5
  if (row >= rect.top && row <= rect.bottom && col >= rect.left && col <= rect.right) {
@@ -29,7 +27,10 @@ export function decodeSqrefRects(sqref) {
29
27
  for (const part of sqref.split(/\s+/)) {
30
28
  if (part === '')
31
29
  continue;
32
- const { top, left, bottom, right } = decodeRange(part);
30
+ const decoded = tryDecodeRange(part);
31
+ if (decoded === undefined)
32
+ continue;
33
+ const { top, left, bottom, right } = decoded;
33
34
  rects.push({
34
35
  top: top ?? 1,
35
36
  left: left ?? 1,
@@ -39,3 +40,33 @@ export function decodeSqrefRects(sqref) {
39
40
  }
40
41
  return rects;
41
42
  }
43
+ export function shiftSqref(sqref, axis, start, count, delta) {
44
+ const areas = [];
45
+ for (const area of sqref.split(/\s+/)) {
46
+ if (area === '')
47
+ continue;
48
+ const shifted = shiftSqrefArea(area, axis, start, count, delta);
49
+ if (shifted !== undefined)
50
+ areas.push(shifted);
51
+ }
52
+ return areas.length > 0 ? areas.join(' ') : undefined;
53
+ }
54
+ function shiftSqrefArea(area, axis, start, count, delta) {
55
+ const decoded = tryDecodeRange(area);
56
+ if (decoded === undefined)
57
+ return area;
58
+ const { top, left, bottom, right } = decoded;
59
+ const [lo, hi] = axis === 'row' ? [top, bottom] : [left, right];
60
+ if (lo === undefined || hi === undefined)
61
+ return area;
62
+ if (isDeletedSpan(lo, hi, start, count))
63
+ return undefined;
64
+ const movedLo = shiftIndex(lo, start, count, delta, axis);
65
+ const movedHi = shiftIndex(hi, start, count, delta, axis);
66
+ if (movedLo === lo && movedHi === hi)
67
+ return area;
68
+ const [tl, br] = axis === 'row'
69
+ ? [encodeCornerRef(left, movedLo), encodeCornerRef(right, movedHi)]
70
+ : [encodeCornerRef(movedLo, top), encodeCornerRef(movedHi, bottom)];
71
+ return area.includes(':') ? `${tl}:${br}` : tl;
72
+ }
@@ -1,9 +1,23 @@
1
+ /**
2
+ * Paper orientation, as `<pageSetup orientation>` carries it.
3
+ *
4
+ * `ST_Orientation` has a third member, `default`, which means "whatever the printer decides" and is
5
+ * indistinguishable from the attribute being absent. The model spells that absence as an unset field,
6
+ * so a file carrying `default` reads back with no orientation and writes back without the attribute.
7
+ */
8
+ export type PageOrientation = 'portrait' | 'landscape';
9
+ /** Narrow a raw `<pageSetup orientation>` token to a known {@link PageOrientation}. */
10
+ export declare function isPageOrientation(value: string): value is PageOrientation;
11
+ /** The order pages are numbered and printed in across a sheet wider and taller than one page. */
12
+ export type PageOrder = 'downThenOver' | 'overThenDown';
13
+ /** Narrow a raw `<pageSetup pageOrder>` token to a known {@link PageOrder}. */
14
+ export declare function isPageOrder(value: string): value is PageOrder;
1
15
  /**
2
16
  * Print-scaling and orientation settings. These map onto two OOXML elements: `fitToPage` is the
3
17
  * `<pageSetUpPr>` flag (a `<sheetPr>` child) that switches Excel from fixed-zoom to fit-to-page
4
18
  * scaling, while the rest are `<pageSetup>` attributes. Excel honours `scale` only when `fitToPage`
5
19
  * is off and the `fitToWidth`/`fitToHeight` page counts only when it is on, but the model carries
6
- * whatever the author set an unset field is omitted so a round-trip never fabricates one. An
20
+ * whatever the author set: an unset field is omitted so a round-trip never fabricates one. An
7
21
  * empty object emits neither element.
8
22
  */
9
23
  export interface PageSetup {
@@ -16,12 +30,12 @@ export interface PageSetup {
16
30
  /** Fixed print zoom as a percentage; Excel honours it only when `fitToPage` is off. */
17
31
  scale?: number;
18
32
  /** Paper orientation. */
19
- orientation?: 'portrait' | 'landscape';
33
+ orientation?: PageOrientation;
20
34
  /** Order pages are numbered/printed in across a multi-page sheet. */
21
- pageOrder?: 'downThenOver' | 'overThenDown';
35
+ pageOrder?: PageOrder;
22
36
  /**
23
37
  * Paper size as Excel's 1-based enumeration index (e.g. `9` = A4, `1` = US Letter). Carried as an
24
- * opaque integer the model does not map it to physical dimensions, only preserves whatever the
38
+ * opaque integer: the model does not map it to physical dimensions, only preserves whatever the
25
39
  * author or source file set.
26
40
  */
27
41
  paperSize?: number;
@@ -35,7 +49,7 @@ export interface PageSetup {
35
49
  }
36
50
  /**
37
51
  * Print-toggle flags from the `<printOptions>` element. Each maps to a boolean OOXML attribute that
38
- * defaults false except `gridLinesSet`, which defaults true and gates whether `gridLines` is
52
+ * defaults false, except `gridLinesSet`, which defaults true and gates whether `gridLines` is
39
53
  * honoured. The model stores only what the source or caller set, so an unset flag is omitted and a
40
54
  * round-trip never fabricates one; an empty object emits no element at all.
41
55
  */
@@ -54,7 +68,7 @@ export interface PrintOptions {
54
68
  /**
55
69
  * A manual page break (`<brk>`). For a row break, `id` is the row the layout splits *before*; for a
56
70
  * column break it is the column. `max` bounds the break's extent across the other axis (Excel writes
57
- * the last row/column index) and `man` marks it author-set rather than automatic the model preserves
71
+ * the last row/column index) and `man` marks it author-set rather than automatic. The model preserves
58
72
  * whatever the source carried so a round-trip reproduces the break's span exactly.
59
73
  */
60
74
  export interface PageBreak {
@@ -1 +1,8 @@
1
- export {};
1
+ const PAGE_ORIENTATIONS = { portrait: true, landscape: true };
2
+ export function isPageOrientation(value) {
3
+ return Object.hasOwn(PAGE_ORIENTATIONS, value);
4
+ }
5
+ const PAGE_ORDERS = { downThenOver: true, overThenDown: true };
6
+ export function isPageOrder(value) {
7
+ return Object.hasOwn(PAGE_ORDERS, value);
8
+ }
@@ -3,12 +3,10 @@ import type { Worksheet } from './worksheet.ts';
3
3
  * names verbatim, so a metric doubles as its `<dataField subtotal="…">` value. Excel performs the
4
4
  * aggregation itself on refresh; the writer only records which function to apply. */
5
5
  export type PivotMetric = 'sum' | 'count' | 'countNums' | 'average' | 'max' | 'min' | 'product' | 'stdDev' | 'stdDevp' | 'var' | 'varp';
6
- /** Narrow a raw `subtotal` attribute (or any string) to a known {@link PivotMetric}. */
7
- export declare function isPivotMetric(value: string): value is PivotMetric;
8
6
  /** Map an OOXML `<dataField subtotal="…">` value back to its metric. The attribute is absent for
9
7
  * `sum` (Excel's implicit default), so `undefined` reads as `sum`; an unrecognised value also reads
10
- * as `sum` rather than throwing, because reconstructing an existing file is a lenient operation
11
- * the strict rejection of unknown metrics belongs on the authoring path, not the read path. */
8
+ * as `sum` rather than throwing, because reconstructing an existing file is a lenient operation.
9
+ * The strict rejection of unknown metrics belongs on the authoring path, not the read path. */
12
10
  export declare function pivotMetricFromSubtotal(subtotal: string | undefined): PivotMetric;
13
11
  /** One field in a loaded pivot's cache catalogue, in declared order; the pivot refers to it by index. */
14
12
  export interface ParsedPivotField {
@@ -19,6 +17,8 @@ export interface ParsedPivotField {
19
17
  * data the reader does not model (an external connection, a range consolidation, or a scenario), and
20
18
  * `unknown` covers a `type` the file declares that is none of these. */
21
19
  export type PivotSourceKind = 'worksheet' | 'external' | 'consolidation' | 'scenario' | 'unknown';
20
+ /** Narrow a raw `<cacheSource type>` token to a {@link PivotSourceKind} a file may declare. */
21
+ export declare function isDeclarablePivotSourceKind(value: string): value is Exclude<PivotSourceKind, 'unknown'>;
22
22
  /** Where a pivot cache draws its rows from. {@link kind} names the source type; {@link sheet} and
23
23
  * {@link ref} locate the range only when it is `worksheet` and are empty strings otherwise, so a
24
24
  * consumer can tell a genuinely non-worksheet source apart from a worksheet source that failed to
@@ -1,27 +1,36 @@
1
1
  import { AuthoringError, InternalError } from '../errors.js';
2
2
  import { encodeAddress } from './address.js';
3
3
  import { isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, richTextToPlain, } from './value.js';
4
- const PIVOT_METRICS = new Set([
5
- 'sum',
6
- 'count',
7
- 'countNums',
8
- 'average',
9
- 'max',
10
- 'min',
11
- 'product',
12
- 'stdDev',
13
- 'stdDevp',
14
- 'var',
15
- 'varp',
16
- ]);
17
- export function isPivotMetric(value) {
18
- return PIVOT_METRICS.has(value);
4
+ const PIVOT_METRICS = {
5
+ sum: true,
6
+ count: true,
7
+ countNums: true,
8
+ average: true,
9
+ max: true,
10
+ min: true,
11
+ product: true,
12
+ stdDev: true,
13
+ stdDevp: true,
14
+ var: true,
15
+ varp: true,
16
+ };
17
+ function isPivotMetric(value) {
18
+ return Object.hasOwn(PIVOT_METRICS, value);
19
19
  }
20
20
  export function pivotMetricFromSubtotal(subtotal) {
21
21
  if (subtotal === undefined)
22
22
  return 'sum';
23
23
  return isPivotMetric(subtotal) ? subtotal : 'sum';
24
24
  }
25
+ const DECLARABLE_PIVOT_SOURCE_KINDS = {
26
+ worksheet: true,
27
+ external: true,
28
+ consolidation: true,
29
+ scenario: true,
30
+ };
31
+ export function isDeclarablePivotSourceKind(value) {
32
+ return Object.hasOwn(DECLARABLE_PIVOT_SOURCE_KINDS, value);
33
+ }
25
34
  const BLANK = { kind: 'blank' };
26
35
  export class PivotTable {
27
36
  metric;
@@ -34,8 +43,8 @@ export class PivotTable {
34
43
  valueField;
35
44
  constructor(options) {
36
45
  const metric = options.metric ?? 'sum';
37
- if (!PIVOT_METRICS.has(metric)) {
38
- throw new AuthoringError(`unsupported pivot metric "${metric}" expected one of ${[...PIVOT_METRICS].join(', ')}`);
46
+ if (!Object.hasOwn(PIVOT_METRICS, metric)) {
47
+ throw new AuthoringError(`unsupported pivot metric "${metric}": expected one of ${Object.keys(PIVOT_METRICS).join(', ')}`);
39
48
  }
40
49
  this.metric = metric;
41
50
  const source = options.source;
@@ -110,7 +119,7 @@ export class PivotTable {
110
119
  records.push(fields.map((_field, fieldIndex) => {
111
120
  const scalar = scalarsForField(columnScalars, fieldIndex)[row];
112
121
  if (scalar === undefined) {
113
- throw new InternalError(`pivot record row ${row} is out of range for field ${fieldIndex} every column was ` +
122
+ throw new InternalError(`pivot record row ${row} is out of range for field ${fieldIndex}: every column was ` +
114
123
  'scanned for the same dataRowCount above, so this index is always in range');
115
124
  }
116
125
  const catalogue = catalogues[fieldIndex];
@@ -127,7 +136,7 @@ export class PivotTable {
127
136
  get valueFieldName() {
128
137
  const field = this.cacheFields[this.valueField];
129
138
  if (field === undefined) {
130
- throw new InternalError(`pivot valueField index ${this.valueField} is out of range resolve() validated it against ` +
139
+ throw new InternalError(`pivot valueField index ${this.valueField} is out of range: resolve() validated it against ` +
131
140
  'the same fields array cacheFields was built from');
132
141
  }
133
142
  return field.name;
@@ -136,7 +145,7 @@ export class PivotTable {
136
145
  function scalarsForField(columnScalars, fieldIndex) {
137
146
  const scalars = columnScalars[fieldIndex];
138
147
  if (scalars === undefined) {
139
- throw new InternalError(`pivot field index ${fieldIndex} is out of range for columnScalars it was built from the same fields array`);
148
+ throw new InternalError(`pivot field index ${fieldIndex} is out of range for columnScalars: it was built from the same fields array`);
140
149
  }
141
150
  return scalars;
142
151
  }