@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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { MAX_COLUMN, MAX_ROW, numberToColumn, tryColumnToNumber } from './address.js';
|
|
2
|
+
import { scanFormula, skipOpaque } from './formula-scan.js';
|
|
3
|
+
import { REF_ERROR } from './value.js';
|
|
4
|
+
const SHIFTABLE_REFERENCE = /(?<![A-Za-z0-9_.])(?:(\$?)([A-Z]{1,3}):(\$?)([A-Z]{1,3})|(\$?)([0-9]{1,7}):(\$?)([0-9]{1,7})|(\$?)([A-Z]{1,3})(\$?)([0-9]{1,7}))(?![A-Za-z0-9_.!(])/g;
|
|
5
|
+
export function translateFormula(formula, colDelta, rowDelta) {
|
|
6
|
+
if (colDelta === 0 && rowDelta === 0)
|
|
7
|
+
return formula;
|
|
8
|
+
const column = (anchor, letters) => {
|
|
9
|
+
const decoded = tryColumnToNumber(letters);
|
|
10
|
+
if (decoded === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
const col = anchor === '$' ? decoded : decoded + colDelta;
|
|
13
|
+
return col < 1 || col > MAX_COLUMN ? undefined : `${anchor}${numberToColumn(col)}`;
|
|
14
|
+
};
|
|
15
|
+
const row = (anchor, digits) => {
|
|
16
|
+
const index = anchor === '$' ? Number(digits) : Number(digits) + rowDelta;
|
|
17
|
+
return index < 1 || index > MAX_ROW ? undefined : `${anchor}${index}`;
|
|
18
|
+
};
|
|
19
|
+
const range = (first, last) => first === undefined || last === undefined ? REF_ERROR : `${first}:${last}`;
|
|
20
|
+
return scanFormula(formula, (code) => code.replace(SHIFTABLE_REFERENCE, (_match, firstColumnAnchor, firstColumn, lastColumnAnchor, lastColumn, firstRowAnchor, firstRow, lastRowAnchor, lastRow, cellColumnAnchor, cellColumn, cellRowAnchor, cellRow) => {
|
|
21
|
+
if (firstColumn !== undefined && lastColumn !== undefined) {
|
|
22
|
+
return range(column(firstColumnAnchor ?? '', firstColumn), column(lastColumnAnchor ?? '', lastColumn));
|
|
23
|
+
}
|
|
24
|
+
if (firstRow !== undefined && lastRow !== undefined) {
|
|
25
|
+
return range(row(firstRowAnchor ?? '', firstRow), row(lastRowAnchor ?? '', lastRow));
|
|
26
|
+
}
|
|
27
|
+
const shiftedColumn = column(cellColumnAnchor ?? '', cellColumn ?? '');
|
|
28
|
+
const shiftedRow = row(cellRowAnchor ?? '', cellRow ?? '');
|
|
29
|
+
return shiftedColumn === undefined || shiftedRow === undefined
|
|
30
|
+
? REF_ERROR
|
|
31
|
+
: `${shiftedColumn}${shiftedRow}`;
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
const REFERENCE = /(?:(\$?)([A-Z]{1,3})(\$?)(\d{1,7}):(\$?)([A-Z]{1,3})(\$?)(\d{1,7})|(\$?)([A-Z]{1,3})(\$?)(\d{1,7})|(\$?)([A-Z]{1,3}):(\$?)([A-Z]{1,3})|(\$?)(\d{1,7}):(\$?)(\d{1,7}))(?![\p{L}\p{N}_.(![])/uy;
|
|
35
|
+
const NAME_CHAR = /[\p{L}\p{N}_.\\]/u;
|
|
36
|
+
export function spliceFormula(formula, home, edit) {
|
|
37
|
+
const target = edit.sheet.toLowerCase();
|
|
38
|
+
const homeIsTarget = home !== undefined && home.toLowerCase() === target;
|
|
39
|
+
let out = '';
|
|
40
|
+
let i = 0;
|
|
41
|
+
const n = formula.length;
|
|
42
|
+
const atTokenStart = (index) => index === 0 || !NAME_CHAR.test(formula[index - 1] ?? '');
|
|
43
|
+
const reference = (moves) => {
|
|
44
|
+
REFERENCE.lastIndex = i;
|
|
45
|
+
const match = REFERENCE.exec(formula);
|
|
46
|
+
if (match === null)
|
|
47
|
+
return false;
|
|
48
|
+
out += moves ? moved(match, edit.splice) : match[0];
|
|
49
|
+
i = REFERENCE.lastIndex;
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
while (i < n) {
|
|
53
|
+
const ch = formula[i] ?? '';
|
|
54
|
+
if (ch === '"') {
|
|
55
|
+
const end = skipOpaque(formula, i);
|
|
56
|
+
out += formula.slice(i, end);
|
|
57
|
+
i = end;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (ch === '[') {
|
|
61
|
+
const end = skipOpaque(formula, i);
|
|
62
|
+
out += formula.slice(i, end);
|
|
63
|
+
i = end;
|
|
64
|
+
const name = nameRun(formula, i);
|
|
65
|
+
if (name > i && formula[name] === '!') {
|
|
66
|
+
out += formula.slice(i, name + 1);
|
|
67
|
+
i = name + 1;
|
|
68
|
+
reference(false);
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === "'") {
|
|
73
|
+
const end = skipOpaque(formula, i);
|
|
74
|
+
const quoted = formula.slice(i + 1, end - 1).replaceAll("''", "'");
|
|
75
|
+
out += formula.slice(i, end);
|
|
76
|
+
i = end;
|
|
77
|
+
if (formula[i] !== '!')
|
|
78
|
+
continue;
|
|
79
|
+
out += '!';
|
|
80
|
+
i += 1;
|
|
81
|
+
reference(!quoted.includes(':') && !quoted.includes('[') && quoted.toLowerCase() === target);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (atTokenStart(i) && NAME_CHAR.test(ch)) {
|
|
85
|
+
const end = nameRun(formula, i);
|
|
86
|
+
const next = formula[end];
|
|
87
|
+
if (next === '!') {
|
|
88
|
+
out += formula.slice(i, end + 1);
|
|
89
|
+
const name = formula.slice(i, end);
|
|
90
|
+
i = end + 1;
|
|
91
|
+
reference(name.toLowerCase() === target);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (next === ':') {
|
|
95
|
+
const last = nameRun(formula, end + 1);
|
|
96
|
+
if (last > end + 1 && formula[last] === '!') {
|
|
97
|
+
out += formula.slice(i, last + 1);
|
|
98
|
+
i = last + 1;
|
|
99
|
+
reference(false);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (reference(homeIsTarget))
|
|
104
|
+
continue;
|
|
105
|
+
out += formula.slice(i, end);
|
|
106
|
+
i = end;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (atTokenStart(i) && ch === '$' && reference(homeIsTarget))
|
|
110
|
+
continue;
|
|
111
|
+
out += ch;
|
|
112
|
+
i += 1;
|
|
113
|
+
}
|
|
114
|
+
return out === formula ? formula : out;
|
|
115
|
+
}
|
|
116
|
+
function nameRun(formula, start) {
|
|
117
|
+
let j = start;
|
|
118
|
+
while (j < formula.length && NAME_CHAR.test(formula[j] ?? ''))
|
|
119
|
+
j += 1;
|
|
120
|
+
return j;
|
|
121
|
+
}
|
|
122
|
+
function moved(match, splice) {
|
|
123
|
+
const g = (index) => match[index] ?? '';
|
|
124
|
+
const ends = match[2] !== undefined
|
|
125
|
+
? [
|
|
126
|
+
[g(1), g(2), g(3), g(4)],
|
|
127
|
+
[g(5), g(6), g(7), g(8)],
|
|
128
|
+
]
|
|
129
|
+
: match[10] !== undefined
|
|
130
|
+
? [[g(9), g(10), g(11), g(12)]]
|
|
131
|
+
: match[14] !== undefined
|
|
132
|
+
? [
|
|
133
|
+
[g(13), g(14), '', ''],
|
|
134
|
+
[g(15), g(16), '', ''],
|
|
135
|
+
]
|
|
136
|
+
: [
|
|
137
|
+
['', '', g(17), g(18)],
|
|
138
|
+
['', '', g(19), g(20)],
|
|
139
|
+
];
|
|
140
|
+
const rows = splice.axis === 'row';
|
|
141
|
+
const lines = ends.map(([, letters = '', , digits = '']) => (rows ? digits : letters));
|
|
142
|
+
if (lines.includes(''))
|
|
143
|
+
return match[0];
|
|
144
|
+
const [first = 0, last = first] = lines.map((line) => (rows ? Number(line) : columnOf(line)));
|
|
145
|
+
const edges = movedEdges(Math.min(first, last), Math.max(first, last), splice, rows ? MAX_ROW : MAX_COLUMN);
|
|
146
|
+
if (edges === undefined)
|
|
147
|
+
return REF_ERROR;
|
|
148
|
+
const placed = first <= last ? edges : [edges[1], edges[0]];
|
|
149
|
+
return ends
|
|
150
|
+
.map(([columnAnchor = '', letters = '', rowAnchor = '', digits = ''], i) => {
|
|
151
|
+
const at = placed[i] ?? 0;
|
|
152
|
+
return rows
|
|
153
|
+
? `${columnAnchor}${letters}${rowAnchor}${at}`
|
|
154
|
+
: `${columnAnchor}${numberToColumn(at)}${rowAnchor}${digits}`;
|
|
155
|
+
})
|
|
156
|
+
.join(':');
|
|
157
|
+
}
|
|
158
|
+
function columnOf(letters) {
|
|
159
|
+
return tryColumnToNumber(letters) ?? MAX_COLUMN + 1;
|
|
160
|
+
}
|
|
161
|
+
function movedEdges(lo, hi, splice, bound) {
|
|
162
|
+
if (hi > bound)
|
|
163
|
+
return [lo, hi];
|
|
164
|
+
const { start, count, delta } = splice;
|
|
165
|
+
const end = start + count;
|
|
166
|
+
if (lo >= start && hi < end)
|
|
167
|
+
return undefined;
|
|
168
|
+
const top = lo < start ? lo : lo < end ? end + delta : lo + delta;
|
|
169
|
+
const bottom = hi < start ? hi : hi < end ? start - 1 : hi + delta;
|
|
170
|
+
if (top > bound)
|
|
171
|
+
return undefined;
|
|
172
|
+
return [top, Math.min(bottom, bound)];
|
|
173
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function skipOpaque(formula, index) {
|
|
2
|
+
const opener = formula[index];
|
|
3
|
+
const n = formula.length;
|
|
4
|
+
if (opener === '"' || opener === "'") {
|
|
5
|
+
let j = index + 1;
|
|
6
|
+
while (j < n) {
|
|
7
|
+
if (formula[j] === opener) {
|
|
8
|
+
if (formula[j + 1] === opener) {
|
|
9
|
+
j += 2;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
return j + 1;
|
|
13
|
+
}
|
|
14
|
+
j += 1;
|
|
15
|
+
}
|
|
16
|
+
return n;
|
|
17
|
+
}
|
|
18
|
+
if (opener === '[') {
|
|
19
|
+
let depth = 0;
|
|
20
|
+
let j = index;
|
|
21
|
+
while (j < n) {
|
|
22
|
+
const ch = formula[j];
|
|
23
|
+
if (ch === "'") {
|
|
24
|
+
j += 2;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (ch === '[')
|
|
28
|
+
depth += 1;
|
|
29
|
+
else if (ch === ']') {
|
|
30
|
+
depth -= 1;
|
|
31
|
+
if (depth === 0)
|
|
32
|
+
return j + 1;
|
|
33
|
+
}
|
|
34
|
+
j += 1;
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
return index;
|
|
39
|
+
}
|
|
40
|
+
export function scanFormula(formula, transform) {
|
|
41
|
+
let out = '';
|
|
42
|
+
let codeStart = 0;
|
|
43
|
+
let i = 0;
|
|
44
|
+
const n = formula.length;
|
|
45
|
+
while (i < n) {
|
|
46
|
+
const past = skipOpaque(formula, i);
|
|
47
|
+
if (past > i) {
|
|
48
|
+
out += transform(formula.slice(codeStart, i));
|
|
49
|
+
out += formula.slice(i, past);
|
|
50
|
+
i = past;
|
|
51
|
+
codeStart = past;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
i += 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out + transform(formula.slice(codeStart));
|
|
58
|
+
}
|
package/dist/core/formula.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Quote a sheet name for use in a reference exactly when Excel would: a name that is not a plain
|
|
3
|
-
* identifier, or that would read as a
|
|
3
|
+
* identifier, or that would read as a reference, 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.
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function quoteSheetName(name: string, last?: string): string;
|
|
13
13
|
/**
|
|
14
|
-
* Prefix every
|
|
14
|
+
* Prefix every future function called by its plain name with the prefix Excel stores it under
|
|
15
|
+
* (`_xlfn.`, or `_xlfn._xlws.` for the worksheet-only FILTER, SORT and PY) so Excel accepts the stored
|
|
15
16
|
* formula. Names already prefixed are left alone (never doubled), unknown/legacy functions pass
|
|
16
17
|
* through untouched, and opaque regions (string literals, sheet names, structured references) are
|
|
17
18
|
* preserved verbatim. No other rewriting occurs: in particular no `@` implicit-intersection operator
|
|
@@ -19,31 +20,89 @@ export declare function quoteSheetName(name: string, last?: string): string;
|
|
|
19
20
|
*/
|
|
20
21
|
export declare function mangleFunctions(formula: string): string;
|
|
21
22
|
/**
|
|
22
|
-
* Strip the `_xlfn.` function prefix
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Strip the `_xlfn.` function prefix (with the `_xlws.` a worksheet-only function adds after it), the
|
|
24
|
+
* `_xlpm.` LET-parameter prefix and the `_xleta.` prefix of a function passed as a value back to the
|
|
25
|
+
* plain names, so the model holds the readable form regardless of how a file stored it. Opaque regions
|
|
26
|
+
* (string literals, sheet names, structured references) are left untouched.
|
|
27
|
+
*
|
|
28
|
+
* `definedNames` is every name the workbook defines, whatever its scope, as {@link definedNameKeys}
|
|
29
|
+
* spells them. A function passed as a value keeps its prefix when one of them is spelled like it,
|
|
30
|
+
* since the bare name could then read as that name. Excel's own formula text does the same: with a
|
|
31
|
+
* `LEN` defined only on another sheet, `Range.Formula` still reports `MAP(A1:A3,_xleta.LEN)`.
|
|
25
32
|
*/
|
|
26
|
-
export declare function unmangleFunctions(formula: string): string;
|
|
33
|
+
export declare function unmangleFunctions(formula: string, definedNames: ReadonlySet<string>): string;
|
|
27
34
|
/**
|
|
28
35
|
* Prefix every LET/LAMBDA parameter identifier with `_xlpm.`, at its declaration and at each
|
|
29
36
|
* reference within the binding call's parentheses, so Excel accepts the stored formula. The prefix
|
|
30
37
|
* is lexically scoped: a name is only rewritten inside the call that binds it, opaque regions are
|
|
31
38
|
* copied verbatim, and a lambda-valued parameter used as a call (`f(…)`) is prefixed too. Formulas
|
|
32
|
-
* with no LET/LAMBDA pass through unchanged.
|
|
39
|
+
* with no LET/LAMBDA pass through unchanged. A parameter matches case-insensitively, as Excel's names
|
|
40
|
+
* do, and every reference to it is written in the spelling it was declared with, as Excel writes it.
|
|
33
41
|
*/
|
|
34
42
|
export declare function mangleParams(formula: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Prefix every built-in function a formula passes as a value with `_xleta.`, uppercased as Excel writes
|
|
45
|
+
* it, so Excel reads the function rather than a defined name spelled like it. A value is any use that
|
|
46
|
+
* is not a call: the `SUM` in `BYROW(A1:A3,SUM)`, in `LET(f,SUM,f(A1:A3))`, in `SUM+1`.
|
|
47
|
+
*
|
|
48
|
+
* `namesInScope` is {@link formulaNamesInScope} for where the formula sits, and a function name among
|
|
49
|
+
* them stays bare, because there it means the defined name. Run after {@link mangleParams}, so that a
|
|
50
|
+
* LET or LAMBDA parameter spelled like a function already carries `_xlpm.`.
|
|
51
|
+
*
|
|
52
|
+
* A name is not a value where it names a sheet (`SUM!A1`, `SUM:Other!A1`) or a table (`Rate[Amount]`),
|
|
53
|
+
* where a sheet or workbook qualifies it (`S1!SUM`), or inside an error literal: the `N` of `#N/A` is a
|
|
54
|
+
* function too.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mangleFunctionValues(formula: string, namesInScope: ReadonlySet<string>): string;
|
|
57
|
+
/**
|
|
58
|
+
* How a formula compares names: uppercased, as Excel's names are case-insensitive. The reader's form of
|
|
59
|
+
* the set {@link unmangleFunctions} takes, built from every name the workbook defines.
|
|
60
|
+
*/
|
|
61
|
+
export declare function definedNameKeys(names: readonly {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
}[]): ReadonlySet<string>;
|
|
64
|
+
/**
|
|
65
|
+
* The names a bare name in a formula resolves to, as {@link definedNameKeys} spells them: every
|
|
66
|
+
* workbook-level name, and the names scoped to `sheet`. For a cell formula `sheet` is the cell's own
|
|
67
|
+
* sheet; for a defined name's formula it is the sheet that name is scoped to, and `undefined` for a
|
|
68
|
+
* workbook-level name. A sheet is matched case-insensitively, as it is everywhere else.
|
|
69
|
+
*/
|
|
70
|
+
export declare function formulaNamesInScope(names: readonly {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly scope?: string | undefined;
|
|
73
|
+
}[], sheet: string | undefined): ReadonlySet<string>;
|
|
35
74
|
/**
|
|
36
75
|
* Mangle a model formula into its on-disk form: LET/LAMBDA parameter names first (`_xlpm.`), then the
|
|
37
|
-
* modern-function prefix (`_xlfn.`). Ordering
|
|
38
|
-
* names before the function pass qualifies them
|
|
76
|
+
* functions it passes as values (`_xleta.`), then the modern-function prefix (`_xlfn.`). Ordering
|
|
77
|
+
* matters: parameter mangling reads the plain LET/LAMBDA names before the function pass qualifies them,
|
|
78
|
+
* and a parameter spelled like a function has to carry `_xlpm.` before the value pass looks for
|
|
79
|
+
* functions. `namesInScope` is {@link formulaNamesInScope} for where the formula sits. The inverse for
|
|
80
|
+
* every prefix is unmangleFunctions.
|
|
81
|
+
*/
|
|
82
|
+
export declare function mangleFormula(formula: string, namesInScope: ReadonlySet<string>): string;
|
|
83
|
+
/**
|
|
84
|
+
* A number as it appears *inside formula text*, which is not the same serialisation as an
|
|
85
|
+
* attribute's.
|
|
86
|
+
*
|
|
87
|
+
* Two divergences, both found as drift rather than designed. The exponent's case: Excel writes
|
|
88
|
+
* `1E+21` where JavaScript writes `1e+21`, so the same literal read back through the two codecs
|
|
89
|
+
* produced two different formula strings depending on which file it came from -- exactly the
|
|
90
|
+
* asymmetry `cell-value.ts` and `cell-accumulator.ts` were extracted to prevent. And the finiteness
|
|
91
|
+
* guard: the BIFF12 decoder had a private copy of this function with none, so a `PtgNum` whose eight
|
|
92
|
+
* bytes decode to an infinity produced the formula text `INFINITY`, which the writer then escaped as
|
|
93
|
+
* ordinary text into a package Excel reports as damaged.
|
|
94
|
+
*
|
|
95
|
+
* Here rather than in `xml/xml.ts` beside the attribute form, because formula text is not XML: the
|
|
96
|
+
* BIFF12 codec produces it too, and reaching the XML serialiser for it put the whole write half of
|
|
97
|
+
* that module into the closure of an entry that has no XML in it.
|
|
98
|
+
*
|
|
99
|
+
* @throws {AuthoringError} when the value is not finite.
|
|
39
100
|
*/
|
|
40
|
-
export declare function
|
|
101
|
+
export declare function formulaNumberLiteral(value: number): string;
|
|
41
102
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* structured references are copied verbatim, and a sheet-qualified reference shifts the cell while its
|
|
47
|
-
* sheet name is untouched. Function names and defined names carry no row digits, so they pass through.
|
|
103
|
+
* Render a formula operand for serialisation: a number becomes its literal, a string is stripped of
|
|
104
|
+
* the single optional leading '=' an author may write (OOXML stores the expression without it, e.g.
|
|
105
|
+
* `=A1>0` on disk is `A1>0`). The result is unescaped: the caller escapes it for its target, whether
|
|
106
|
+
* that is element text or an attribute value.
|
|
48
107
|
*/
|
|
49
|
-
export declare function
|
|
108
|
+
export declare function stripFormulaEquals(value: string | number): string;
|
package/dist/core/formula.js
CHANGED
|
@@ -1,77 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { assertWritableNumber } from '../errors.js';
|
|
2
|
+
import { nameReadsAsReference } from './address.js';
|
|
3
|
+
import { scanFormula, skipOpaque } from './formula-scan.js';
|
|
4
|
+
import { FUNCTION_VALUE_NAMES } from './function-values.js';
|
|
5
|
+
import { FUTURE_FUNCTION_PREFIXES } from './future-functions.js';
|
|
4
6
|
const XLPM = '_xlpm.';
|
|
7
|
+
const XLETA = '_xleta.';
|
|
5
8
|
export function quoteSheetName(name, last) {
|
|
6
9
|
const names = last === undefined ? [name] : [name, last];
|
|
7
10
|
const joined = names.join(':');
|
|
8
11
|
return names.every(isBareSheetName) ? joined : `'${joined.replace(/'/g, "''")}'`;
|
|
9
12
|
}
|
|
10
13
|
function isBareSheetName(name) {
|
|
11
|
-
return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(name) &&
|
|
14
|
+
return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(name) && !nameReadsAsReference(name);
|
|
12
15
|
}
|
|
13
16
|
const SCOPING_FUNCTIONS = new Set(['LET', 'LAMBDA']);
|
|
14
17
|
const FUNCTION_CALL = /(?<![A-Za-z0-9_.])([A-Za-z_][A-Za-z0-9_.]*)(\s*\()/g;
|
|
15
|
-
const PREFIX = /_xlfn
|
|
16
|
-
|
|
17
|
-
const opener = formula[index];
|
|
18
|
-
const n = formula.length;
|
|
19
|
-
if (opener === '"' || opener === "'") {
|
|
20
|
-
let j = index + 1;
|
|
21
|
-
while (j < n) {
|
|
22
|
-
if (formula[j] === opener) {
|
|
23
|
-
if (formula[j + 1] === opener) {
|
|
24
|
-
j += 2;
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
return j + 1;
|
|
28
|
-
}
|
|
29
|
-
j += 1;
|
|
30
|
-
}
|
|
31
|
-
return n;
|
|
32
|
-
}
|
|
33
|
-
if (opener === '[') {
|
|
34
|
-
let depth = 0;
|
|
35
|
-
let j = index;
|
|
36
|
-
while (j < n) {
|
|
37
|
-
const ch = formula[j];
|
|
38
|
-
if (ch === '[')
|
|
39
|
-
depth += 1;
|
|
40
|
-
else if (ch === ']') {
|
|
41
|
-
depth -= 1;
|
|
42
|
-
if (depth === 0)
|
|
43
|
-
return j + 1;
|
|
44
|
-
}
|
|
45
|
-
j += 1;
|
|
46
|
-
}
|
|
47
|
-
return n;
|
|
48
|
-
}
|
|
49
|
-
return index;
|
|
50
|
-
}
|
|
51
|
-
function scanFormula(formula, transform) {
|
|
52
|
-
let out = '';
|
|
53
|
-
let codeStart = 0;
|
|
54
|
-
let i = 0;
|
|
55
|
-
const n = formula.length;
|
|
56
|
-
while (i < n) {
|
|
57
|
-
const past = skipOpaque(formula, i);
|
|
58
|
-
if (past > i) {
|
|
59
|
-
out += transform(formula.slice(codeStart, i));
|
|
60
|
-
out += formula.slice(i, past);
|
|
61
|
-
i = past;
|
|
62
|
-
codeStart = past;
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
i += 1;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return out + transform(formula.slice(codeStart));
|
|
69
|
-
}
|
|
18
|
+
const PREFIX = /_xlfn\.(?:_xlws\.)?|_xlpm\./g;
|
|
19
|
+
const FUNCTION_VALUE_PREFIX = /_xleta\.([A-Za-z_][A-Za-z0-9_.]*)/g;
|
|
70
20
|
export function mangleFunctions(formula) {
|
|
71
|
-
return scanFormula(formula, (code) => code.replace(FUNCTION_CALL, (whole, name, open) =>
|
|
21
|
+
return scanFormula(formula, (code) => code.replace(FUNCTION_CALL, (whole, name, open) => {
|
|
22
|
+
const prefix = FUTURE_FUNCTION_PREFIXES.get(name.toUpperCase());
|
|
23
|
+
return prefix === undefined ? whole : `${prefix}${name}${open}`;
|
|
24
|
+
}));
|
|
72
25
|
}
|
|
73
|
-
export function unmangleFunctions(formula) {
|
|
74
|
-
return scanFormula(formula, (code) => code
|
|
26
|
+
export function unmangleFunctions(formula, definedNames) {
|
|
27
|
+
return scanFormula(formula, (code) => code
|
|
28
|
+
.replace(PREFIX, '')
|
|
29
|
+
.replace(FUNCTION_VALUE_PREFIX, (whole, name) => definedNames.has(name.toUpperCase()) ? whole : name));
|
|
75
30
|
}
|
|
76
31
|
const NAME_START = /[A-Za-z_]/;
|
|
77
32
|
const NAME_CHAR = /[A-Za-z0-9_.]/;
|
|
@@ -134,13 +89,13 @@ function boundName(formula, [start, end]) {
|
|
|
134
89
|
return name.startsWith(XLPM) ? undefined : name;
|
|
135
90
|
}
|
|
136
91
|
function parameterNames(formula, keyword, args) {
|
|
137
|
-
const names = new
|
|
92
|
+
const names = new Map();
|
|
138
93
|
const isLambda = keyword === 'LAMBDA';
|
|
139
94
|
for (let a = 0; a < args.length - 1; a += 1) {
|
|
140
95
|
if (isLambda || a % 2 === 0) {
|
|
141
96
|
const name = boundName(formula, args[a]);
|
|
142
|
-
if (name !== undefined)
|
|
143
|
-
names.
|
|
97
|
+
if (name !== undefined && !names.has(name.toUpperCase()))
|
|
98
|
+
names.set(name.toUpperCase(), name);
|
|
144
99
|
}
|
|
145
100
|
}
|
|
146
101
|
return names;
|
|
@@ -150,7 +105,15 @@ export function mangleParams(formula) {
|
|
|
150
105
|
let i = 0;
|
|
151
106
|
const n = formula.length;
|
|
152
107
|
const frames = [];
|
|
153
|
-
const
|
|
108
|
+
const declared = (name) => {
|
|
109
|
+
const key = name.toUpperCase();
|
|
110
|
+
for (let f = frames.length - 1; f >= 0; f -= 1) {
|
|
111
|
+
const spelling = frames[f]?.names.get(key);
|
|
112
|
+
if (spelling !== undefined)
|
|
113
|
+
return spelling;
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
};
|
|
154
117
|
while (i < n) {
|
|
155
118
|
const top = frames[frames.length - 1];
|
|
156
119
|
if (top !== undefined && i >= top.end) {
|
|
@@ -175,28 +138,72 @@ export function mangleParams(formula) {
|
|
|
175
138
|
while (k < n && WHITESPACE.test(formula[k] ?? ''))
|
|
176
139
|
k += 1;
|
|
177
140
|
const heads = formula[k] === '(';
|
|
178
|
-
if (heads && SCOPING_FUNCTIONS.has(name.toUpperCase()) &&
|
|
141
|
+
if (heads && SCOPING_FUNCTIONS.has(name.toUpperCase()) && declared(name) === undefined) {
|
|
179
142
|
const { close, args } = parseCall(formula, k);
|
|
180
143
|
out += formula.slice(i, k + 1);
|
|
181
144
|
frames.push({ end: close, names: parameterNames(formula, name.toUpperCase(), args) });
|
|
182
145
|
i = k + 1;
|
|
183
146
|
continue;
|
|
184
147
|
}
|
|
185
|
-
|
|
148
|
+
const spelling = declared(name);
|
|
149
|
+
out += spelling === undefined ? name : `${XLPM}${spelling}`;
|
|
186
150
|
i = nameEnd;
|
|
187
151
|
}
|
|
188
152
|
return out;
|
|
189
153
|
}
|
|
190
|
-
export function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
154
|
+
export function mangleFunctionValues(formula, namesInScope) {
|
|
155
|
+
let out = '';
|
|
156
|
+
let i = 0;
|
|
157
|
+
while (i < formula.length) {
|
|
158
|
+
const past = skipOpaque(formula, i);
|
|
159
|
+
if (past > i) {
|
|
160
|
+
out += formula.slice(i, past);
|
|
161
|
+
i = past;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const end = readName(formula, i);
|
|
165
|
+
if (end === i) {
|
|
166
|
+
out += formula.charAt(i);
|
|
167
|
+
i += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const name = formula.slice(i, end);
|
|
171
|
+
out += isFunctionValue(formula, i, end, namesInScope) ? `${XLETA}${name.toUpperCase()}` : name;
|
|
172
|
+
i = end;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
const QUALIFIES_NAME = /[A-Za-z0-9_.!#\]]/;
|
|
177
|
+
const NAMES_A_CONTAINER = /[!:[]/;
|
|
178
|
+
function isFunctionValue(formula, start, end, namesInScope) {
|
|
179
|
+
const name = formula.slice(start, end).toUpperCase();
|
|
180
|
+
if (!FUNCTION_VALUE_NAMES.has(name) || namesInScope.has(name))
|
|
181
|
+
return false;
|
|
182
|
+
if (QUALIFIES_NAME.test(formula.charAt(start - 1)))
|
|
183
|
+
return false;
|
|
184
|
+
if (NAMES_A_CONTAINER.test(formula.charAt(end)))
|
|
185
|
+
return false;
|
|
186
|
+
let next = end;
|
|
187
|
+
while (WHITESPACE.test(formula.charAt(next)))
|
|
188
|
+
next += 1;
|
|
189
|
+
return formula.charAt(next) !== '(';
|
|
190
|
+
}
|
|
191
|
+
export function definedNameKeys(names) {
|
|
192
|
+
return new Set(names.map(({ name }) => name.toUpperCase()));
|
|
193
|
+
}
|
|
194
|
+
export function formulaNamesInScope(names, sheet) {
|
|
195
|
+
const wanted = sheet?.toLowerCase();
|
|
196
|
+
return definedNameKeys(names.filter(({ scope }) => scope === undefined || scope.toLowerCase() === wanted));
|
|
197
|
+
}
|
|
198
|
+
export function mangleFormula(formula, namesInScope) {
|
|
199
|
+
return mangleFunctions(mangleFunctionValues(mangleParams(formula), namesInScope));
|
|
200
|
+
}
|
|
201
|
+
export function formulaNumberLiteral(value) {
|
|
202
|
+
assertWritableNumber(value);
|
|
203
|
+
return String(value).toUpperCase();
|
|
204
|
+
}
|
|
205
|
+
export function stripFormulaEquals(value) {
|
|
206
|
+
if (typeof value === 'number')
|
|
207
|
+
return formulaNumberLiteral(value);
|
|
208
|
+
return value.startsWith('=') ? value.slice(1) : value;
|
|
202
209
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const NOT_VALUES: ReadonlySet<string>;
|
|
2
|
+
export declare const OLDER_FUNCTION_VALUES: readonly string[];
|
|
3
|
+
/** Every built-in function Excel stores under `_xleta.` when a formula passes it as a value. */
|
|
4
|
+
export declare const FUNCTION_VALUE_NAMES: ReadonlySet<string>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { FUTURE_FUNCTION_PREFIXES } from './future-functions.js';
|
|
2
|
+
const OLDER_FUNCTIONS = `
|
|
3
|
+
ABS ACCRINT ACCRINTM ACOS ACOSH ADDRESS AMORDEGRC AMORLINC AND AREAS ASC ASIN ASINH ATAN ATAN2 ATANH
|
|
4
|
+
AVEDEV AVERAGE AVERAGEA AVERAGEIF AVERAGEIFS
|
|
5
|
+
BAHTTEXT BESSELI BESSELJ BESSELK BESSELY BETADIST BETAINV BIN2DEC BIN2HEX BIN2OCT BINOMDIST
|
|
6
|
+
CEILING CELL CHAR CHIDIST CHIINV CHITEST CHOOSE CLEAN CODE COLUMN COLUMNS COMBIN COMPLEX CONCATENATE
|
|
7
|
+
CONFIDENCE CONVERT CORREL COS COSH COUNT COUNTA COUNTBLANK COUNTIF COUNTIFS COUPDAYBS COUPDAYS
|
|
8
|
+
COUPDAYSNC COUPNCD COUPNUM COUPPCD COVAR CRITBINOM CUBEKPIMEMBER CUBEMEMBER CUBEMEMBERPROPERTY
|
|
9
|
+
CUBERANKEDMEMBER CUBESET CUBESETCOUNT CUBEVALUE CUMIPMT CUMPRINC
|
|
10
|
+
DATE DATEDIF DATESTRING DATEVALUE DAVERAGE DAY DAYS360 DB DBCS DCOUNT DCOUNTA DDB DEC2BIN DEC2HEX
|
|
11
|
+
DEC2OCT DEGREES DELTA DEVSQ DGET DISC DMAX DMIN DOLLAR DOLLARDE DOLLARFR DPRODUCT DSTDEV DSTDEVP DSUM
|
|
12
|
+
DURATION DVAR DVARP
|
|
13
|
+
EDATE EFFECT EOMONTH ERF ERFC ERROR.TYPE EVEN EXACT EXP EXPONDIST
|
|
14
|
+
FACT FACTDOUBLE FDIST FIND FINDB FINV FISHER FISHERINV FIXED FLOOR FORECAST FREQUENCY FTEST FV
|
|
15
|
+
FVSCHEDULE
|
|
16
|
+
GAMMADIST GAMMAINV GAMMALN GCD GEOMEAN GESTEP GETPIVOTDATA GROWTH
|
|
17
|
+
HARMEAN HEX2BIN HEX2DEC HEX2OCT HLOOKUP HOUR HYPERLINK HYPGEOMDIST
|
|
18
|
+
IF IFERROR IMABS IMAGINARY IMARGUMENT IMCONJUGATE IMCOS IMDIV IMEXP IMLN IMLOG10 IMLOG2 IMPOWER
|
|
19
|
+
IMPRODUCT IMREAL IMSIN IMSQRT IMSUB IMSUM INDEX INDIRECT INFO INT INTERCEPT INTRATE IPMT IRR ISBLANK
|
|
20
|
+
ISERR ISERROR ISEVEN ISLOGICAL ISNA ISNONTEXT ISNUMBER ISODD ISPMT ISREF ISTEXT ISTHAIDIGIT
|
|
21
|
+
KURT
|
|
22
|
+
LARGE LCM LEFT LEFTB LEN LENB LINEST LN LOG LOGEST LOGINV LOGNORMDIST LOOKUP LOWER
|
|
23
|
+
MATCH MAX MAXA MDETERM MDURATION MEDIAN MID MIDB MIN MINA MINUTE MINVERSE MIRR MMULT MOD MODE MONTH
|
|
24
|
+
MROUND MULTINOMIAL
|
|
25
|
+
N NA NEGBINOMDIST NETWORKDAYS NOMINAL NORMDIST NORMINV NORMSDIST NORMSINV NOT NOW NPER NPV
|
|
26
|
+
NUMBERSTRING
|
|
27
|
+
OCT2BIN OCT2DEC OCT2HEX ODD ODDFPRICE ODDFYIELD ODDLPRICE ODDLYIELD OFFSET OR
|
|
28
|
+
PEARSON PERCENTILE PERCENTRANK PERMUT PHONETIC PI PMT POISSON POWER PPMT PRICE PRICEDISC PRICEMAT
|
|
29
|
+
PROB PRODUCT PROPER PV
|
|
30
|
+
QUARTILE QUOTIENT
|
|
31
|
+
RADIANS RAND RANDBETWEEN RANK RATE RECEIVED REPLACE REPLACEB REPT RIGHT RIGHTB ROMAN ROUND
|
|
32
|
+
ROUNDBAHTDOWN ROUNDBAHTUP ROUNDDOWN ROUNDUP ROW ROWS RSQ RTD
|
|
33
|
+
SEARCH SEARCHB SECOND SERIES SERIESSUM SIGN SIN SINH SKEW SLN SLOPE SMALL SQRT SQRTPI STANDARDIZE
|
|
34
|
+
STDEV STDEVA STDEVP STDEVPA STEYX SUBSTITUTE SUBTOTAL SUM SUMIF SUMIFS SUMPRODUCT SUMSQ SUMX2MY2
|
|
35
|
+
SUMX2PY2 SUMXMY2 SYD
|
|
36
|
+
T TAN TANH TBILLEQ TBILLPRICE TBILLYIELD TDIST TEXT THAIDAYOFWEEK THAIDIGIT THAIMONTHOFYEAR
|
|
37
|
+
THAINUMSOUND THAINUMSTRING THAISTRINGLENGTH THAIYEAR TIME TIMEVALUE TINV TODAY TRANSPOSE TREND TRIM
|
|
38
|
+
TRIMMEAN TRUNC TTEST TYPE
|
|
39
|
+
UPPER USDOLLAR
|
|
40
|
+
VALUE VAR VARA VARP VARPA VDB VLOOKUP
|
|
41
|
+
WEEKDAY WEEKNUM WEIBULL WORKDAY
|
|
42
|
+
XIRR XNPV
|
|
43
|
+
YEAR YEARFRAC YIELD YIELDDISC YIELDMAT
|
|
44
|
+
ZTEST
|
|
45
|
+
`;
|
|
46
|
+
export const NOT_VALUES = new Set([
|
|
47
|
+
'LAMBDA',
|
|
48
|
+
'LET',
|
|
49
|
+
'PYTHON_STR',
|
|
50
|
+
'PYTHON_TYPE',
|
|
51
|
+
'PYTHON_TYPENAME',
|
|
52
|
+
'QUERYSTRING',
|
|
53
|
+
]);
|
|
54
|
+
export const OLDER_FUNCTION_VALUES = OLDER_FUNCTIONS.trim().split(/\s+/);
|
|
55
|
+
export const FUNCTION_VALUE_NAMES = new Set([
|
|
56
|
+
...OLDER_FUNCTION_VALUES,
|
|
57
|
+
...[...FUTURE_FUNCTION_PREFIXES.keys()].filter((name) => !NOT_VALUES.has(name)),
|
|
58
|
+
]);
|