@shbernal/ts-xlsx 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -24
- package/dist/bytes.d.ts +10 -2
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +96 -2
- package/dist/core/address.js +87 -33
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/autofilter.d.ts +4 -3
- package/dist/core/autofilter.js +31 -30
- package/dist/core/axis-handle.d.ts +2 -0
- package/dist/core/axis-handle.js +5 -2
- package/dist/core/cell.d.ts +4 -3
- package/dist/core/cell.js +4 -6
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +4 -6
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +6 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +11 -1
- package/dist/core/conditional-formatting-overlay.js +30 -3
- package/dist/core/conditional-formatting.d.ts +107 -11
- package/dist/core/conditional-formatting.js +76 -19
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +7 -1
- package/dist/core/data-validation-overlay.js +19 -4
- package/dist/core/data-validation.d.ts +22 -0
- package/dist/core/data-validation.js +30 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +170 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +19 -3
- package/dist/core/formula-references.d.ts +26 -0
- package/dist/core/formula-references.js +173 -0
- package/dist/core/formula-scan.d.ts +2 -0
- package/dist/core/formula-scan.js +58 -0
- package/dist/core/formula.d.ts +76 -17
- package/dist/core/formula.js +87 -80
- package/dist/core/function-values.d.ts +4 -0
- package/dist/core/function-values.js +58 -0
- package/dist/core/future-functions.d.ts +3 -0
- package/dist/core/{modern-functions.js → future-functions.js} +119 -88
- package/dist/core/grid-edits.d.ts +30 -3
- package/dist/core/grid-edits.js +248 -81
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +49 -14
- package/dist/core/image.js +16 -5
- package/dist/core/internal.d.ts +10 -2
- package/dist/core/limits.d.ts +25 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +18 -4
- package/dist/core/merge.js +33 -20
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +112 -60
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +6 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +1 -0
- package/dist/core/row.js +6 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/style.d.ts +85 -70
- package/dist/core/style.js +92 -28
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +64 -16
- package/dist/core/table.js +87 -40
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +9 -5
- package/dist/core/value.d.ts +60 -28
- package/dist/core/value.js +48 -43
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +83 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +41 -3
- package/dist/core/workbook.js +46 -41
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +49 -0
- package/dist/core/worksheet-merges.js +65 -0
- package/dist/core/worksheet-model.d.ts +10 -2
- package/dist/core/worksheet-model.js +28 -9
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +165 -27
- package/dist/core/worksheet.js +206 -89
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +21 -14
- package/dist/entries/core.js +14 -7
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/cell-metadata/metadata.d.ts +42 -0
- package/dist/io/cell-metadata/metadata.js +35 -0
- package/dist/io/cell-metadata/rich-values.d.ts +11 -0
- package/dist/io/cell-metadata/rich-values.js +80 -0
- package/dist/io/csv/delimiter.d.ts +21 -0
- package/dist/io/csv/delimiter.js +18 -0
- package/dist/io/csv/errors.d.ts +14 -0
- package/dist/io/csv/errors.js +5 -0
- package/dist/io/csv/read.d.ts +6 -1
- package/dist/io/csv/read.js +51 -22
- package/dist/io/csv/write.d.ts +19 -5
- package/dist/io/csv/write.js +24 -44
- package/dist/io/opc/inflate.js +14 -0
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +9 -0
- package/dist/io/opc/part-paths.js +5 -5
- package/dist/io/opc/read-opc.d.ts +58 -14
- package/dist/io/opc/read-opc.js +49 -21
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/opc/strict-relationships.d.ts +2 -0
- package/dist/io/opc/strict-relationships.js +12 -0
- package/dist/io/read-policy/column-budget.d.ts +26 -0
- package/dist/io/read-policy/column-budget.js +18 -0
- package/dist/io/read-policy/read-repair.d.ts +74 -0
- package/dist/io/read-policy/read-repair.js +119 -0
- package/dist/io/style/cell-style-resolution.d.ts +25 -0
- package/dist/io/style/cell-style-resolution.js +30 -0
- package/dist/io/style/xf-style.d.ts +23 -1
- package/dist/io/style/xf-style.js +8 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +52 -35
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +495 -596
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +26 -1
- package/dist/io/xlsb/read-worksheet.js +148 -106
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +70 -31
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +14 -5
- package/dist/io/xlsb/record-types.js +14 -5
- package/dist/io/xlsb/sheet-protection.d.ts +20 -0
- package/dist/io/xlsb/sheet-protection.js +60 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
- package/dist/io/xlsx/cell-accumulator.js +74 -21
- package/dist/io/xlsx/cell-metadata.d.ts +39 -0
- package/dist/io/xlsx/cell-metadata.js +162 -0
- package/dist/io/xlsx/cell-value.d.ts +19 -5
- package/dist/io/xlsx/cell-value.js +45 -30
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-span.d.ts +11 -0
- package/dist/io/xlsx/column-span.js +9 -0
- package/dist/io/xlsx/comments.d.ts +9 -30
- package/dist/io/xlsx/comments.js +25 -83
- package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
- package/dist/io/xlsx/conditional-formatting.js +437 -138
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +67 -53
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +52 -52
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +19 -21
- package/dist/io/xlsx/hyperlinks.js +12 -32
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +149 -50
- package/dist/io/xlsx/namespaces.d.ts +24 -0
- package/dist/io/xlsx/namespaces.js +11 -0
- package/dist/io/xlsx/package-plan.d.ts +60 -15
- package/dist/io/xlsx/package-plan.js +83 -18
- package/dist/io/xlsx/part-names.d.ts +6 -8
- package/dist/io/xlsx/part-names.js +3 -7
- package/dist/io/xlsx/pivot.d.ts +5 -5
- package/dist/io/xlsx/pivot.js +26 -24
- package/dist/io/xlsx/preserved-splices.d.ts +6 -0
- package/dist/io/xlsx/preserved-splices.js +74 -0
- package/dist/io/xlsx/read-comments.d.ts +29 -0
- package/dist/io/xlsx/read-comments.js +75 -0
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +259 -0
- package/dist/io/xlsx/read-pivot.js +11 -4
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
- package/dist/io/xlsx/read-rows.d.ts +35 -11
- package/dist/io/xlsx/read-rows.js +73 -49
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +0 -30
- package/dist/io/xlsx/read-styles.js +45 -105
- package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
- package/dist/io/xlsx/read-workbook-xml.js +175 -0
- package/dist/io/xlsx/read-worksheet.d.ts +9 -3
- package/dist/io/xlsx/read-worksheet.js +70 -177
- package/dist/io/xlsx/read.d.ts +3 -14
- package/dist/io/xlsx/read.js +74 -411
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +20 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +139 -0
- package/dist/io/xlsx/row-xml.js +222 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +182 -20
- package/dist/io/xlsx/strict-parts.d.ts +7 -0
- package/dist/io/xlsx/strict-parts.js +171 -0
- package/dist/io/xlsx/style-elements.d.ts +41 -0
- package/dist/io/xlsx/style-elements.js +136 -0
- package/dist/io/xlsx/styles.d.ts +17 -3
- package/dist/io/xlsx/styles.js +13 -209
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +55 -52
- package/dist/io/xlsx/theme-xml.d.ts +32 -4
- package/dist/io/xlsx/theme-xml.js +106 -71
- package/dist/io/xlsx/threaded-comments.d.ts +14 -11
- package/dist/io/xlsx/threaded-comments.js +16 -8
- package/dist/io/xlsx/workbook-xml.d.ts +45 -6
- package/dist/io/xlsx/workbook-xml.js +62 -54
- package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
- package/dist/io/xlsx/worksheet-xml.js +69 -282
- package/dist/io/xlsx/write-stream.d.ts +90 -19
- package/dist/io/xlsx/write-stream.js +80 -25
- package/dist/io/xlsx/write.d.ts +12 -6
- package/dist/io/xlsx/write.js +226 -123
- package/dist/rel-type.d.ts +25 -0
- package/dist/rel-type.js +9 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +16 -5
- package/dist/vba/bytes.js +27 -6
- package/dist/vba/cfb-format.d.ts +55 -0
- package/dist/vba/cfb-format.js +35 -0
- package/dist/vba/cfb-writer.d.ts +8 -0
- package/dist/vba/cfb-writer.js +40 -40
- package/dist/vba/cfb.d.ts +21 -4
- package/dist/vba/cfb.js +124 -90
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +11 -4
- package/dist/vba/index.d.ts +0 -2
- package/dist/vba/index.js +0 -2
- package/dist/vba/ms-ovba.d.ts +6 -2
- package/dist/vba/ms-ovba.js +66 -22
- package/dist/vba/project-editor.js +129 -119
- package/dist/vba/project.d.ts +15 -1
- package/dist/vba/project.js +38 -13
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +3 -17
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +101 -11
- package/dist/xml/xml-read.js +150 -18
- package/dist/xml/xml-scan.d.ts +25 -34
- package/dist/xml/xml-scan.js +69 -46
- package/dist/xml/xml.d.ts +59 -26
- package/dist/xml/xml.js +45 -23
- package/package.json +15 -9
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
package/dist/core/table.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import { AuthoringError } from '../errors.js';
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
2
|
import { tokenSet } from '../token-set.js';
|
|
3
|
-
import { decodeCellRef, encodeAddress } from './address.js';
|
|
3
|
+
import { decodeCellRef, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, nameReadsAsReference, numberToColumn, } from './address.js';
|
|
4
|
+
import { cloneWith } from './clone.js';
|
|
4
5
|
import { isDeletedSpan, shiftIndex } from './grid-shift.js';
|
|
6
|
+
import { INTERNAL } from './internal.js';
|
|
7
|
+
import { MAX_TABLE_NAME_LENGTH, TABLE_NAME_PATTERN } from './limits.js';
|
|
8
|
+
import { structuredColumnReference } from './structured-reference.js';
|
|
9
|
+
const STYLE_INFO_CLONE = {
|
|
10
|
+
name: (name) => (name === 'None' ? undefined : name),
|
|
11
|
+
showFirstColumn: 'value',
|
|
12
|
+
showLastColumn: 'value',
|
|
13
|
+
showRowStripes: 'value',
|
|
14
|
+
showColumnStripes: 'value',
|
|
15
|
+
};
|
|
16
|
+
export const TABLE_STYLE_FLAGS = [
|
|
17
|
+
'showFirstColumn',
|
|
18
|
+
'showLastColumn',
|
|
19
|
+
'showRowStripes',
|
|
20
|
+
'showColumnStripes',
|
|
21
|
+
];
|
|
5
22
|
function cloneStyleInfo(style) {
|
|
6
|
-
const clone =
|
|
7
|
-
if (
|
|
8
|
-
clone.name
|
|
9
|
-
if (style.showFirstColumn !== undefined)
|
|
10
|
-
clone.showFirstColumn = style.showFirstColumn;
|
|
11
|
-
if (style.showLastColumn !== undefined)
|
|
12
|
-
clone.showLastColumn = style.showLastColumn;
|
|
13
|
-
if (style.showRowStripes !== undefined)
|
|
14
|
-
clone.showRowStripes = style.showRowStripes;
|
|
15
|
-
if (style.showColumnStripes !== undefined)
|
|
16
|
-
clone.showColumnStripes = style.showColumnStripes;
|
|
23
|
+
const clone = cloneWith(style, STYLE_INFO_CLONE);
|
|
24
|
+
if (clone.name === undefined)
|
|
25
|
+
delete clone.name;
|
|
17
26
|
return clone;
|
|
18
27
|
}
|
|
19
28
|
export const TOTALS_ROW_SUBTOTAL_CODE = {
|
|
@@ -38,7 +47,6 @@ export const isTotalsRowFunction = tokenSet({
|
|
|
38
47
|
custom: true,
|
|
39
48
|
none: true,
|
|
40
49
|
});
|
|
41
|
-
const IDENTIFIER = /^[\p{L}\\_][\p{L}\p{N}._]*$/u;
|
|
42
50
|
function disambiguateColumnNames(columns) {
|
|
43
51
|
const seen = new Set();
|
|
44
52
|
return columns.map((column) => {
|
|
@@ -50,18 +58,20 @@ function disambiguateColumnNames(columns) {
|
|
|
50
58
|
});
|
|
51
59
|
}
|
|
52
60
|
function validateTableName(name) {
|
|
53
|
-
if (name.length === 0 || name.length >
|
|
54
|
-
throw new RangeError(`table name ${
|
|
61
|
+
if (name.length === 0 || name.length > MAX_TABLE_NAME_LENGTH) {
|
|
62
|
+
throw new RangeError(`table name ${quoted(name)} must be between 1 and ${MAX_TABLE_NAME_LENGTH} characters`);
|
|
55
63
|
}
|
|
56
|
-
if (!
|
|
57
|
-
throw new SyntaxError(`table name ${
|
|
64
|
+
if (!TABLE_NAME_PATTERN.test(name)) {
|
|
65
|
+
throw new SyntaxError(`table name ${quoted(name)} is not a valid Excel identifier: it must start with a letter, ` +
|
|
58
66
|
'underscore, or backslash and contain only letters, digits, periods, and underscores');
|
|
59
67
|
}
|
|
68
|
+
if (nameReadsAsReference(name)) {
|
|
69
|
+
throw new SyntaxError(`table name ${quoted(name)} reads as a cell reference, an R1C1 reference or a boolean, ` +
|
|
70
|
+
'which Excel does not accept as a name');
|
|
71
|
+
}
|
|
60
72
|
}
|
|
61
73
|
export class Table {
|
|
62
74
|
name;
|
|
63
|
-
displayName;
|
|
64
|
-
columns;
|
|
65
75
|
headerRow;
|
|
66
76
|
totalsRow;
|
|
67
77
|
totalsRowShown;
|
|
@@ -70,28 +80,46 @@ export class Table {
|
|
|
70
80
|
#anchorCol;
|
|
71
81
|
#anchorRow;
|
|
72
82
|
#dataRowCount;
|
|
83
|
+
#columns;
|
|
73
84
|
#grid;
|
|
85
|
+
[INTERNAL] = {
|
|
86
|
+
rewriteFormulas: (rewrite) => {
|
|
87
|
+
this.#columns = this.#columns.map((column) => {
|
|
88
|
+
const { calculatedColumnFormula: calculated, totalsRowFormula: totals } = column;
|
|
89
|
+
const nextCalculated = calculated === undefined ? undefined : rewrite(calculated);
|
|
90
|
+
const nextTotals = totals === undefined ? undefined : rewrite(totals);
|
|
91
|
+
if (nextCalculated === calculated && nextTotals === totals)
|
|
92
|
+
return column;
|
|
93
|
+
return {
|
|
94
|
+
...column,
|
|
95
|
+
...(nextCalculated === undefined ? {} : { calculatedColumnFormula: nextCalculated }),
|
|
96
|
+
...(nextTotals === undefined ? {} : { totalsRowFormula: nextTotals }),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
74
101
|
constructor(options, grid) {
|
|
75
102
|
validateTableName(options.name);
|
|
76
103
|
if (options.columns.length === 0) {
|
|
77
|
-
throw new AuthoringError(`table
|
|
104
|
+
throw new AuthoringError(`table ${quoted(options.name)} must declare at least one column`);
|
|
78
105
|
}
|
|
79
106
|
if (!Number.isInteger(options.rowCount) || options.rowCount < 0) {
|
|
80
|
-
throw new RangeError(`table
|
|
107
|
+
throw new RangeError(`table ${quoted(options.name)} has an invalid data-row count (${options.rowCount})`);
|
|
81
108
|
}
|
|
82
109
|
let anchor;
|
|
83
110
|
try {
|
|
84
111
|
anchor = decodeCellRef(options.ref);
|
|
85
112
|
}
|
|
86
113
|
catch (cause) {
|
|
87
|
-
|
|
114
|
+
if (cause instanceof RangeError)
|
|
115
|
+
throw cause;
|
|
116
|
+
throw new SyntaxError(`table ref ${quoted(options.ref)} must anchor at a single cell (e.g. "A1")`, {
|
|
88
117
|
cause,
|
|
89
118
|
});
|
|
90
119
|
}
|
|
91
120
|
const { col, row } = anchor;
|
|
92
121
|
this.name = options.name;
|
|
93
|
-
this
|
|
94
|
-
this.columns = disambiguateColumnNames(options.columns);
|
|
122
|
+
this.#columns = disambiguateColumnNames(options.columns);
|
|
95
123
|
this.headerRow = options.headerRow ?? true;
|
|
96
124
|
this.totalsRow = options.totalsRow ?? false;
|
|
97
125
|
this.totalsRowShown = options.totalsRowShown;
|
|
@@ -102,11 +130,22 @@ export class Table {
|
|
|
102
130
|
this.#dataRowCount = options.rowCount;
|
|
103
131
|
this.#grid = grid;
|
|
104
132
|
if (this.#rowSpan < 1) {
|
|
105
|
-
throw new AuthoringError(`table
|
|
133
|
+
throw new AuthoringError(`table ${quoted(this.name)} has no rows: it needs a header row or at least one data row`);
|
|
134
|
+
}
|
|
135
|
+
if (this.#right > MAX_COLUMN) {
|
|
136
|
+
throw new RangeError(`table ${quoted(this.name)} spans ${this.columns.length} columns from ` +
|
|
137
|
+
`${numberToColumn(this.#anchorCol)}, past the last column (${numberToColumn(MAX_COLUMN)})`);
|
|
138
|
+
}
|
|
139
|
+
if (this.#bottom > MAX_ROW) {
|
|
140
|
+
throw new RangeError(`table ${quoted(this.name)} spans ${this.#rowSpan} rows from ${this.#anchorRow}, ` +
|
|
141
|
+
`past the last row (${MAX_ROW})`);
|
|
106
142
|
}
|
|
107
143
|
if (grid !== undefined)
|
|
108
144
|
this.#materializeFrame(grid);
|
|
109
145
|
}
|
|
146
|
+
get columns() {
|
|
147
|
+
return this.#columns;
|
|
148
|
+
}
|
|
110
149
|
get columnCount() {
|
|
111
150
|
return this.columns.length;
|
|
112
151
|
}
|
|
@@ -115,13 +154,13 @@ export class Table {
|
|
|
115
154
|
}
|
|
116
155
|
addRow(values = []) {
|
|
117
156
|
if (values.length > this.columnCount) {
|
|
118
|
-
throw new RangeError(`row has ${values.length} values but table
|
|
157
|
+
throw new RangeError(`row has ${values.length} values but table ${quoted(this.name)} has ${this.columnCount} columns`);
|
|
119
158
|
}
|
|
120
159
|
const target = this.#anchorRow + (this.headerRow ? 1 : 0) + this.#dataRowCount;
|
|
121
160
|
const grid = this.#grid;
|
|
122
161
|
if (this.totalsRow) {
|
|
123
162
|
if (grid === undefined) {
|
|
124
|
-
throw new AuthoringError(`table
|
|
163
|
+
throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot relocate its totals row to append a data row`);
|
|
125
164
|
}
|
|
126
165
|
grid.insertRow(target);
|
|
127
166
|
this.#writeRow(grid, target, values);
|
|
@@ -129,7 +168,7 @@ export class Table {
|
|
|
129
168
|
}
|
|
130
169
|
if (values.length > 0) {
|
|
131
170
|
if (grid === undefined) {
|
|
132
|
-
throw new AuthoringError(`table
|
|
171
|
+
throw new AuthoringError(`table ${quoted(this.name)} is not attached to a worksheet: cannot write appended row values`);
|
|
133
172
|
}
|
|
134
173
|
this.#writeRow(grid, target, values);
|
|
135
174
|
}
|
|
@@ -165,7 +204,7 @@ export class Table {
|
|
|
165
204
|
const code = TOTALS_ROW_SUBTOTAL_CODE[column.totalsRowFunction];
|
|
166
205
|
if (code !== undefined) {
|
|
167
206
|
grid.writeCell(bottom, col, {
|
|
168
|
-
formula: `SUBTOTAL(${code},${this.name
|
|
207
|
+
formula: `SUBTOTAL(${code},${structuredColumnReference(this.name, column.name)})`,
|
|
169
208
|
});
|
|
170
209
|
}
|
|
171
210
|
else if (column.totalsRowFunction === 'custom' && column.totalsRowFormula !== undefined) {
|
|
@@ -174,11 +213,11 @@ export class Table {
|
|
|
174
213
|
});
|
|
175
214
|
}
|
|
176
215
|
}
|
|
177
|
-
shiftRows(
|
|
178
|
-
if (isDeletedSpan(this.#anchorRow, this.#bottom,
|
|
216
|
+
shiftRows(splice) {
|
|
217
|
+
if (isDeletedSpan(this.#anchorRow, this.#bottom, splice))
|
|
179
218
|
return false;
|
|
180
|
-
const top = shiftIndex(this.#anchorRow,
|
|
181
|
-
const bottom = shiftIndex(this.#bottom,
|
|
219
|
+
const top = shiftIndex(this.#anchorRow, splice);
|
|
220
|
+
const bottom = shiftIndex(this.#bottom, splice);
|
|
182
221
|
const span = bottom - top + 1;
|
|
183
222
|
const fixedRows = (this.headerRow ? 1 : 0) + (this.totalsRow ? 1 : 0);
|
|
184
223
|
const dataRows = span - fixedRows;
|
|
@@ -188,15 +227,18 @@ export class Table {
|
|
|
188
227
|
this.#dataRowCount = dataRows;
|
|
189
228
|
return true;
|
|
190
229
|
}
|
|
191
|
-
shiftColumns(
|
|
192
|
-
if (this.#anchorCol
|
|
193
|
-
|
|
230
|
+
shiftColumns(splice) {
|
|
231
|
+
if (isDeletedSpan(this.#anchorCol, this.#right, splice))
|
|
232
|
+
return false;
|
|
233
|
+
const anchor = shiftIndex(this.#anchorCol, splice);
|
|
234
|
+
if (anchor + this.columns.length - 1 > MAX_COLUMN)
|
|
235
|
+
return false;
|
|
236
|
+
this.#anchorCol = anchor;
|
|
194
237
|
return true;
|
|
195
238
|
}
|
|
196
239
|
get options() {
|
|
197
240
|
const options = {
|
|
198
241
|
name: this.name,
|
|
199
|
-
displayName: this.displayName,
|
|
200
242
|
ref: encodeAddress(this.#anchorCol, this.#anchorRow),
|
|
201
243
|
columns: this.columns.map((column) => ({ ...column })),
|
|
202
244
|
rowCount: this.#dataRowCount,
|
|
@@ -211,13 +253,18 @@ export class Table {
|
|
|
211
253
|
return options;
|
|
212
254
|
}
|
|
213
255
|
get range() {
|
|
214
|
-
return
|
|
256
|
+
return encodeRect({
|
|
257
|
+
top: this.#anchorRow,
|
|
258
|
+
left: this.#anchorCol,
|
|
259
|
+
bottom: this.#bottom,
|
|
260
|
+
right: this.#right,
|
|
261
|
+
});
|
|
215
262
|
}
|
|
216
263
|
get autoFilterRef() {
|
|
217
264
|
if (!this.autoFilter)
|
|
218
265
|
return undefined;
|
|
219
266
|
const bottom = this.#anchorRow + this.#dataRowCount;
|
|
220
|
-
return
|
|
267
|
+
return encodeRect({ top: this.#anchorRow, left: this.#anchorCol, bottom, right: this.#right });
|
|
221
268
|
}
|
|
222
269
|
get region() {
|
|
223
270
|
return { top: this.#anchorRow, left: this.#anchorCol, bottom: this.#bottom, right: this.#right };
|
package/dist/core/theme.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface ThemeOverrides {
|
|
|
48
48
|
readonly colors?: Readonly<Partial<Record<ThemeColorSlot, string>>> | undefined;
|
|
49
49
|
readonly fonts?: ThemeFontScheme | undefined;
|
|
50
50
|
}
|
|
51
|
+
/** Whether a value is the bare 6-hex RGB a theme colour slot holds, in either casing. */
|
|
52
|
+
export declare function isThemeRgb(value: string): boolean;
|
|
51
53
|
/** Reduce an authored theme colour to the bare 6-hex RGB DrawingML wants.
|
|
52
54
|
*
|
|
53
55
|
* A theme colour is a bare 6-hex RGB: DrawingML has no alpha channel on `<a:srgbClr val>`. The two
|
package/dist/core/theme.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
2
|
+
import { parseArgb } from './style.js';
|
|
1
3
|
export const THEME_COLOR_SLOTS = [
|
|
2
4
|
'lt1',
|
|
3
5
|
'dk1',
|
|
@@ -35,11 +37,13 @@ export const DEFAULT_THEME_FONTS = {
|
|
|
35
37
|
major: 'Calibri Light',
|
|
36
38
|
minor: OFFICE_BODY_FACE,
|
|
37
39
|
};
|
|
40
|
+
export function isThemeRgb(value) {
|
|
41
|
+
return /^[0-9A-Fa-f]{6}$/.test(value);
|
|
42
|
+
}
|
|
38
43
|
export function normalizeThemeColor(value) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new SyntaxError(`Invalid theme colour ${JSON.stringify(value)}: expected 6 hexadecimal digits (RRGGBB)`);
|
|
44
|
+
const argb = parseArgb(value);
|
|
45
|
+
if (argb === undefined) {
|
|
46
|
+
throw new SyntaxError(`invalid theme colour ${quoted(value)}: expected RRGGBB or AARRGGBB hexadecimal digits, optionally after '#'`);
|
|
43
47
|
}
|
|
44
|
-
return
|
|
48
|
+
return argb.slice(2).toUpperCase();
|
|
45
49
|
}
|
package/dist/core/value.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Font } from './style.ts';
|
|
2
|
-
/** The observable kind of a cell's value.
|
|
2
|
+
/** The observable kind of a cell's value. Every formula kind reports as `Formula`. */
|
|
3
3
|
export declare const ValueType: {
|
|
4
4
|
readonly Null: 'null';
|
|
5
5
|
readonly Number: 'number';
|
|
@@ -9,12 +9,29 @@ export declare const ValueType: {
|
|
|
9
9
|
readonly Error: 'error';
|
|
10
10
|
readonly Formula: 'formula';
|
|
11
11
|
readonly RichText: 'richText';
|
|
12
|
-
readonly Hyperlink: 'hyperlink';
|
|
13
12
|
};
|
|
14
13
|
export type ValueType = (typeof ValueType)[keyof typeof ValueType];
|
|
15
|
-
/**
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The errors a cell (or formula result) can hold.
|
|
16
|
+
*
|
|
17
|
+
* They are stored two ways. The classic seven, `#GETTING_DATA` and `#BUSY!` are literals: a typed cell
|
|
18
|
+
* holds the spelling and Excel reads it back as that error. Excel has no literal for `#SPILL!`,
|
|
19
|
+
* `#CONNECT!`, `#BLOCKED!`, `#UNKNOWN!`, `#FIELD!` and `#CALC!`, and stores each as `#VALUE!` beside a
|
|
20
|
+
* rich value naming the real one, which is how they are read and written here. `#PYTHON!`, `#EXTERNAL!`
|
|
21
|
+
* and `#TIMEOUT!` are not here: Excel reads no literal spelling of them, and no rich value was seen to
|
|
22
|
+
* read back as one of them unambiguously (Excel 16.0 build 20326).
|
|
23
|
+
*/
|
|
24
|
+
export declare const ERROR_CODES: readonly ['#N/A', '#REF!', '#NAME?', '#DIV/0!', '#NULL!', '#VALUE!', '#NUM!', '#GETTING_DATA', '#BUSY!', '#SPILL!', '#CONNECT!', '#BLOCKED!', '#UNKNOWN!', '#FIELD!', '#CALC!'];
|
|
17
25
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
26
|
+
/**
|
|
27
|
+
* The error a spreadsheet puts in place of a reference that has nowhere left to point.
|
|
28
|
+
*
|
|
29
|
+
* Named here rather than in the two formula codecs that produce it. Both of them -- the A1 renderer
|
|
30
|
+
* in `core/formula.ts` and the BIFF12 one in `io/xlsb/formula.ts` -- had a private literal of their
|
|
31
|
+
* own, spelled the same and named differently, for a value {@link ERROR_CODES} already publishes and
|
|
32
|
+
* `isErrorCode` already recognises.
|
|
33
|
+
*/
|
|
34
|
+
export declare const REF_ERROR: ErrorCode;
|
|
18
35
|
/** An in-cell error, e.g. `{error: '#REF!'}`. */
|
|
19
36
|
export interface ErrorValue {
|
|
20
37
|
readonly error: ErrorCode;
|
|
@@ -28,16 +45,6 @@ export interface RichTextRun {
|
|
|
28
45
|
export interface RichTextValue {
|
|
29
46
|
readonly richText: readonly RichTextRun[];
|
|
30
47
|
}
|
|
31
|
-
/** A hyperlink cell: a URL plus the text (plain or rich) shown in the cell. */
|
|
32
|
-
export interface HyperlinkValue {
|
|
33
|
-
readonly hyperlink: string;
|
|
34
|
-
readonly text: string | RichTextValue;
|
|
35
|
-
readonly tooltip?: string;
|
|
36
|
-
/** The clickable extent (`'D1:H1'`) when the link spans a range whose top-left corner is this
|
|
37
|
-
* cell. Absent for an ordinary single-cell link. The destination and label live on the top-left
|
|
38
|
-
* cell; `range` records how far Excel highlights the clickable area so it survives a round-trip. */
|
|
39
|
-
readonly range?: string;
|
|
40
|
-
}
|
|
41
48
|
/** The cached result a formula carries: any scalar, a date, or an error. */
|
|
42
49
|
export type FormulaResult = number | string | boolean | Date | ErrorValue;
|
|
43
50
|
/** A cell whose value is computed by its own formula. */
|
|
@@ -59,6 +66,26 @@ export interface SharedFormulaValue {
|
|
|
59
66
|
readonly formula?: string;
|
|
60
67
|
readonly result?: FormulaResult;
|
|
61
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* A cell holding an array formula (`<f t="array">`): one formula whose result fills {@link ref}, a
|
|
71
|
+
* range starting at this cell. The other cells of the range hold only the values the formula
|
|
72
|
+
* produced, which is how Excel stores them, so they are plain values here too.
|
|
73
|
+
*
|
|
74
|
+
* Excel stores two kinds this way. A legacy array formula, entered with Ctrl+Shift+Enter, shows in
|
|
75
|
+
* braces and fills the range it was entered over. A dynamic-array formula is {@link dynamic}: it
|
|
76
|
+
* spills, `ref` is the range its last calculation filled, and no braces are shown. Excel keeps that
|
|
77
|
+
* mark in the workbook's cell metadata rather than on the formula, and a formula that loses it opens
|
|
78
|
+
* as a legacy array formula that no longer spills.
|
|
79
|
+
*/
|
|
80
|
+
export interface ArrayFormulaValue {
|
|
81
|
+
readonly shareType: 'array';
|
|
82
|
+
readonly formula: string;
|
|
83
|
+
/** The range the result fills, starting at this cell: `'B1:B3'`, or `'B1'` for one cell. */
|
|
84
|
+
readonly ref: string;
|
|
85
|
+
/** Whether Excel evaluates the formula as a dynamic array rather than a Ctrl+Shift+Enter one. */
|
|
86
|
+
readonly dynamic?: boolean;
|
|
87
|
+
readonly result?: FormulaResult;
|
|
88
|
+
}
|
|
62
89
|
/**
|
|
63
90
|
* A cell computed by a What-If-Analysis data table (`<f t="dataTable">`), the OOXML formula kind that
|
|
64
91
|
* fills a range by re-evaluating a model against a grid of substituted input cells. The library does
|
|
@@ -77,25 +104,36 @@ export interface DataTableFormulaValue {
|
|
|
77
104
|
readonly r1?: string;
|
|
78
105
|
/** The second (column) input-cell reference, present for a 2-D table. */
|
|
79
106
|
readonly r2?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Whether the cell {@link r1} named has been deleted. Excel keeps the reference as it was written and
|
|
109
|
+
* sets this flag, and the table then shows `#REF!`; a row or column delete that takes the input cell
|
|
110
|
+
* does the same here.
|
|
111
|
+
*/
|
|
112
|
+
readonly r1Deleted?: boolean;
|
|
113
|
+
/** Whether the cell {@link r2} named has been deleted, as {@link r1Deleted} is for {@link r1}. */
|
|
114
|
+
readonly r2Deleted?: boolean;
|
|
80
115
|
readonly result?: FormulaResult;
|
|
81
116
|
}
|
|
82
117
|
/** Everything a cell's value can be. `null` is the empty cell. */
|
|
83
|
-
export type CellValue = null | number | string | boolean | Date | ErrorValue | FormulaValue | SharedFormulaValue |
|
|
118
|
+
export type CellValue = null | number | string | boolean | Date | ErrorValue | FormulaValue | SharedFormulaValue | ArrayFormulaValue | DataTableFormulaValue | RichTextValue;
|
|
84
119
|
/**
|
|
85
120
|
* Whether a value is an in-cell error ({@link ErrorValue}). The narrowing counterpart of
|
|
86
121
|
* `detectValueType(value) === ValueType.Error`: use this one when the branch goes on to read
|
|
87
|
-
* `.error`, and {@link detectValueType} when it dispatches over all
|
|
122
|
+
* `.error`, and {@link detectValueType} when it dispatches over all eight kinds at once.
|
|
88
123
|
*/
|
|
89
124
|
export declare function isErrorValue(value: CellValue): value is ErrorValue;
|
|
90
125
|
/**
|
|
91
|
-
* Whether a value is a cell's own formula ({@link FormulaValue}): a master, or a
|
|
92
|
-
* belonging to no
|
|
93
|
-
* {@link isSharedFormulaValue}
|
|
94
|
-
* formula-shaped cell" wants
|
|
126
|
+
* Whether a value is a cell's own plain formula ({@link FormulaValue}): a shared-formula master, or a
|
|
127
|
+
* formula belonging to no group. A shared-formula clone is **not** one of these, and nor is an array
|
|
128
|
+
* formula; see {@link isSharedFormulaValue} and {@link isArrayFormulaValue}. Every formula kind reports
|
|
129
|
+
* as `ValueType.Formula`, so a caller that means "any formula-shaped cell" wants
|
|
130
|
+
* {@link detectValueType}, not this.
|
|
95
131
|
*/
|
|
96
132
|
export declare function isFormulaValue(value: CellValue): value is FormulaValue;
|
|
97
133
|
/** Whether a value is a clone participating in a shared formula ({@link SharedFormulaValue}). */
|
|
98
134
|
export declare function isSharedFormulaValue(value: CellValue): value is SharedFormulaValue;
|
|
135
|
+
/** Whether a value is an array formula, legacy or dynamic ({@link ArrayFormulaValue}). */
|
|
136
|
+
export declare function isArrayFormulaValue(value: CellValue): value is ArrayFormulaValue;
|
|
99
137
|
/** Whether a value is a What-If-Analysis data-table formula ({@link DataTableFormulaValue}). */
|
|
100
138
|
export declare function isDataTableFormulaValue(value: CellValue): value is DataTableFormulaValue;
|
|
101
139
|
/**
|
|
@@ -103,11 +141,6 @@ export declare function isDataTableFormulaValue(value: CellValue): value is Data
|
|
|
103
141
|
* make before {@link richTextToPlain}, which accepts nothing else.
|
|
104
142
|
*/
|
|
105
143
|
export declare function isRichTextValue(value: CellValue): value is RichTextValue;
|
|
106
|
-
/**
|
|
107
|
-
* Whether a value is a hyperlink ({@link HyperlinkValue}). Note that its `text` is itself either
|
|
108
|
-
* a string or a {@link RichTextValue}, so reading the label out means one more narrowing.
|
|
109
|
-
*/
|
|
110
|
-
export declare function isHyperlinkValue(value: CellValue): value is HyperlinkValue;
|
|
111
144
|
/**
|
|
112
145
|
* Flatten a rich-text value to its plain text by concatenating every run's text in order. This is the
|
|
113
146
|
* text a consumer that cannot render per-run formatting (a CSV field, a pivot cache entry) sees, and
|
|
@@ -128,9 +161,8 @@ export declare function richTextToPlain(value: RichTextValue): string;
|
|
|
128
161
|
* - a `Date` → a full ISO-8601 timestamp
|
|
129
162
|
* - an error → its literal, e.g. `"#REF!"`, the same string the grid shows
|
|
130
163
|
* - rich text → every run concatenated ({@link richTextToPlain})
|
|
131
|
-
* -
|
|
132
|
-
*
|
|
133
|
-
* carries no cached result: the formula source is not text the sheet ever displayed
|
|
164
|
+
* - any formula kind → the text of the *cached result*, and `""` when the cell carries no cached
|
|
165
|
+
* result: the formula source is not text the sheet ever displayed
|
|
134
166
|
*/
|
|
135
167
|
export declare function cellValueToText(value: CellValue): string;
|
|
136
168
|
/**
|
package/dist/core/value.js
CHANGED
|
@@ -7,7 +7,6 @@ export const ValueType = {
|
|
|
7
7
|
Error: 'error',
|
|
8
8
|
Formula: 'formula',
|
|
9
9
|
RichText: 'richText',
|
|
10
|
-
Hyperlink: 'hyperlink',
|
|
11
10
|
};
|
|
12
11
|
export const ERROR_CODES = [
|
|
13
12
|
'#N/A',
|
|
@@ -17,10 +16,16 @@ export const ERROR_CODES = [
|
|
|
17
16
|
'#NULL!',
|
|
18
17
|
'#VALUE!',
|
|
19
18
|
'#NUM!',
|
|
19
|
+
'#GETTING_DATA',
|
|
20
|
+
'#BUSY!',
|
|
20
21
|
'#SPILL!',
|
|
22
|
+
'#CONNECT!',
|
|
23
|
+
'#BLOCKED!',
|
|
24
|
+
'#UNKNOWN!',
|
|
25
|
+
'#FIELD!',
|
|
21
26
|
'#CALC!',
|
|
22
|
-
'#GETTING_DATA',
|
|
23
27
|
];
|
|
28
|
+
export const REF_ERROR = '#REF!';
|
|
24
29
|
const ERROR_SET = new Set(ERROR_CODES);
|
|
25
30
|
function hasKey(value, key) {
|
|
26
31
|
return typeof value === 'object' && value !== null && key in value;
|
|
@@ -29,76 +34,76 @@ export function isErrorValue(value) {
|
|
|
29
34
|
return hasKey(value, 'error');
|
|
30
35
|
}
|
|
31
36
|
export function isFormulaValue(value) {
|
|
32
|
-
return hasKey(value, 'formula') && !('sharedFormula' in value);
|
|
37
|
+
return hasKey(value, 'formula') && !('sharedFormula' in value) && !('shareType' in value);
|
|
33
38
|
}
|
|
34
39
|
export function isSharedFormulaValue(value) {
|
|
35
40
|
return hasKey(value, 'sharedFormula');
|
|
36
41
|
}
|
|
42
|
+
export function isArrayFormulaValue(value) {
|
|
43
|
+
return hasKey(value, 'shareType') && value.shareType === 'array';
|
|
44
|
+
}
|
|
37
45
|
export function isDataTableFormulaValue(value) {
|
|
38
46
|
return hasKey(value, 'shareType') && value.shareType === 'dataTable';
|
|
39
47
|
}
|
|
40
48
|
export function isRichTextValue(value) {
|
|
41
49
|
return hasKey(value, 'richText');
|
|
42
50
|
}
|
|
43
|
-
export function isHyperlinkValue(value) {
|
|
44
|
-
return hasKey(value, 'hyperlink');
|
|
45
|
-
}
|
|
46
51
|
export function richTextToPlain(value) {
|
|
47
52
|
return value.richText.map((run) => run.text).join('');
|
|
48
53
|
}
|
|
49
54
|
export function cellValueToText(value) {
|
|
50
|
-
|
|
51
|
-
return '';
|
|
52
|
-
switch (typeof value) {
|
|
53
|
-
case 'number':
|
|
54
|
-
return String(value);
|
|
55
|
-
case 'string':
|
|
56
|
-
return value;
|
|
57
|
-
case 'boolean':
|
|
58
|
-
return value ? 'TRUE' : 'FALSE';
|
|
59
|
-
default:
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
if (value instanceof Date)
|
|
63
|
-
return Number.isNaN(value.getTime()) ? '' : value.toISOString();
|
|
64
|
-
if (isHyperlinkValue(value)) {
|
|
65
|
-
return typeof value.text === 'string' ? value.text : richTextToPlain(value.text);
|
|
66
|
-
}
|
|
67
|
-
if (isFormulaValue(value) || isSharedFormulaValue(value) || isDataTableFormulaValue(value)) {
|
|
68
|
-
return value.result === undefined ? '' : cellValueToText(value.result);
|
|
69
|
-
}
|
|
70
|
-
if (isRichTextValue(value))
|
|
71
|
-
return richTextToPlain(value);
|
|
72
|
-
if (isErrorValue(value))
|
|
73
|
-
return value.error;
|
|
74
|
-
return unsupportedValue(value);
|
|
55
|
+
return classify(value, TO_TEXT);
|
|
75
56
|
}
|
|
76
57
|
export function detectValueType(value) {
|
|
58
|
+
return classify(value, TO_TYPE);
|
|
59
|
+
}
|
|
60
|
+
function classify(value, visit) {
|
|
77
61
|
if (value === null)
|
|
78
|
-
return
|
|
62
|
+
return visit.null(value);
|
|
79
63
|
switch (typeof value) {
|
|
80
64
|
case 'number':
|
|
81
|
-
return
|
|
65
|
+
return visit.number(value);
|
|
82
66
|
case 'string':
|
|
83
|
-
return
|
|
67
|
+
return visit.string(value);
|
|
84
68
|
case 'boolean':
|
|
85
|
-
return
|
|
69
|
+
return visit.boolean(value);
|
|
86
70
|
default:
|
|
87
71
|
break;
|
|
88
72
|
}
|
|
89
73
|
if (value instanceof Date)
|
|
90
|
-
return
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
74
|
+
return visit.date(value);
|
|
75
|
+
if (isFormulaValue(value) ||
|
|
76
|
+
isSharedFormulaValue(value) ||
|
|
77
|
+
isArrayFormulaValue(value) ||
|
|
78
|
+
isDataTableFormulaValue(value)) {
|
|
79
|
+
return visit.formula(value);
|
|
95
80
|
}
|
|
96
81
|
if (isRichTextValue(value))
|
|
97
|
-
return
|
|
82
|
+
return visit.richText(value);
|
|
98
83
|
if (isErrorValue(value))
|
|
99
|
-
return
|
|
84
|
+
return visit.error(value);
|
|
100
85
|
return unsupportedValue(value);
|
|
101
86
|
}
|
|
87
|
+
const TO_TYPE = {
|
|
88
|
+
null: () => ValueType.Null,
|
|
89
|
+
number: () => ValueType.Number,
|
|
90
|
+
string: () => ValueType.String,
|
|
91
|
+
boolean: () => ValueType.Boolean,
|
|
92
|
+
date: () => ValueType.Date,
|
|
93
|
+
error: () => ValueType.Error,
|
|
94
|
+
formula: () => ValueType.Formula,
|
|
95
|
+
richText: () => ValueType.RichText,
|
|
96
|
+
};
|
|
97
|
+
const TO_TEXT = {
|
|
98
|
+
null: () => '',
|
|
99
|
+
number: (value) => String(value),
|
|
100
|
+
string: (value) => value,
|
|
101
|
+
boolean: (value) => (value ? 'TRUE' : 'FALSE'),
|
|
102
|
+
date: (value) => (Number.isNaN(value.getTime()) ? '' : value.toISOString()),
|
|
103
|
+
error: (value) => value.error,
|
|
104
|
+
formula: (value) => (value.result === undefined ? '' : cellValueToText(value.result)),
|
|
105
|
+
richText: richTextToPlain,
|
|
106
|
+
};
|
|
102
107
|
function unsupportedValue(value) {
|
|
103
108
|
throw new TypeError(`unsupported cell value: ${describe(value)}`);
|
|
104
109
|
}
|
|
@@ -115,7 +120,7 @@ export function coerceCellValue(value) {
|
|
|
115
120
|
if (value === undefined)
|
|
116
121
|
return null;
|
|
117
122
|
detectValueType(value);
|
|
118
|
-
if (isFormulaValue(value)) {
|
|
123
|
+
if (isFormulaValue(value) || isArrayFormulaValue(value)) {
|
|
119
124
|
const formula = stripLeadingEquals(value.formula);
|
|
120
125
|
return formula === value.formula ? value : { ...value, formula };
|
|
121
126
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type WorkbookImage } from './image.ts';
|
|
2
|
+
export declare class WorkbookMedia {
|
|
3
|
+
#private;
|
|
4
|
+
/** The registered images, indexed by the id {@link register} returned. Live, not a copy. */
|
|
5
|
+
get all(): readonly WorkbookImage[];
|
|
6
|
+
/** Look up a registered image by its id, or `undefined` if no image carries that id. */
|
|
7
|
+
get(id: number): WorkbookImage | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* {@link get} for a caller that has no answer for an absent id.
|
|
10
|
+
*
|
|
11
|
+
* An unregistered id is what a sheet belonging to *another* workbook looks like from here, which
|
|
12
|
+
* is why the message names the sheet rather than the id alone. Emitting a package with a drawing
|
|
13
|
+
* pointing at media nobody registered is the silently-broken-image failure this refuses to start.
|
|
14
|
+
*
|
|
15
|
+
* @throws {AuthoringError} if no image carries that id.
|
|
16
|
+
*/
|
|
17
|
+
require(id: number, forSheetName: string): WorkbookImage;
|
|
18
|
+
/** Store a picture's bytes and return the id that names them. */
|
|
19
|
+
register(extension: string | undefined, buffer: Uint8Array): number;
|
|
20
|
+
/**
|
|
21
|
+
* Register a picture arriving from elsewhere, re-using an identical one already held.
|
|
22
|
+
*
|
|
23
|
+
* The extension is re-normalised rather than trusted: a hand-built {@link WorkbookImage} may carry
|
|
24
|
+
* `".PNG"` where the registry holds `"png"`, and two spellings of one kind must not read as two
|
|
25
|
+
* pictures.
|
|
26
|
+
*
|
|
27
|
+
* Through the content index rather than a scan. Comparing byte-by-byte against every held picture
|
|
28
|
+
* made importing n distinct images cost n squared byte comparisons: fifty 1 MB pictures carried
|
|
29
|
+
* between workbooks compared about 2.5 GB. The *rule* is unchanged, and `imageContentKey` states
|
|
30
|
+
* why identity here is content and never object identity. The index is built in reverse so the
|
|
31
|
+
* FIRST id registered under a key wins, which is the answer the scan gave.
|
|
32
|
+
*/
|
|
33
|
+
registerExisting(image: WorkbookImage): number;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
|
+
import { imageContentKey, normalizeImageExtension } from './image.js';
|
|
3
|
+
export class WorkbookMedia {
|
|
4
|
+
#images = [];
|
|
5
|
+
#byContent;
|
|
6
|
+
get all() {
|
|
7
|
+
return this.#images;
|
|
8
|
+
}
|
|
9
|
+
get(id) {
|
|
10
|
+
return this.#images[id];
|
|
11
|
+
}
|
|
12
|
+
require(id, forSheetName) {
|
|
13
|
+
const image = this.#images[id];
|
|
14
|
+
if (image === undefined) {
|
|
15
|
+
throw new AuthoringError(`worksheet ${quoted(forSheetName)} shows image id ${id}, which is not registered on this ` +
|
|
16
|
+
"workbook: a sheet's images can only be exported by the workbook that holds them");
|
|
17
|
+
}
|
|
18
|
+
return image;
|
|
19
|
+
}
|
|
20
|
+
register(extension, buffer) {
|
|
21
|
+
const image = {
|
|
22
|
+
extension: normalizeImageExtension(extension, buffer),
|
|
23
|
+
data: buffer,
|
|
24
|
+
};
|
|
25
|
+
this.#images.push(image);
|
|
26
|
+
const id = this.#images.length - 1;
|
|
27
|
+
const index = this.#byContent;
|
|
28
|
+
if (index !== undefined) {
|
|
29
|
+
const key = imageContentKey(image);
|
|
30
|
+
if (!index.has(key))
|
|
31
|
+
index.set(key, id);
|
|
32
|
+
}
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
35
|
+
registerExisting(image) {
|
|
36
|
+
const candidate = {
|
|
37
|
+
extension: normalizeImageExtension(image.extension, image.data),
|
|
38
|
+
data: image.data,
|
|
39
|
+
};
|
|
40
|
+
this.#byContent ??= new Map(this.#images.map((held, id) => [imageContentKey(held), id]).reverse());
|
|
41
|
+
return (this.#byContent.get(imageContentKey(candidate)) ??
|
|
42
|
+
this.register(candidate.extension, candidate.data));
|
|
43
|
+
}
|
|
44
|
+
}
|