@shbernal/ts-xlsx 3.1.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.
Files changed (214) hide show
  1. package/README.md +30 -23
  2. package/dist/bytes.d.ts +3 -2
  3. package/dist/core/address.d.ts +36 -0
  4. package/dist/core/address.js +47 -9
  5. package/dist/core/array-formula-ranges.d.ts +33 -0
  6. package/dist/core/array-formula-ranges.js +62 -0
  7. package/dist/core/color-resolution.js +2 -2
  8. package/dist/core/column.js +3 -2
  9. package/dist/core/comment-thread.d.ts +7 -3
  10. package/dist/core/conditional-formatting-overlay.d.ts +9 -0
  11. package/dist/core/conditional-formatting-overlay.js +28 -1
  12. package/dist/core/conditional-formatting.d.ts +98 -10
  13. package/dist/core/conditional-formatting.js +42 -1
  14. package/dist/core/data-validation-overlay.d.ts +5 -0
  15. package/dist/core/data-validation-overlay.js +15 -0
  16. package/dist/core/data-validation.d.ts +16 -0
  17. package/dist/core/data-validation.js +15 -0
  18. package/dist/core/date-format.js +14 -4
  19. package/dist/core/date.js +1 -1
  20. package/dist/core/formula-references.d.ts +26 -0
  21. package/dist/core/formula-references.js +173 -0
  22. package/dist/core/formula-scan.d.ts +2 -0
  23. package/dist/core/formula-scan.js +58 -0
  24. package/dist/core/formula.d.ts +51 -18
  25. package/dist/core/formula.js +77 -86
  26. package/dist/core/function-values.d.ts +4 -0
  27. package/dist/core/function-values.js +58 -0
  28. package/dist/core/future-functions.d.ts +3 -0
  29. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  30. package/dist/core/grid-edits.d.ts +28 -0
  31. package/dist/core/grid-edits.js +193 -25
  32. package/dist/core/hyperlink.d.ts +39 -0
  33. package/dist/core/hyperlink.js +88 -0
  34. package/dist/core/image.d.ts +36 -5
  35. package/dist/core/image.js +9 -0
  36. package/dist/core/internal.d.ts +3 -1
  37. package/dist/core/limits.d.ts +5 -3
  38. package/dist/core/merge.d.ts +13 -0
  39. package/dist/core/merge.js +15 -1
  40. package/dist/core/page-setup.d.ts +26 -11
  41. package/dist/core/pivot-table.d.ts +49 -10
  42. package/dist/core/pivot-table.js +55 -21
  43. package/dist/core/protection.d.ts +66 -4
  44. package/dist/core/protection.js +4 -0
  45. package/dist/core/row.js +3 -2
  46. package/dist/core/structured-reference.d.ts +7 -0
  47. package/dist/core/structured-reference.js +7 -0
  48. package/dist/core/table.d.ts +38 -8
  49. package/dist/core/table.js +37 -7
  50. package/dist/core/theme.d.ts +2 -0
  51. package/dist/core/theme.js +4 -1
  52. package/dist/core/value.d.ts +51 -28
  53. package/dist/core/value.js +15 -12
  54. package/dist/core/workbook-styles.d.ts +17 -1
  55. package/dist/core/workbook-styles.js +4 -0
  56. package/dist/core/workbook-theme.js +5 -3
  57. package/dist/core/workbook.d.ts +20 -3
  58. package/dist/core/workbook.js +26 -5
  59. package/dist/core/worksheet-merges.d.ts +9 -4
  60. package/dist/core/worksheet-merges.js +17 -11
  61. package/dist/core/worksheet-model.d.ts +1 -1
  62. package/dist/core/worksheet-model.js +4 -0
  63. package/dist/core/worksheet-pictures.d.ts +3 -3
  64. package/dist/core/worksheet-pictures.js +7 -6
  65. package/dist/core/worksheet.d.ts +110 -21
  66. package/dist/core/worksheet.js +174 -49
  67. package/dist/entries/core.d.ts +7 -5
  68. package/dist/entries/core.js +4 -3
  69. package/dist/entries/errors.d.ts +1 -0
  70. package/dist/entries/errors.js +1 -0
  71. package/dist/errors.js +1 -1
  72. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  73. package/dist/io/cell-metadata/metadata.js +35 -0
  74. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  75. package/dist/io/cell-metadata/rich-values.js +80 -0
  76. package/dist/io/csv/delimiter.d.ts +14 -2
  77. package/dist/io/csv/delimiter.js +12 -0
  78. package/dist/io/csv/errors.d.ts +14 -0
  79. package/dist/io/csv/errors.js +5 -0
  80. package/dist/io/csv/read.d.ts +6 -1
  81. package/dist/io/csv/read.js +46 -24
  82. package/dist/io/csv/write.d.ts +10 -3
  83. package/dist/io/csv/write.js +16 -7
  84. package/dist/io/opc/part-paths.js +4 -4
  85. package/dist/io/opc/read-opc.d.ts +4 -0
  86. package/dist/io/opc/read-opc.js +10 -4
  87. package/dist/io/opc/strict-relationships.d.ts +2 -0
  88. package/dist/io/opc/strict-relationships.js +12 -0
  89. package/dist/io/read-policy/column-budget.d.ts +26 -0
  90. package/dist/io/{xlsx → read-policy}/column-budget.js +4 -8
  91. package/dist/io/read-policy/read-repair.d.ts +74 -0
  92. package/dist/io/read-policy/read-repair.js +119 -0
  93. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  94. package/dist/io/style/cell-style-resolution.js +30 -0
  95. package/dist/io/xlsb/formula.js +4 -2
  96. package/dist/io/xlsb/ptg-functions.js +110 -0
  97. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  98. package/dist/io/xlsb/read-metadata.js +69 -0
  99. package/dist/io/xlsb/read-worksheet.d.ts +6 -0
  100. package/dist/io/xlsb/read-worksheet.js +95 -37
  101. package/dist/io/xlsb/read.js +26 -8
  102. package/dist/io/xlsb/record-types.d.ts +13 -0
  103. package/dist/io/xlsb/record-types.js +13 -0
  104. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  105. package/dist/io/xlsb/sheet-protection.js +60 -0
  106. package/dist/io/xlsx/cell-accumulator.d.ts +20 -12
  107. package/dist/io/xlsx/cell-accumulator.js +49 -19
  108. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  109. package/dist/io/xlsx/cell-metadata.js +162 -0
  110. package/dist/io/xlsx/cell-value.d.ts +16 -4
  111. package/dist/io/xlsx/cell-value.js +38 -21
  112. package/dist/io/xlsx/color-xml.js +1 -1
  113. package/dist/io/xlsx/column-span.d.ts +11 -0
  114. package/dist/io/xlsx/column-span.js +9 -0
  115. package/dist/io/xlsx/comments.d.ts +3 -28
  116. package/dist/io/xlsx/comments.js +3 -65
  117. package/dist/io/xlsx/conditional-formatting.d.ts +42 -25
  118. package/dist/io/xlsx/conditional-formatting.js +424 -129
  119. package/dist/io/xlsx/data-validation.d.ts +12 -7
  120. package/dist/io/xlsx/data-validation.js +41 -29
  121. package/dist/io/xlsx/edit-vba.js +43 -36
  122. package/dist/io/xlsx/hyperlinks.d.ts +16 -50
  123. package/dist/io/xlsx/hyperlinks.js +8 -37
  124. package/dist/io/xlsx/images.d.ts +49 -16
  125. package/dist/io/xlsx/images.js +145 -44
  126. package/dist/io/xlsx/namespaces.d.ts +8 -0
  127. package/dist/io/xlsx/namespaces.js +4 -0
  128. package/dist/io/xlsx/package-plan.d.ts +48 -15
  129. package/dist/io/xlsx/package-plan.js +46 -9
  130. package/dist/io/xlsx/part-names.d.ts +6 -8
  131. package/dist/io/xlsx/part-names.js +3 -7
  132. package/dist/io/xlsx/pivot.d.ts +5 -5
  133. package/dist/io/xlsx/pivot.js +23 -22
  134. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  135. package/dist/io/xlsx/preserved-splices.js +74 -0
  136. package/dist/io/xlsx/read-comments.d.ts +29 -0
  137. package/dist/io/xlsx/read-comments.js +75 -0
  138. package/dist/io/xlsx/read-parts.d.ts +2 -2
  139. package/dist/io/xlsx/read-parts.js +44 -37
  140. package/dist/io/xlsx/read-pivot.js +9 -3
  141. package/dist/io/xlsx/read-rich-runs.d.ts +8 -5
  142. package/dist/io/xlsx/read-rich-runs.js +18 -1
  143. package/dist/io/xlsx/read-rows.d.ts +18 -7
  144. package/dist/io/xlsx/read-rows.js +47 -37
  145. package/dist/io/xlsx/read-styles.d.ts +0 -25
  146. package/dist/io/xlsx/read-styles.js +39 -34
  147. package/dist/io/xlsx/read-workbook-xml.d.ts +6 -0
  148. package/dist/io/xlsx/read-workbook-xml.js +9 -4
  149. package/dist/io/xlsx/read-worksheet.d.ts +8 -3
  150. package/dist/io/xlsx/read-worksheet.js +45 -23
  151. package/dist/io/xlsx/read.d.ts +0 -4
  152. package/dist/io/xlsx/read.js +17 -21
  153. package/dist/io/xlsx/relationships.d.ts +3 -0
  154. package/dist/io/xlsx/relationships.js +4 -0
  155. package/dist/io/xlsx/row-position.d.ts +3 -1
  156. package/dist/io/xlsx/row-xml.d.ts +18 -7
  157. package/dist/io/xlsx/row-xml.js +54 -35
  158. package/dist/io/xlsx/sheet-properties.js +68 -42
  159. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  160. package/dist/io/xlsx/strict-parts.js +171 -0
  161. package/dist/io/xlsx/style-elements.d.ts +0 -2
  162. package/dist/io/xlsx/style-elements.js +9 -29
  163. package/dist/io/xlsx/styles.d.ts +5 -0
  164. package/dist/io/xlsx/styles.js +11 -5
  165. package/dist/io/xlsx/tables.d.ts +10 -2
  166. package/dist/io/xlsx/tables.js +49 -42
  167. package/dist/io/xlsx/theme-xml.d.ts +6 -0
  168. package/dist/io/xlsx/theme-xml.js +16 -19
  169. package/dist/io/xlsx/threaded-comments.d.ts +6 -1
  170. package/dist/io/xlsx/threaded-comments.js +14 -7
  171. package/dist/io/xlsx/workbook-xml.d.ts +13 -24
  172. package/dist/io/xlsx/workbook-xml.js +15 -33
  173. package/dist/io/xlsx/worksheet-xml.d.ts +5 -2
  174. package/dist/io/xlsx/worksheet-xml.js +37 -69
  175. package/dist/io/xlsx/write-stream.d.ts +28 -6
  176. package/dist/io/xlsx/write-stream.js +42 -17
  177. package/dist/io/xlsx/write.d.ts +11 -4
  178. package/dist/io/xlsx/write.js +153 -90
  179. package/dist/rel-type.d.ts +9 -0
  180. package/dist/rel-type.js +3 -0
  181. package/dist/vba/bytes.d.ts +2 -0
  182. package/dist/vba/bytes.js +11 -0
  183. package/dist/vba/cfb-format.d.ts +47 -0
  184. package/dist/vba/cfb-format.js +34 -0
  185. package/dist/vba/cfb-writer.d.ts +1 -2
  186. package/dist/vba/cfb-writer.js +33 -36
  187. package/dist/vba/cfb.d.ts +9 -4
  188. package/dist/vba/cfb.js +55 -43
  189. package/dist/vba/dir-records.d.ts +6 -0
  190. package/dist/vba/dir-records.js +10 -2
  191. package/dist/vba/errors.d.ts +4 -4
  192. package/dist/vba/index.d.ts +0 -2
  193. package/dist/vba/index.js +0 -2
  194. package/dist/vba/ms-ovba.d.ts +6 -2
  195. package/dist/vba/ms-ovba.js +15 -6
  196. package/dist/vba/project-editor.js +102 -87
  197. package/dist/vba/project.d.ts +4 -0
  198. package/dist/vba/project.js +12 -6
  199. package/dist/vba/vba-encoding.d.ts +4 -10
  200. package/dist/vba/vba-encoding.js +2 -15
  201. package/dist/xml/xml-read.d.ts +29 -1
  202. package/dist/xml/xml-read.js +56 -0
  203. package/dist/xml/xml-scan.d.ts +13 -0
  204. package/dist/xml/xml-scan.js +58 -7
  205. package/dist/xml/xml.d.ts +33 -1
  206. package/dist/xml/xml.js +22 -0
  207. package/package.json +5 -4
  208. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  209. package/dist/core/modern-functions.d.ts +0 -1
  210. package/dist/io/xlsx/cell-style-resolution.d.ts +0 -25
  211. package/dist/io/xlsx/cell-style-resolution.js +0 -29
  212. package/dist/io/xlsx/column-budget.d.ts +0 -26
  213. package/dist/io/xlsx/read-repair.d.ts +0 -26
  214. package/dist/io/xlsx/read-repair.js +0 -39
@@ -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 cell address, is wrapped in single quotes with its internal
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 modern function called by its plain name with `_xlfn.` so Excel accepts the stored
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,34 +20,66 @@ 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 and the `_xlpm.` LET-parameter prefix back to the plain names,
23
- * so the model holds the readable form regardless of how a file stored it. Opaque regions (string
24
- * literals, sheet names, structured references) are left untouched.
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;
35
43
  /**
36
- * Mangle a model formula into its on-disk form: LET/LAMBDA parameter names first (`_xlpm.`), then the
37
- * modern-function prefix (`_xlfn.`). Ordering matters: parameter mangling reads the plain LET/LAMBDA
38
- * names before the function pass qualifies them. The inverse for both prefixes is unmangleFunctions.
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.
39
55
  */
40
- export declare function mangleFormula(formula: string): string;
56
+ export declare function mangleFunctionValues(formula: string, namesInScope: ReadonlySet<string>): string;
41
57
  /**
42
- * Shift every relative cell reference in a formula by `colDelta` columns and `rowDelta` rows, leaving
43
- * absolute (`$`-anchored) axes fixed. This is how a shared-formula clone recovers its own formula from
44
- * the master's: a master `A1*2` shared one row down reads back as `A2*2`, and `$A$1*B1` shared one row
45
- * and one column across as `$A$1*C2`. String literals, single-quoted sheet names, and bracketed
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.
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>;
74
+ /**
75
+ * Mangle a model formula into its on-disk form: LET/LAMBDA parameter names first (`_xlpm.`), then the
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.
48
81
  */
49
- export declare function translateFormula(formula: string, colDelta: number, rowDelta: number): string;
82
+ export declare function mangleFormula(formula: string, namesInScope: ReadonlySet<string>): string;
50
83
  /**
51
84
  * A number as it appears *inside formula text*, which is not the same serialisation as an
52
85
  * attribute's.
@@ -1,79 +1,32 @@
1
1
  import { assertWritableNumber } from '../errors.js';
2
- import { MAX_COLUMN, MAX_ROW, numberToColumn, tryColumnToNumber } from './address.js';
3
- import { MODERN_FUNCTIONS } from './modern-functions.js';
4
- import { REF_ERROR } from './value.js';
5
- const XLFN = '_xlfn.';
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';
6
6
  const XLPM = '_xlpm.';
7
+ const XLETA = '_xleta.';
7
8
  export function quoteSheetName(name, last) {
8
9
  const names = last === undefined ? [name] : [name, last];
9
10
  const joined = names.join(':');
10
11
  return names.every(isBareSheetName) ? joined : `'${joined.replace(/'/g, "''")}'`;
11
12
  }
12
13
  function isBareSheetName(name) {
13
- return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(name) && !/^[A-Za-z]{1,3}\d+$/.test(name);
14
+ return /^[A-Za-z_][A-Za-z0-9_.]*$/.test(name) && !nameReadsAsReference(name);
14
15
  }
15
16
  const SCOPING_FUNCTIONS = new Set(['LET', 'LAMBDA']);
16
17
  const FUNCTION_CALL = /(?<![A-Za-z0-9_.])([A-Za-z_][A-Za-z0-9_.]*)(\s*\()/g;
17
- const PREFIX = /_xlfn\.|_xlpm\./g;
18
- function skipOpaque(formula, index) {
19
- const opener = formula[index];
20
- const n = formula.length;
21
- if (opener === '"' || opener === "'") {
22
- let j = index + 1;
23
- while (j < n) {
24
- if (formula[j] === opener) {
25
- if (formula[j + 1] === opener) {
26
- j += 2;
27
- continue;
28
- }
29
- return j + 1;
30
- }
31
- j += 1;
32
- }
33
- return n;
34
- }
35
- if (opener === '[') {
36
- let depth = 0;
37
- let j = index;
38
- while (j < n) {
39
- const ch = formula[j];
40
- if (ch === '[')
41
- depth += 1;
42
- else if (ch === ']') {
43
- depth -= 1;
44
- if (depth === 0)
45
- return j + 1;
46
- }
47
- j += 1;
48
- }
49
- return n;
50
- }
51
- return index;
52
- }
53
- function scanFormula(formula, transform) {
54
- let out = '';
55
- let codeStart = 0;
56
- let i = 0;
57
- const n = formula.length;
58
- while (i < n) {
59
- const past = skipOpaque(formula, i);
60
- if (past > i) {
61
- out += transform(formula.slice(codeStart, i));
62
- out += formula.slice(i, past);
63
- i = past;
64
- codeStart = past;
65
- }
66
- else {
67
- i += 1;
68
- }
69
- }
70
- return out + transform(formula.slice(codeStart));
71
- }
18
+ const PREFIX = /_xlfn\.(?:_xlws\.)?|_xlpm\./g;
19
+ const FUNCTION_VALUE_PREFIX = /_xleta\.([A-Za-z_][A-Za-z0-9_.]*)/g;
72
20
  export function mangleFunctions(formula) {
73
- return scanFormula(formula, (code) => code.replace(FUNCTION_CALL, (whole, name, open) => MODERN_FUNCTIONS.has(name.toUpperCase()) ? `${XLFN}${name}${open}` : whole));
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
+ }));
74
25
  }
75
- export function unmangleFunctions(formula) {
76
- return scanFormula(formula, (code) => code.replace(PREFIX, ''));
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));
77
30
  }
78
31
  const NAME_START = /[A-Za-z_]/;
79
32
  const NAME_CHAR = /[A-Za-z0-9_.]/;
@@ -136,13 +89,13 @@ function boundName(formula, [start, end]) {
136
89
  return name.startsWith(XLPM) ? undefined : name;
137
90
  }
138
91
  function parameterNames(formula, keyword, args) {
139
- const names = new Set();
92
+ const names = new Map();
140
93
  const isLambda = keyword === 'LAMBDA';
141
94
  for (let a = 0; a < args.length - 1; a += 1) {
142
95
  if (isLambda || a % 2 === 0) {
143
96
  const name = boundName(formula, args[a]);
144
- if (name !== undefined)
145
- names.add(name);
97
+ if (name !== undefined && !names.has(name.toUpperCase()))
98
+ names.set(name.toUpperCase(), name);
146
99
  }
147
100
  }
148
101
  return names;
@@ -152,7 +105,15 @@ export function mangleParams(formula) {
152
105
  let i = 0;
153
106
  const n = formula.length;
154
107
  const frames = [];
155
- const inScope = (name) => frames.some((frame) => frame.names.has(name));
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
+ };
156
117
  while (i < n) {
157
118
  const top = frames[frames.length - 1];
158
119
  if (top !== undefined && i >= top.end) {
@@ -177,35 +138,65 @@ export function mangleParams(formula) {
177
138
  while (k < n && WHITESPACE.test(formula[k] ?? ''))
178
139
  k += 1;
179
140
  const heads = formula[k] === '(';
180
- if (heads && SCOPING_FUNCTIONS.has(name.toUpperCase()) && !inScope(name)) {
141
+ if (heads && SCOPING_FUNCTIONS.has(name.toUpperCase()) && declared(name) === undefined) {
181
142
  const { close, args } = parseCall(formula, k);
182
143
  out += formula.slice(i, k + 1);
183
144
  frames.push({ end: close, names: parameterNames(formula, name.toUpperCase(), args) });
184
145
  i = k + 1;
185
146
  continue;
186
147
  }
187
- out += inScope(name) ? `${XLPM}${name}` : name;
148
+ const spelling = declared(name);
149
+ out += spelling === undefined ? name : `${XLPM}${spelling}`;
188
150
  i = nameEnd;
189
151
  }
190
152
  return out;
191
153
  }
192
- export function mangleFormula(formula) {
193
- return mangleFunctions(mangleParams(formula));
194
- }
195
- const CELL_REFERENCE = /(?<![A-Za-z0-9_.])(\$?)([A-Z]{1,3})(\$?)([0-9]{1,7})(?![A-Za-z0-9_.!(])/g;
196
- export function translateFormula(formula, colDelta, rowDelta) {
197
- if (colDelta === 0 && rowDelta === 0)
198
- return formula;
199
- return scanFormula(formula, (code) => code.replace(CELL_REFERENCE, (_match, colAbs, colLetters, rowAbs, rowDigits) => {
200
- const decoded = tryColumnToNumber(colLetters);
201
- if (decoded === undefined)
202
- return REF_ERROR;
203
- const col = colAbs === '$' ? decoded : decoded + colDelta;
204
- const row = rowAbs === '$' ? Number(rowDigits) : Number(rowDigits) + rowDelta;
205
- if (col < 1 || col > MAX_COLUMN || row < 1 || row > MAX_ROW)
206
- return REF_ERROR;
207
- return `${colAbs}${numberToColumn(col)}${rowAbs}${row}`;
208
- }));
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));
209
200
  }
210
201
  export function formulaNumberLiteral(value) {
211
202
  assertWritableNumber(value);
@@ -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
+ ]);
@@ -0,0 +1,3 @@
1
+ /** The prefix a future function carries on disk. */
2
+ export type FutureFunctionPrefix = '_xlfn.' | '_xlfn._xlws.';
3
+ export declare const FUTURE_FUNCTION_PREFIXES: ReadonlyMap<string, FutureFunctionPrefix>;
@@ -1,148 +1,179 @@
1
- export const MODERN_FUNCTIONS = new Set([
2
- 'FILTER',
3
- 'SORT',
4
- 'SORTBY',
5
- 'UNIQUE',
6
- 'SEQUENCE',
7
- 'RANDARRAY',
8
- 'XLOOKUP',
9
- 'XMATCH',
10
- 'LAMBDA',
11
- 'LET',
12
- 'BYROW',
13
- 'BYCOL',
14
- 'MAKEARRAY',
15
- 'MAP',
16
- 'REDUCE',
17
- 'SCAN',
18
- 'ISOMITTED',
19
- 'VSTACK',
20
- 'HSTACK',
21
- 'TOROW',
22
- 'TOCOL',
23
- 'WRAPROWS',
24
- 'WRAPCOLS',
25
- 'TAKE',
26
- 'DROP',
27
- 'EXPAND',
28
- 'CHOOSEROWS',
29
- 'CHOOSECOLS',
30
- 'TEXTJOIN',
31
- 'CONCAT',
32
- 'TEXTBEFORE',
33
- 'TEXTAFTER',
34
- 'TEXTSPLIT',
35
- 'ARRAYTOTEXT',
36
- 'VALUETOTEXT',
37
- 'IFS',
38
- 'SWITCH',
39
- 'MAXIFS',
40
- 'MINIFS',
41
- 'AGGREGATE',
1
+ const WORKSHEET_ONLY_FUNCTIONS = ['FILTER', 'PY', 'SORT'];
2
+ const SPECIFIED_FUNCTIONS = [
42
3
  'ACOT',
43
4
  'ACOTH',
44
- 'COT',
45
- 'COTH',
46
- 'CSC',
47
- 'CSCH',
48
- 'SEC',
49
- 'SECH',
5
+ 'AGGREGATE',
50
6
  'ARABIC',
51
7
  'BASE',
52
- 'DECIMAL',
53
- 'COMBINA',
54
- 'PERMUTATIONA',
55
- 'GAMMA',
56
- 'GAUSS',
57
- 'PHI',
58
- 'MUNIT',
59
- 'BITAND',
60
- 'BITOR',
61
- 'BITXOR',
62
- 'BITLSHIFT',
63
- 'BITRSHIFT',
64
- 'IMCOSH',
65
- 'IMCOT',
66
- 'IMCSC',
67
- 'IMCSCH',
68
- 'IMSEC',
69
- 'IMSECH',
70
- 'IMSINH',
71
- 'IMTAN',
72
- 'DAYS',
73
- 'ISOWEEKNUM',
74
- 'IFNA',
75
- 'NUMBERVALUE',
76
- 'SHEET',
77
- 'SHEETS',
78
- 'FORMULATEXT',
79
- 'ISFORMULA',
80
- 'ENCODEURL',
81
- 'WEBSERVICE',
82
- 'FILTERXML',
83
- 'UNICHAR',
84
- 'UNICODE',
85
- 'XOR',
86
- 'PDURATION',
87
- 'RRI',
88
8
  'BETA.DIST',
89
9
  'BETA.INV',
90
10
  'BINOM.DIST',
91
11
  'BINOM.DIST.RANGE',
92
12
  'BINOM.INV',
13
+ 'BITAND',
14
+ 'BITLSHIFT',
15
+ 'BITOR',
16
+ 'BITRSHIFT',
17
+ 'BITXOR',
18
+ 'BYCOL',
19
+ 'BYROW',
20
+ 'CEILING.MATH',
21
+ 'CEILING.PRECISE',
93
22
  'CHISQ.DIST',
94
23
  'CHISQ.DIST.RT',
95
24
  'CHISQ.INV',
96
25
  'CHISQ.INV.RT',
97
26
  'CHISQ.TEST',
27
+ 'CHOOSECOLS',
28
+ 'CHOOSEROWS',
29
+ 'COMBINA',
30
+ 'CONCAT',
98
31
  'CONFIDENCE.NORM',
99
32
  'CONFIDENCE.T',
33
+ 'COT',
34
+ 'COTH',
100
35
  'COVARIANCE.P',
101
36
  'COVARIANCE.S',
37
+ 'CSC',
38
+ 'CSCH',
39
+ 'DAYS',
40
+ 'DECIMAL',
41
+ 'DROP',
42
+ 'ERF.PRECISE',
43
+ 'ERFC.PRECISE',
44
+ 'EXPAND',
102
45
  'EXPON.DIST',
103
46
  'F.DIST',
104
47
  'F.DIST.RT',
105
48
  'F.INV',
106
49
  'F.INV.RT',
107
50
  'F.TEST',
51
+ 'FIELDVALUE',
52
+ 'FILTERXML',
53
+ 'FLOOR.MATH',
54
+ 'FLOOR.PRECISE',
55
+ 'FORECAST.ETS',
56
+ 'FORECAST.ETS.CONFINT',
57
+ 'FORECAST.ETS.SEASONALITY',
58
+ 'FORECAST.ETS.STAT',
59
+ 'FORECAST.LINEAR',
60
+ 'FORMULATEXT',
61
+ 'GAMMA',
108
62
  'GAMMA.DIST',
109
63
  'GAMMA.INV',
110
64
  'GAMMALN.PRECISE',
65
+ 'GAUSS',
66
+ 'HSTACK',
111
67
  'HYPGEOM.DIST',
68
+ 'IFNA',
69
+ 'IFS',
70
+ 'IMCOSH',
71
+ 'IMCOT',
72
+ 'IMCSC',
73
+ 'IMCSCH',
74
+ 'IMSEC',
75
+ 'IMSECH',
76
+ 'IMSINH',
77
+ 'IMTAN',
78
+ 'ISFORMULA',
79
+ 'ISOMITTED',
80
+ 'ISOWEEKNUM',
81
+ 'LAMBDA',
82
+ 'LET',
112
83
  'LOGNORM.DIST',
113
84
  'LOGNORM.INV',
85
+ 'MAKEARRAY',
86
+ 'MAP',
87
+ 'MAXIFS',
88
+ 'MINIFS',
114
89
  'MODE.MULT',
115
90
  'MODE.SNGL',
91
+ 'MUNIT',
116
92
  'NEGBINOM.DIST',
117
93
  'NORM.DIST',
118
94
  'NORM.INV',
119
95
  'NORM.S.DIST',
120
96
  'NORM.S.INV',
97
+ 'NUMBERVALUE',
98
+ 'PDURATION',
121
99
  'PERCENTILE.EXC',
122
100
  'PERCENTILE.INC',
123
101
  'PERCENTRANK.EXC',
124
102
  'PERCENTRANK.INC',
103
+ 'PERMUTATIONA',
104
+ 'PHI',
125
105
  'POISSON.DIST',
106
+ 'PQSOURCE',
107
+ 'PYTHON_STR',
108
+ 'PYTHON_TYPE',
109
+ 'PYTHON_TYPENAME',
126
110
  'QUARTILE.EXC',
127
111
  'QUARTILE.INC',
112
+ 'QUERYSTRING',
113
+ 'RANDARRAY',
128
114
  'RANK.AVG',
129
115
  'RANK.EQ',
116
+ 'REDUCE',
117
+ 'RRI',
118
+ 'SCAN',
119
+ 'SEC',
120
+ 'SECH',
121
+ 'SEQUENCE',
122
+ 'SHEET',
123
+ 'SHEETS',
130
124
  'SKEW.P',
125
+ 'SORTBY',
131
126
  'STDEV.P',
132
127
  'STDEV.S',
128
+ 'SWITCH',
133
129
  'T.DIST',
134
130
  'T.DIST.2T',
135
131
  'T.DIST.RT',
136
132
  'T.INV',
137
133
  'T.INV.2T',
138
134
  'T.TEST',
135
+ 'TAKE',
136
+ 'TEXTAFTER',
137
+ 'TEXTBEFORE',
138
+ 'TEXTJOIN',
139
+ 'TEXTSPLIT',
140
+ 'TOCOL',
141
+ 'TOROW',
142
+ 'UNICHAR',
143
+ 'UNICODE',
144
+ 'UNIQUE',
139
145
  'VAR.P',
140
146
  'VAR.S',
147
+ 'VSTACK',
148
+ 'WEBSERVICE',
141
149
  'WEIBULL.DIST',
150
+ 'WRAPCOLS',
151
+ 'WRAPROWS',
152
+ 'XLOOKUP',
153
+ 'XOR',
142
154
  'Z.TEST',
143
- 'CEILING.PRECISE',
144
- 'FLOOR.PRECISE',
145
- 'ISO.CEILING',
146
- 'ERF.PRECISE',
147
- 'ERFC.PRECISE',
155
+ ];
156
+ const OBSERVED_FUNCTIONS = [
157
+ 'ANCHORARRAY',
158
+ 'ARRAYTOTEXT',
159
+ 'DETECTLANGUAGE',
160
+ 'ENCODEURL',
161
+ 'GROUPBY',
162
+ 'IMAGE',
163
+ 'PERCENTOF',
164
+ 'PIVOTBY',
165
+ 'REGEXEXTRACT',
166
+ 'REGEXREPLACE',
167
+ 'REGEXTEST',
168
+ 'SINGLE',
169
+ 'STOCKHISTORY',
170
+ 'TRANSLATE',
171
+ 'TRIMRANGE',
172
+ 'VALUETOTEXT',
173
+ 'XMATCH',
174
+ ];
175
+ export const FUTURE_FUNCTION_PREFIXES = new Map([
176
+ ...WORKSHEET_ONLY_FUNCTIONS.map((name) => [name, '_xlfn._xlws.']),
177
+ ...SPECIFIED_FUNCTIONS.map((name) => [name, '_xlfn.']),
178
+ ...OBSERVED_FUNCTIONS.map((name) => [name, '_xlfn.']),
148
179
  ]);