@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/pivot-table.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { AuthoringError, InternalError } from '../errors.js';
|
|
1
|
+
import { AuthoringError, InternalError, quoted } from '../errors.js';
|
|
2
2
|
import { tokenSet } from '../token-set.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { encodeRect } from './address.js';
|
|
4
|
+
import { spliceFormula } from './formula-references.js';
|
|
5
|
+
import { INTERNAL } from './internal.js';
|
|
6
|
+
import { isArrayFormulaValue, isErrorValue, isFormulaValue, isRichTextValue, isSharedFormulaValue, REF_ERROR, richTextToPlain, } from './value.js';
|
|
5
7
|
const PIVOT_METRICS = {
|
|
6
8
|
sum: true,
|
|
7
9
|
count: true,
|
|
@@ -31,7 +33,7 @@ const BLANK = { kind: 'blank' };
|
|
|
31
33
|
export class PivotTable {
|
|
32
34
|
metric;
|
|
33
35
|
sourceSheetName;
|
|
34
|
-
sourceRef;
|
|
36
|
+
#sourceRef;
|
|
35
37
|
cacheFields;
|
|
36
38
|
records;
|
|
37
39
|
rowFields;
|
|
@@ -40,7 +42,7 @@ export class PivotTable {
|
|
|
40
42
|
constructor(options) {
|
|
41
43
|
const metric = options.metric ?? 'sum';
|
|
42
44
|
if (!Object.hasOwn(PIVOT_METRICS, metric)) {
|
|
43
|
-
throw new AuthoringError(`unsupported pivot metric
|
|
45
|
+
throw new AuthoringError(`unsupported pivot metric ${quoted(metric)}: expected one of ${Object.keys(PIVOT_METRICS).join(', ')}`);
|
|
44
46
|
}
|
|
45
47
|
this.metric = metric;
|
|
46
48
|
const source = options.source;
|
|
@@ -49,52 +51,41 @@ export class PivotTable {
|
|
|
49
51
|
if (columnCount < 1 || lastRow < 2) {
|
|
50
52
|
throw new AuthoringError('a pivot source needs a header row and at least one data row');
|
|
51
53
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const resolve = (role, name) => {
|
|
61
|
-
const index = fields.findIndex((field) => field.name === name);
|
|
62
|
-
if (index < 0) {
|
|
63
|
-
throw new AuthoringError(`pivot ${role} field "${name}" is not a column header in the source sheet`);
|
|
64
|
-
}
|
|
65
|
-
return index;
|
|
66
|
-
};
|
|
67
|
-
if (options.rows.length === 0)
|
|
68
|
-
throw new AuthoringError('a pivot table needs at least one row field');
|
|
69
|
-
if (options.columns.length === 0)
|
|
70
|
-
throw new AuthoringError('a pivot table needs at least one column field');
|
|
71
|
-
const [valueName, ...extraValues] = options.values;
|
|
72
|
-
if (valueName === undefined || extraValues.length > 0) {
|
|
73
|
-
throw new AuthoringError('a pivot table needs exactly one value field');
|
|
74
|
-
}
|
|
75
|
-
this.rowFields = options.rows.map((name) => resolve('row', name));
|
|
76
|
-
this.columnFields = options.columns.map((name) => resolve('column', name));
|
|
77
|
-
this.valueField = resolve('value', valueName);
|
|
78
|
-
const axisFields = new Set([...this.rowFields, ...this.columnFields]);
|
|
54
|
+
const internals = source[INTERNAL];
|
|
55
|
+
const valueAt = (row, col) => internals.peekCell(row, col)?.value ?? null;
|
|
56
|
+
const fields = discoverFields(valueAt, columnCount);
|
|
57
|
+
const roles = resolveRoles(options, fields);
|
|
58
|
+
this.rowFields = roles.rowFields;
|
|
59
|
+
this.columnFields = roles.columnFields;
|
|
60
|
+
this.valueField = roles.valueField;
|
|
61
|
+
const axisFields = new Set([...roles.rowFields, ...roles.columnFields]);
|
|
79
62
|
const firstField = fields[0];
|
|
80
|
-
const lastField = fields
|
|
63
|
+
const lastField = fields.at(-1);
|
|
81
64
|
if (firstField === undefined || lastField === undefined) {
|
|
82
|
-
throw new
|
|
65
|
+
throw new InternalError('pivot field list is empty after discoverFields guaranteed it is not');
|
|
83
66
|
}
|
|
84
67
|
this.sourceSheetName = source.name;
|
|
85
|
-
this
|
|
68
|
+
this.#sourceRef = encodeRect({
|
|
69
|
+
top: 1,
|
|
70
|
+
left: firstField.col,
|
|
71
|
+
bottom: lastRow,
|
|
72
|
+
right: lastField.col,
|
|
73
|
+
});
|
|
86
74
|
const dataRowCount = lastRow - 1;
|
|
87
75
|
const columnScalars = fields.map((field) => {
|
|
88
76
|
const scalars = [];
|
|
89
|
-
for (let row = 2; row <= lastRow; row++)
|
|
90
|
-
scalars.push(scalarOf(
|
|
91
|
-
}
|
|
77
|
+
for (let row = 2; row <= lastRow; row++)
|
|
78
|
+
scalars.push(scalarOf(valueAt(row, field.col)));
|
|
92
79
|
return scalars;
|
|
93
80
|
});
|
|
94
81
|
const catalogues = fields.map(() => null);
|
|
95
82
|
this.cacheFields = fields.map((field, fieldIndex) => {
|
|
96
|
-
const scalars =
|
|
97
|
-
const
|
|
83
|
+
const scalars = columnScalars[fieldIndex] ?? missingColumn(fieldIndex);
|
|
84
|
+
const kinds = {
|
|
85
|
+
containsString: scalars.some((scalar) => scalar.kind === 'string'),
|
|
86
|
+
containsBlank: scalars.some((scalar) => scalar.kind === 'blank'),
|
|
87
|
+
numeric: numericSummary(scalars),
|
|
88
|
+
};
|
|
98
89
|
if (axisFields.has(fieldIndex)) {
|
|
99
90
|
const items = [];
|
|
100
91
|
const catalogue = new Map();
|
|
@@ -106,19 +97,22 @@ export class PivotTable {
|
|
|
106
97
|
}
|
|
107
98
|
}
|
|
108
99
|
catalogues[fieldIndex] = catalogue;
|
|
109
|
-
return { name: field.name, sharedItems: items,
|
|
100
|
+
return { name: field.name, sharedItems: items, ...kinds };
|
|
110
101
|
}
|
|
111
|
-
return { name: field.name, sharedItems: null,
|
|
102
|
+
return { name: field.name, sharedItems: null, ...kinds };
|
|
112
103
|
});
|
|
104
|
+
const byField = fields.map((_field, fieldIndex) => ({
|
|
105
|
+
scalars: columnScalars[fieldIndex] ?? missingColumn(fieldIndex),
|
|
106
|
+
catalogue: catalogues[fieldIndex],
|
|
107
|
+
}));
|
|
113
108
|
const records = [];
|
|
114
109
|
for (let row = 0; row < dataRowCount; row++) {
|
|
115
|
-
records.push(
|
|
116
|
-
const scalar =
|
|
110
|
+
records.push(byField.map(({ scalars, catalogue }, fieldIndex) => {
|
|
111
|
+
const scalar = scalars[row];
|
|
117
112
|
if (scalar === undefined) {
|
|
118
113
|
throw new InternalError(`pivot record row ${row} is out of range for field ${fieldIndex}: every column was ` +
|
|
119
114
|
'scanned for the same dataRowCount above, so this index is always in range');
|
|
120
115
|
}
|
|
121
|
-
const catalogue = catalogues[fieldIndex];
|
|
122
116
|
if (!catalogue)
|
|
123
117
|
return scalar;
|
|
124
118
|
const index = catalogue.get(itemKey(scalar));
|
|
@@ -129,6 +123,14 @@ export class PivotTable {
|
|
|
129
123
|
}
|
|
130
124
|
this.records = records;
|
|
131
125
|
}
|
|
126
|
+
get sourceRef() {
|
|
127
|
+
return this.#sourceRef;
|
|
128
|
+
}
|
|
129
|
+
[INTERNAL] = {
|
|
130
|
+
spliceSource: (edit) => {
|
|
131
|
+
this.#sourceRef = splicePivotSource(this.sourceSheetName, this.#sourceRef, edit);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
132
134
|
get valueFieldName() {
|
|
133
135
|
const field = this.cacheFields[this.valueField];
|
|
134
136
|
if (field === undefined) {
|
|
@@ -138,12 +140,64 @@ export class PivotTable {
|
|
|
138
140
|
return field.name;
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
|
-
function
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
function discoverFields(valueAt, columnCount) {
|
|
144
|
+
const fields = [];
|
|
145
|
+
const seen = new Map();
|
|
146
|
+
for (let col = 1; col <= columnCount; col++) {
|
|
147
|
+
const name = textOf(scalarOf(valueAt(1, col)));
|
|
148
|
+
if (name === '')
|
|
149
|
+
continue;
|
|
150
|
+
const clash = seen.get(name.toLowerCase());
|
|
151
|
+
if (clash !== undefined) {
|
|
152
|
+
throw new AuthoringError(`the pivot source header ${quoted(name)} repeats ${quoted(clash)}: pivot field names must ` +
|
|
153
|
+
'be unique, ignoring case');
|
|
154
|
+
}
|
|
155
|
+
seen.set(name.toLowerCase(), name);
|
|
156
|
+
fields.push({ name, col });
|
|
157
|
+
}
|
|
158
|
+
if (fields.length === 0)
|
|
159
|
+
throw new AuthoringError('the pivot source header row is empty');
|
|
160
|
+
return fields;
|
|
161
|
+
}
|
|
162
|
+
function resolveRoles(options, fields) {
|
|
163
|
+
const resolve = (role, name) => {
|
|
164
|
+
const key = name.toLowerCase();
|
|
165
|
+
const index = fields.findIndex((field) => field.name.toLowerCase() === key);
|
|
166
|
+
if (index < 0) {
|
|
167
|
+
throw new AuthoringError(`pivot ${role} field ${quoted(name)} is not a column header in the source sheet`);
|
|
168
|
+
}
|
|
169
|
+
return index;
|
|
170
|
+
};
|
|
171
|
+
if (options.rows.length === 0)
|
|
172
|
+
throw new AuthoringError('a pivot table needs at least one row field');
|
|
173
|
+
if (options.columns.length === 0)
|
|
174
|
+
throw new AuthoringError('a pivot table needs at least one column field');
|
|
175
|
+
const [valueName, ...extraValues] = options.values;
|
|
176
|
+
if (valueName === undefined || extraValues.length > 0) {
|
|
177
|
+
throw new AuthoringError('a pivot table needs exactly one value field');
|
|
145
178
|
}
|
|
146
|
-
|
|
179
|
+
const axisRoles = new Map();
|
|
180
|
+
const resolveAxis = (role, names) => names.map((name) => {
|
|
181
|
+
const index = resolve(role, name);
|
|
182
|
+
const earlier = axisRoles.get(index);
|
|
183
|
+
if (earlier === role) {
|
|
184
|
+
throw new AuthoringError(`pivot ${role} field ${quoted(name)} is named more than once`);
|
|
185
|
+
}
|
|
186
|
+
if (earlier !== undefined) {
|
|
187
|
+
throw new AuthoringError(`pivot field ${quoted(name)} is named as both a ${earlier} and a ${role} field: a field ` +
|
|
188
|
+
'sits on one axis');
|
|
189
|
+
}
|
|
190
|
+
axisRoles.set(index, role);
|
|
191
|
+
return index;
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
rowFields: resolveAxis('row', options.rows),
|
|
195
|
+
columnFields: resolveAxis('column', options.columns),
|
|
196
|
+
valueField: resolve('value', valueName),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function missingColumn(fieldIndex) {
|
|
200
|
+
throw new InternalError(`pivot field index ${fieldIndex} is out of range for columnScalars: it was built from the same fields array`);
|
|
147
201
|
}
|
|
148
202
|
function itemKey(item) {
|
|
149
203
|
switch (item.kind) {
|
|
@@ -161,10 +215,8 @@ function numericSummary(scalars) {
|
|
|
161
215
|
let allInteger = true;
|
|
162
216
|
let sawNumber = false;
|
|
163
217
|
for (const scalar of scalars) {
|
|
164
|
-
if (scalar.kind === 'blank')
|
|
165
|
-
continue;
|
|
166
218
|
if (scalar.kind !== 'number')
|
|
167
|
-
|
|
219
|
+
continue;
|
|
168
220
|
sawNumber = true;
|
|
169
221
|
if (scalar.value < min)
|
|
170
222
|
min = scalar.value;
|
|
@@ -195,16 +247,16 @@ function scalarOf(value) {
|
|
|
195
247
|
return { kind: 'string', value: value.toISOString() };
|
|
196
248
|
if (isRichTextValue(value))
|
|
197
249
|
return { kind: 'string', value: richTextToPlain(value) };
|
|
198
|
-
if (isHyperlinkValue(value)) {
|
|
199
|
-
return {
|
|
200
|
-
kind: 'string',
|
|
201
|
-
value: typeof value.text === 'string' ? value.text : richTextToPlain(value.text),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
250
|
if (isErrorValue(value))
|
|
205
251
|
return { kind: 'string', value: value.error };
|
|
206
|
-
if (isFormulaValue(value) || isSharedFormulaValue(value)) {
|
|
252
|
+
if (isFormulaValue(value) || isSharedFormulaValue(value) || isArrayFormulaValue(value)) {
|
|
207
253
|
return value.result === undefined ? BLANK : scalarOf(value.result);
|
|
208
254
|
}
|
|
209
255
|
return BLANK;
|
|
210
256
|
}
|
|
257
|
+
export function splicePivotSource(sheet, ref, edit) {
|
|
258
|
+
if (sheet.toLowerCase() !== edit.sheet.toLowerCase())
|
|
259
|
+
return ref;
|
|
260
|
+
const moved = spliceFormula(ref, sheet, edit);
|
|
261
|
+
return moved === REF_ERROR ? ref : moved;
|
|
262
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AssertNever } from './internal.ts';
|
|
1
2
|
/**
|
|
2
3
|
* Whether each protected-sheet operation stays available to a user. Every flag is an
|
|
3
4
|
* *allow* flag: `true` keeps the operation permitted, `false` forbids it, and an absent
|
|
@@ -46,7 +47,20 @@ export interface SheetProtectionCredential {
|
|
|
46
47
|
export interface SheetProtection {
|
|
47
48
|
readonly flags: SheetProtectionFlags;
|
|
48
49
|
readonly credential?: SheetProtectionCredential;
|
|
50
|
+
/**
|
|
51
|
+
* The legacy 16-bit password hash (`password="CC3D"`) a file protected the sheet with, kept verbatim
|
|
52
|
+
* so the sheet stays guarded after a save. Pre-2010 Excel, XlsxWriter, openpyxl and LibreOffice write
|
|
53
|
+
* it instead of the agile credential. It is never derived from a password: `protect` writes only the
|
|
54
|
+
* agile form, and this hash is weak enough that nobody should want it authored.
|
|
55
|
+
*/
|
|
56
|
+
readonly legacyPasswordHash?: string;
|
|
49
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether a string is a legacy sheet password hash as its schema type (`ST_UnsignedShortHex`) spells
|
|
60
|
+
* one: exactly four hexadecimal digits. The reader keeps only such a value and the writer refuses
|
|
61
|
+
* anything else, since the hash is interpolated into an attribute.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isLegacyPasswordHash(value: string): boolean;
|
|
50
64
|
/**
|
|
51
65
|
* The OOXML encoding table for the protection flags: each `<sheetProtection>` attribute paired
|
|
52
66
|
* with whether that operation is *forbidden by default* once a sheet is protected. Both directions
|
|
@@ -55,10 +69,58 @@ export interface SheetProtection {
|
|
|
55
69
|
* and deserialization can never fall out of step. Most editing operations default to forbidden
|
|
56
70
|
* under protection; selecting cells and the object/scenario operations default to permitted.
|
|
57
71
|
*/
|
|
58
|
-
export declare const SHEET_PROTECTION_FLAGS: readonly {
|
|
59
|
-
readonly key:
|
|
60
|
-
readonly defaultForbidden:
|
|
61
|
-
}
|
|
72
|
+
export declare const SHEET_PROTECTION_FLAGS: readonly [{
|
|
73
|
+
readonly key: 'formatCells';
|
|
74
|
+
readonly defaultForbidden: true;
|
|
75
|
+
}, {
|
|
76
|
+
readonly key: 'formatColumns';
|
|
77
|
+
readonly defaultForbidden: true;
|
|
78
|
+
}, {
|
|
79
|
+
readonly key: 'formatRows';
|
|
80
|
+
readonly defaultForbidden: true;
|
|
81
|
+
}, {
|
|
82
|
+
readonly key: 'insertColumns';
|
|
83
|
+
readonly defaultForbidden: true;
|
|
84
|
+
}, {
|
|
85
|
+
readonly key: 'insertRows';
|
|
86
|
+
readonly defaultForbidden: true;
|
|
87
|
+
}, {
|
|
88
|
+
readonly key: 'insertHyperlinks';
|
|
89
|
+
readonly defaultForbidden: true;
|
|
90
|
+
}, {
|
|
91
|
+
readonly key: 'deleteColumns';
|
|
92
|
+
readonly defaultForbidden: true;
|
|
93
|
+
}, {
|
|
94
|
+
readonly key: 'deleteRows';
|
|
95
|
+
readonly defaultForbidden: true;
|
|
96
|
+
}, {
|
|
97
|
+
readonly key: 'sort';
|
|
98
|
+
readonly defaultForbidden: true;
|
|
99
|
+
}, {
|
|
100
|
+
readonly key: 'autoFilter';
|
|
101
|
+
readonly defaultForbidden: true;
|
|
102
|
+
}, {
|
|
103
|
+
readonly key: 'pivotTables';
|
|
104
|
+
readonly defaultForbidden: true;
|
|
105
|
+
}, {
|
|
106
|
+
readonly key: 'objects';
|
|
107
|
+
readonly defaultForbidden: false;
|
|
108
|
+
}, {
|
|
109
|
+
readonly key: 'scenarios';
|
|
110
|
+
readonly defaultForbidden: false;
|
|
111
|
+
}, {
|
|
112
|
+
readonly key: 'selectLockedCells';
|
|
113
|
+
readonly defaultForbidden: false;
|
|
114
|
+
}, {
|
|
115
|
+
readonly key: 'selectUnlockedCells';
|
|
116
|
+
readonly defaultForbidden: false;
|
|
117
|
+
}];
|
|
118
|
+
/**
|
|
119
|
+
* Compile-time proof that {@link SHEET_PROTECTION_FLAGS} covers every {@link SheetProtectionFlags}
|
|
120
|
+
* operation. The table's doc says the writer and reader can never fall out of step; that holds only
|
|
121
|
+
* while every flag is in it, and a flag added to the interface alone would be neither written nor read.
|
|
122
|
+
*/
|
|
123
|
+
export type EverySheetProtectionFlagIsDeclared = AssertNever<Exclude<keyof SheetProtectionFlags, (typeof SHEET_PROTECTION_FLAGS)[number]['key']>>;
|
|
62
124
|
/**
|
|
63
125
|
* Derive a fresh {@link SheetProtectionCredential} for a password. Each call generates a new
|
|
64
126
|
* random salt, so protecting two sheets with the same password yields different credentials:
|
package/dist/core/protection.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { concat, toBase64 } from '../bytes.js';
|
|
1
|
+
import { concat, toBase64, utf16leBytes } from '../bytes.js';
|
|
2
2
|
import { sha512 } from '../sha512.js';
|
|
3
|
+
const LEGACY_PASSWORD_HASH = /^[0-9A-Fa-f]{4}$/;
|
|
4
|
+
export function isLegacyPasswordHash(value) {
|
|
5
|
+
return LEGACY_PASSWORD_HASH.test(value);
|
|
6
|
+
}
|
|
3
7
|
export const SHEET_PROTECTION_FLAGS = [
|
|
4
8
|
{ key: 'formatCells', defaultForbidden: true },
|
|
5
9
|
{ key: 'formatColumns', defaultForbidden: true },
|
|
@@ -22,7 +26,7 @@ const DEFAULT_SPIN_COUNT = 100000;
|
|
|
22
26
|
const SALT_BYTES = 16;
|
|
23
27
|
export function deriveCredential(password, spinCount = DEFAULT_SPIN_COUNT) {
|
|
24
28
|
const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
|
|
25
|
-
let hash = sha512(concat([salt,
|
|
29
|
+
let hash = sha512(concat([salt, utf16leBytes(password)]));
|
|
26
30
|
const spun = new Uint8Array(hash.length + 4);
|
|
27
31
|
const counter = new DataView(spun.buffer, hash.length, 4);
|
|
28
32
|
for (let i = 0; i < spinCount; i++) {
|
|
@@ -37,12 +41,3 @@ export function deriveCredential(password, spinCount = DEFAULT_SPIN_COUNT) {
|
|
|
37
41
|
spinCount,
|
|
38
42
|
};
|
|
39
43
|
}
|
|
40
|
-
function utf16le(text) {
|
|
41
|
-
const bytes = new Uint8Array(text.length * 2);
|
|
42
|
-
for (let i = 0; i < text.length; i++) {
|
|
43
|
-
const unit = text.charCodeAt(i);
|
|
44
|
-
bytes[i * 2] = unit & 0xff;
|
|
45
|
-
bytes[i * 2 + 1] = unit >>> 8;
|
|
46
|
-
}
|
|
47
|
-
return bytes;
|
|
48
|
-
}
|
package/dist/core/range.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
2
|
+
import { assertAxisInBounds, decodeRange, encodeAddress, encodeRect, MAX_COLUMN, MAX_ROW, } from './address.js';
|
|
2
3
|
import { applyCellStyle } from './cell.js';
|
|
4
|
+
import { INTERNAL } from './internal.js';
|
|
3
5
|
import { CELL_STYLE_FACETS, } from './style.js';
|
|
4
6
|
export class Range {
|
|
5
7
|
#sheet;
|
|
@@ -8,10 +10,10 @@ export class Range {
|
|
|
8
10
|
bottom;
|
|
9
11
|
right;
|
|
10
12
|
constructor(sheet, top, left, bottom, right) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
assertAxisInBounds('row', top);
|
|
14
|
+
assertAxisInBounds('row', bottom);
|
|
15
|
+
assertAxisInBounds('column', left);
|
|
16
|
+
assertAxisInBounds('column', right);
|
|
15
17
|
this.#sheet = sheet;
|
|
16
18
|
this.top = Math.min(top, bottom);
|
|
17
19
|
this.bottom = Math.max(top, bottom);
|
|
@@ -22,7 +24,7 @@ export class Range {
|
|
|
22
24
|
return this.#sheet;
|
|
23
25
|
}
|
|
24
26
|
get address() {
|
|
25
|
-
return
|
|
27
|
+
return encodeRect(this);
|
|
26
28
|
}
|
|
27
29
|
get rowCount() {
|
|
28
30
|
return this.bottom - this.top + 1;
|
|
@@ -45,12 +47,8 @@ export class Range {
|
|
|
45
47
|
}
|
|
46
48
|
get cells() {
|
|
47
49
|
const cells = [];
|
|
48
|
-
for (
|
|
49
|
-
|
|
50
|
-
if (this.#sheet.hasCell(row, col))
|
|
51
|
-
cells.push(this.#sheet.getCell(encodeAddress(col, row)));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
50
|
+
for (const cell of this.#storedCells())
|
|
51
|
+
cells.push(cell);
|
|
54
52
|
return cells;
|
|
55
53
|
}
|
|
56
54
|
get style() {
|
|
@@ -64,7 +62,7 @@ export class Range {
|
|
|
64
62
|
applyCellStyle(cell, style);
|
|
65
63
|
}
|
|
66
64
|
clearStyle() {
|
|
67
|
-
for (const cell of this
|
|
65
|
+
for (const cell of this.#storedCells()) {
|
|
68
66
|
for (const facet of CELL_STYLE_FACETS)
|
|
69
67
|
setFacet(cell, facet, undefined);
|
|
70
68
|
}
|
|
@@ -105,28 +103,42 @@ export class Range {
|
|
|
105
103
|
set protection(protection) {
|
|
106
104
|
this.#writeFacet('protection', protection);
|
|
107
105
|
}
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
*#materialise() {
|
|
107
|
+
const internals = this.#sheet[INTERNAL];
|
|
108
|
+
for (let row = this.top; row <= this.bottom; row++) {
|
|
109
|
+
for (let col = this.left; col <= this.right; col++) {
|
|
110
|
+
yield internals.masterAt(row, col);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
110
113
|
}
|
|
111
114
|
#writeFacet(facet, value) {
|
|
112
|
-
const cells = value === undefined ? this
|
|
115
|
+
const cells = value === undefined ? this.#storedCells() : this.#materialise();
|
|
113
116
|
for (const cell of cells)
|
|
114
117
|
setFacet(cell, facet, value);
|
|
115
118
|
}
|
|
119
|
+
*#storedCells() {
|
|
120
|
+
const internals = this.#sheet[INTERNAL];
|
|
121
|
+
for (let row = this.top; row <= this.bottom; row++) {
|
|
122
|
+
for (let col = this.left; col <= this.right; col++) {
|
|
123
|
+
const cell = internals.peekCell(row, col);
|
|
124
|
+
if (cell !== undefined)
|
|
125
|
+
yield cell;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
116
129
|
#collectShared(target, facet) {
|
|
117
130
|
const shared = this.#sharedFacet(facet);
|
|
118
131
|
if (shared !== undefined)
|
|
119
132
|
target[facet] = shared;
|
|
120
133
|
}
|
|
121
134
|
#sharedFacet(facet) {
|
|
135
|
+
const internals = this.#sheet[INTERNAL];
|
|
122
136
|
let first;
|
|
123
137
|
let firstKey;
|
|
124
138
|
let seen = 0;
|
|
125
139
|
for (let row = this.top; row <= this.bottom; row++) {
|
|
126
140
|
for (let col = this.left; col <= this.right; col++) {
|
|
127
|
-
const value =
|
|
128
|
-
? this.#sheet.getCell(encodeAddress(col, row))[facet]
|
|
129
|
-
: undefined;
|
|
141
|
+
const value = internals.peekCell(row, col)?.[facet];
|
|
130
142
|
const key = facetKey(value);
|
|
131
143
|
if (seen === 0) {
|
|
132
144
|
first = value;
|
|
@@ -152,24 +164,16 @@ function facetKey(value) {
|
|
|
152
164
|
? Object.fromEntries(Object.entries(inner).sort(([a], [b]) => (a < b ? -1 : 1)))
|
|
153
165
|
: inner);
|
|
154
166
|
}
|
|
155
|
-
function checkBound(axis, value, max) {
|
|
156
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
157
|
-
throw new RangeError(`${axis} ${value} is out of bounds: ${axis}s start at 1`);
|
|
158
|
-
}
|
|
159
|
-
if (value > max) {
|
|
160
|
-
throw new RangeError(`${axis} ${value} is out of bounds: the sheet ends at ${max}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
167
|
export function rangeFrom(sheet, reference) {
|
|
164
168
|
const { top, left, bottom, right, sheetName, dimensions } = decodeRange(reference);
|
|
165
169
|
if (sheetName !== undefined && sheetName.toLowerCase() !== sheet.name.toLowerCase()) {
|
|
166
|
-
throw new SyntaxError(
|
|
170
|
+
throw new SyntaxError(`${quoted(reference)} names worksheet ${quoted(sheetName)}, not ${quoted(sheet.name)}: a range belongs to the sheet it came from`);
|
|
167
171
|
}
|
|
168
172
|
if (top === undefined || bottom === undefined) {
|
|
169
|
-
throw new SyntaxError(
|
|
173
|
+
throw new SyntaxError(`${quoted(reference)} spans whole columns (${dimensions}): style them through getColumn(n), which says the same thing in one attribute instead of ${MAX_ROW} cells`);
|
|
170
174
|
}
|
|
171
175
|
if (left === undefined || right === undefined) {
|
|
172
|
-
throw new SyntaxError(
|
|
176
|
+
throw new SyntaxError(`${quoted(reference)} spans whole rows (${dimensions}): style them through getRow(n), which says the same thing in one attribute instead of ${MAX_COLUMN} cells`);
|
|
173
177
|
}
|
|
174
178
|
return new Range(sheet, top, left, bottom, right);
|
|
175
179
|
}
|
package/dist/core/row-input.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthoringError } from '../errors.js';
|
|
1
|
+
import { AuthoringError, quoted } from '../errors.js';
|
|
2
2
|
import { Cell } from './cell.js';
|
|
3
3
|
export function positionalPlacements(values) {
|
|
4
4
|
const placements = [];
|
|
@@ -27,5 +27,5 @@ function columnIndexByKey(columns, key) {
|
|
|
27
27
|
if (properties.key === key)
|
|
28
28
|
return index;
|
|
29
29
|
}
|
|
30
|
-
throw new AuthoringError(`no column is keyed ${
|
|
30
|
+
throw new AuthoringError(`no column is keyed ${quoted(key)}: set getColumn(n).key first`);
|
|
31
31
|
}
|
package/dist/core/row.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class Row extends AxisHandle<RowProperties> {
|
|
|
19
19
|
get properties(): Readonly<RowProperties> | undefined;
|
|
20
20
|
protected propertiesOf(): RowProperties | undefined;
|
|
21
21
|
protected ensureProperties(): RowProperties;
|
|
22
|
+
protected dropProperties(): void;
|
|
22
23
|
/**
|
|
23
24
|
* Row height in points; `undefined` leaves the sheet default in force.
|
|
24
25
|
*
|
package/dist/core/row.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertRowInBounds, columnToNumber
|
|
1
|
+
import { assertColumnInBounds, assertRowInBounds, columnToNumber } from './address.js';
|
|
2
2
|
import { AxisHandle } from './axis-handle.js';
|
|
3
3
|
import { INTERNAL } from './internal.js';
|
|
4
4
|
export class Row extends AxisHandle {
|
|
@@ -19,6 +19,9 @@ export class Row extends AxisHandle {
|
|
|
19
19
|
ensureProperties() {
|
|
20
20
|
return this.#sheet[INTERNAL].ensureRowProperties(this.number);
|
|
21
21
|
}
|
|
22
|
+
dropProperties() {
|
|
23
|
+
this.#sheet[INTERNAL].dropRowProperties(this.number);
|
|
24
|
+
}
|
|
22
25
|
get height() {
|
|
23
26
|
return this.read('height');
|
|
24
27
|
}
|
|
@@ -51,7 +54,8 @@ export class Row extends AxisHandle {
|
|
|
51
54
|
}
|
|
52
55
|
getCell(column) {
|
|
53
56
|
const index = typeof column === 'number' ? column : columnToNumber(column);
|
|
54
|
-
|
|
57
|
+
assertColumnInBounds(index);
|
|
58
|
+
return this.#sheet[INTERNAL].masterAt(this.number, index);
|
|
55
59
|
}
|
|
56
60
|
get cells() {
|
|
57
61
|
return this.#sheet[INTERNAL].rowCells(this.number);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A structured reference to one column of a table, spelled as Excel spells it: `Table[Column]` for a
|
|
3
|
+
* plain name, and `Table[[Column]]` for a name holding a character from Microsoft's special list, with
|
|
4
|
+
* `[`, `]`, `#`, `'` and `@` escaped by a leading `'`. Interpolating the name bare wrote
|
|
5
|
+
* `SUBTOTAL(109,T[Price [USD]])`, a formula whose brackets close in the middle of the name.
|
|
6
|
+
*/
|
|
7
|
+
export declare function structuredColumnReference(tableName: string, columnName: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const STRUCTURED_REFERENCE_SPECIAL = /[\t\n\r,:.[\]#'"{}$^&*+=></@\\!()%?`;~_-]/;
|
|
2
|
+
const STRUCTURED_REFERENCE_ESCAPED = /[[\]#'@]/g;
|
|
3
|
+
export function structuredColumnReference(tableName, columnName) {
|
|
4
|
+
if (!STRUCTURED_REFERENCE_SPECIAL.test(columnName))
|
|
5
|
+
return `${tableName}[${columnName}]`;
|
|
6
|
+
return `${tableName}[[${columnName.replace(STRUCTURED_REFERENCE_ESCAPED, "'$&")}]]`;
|
|
7
|
+
}
|