@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.
- package/README.md +20 -10
- package/dist/bytes.d.ts +24 -0
- package/dist/bytes.js +34 -0
- package/dist/core/address.d.ts +72 -3
- package/dist/core/address.js +50 -10
- package/dist/core/autofilter.d.ts +13 -2
- package/dist/core/autofilter.js +35 -10
- package/dist/core/cell.d.ts +17 -16
- package/dist/core/cell.js +3 -7
- package/dist/core/color-resolution.d.ts +8 -8
- package/dist/core/color-resolution.js +7 -8
- package/dist/core/column.d.ts +8 -9
- package/dist/core/column.js +2 -4
- package/dist/core/comment-thread.d.ts +32 -23
- package/dist/core/comment-thread.js +4 -4
- package/dist/core/conditional-formatting-overlay.d.ts +6 -1
- package/dist/core/conditional-formatting-overlay.js +11 -0
- package/dist/core/conditional-formatting.d.ts +48 -8
- package/dist/core/conditional-formatting.js +88 -0
- package/dist/core/data-validation-overlay.d.ts +12 -1
- package/dist/core/data-validation-overlay.js +21 -2
- package/dist/core/data-validation.d.ts +12 -5
- package/dist/core/data-validation.js +34 -0
- package/dist/core/date.d.ts +3 -3
- package/dist/core/formula.d.ts +6 -6
- package/dist/core/grid-edits.d.ts +16 -0
- package/dist/core/grid-edits.js +29 -11
- package/dist/core/grid-shift.d.ts +19 -0
- package/dist/core/grid-shift.js +8 -0
- package/dist/core/image.d.ts +39 -2
- package/dist/core/image.js +10 -0
- package/dist/core/internal.d.ts +9 -1
- package/dist/core/limits.d.ts +4 -4
- package/dist/core/merge.d.ts +17 -12
- package/dist/core/merge.js +36 -5
- package/dist/core/page-setup.d.ts +20 -6
- package/dist/core/page-setup.js +8 -1
- package/dist/core/pivot-table.d.ts +4 -4
- package/dist/core/pivot-table.js +29 -20
- package/dist/core/preserved.d.ts +3 -3
- package/dist/core/protection.d.ts +7 -4
- package/dist/core/protection.js +20 -14
- package/dist/core/range.d.ts +16 -14
- package/dist/core/range.js +5 -5
- package/dist/core/row-input.d.ts +9 -8
- package/dist/core/row-input.js +12 -10
- package/dist/core/row.d.ts +7 -8
- package/dist/core/row.js +2 -4
- package/dist/core/style.d.ts +41 -14
- package/dist/core/style.js +82 -67
- package/dist/core/table-style.d.ts +9 -9
- package/dist/core/table-style.js +1 -1
- package/dist/core/table.d.ts +53 -51
- package/dist/core/table.js +83 -41
- package/dist/core/theme.d.ts +16 -44
- package/dist/core/theme.js +6 -148
- package/dist/core/value.d.ts +7 -7
- package/dist/core/workbook-protection.d.ts +2 -2
- package/dist/core/workbook-styles.d.ts +26 -0
- package/dist/core/workbook-styles.js +48 -0
- package/dist/core/workbook-theme.d.ts +25 -0
- package/dist/core/workbook-theme.js +49 -0
- package/dist/core/workbook-vba.d.ts +17 -0
- package/dist/core/workbook-vba.js +79 -0
- package/dist/core/workbook.d.ts +133 -87
- package/dist/core/workbook.js +74 -133
- package/dist/core/worksheet-comments.d.ts +17 -0
- package/dist/core/worksheet-comments.js +70 -0
- package/dist/core/worksheet-model.d.ts +4 -4
- package/dist/core/worksheet-model.js +1 -0
- package/dist/core/worksheet-pictures.d.ts +29 -0
- package/dist/core/worksheet-pictures.js +42 -0
- package/dist/core/worksheet.d.ts +100 -79
- package/dist/core/worksheet.js +63 -142
- package/dist/customui/errors.d.ts +1 -1
- package/dist/customui/ribbon.d.ts +3 -3
- package/dist/customui/ribbon.js +24 -23
- package/dist/entries/core.d.ts +7 -7
- package/dist/entries/core.js +1 -1
- package/dist/entries/csv.d.ts +1 -1
- package/dist/entries/node-unavailable.d.ts +15 -0
- package/dist/entries/node-unavailable.js +20 -0
- package/dist/entries/node.d.ts +1 -0
- package/dist/entries/node.js +1 -0
- package/dist/entries/vba.d.ts +1 -1
- package/dist/entries/vba.js +1 -1
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/entries/xlsx.js +1 -1
- package/dist/errors.d.ts +12 -12
- package/dist/io/csv/read.js +2 -3
- package/dist/io/csv/write.d.ts +19 -4
- package/dist/io/csv/write.js +34 -4
- package/dist/io/opc/errors.d.ts +6 -6
- package/dist/io/opc/inflate.d.ts +1 -1
- package/dist/io/opc/inflate.js +2 -12
- package/dist/io/opc/namespaces.d.ts +1 -1
- package/dist/io/opc/read-opc.d.ts +19 -3
- package/dist/io/opc/read-opc.js +14 -8
- package/dist/io/opc/read-options.d.ts +2 -2
- package/dist/io/opc/rels.d.ts +11 -0
- package/dist/io/opc/rels.js +3 -2
- package/dist/io/opc/sniff-format.d.ts +2 -2
- package/dist/io/style/xf-style.d.ts +44 -7
- package/dist/io/style/xf-style.js +27 -0
- package/dist/io/xlsb/errors.d.ts +1 -1
- package/dist/io/xlsb/formula.d.ts +4 -4
- package/dist/io/xlsb/primitives.d.ts +8 -8
- package/dist/io/xlsb/ptg-functions.d.ts +1 -1
- package/dist/io/xlsb/read-styles.js +2 -20
- package/dist/io/xlsb/read.d.ts +2 -2
- package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
- package/dist/io/xlsx/cell-accumulator.js +89 -46
- package/dist/io/xlsx/cell-value.d.ts +3 -3
- package/dist/io/xlsx/cell-value.js +9 -6
- package/dist/io/xlsx/color-xml.d.ts +1 -1
- package/dist/io/xlsx/color-xml.js +18 -23
- package/dist/io/xlsx/comments.d.ts +5 -5
- package/dist/io/xlsx/comments.js +17 -8
- package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
- package/dist/io/xlsx/conditional-formatting.js +64 -58
- package/dist/io/xlsx/data-validation.d.ts +3 -3
- package/dist/io/xlsx/data-validation.js +35 -33
- package/dist/io/xlsx/errors.d.ts +3 -3
- package/dist/io/xlsx/hyperlinks.d.ts +4 -2
- package/dist/io/xlsx/hyperlinks.js +10 -18
- package/dist/io/xlsx/images.d.ts +1 -1
- package/dist/io/xlsx/images.js +25 -28
- package/dist/io/xlsx/namespaces.d.ts +2 -2
- package/dist/io/xlsx/package-plan.js +5 -2
- package/dist/io/xlsx/read-pivot.js +4 -13
- package/dist/io/xlsx/read-rows.d.ts +13 -13
- package/dist/io/xlsx/read-rows.js +19 -55
- package/dist/io/xlsx/read-shared-strings.js +7 -44
- package/dist/io/xlsx/read-styles.d.ts +3 -3
- package/dist/io/xlsx/read-styles.js +42 -66
- package/dist/io/xlsx/read-worksheet.js +73 -129
- package/dist/io/xlsx/read.d.ts +4 -6
- package/dist/io/xlsx/read.js +104 -142
- package/dist/io/xlsx/rich-runs.d.ts +41 -7
- package/dist/io/xlsx/rich-runs.js +94 -30
- package/dist/io/xlsx/rich-text.d.ts +1 -1
- package/dist/io/xlsx/shared-strings.d.ts +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +1 -1
- package/dist/io/xlsx/sheet-properties.js +14 -10
- package/dist/io/xlsx/styles.d.ts +9 -22
- package/dist/io/xlsx/styles.js +65 -89
- package/dist/io/xlsx/tables.d.ts +2 -2
- package/dist/io/xlsx/tables.js +20 -25
- package/dist/io/xlsx/theme-xml.d.ts +37 -0
- package/dist/io/xlsx/theme-xml.js +145 -0
- package/dist/io/xlsx/threaded-comments.d.ts +18 -27
- package/dist/io/xlsx/threaded-comments.js +26 -38
- package/dist/io/xlsx/workbook-xml.js +14 -8
- package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
- package/dist/io/xlsx/worksheet-xml.js +40 -48
- package/dist/io/xlsx/write-stream.d.ts +23 -16
- package/dist/io/xlsx/write-stream.js +13 -14
- package/dist/io/xlsx/write.d.ts +8 -7
- package/dist/io/xlsx/write.js +6 -3
- package/dist/io/xlsx/x14-ext.d.ts +1 -1
- package/dist/sha512.d.ts +2 -0
- package/dist/sha512.js +146 -0
- package/dist/vba/bytes.d.ts +11 -0
- package/dist/vba/bytes.js +29 -0
- package/dist/vba/cfb-writer.d.ts +1 -1
- package/dist/vba/cfb.d.ts +1 -1
- package/dist/vba/cfb.js +15 -51
- package/dist/vba/codepage.d.ts +2 -2
- package/dist/vba/dir-records.d.ts +53 -0
- package/dist/vba/dir-records.js +28 -0
- package/dist/vba/errors.d.ts +2 -2
- package/dist/vba/index.d.ts +2 -2
- package/dist/vba/index.js +2 -2
- package/dist/vba/ms-ovba.d.ts +1 -1
- package/dist/vba/ms-ovba.js +1 -3
- package/dist/vba/project-editor.d.ts +9 -9
- package/dist/vba/project-editor.js +6 -44
- package/dist/vba/project.d.ts +5 -5
- package/dist/vba/project.js +3 -28
- package/dist/vba/vba-encoding.d.ts +1 -1
- package/dist/xml/errors.d.ts +1 -1
- package/dist/xml/xml-read.d.ts +98 -9
- package/dist/xml/xml-read.js +70 -0
- package/dist/xml/xml.d.ts +74 -10
- package/dist/xml/xml.js +39 -7
- package/package.json +59 -36
package/dist/core/formula.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
29
|
-
* reference within the binding call's parentheses
|
|
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
|
|
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;
|
package/dist/core/grid-edits.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
99
|
-
if (master
|
|
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
|
|
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
|
+
}
|
package/dist/core/image.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
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;
|
package/dist/core/image.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/core/internal.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/core/limits.d.ts
CHANGED
|
@@ -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
|
|
8
|
-
*
|
|
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
|
|
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
|
|
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
|
package/dist/core/merge.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
+
import { type GridRect } from './address.ts';
|
|
1
2
|
import type { Cell } from './cell.ts';
|
|
2
|
-
/** A merged region as
|
|
3
|
-
export
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/dist/core/merge.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
|
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
|
|
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?:
|
|
33
|
+
orientation?: PageOrientation;
|
|
20
34
|
/** Order pages are numbered/printed in across a multi-page sheet. */
|
|
21
|
-
pageOrder?:
|
|
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
|
|
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
|
|
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
|
|
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 {
|
package/dist/core/page-setup.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
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
|
-
*
|
|
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
|
package/dist/core/pivot-table.js
CHANGED
|
@@ -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 =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return
|
|
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 (!
|
|
38
|
-
throw new AuthoringError(`unsupported pivot metric "${metric}"
|
|
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}
|
|
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
|
|
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
|
|
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
|
}
|