@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/cell-types.js +20 -0
- package/dist/cell-types.js.map +1 -0
- package/dist/code/code-formula-bar.js +6 -0
- package/dist/code/code-formula-bar.js.map +1 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +12 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +51 -0
- package/dist/code/language.js.map +1 -0
- package/dist/code/style.d.ts +1 -0
- package/dist/code/style.js +2 -0
- package/dist/collaboration/anchor.js +143 -0
- package/dist/collaboration/anchor.js.map +1 -0
- package/dist/collaboration/index.js +770 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +87 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/collaboration/shared.js +450 -0
- package/dist/collaboration/shared.js.map +1 -0
- package/dist/components/cell-panel.js +6 -0
- package/dist/components/cell-panel.js.map +1 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/cell-renders.js +40 -0
- package/dist/components/cell-renders.js.map +1 -0
- package/dist/components/date-panel.js +6 -0
- package/dist/components/date-panel.js.map +1 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/filter-panel.js +6 -0
- package/dist/components/filter-panel.js.map +1 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-dialog.js +6 -0
- package/dist/components/format-dialog.js.map +1 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-list.js +6 -0
- package/dist/components/format-list.js.map +1 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-presets.js +97 -0
- package/dist/components/format-presets.js.map +1 -0
- package/dist/components/option-panel.js +6 -0
- package/dist/components/option-panel.js.map +1 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-comment.js +6 -0
- package/dist/components/sheet-comment.js.map +1 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-formula-bar.js +6 -0
- package/dist/components/sheet-formula-bar.js.map +1 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-link.js +6 -0
- package/dist/components/sheet-link.js.map +1 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-menu.js +6 -0
- package/dist/components/sheet-menu.js.map +1 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-name-box.js +6 -0
- package/dist/components/sheet-name-box.js.map +1 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-note.js +6 -0
- package/dist/components/sheet-note.js.map +1 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-outline.js +6 -0
- package/dist/components/sheet-outline.js.map +1 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-tabs.js +6 -0
- package/dist/components/sheet-tabs.js.map +1 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-toolbar.js +6 -0
- package/dist/components/sheet-toolbar.js.map +1 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/validation-dialog.js +6 -0
- package/dist/components/validation-dialog.js.map +1 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/core/address.js +256 -0
- package/dist/core/address.js.map +1 -0
- package/dist/core/axis.js +72 -0
- package/dist/core/axis.js.map +1 -0
- package/dist/core/cell-type.js +30 -0
- package/dist/core/cell-type.js.map +1 -0
- package/dist/core/clipboard.js +141 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/commands.js +63 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +74 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/conditional.js +362 -0
- package/dist/core/conditional.js.map +1 -0
- package/dist/core/csv.js +191 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/data.js +118 -0
- package/dist/core/data.js.map +1 -0
- package/dist/core/document.js +187 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/extension.js +60 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file.js +336 -0
- package/dist/core/file.js.map +1 -0
- package/dist/core/fill.js +153 -0
- package/dist/core/fill.js.map +1 -0
- package/dist/core/find.js +42 -0
- package/dist/core/find.js.map +1 -0
- package/dist/core/format.js +404 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/group.js +134 -0
- package/dist/core/group.js.map +1 -0
- package/dist/core/history.js +68 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/index.js +17 -0
- package/dist/core/link.js +34 -0
- package/dist/core/link.js.map +1 -0
- package/dist/core/merge.js +105 -0
- package/dist/core/merge.js.map +1 -0
- package/dist/core/model.js +48 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/nav.js +44 -0
- package/dist/core/nav.js.map +1 -0
- package/dist/core/paint.js +106 -0
- package/dist/core/paint.js.map +1 -0
- package/dist/core/plain-cell-types.js +54 -0
- package/dist/core/plain-cell-types.js.map +1 -0
- package/dist/core/protect.js +246 -0
- package/dist/core/protect.js.map +1 -0
- package/dist/core/selection.js +37 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/transaction.js +1582 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/validation.js +135 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/versions.js +161 -0
- package/dist/core/versions.js.map +1 -0
- package/dist/export/color.js +36 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/draw.js +316 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/export/index.js +390 -0
- package/dist/export/index.js.map +1 -0
- package/dist/formula/edit.js +210 -0
- package/dist/formula/edit.js.map +1 -0
- package/dist/formula/engine.js +526 -0
- package/dist/formula/engine.js.map +1 -0
- package/dist/formula/error.js +39 -0
- package/dist/formula/error.js.map +1 -0
- package/dist/formula/functions.js +1493 -0
- package/dist/formula/functions.js.map +1 -0
- package/dist/formula/help.js +408 -0
- package/dist/formula/help.js.map +1 -0
- package/dist/formula/index.js +7 -0
- package/dist/formula/parse.js +369 -0
- package/dist/formula/parse.js.map +1 -0
- package/dist/formula/values.js +123 -0
- package/dist/formula/values.js.map +1 -0
- package/dist/formulas.js +30 -0
- package/dist/formulas.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +1312 -0
- package/dist/locale.js.map +1 -0
- package/dist/sheet.js +6 -0
- package/dist/sheet.js.map +1 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +17 -0
- package/dist/types/cell-types.d.ts +2 -0
- package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +13 -0
- package/dist/types/collaboration/anchor.d.ts +65 -0
- package/dist/types/collaboration/index.d.ts +59 -0
- package/dist/types/collaboration/peers.d.ts +21 -0
- package/dist/types/collaboration/shared.d.ts +75 -0
- package/dist/types/components/cell-panel.vue.d.ts +20 -0
- package/dist/types/components/cell-renders.d.ts +9 -0
- package/dist/types/components/date-panel.vue.d.ts +9 -0
- package/dist/types/components/filter-panel.vue.d.ts +21 -0
- package/dist/types/components/format-dialog.vue.d.ts +24 -0
- package/dist/types/components/format-list.vue.d.ts +25 -0
- package/dist/types/components/format-presets.d.ts +30 -0
- package/dist/types/components/option-panel.vue.d.ts +13 -0
- package/dist/types/components/sheet-comment.vue.d.ts +29 -0
- package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
- package/dist/types/components/sheet-link.vue.d.ts +32 -0
- package/dist/types/components/sheet-menu.vue.d.ts +20 -0
- package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
- package/dist/types/components/sheet-note.vue.d.ts +22 -0
- package/dist/types/components/sheet-outline.vue.d.ts +36 -0
- package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
- package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
- package/dist/types/components/validation-dialog.vue.d.ts +25 -0
- package/dist/types/context.d.ts +189 -0
- package/dist/types/core/address.d.ts +109 -0
- package/dist/types/core/axis.d.ts +23 -0
- package/dist/types/core/cell-type.d.ts +157 -0
- package/dist/types/core/clipboard.d.ts +48 -0
- package/dist/types/core/commands.d.ts +76 -0
- package/dist/types/core/comments.d.ts +22 -0
- package/dist/types/core/conditional.d.ts +114 -0
- package/dist/types/core/csv.d.ts +93 -0
- package/dist/types/core/data.d.ts +26 -0
- package/dist/types/core/document.d.ts +49 -0
- package/dist/types/core/extension.d.ts +177 -0
- package/dist/types/core/file.d.ts +301 -0
- package/dist/types/core/fill.d.ts +23 -0
- package/dist/types/core/find.d.ts +39 -0
- package/dist/types/core/format.d.ts +47 -0
- package/dist/types/core/group.d.ts +76 -0
- package/dist/types/core/history.d.ts +27 -0
- package/dist/types/core/index.d.ts +33 -0
- package/dist/types/core/link.d.ts +14 -0
- package/dist/types/core/merge.d.ts +26 -0
- package/dist/types/core/model.d.ts +363 -0
- package/dist/types/core/nav.d.ts +15 -0
- package/dist/types/core/paint.d.ts +19 -0
- package/dist/types/core/plain-cell-types.d.ts +6 -0
- package/dist/types/core/protect.d.ts +34 -0
- package/dist/types/core/selection.d.ts +25 -0
- package/dist/types/core/transaction.d.ts +470 -0
- package/dist/types/core/validation.d.ts +55 -0
- package/dist/types/core/versions.d.ts +38 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/draw.d.ts +98 -0
- package/dist/types/export/index.d.ts +47 -0
- package/dist/types/formula/edit.d.ts +62 -0
- package/dist/types/formula/engine.d.ts +99 -0
- package/dist/types/formula/error.d.ts +30 -0
- package/dist/types/formula/functions.d.ts +62 -0
- package/dist/types/formula/help.d.ts +12 -0
- package/dist/types/formula/index.d.ts +13 -0
- package/dist/types/formula/parse.d.ts +92 -0
- package/dist/types/formula/values.d.ts +65 -0
- package/dist/types/formulas.d.ts +32 -0
- package/dist/types/index.d.ts +225 -0
- package/dist/types/locale.d.ts +531 -0
- package/dist/types/sheet.vue.d.ts +60 -0
- package/dist/types/types.d.ts +498 -0
- package/dist/types/use-comments.d.ts +47 -0
- package/dist/types/use-messages.d.ts +7 -0
- package/dist/use-comments.js +130 -0
- package/dist/use-comments.js.map +1 -0
- package/dist/use-messages.js +29 -0
- package/dist/use-messages.js.map +1 -0
- package/package.json +93 -4
|
@@ -0,0 +1,1493 @@
|
|
|
1
|
+
import { formatValue, isDateFormat, parseInput } from "../core/format.js";
|
|
2
|
+
import { DIV0, NA, NUM, SheetError, VALUE, isSheetError } from "./error.js";
|
|
3
|
+
import { compareValues, dateParts, dateText, isDateText, isRange, isTimeText, serialOf, timeOrDays, timeText, toBoolean, toNumber, toText } from "./values.js";
|
|
4
|
+
// src/formula/functions.ts
|
|
5
|
+
/** Declares a formula function; only gives it a type. */
|
|
6
|
+
function defineSheetFunction(definition) {
|
|
7
|
+
return definition;
|
|
8
|
+
}
|
|
9
|
+
/** A single value from an argument: a range of one cell is that cell, a bigger one `#VALUE!`. */
|
|
10
|
+
function scalarOf(arg) {
|
|
11
|
+
if (!isRange(arg)) return arg;
|
|
12
|
+
return arg.rows === 1 && arg.cols === 1 ? arg.at(0, 0) : VALUE;
|
|
13
|
+
}
|
|
14
|
+
/** A date or time written in a cell, as its day number; `null` for anything else. */
|
|
15
|
+
var serialIn = (value) => typeof value === "string" && (isDateText(value) || isTimeText(value)) ? serialOf(value) : null;
|
|
16
|
+
/**
|
|
17
|
+
* The numbers in some arguments, the way sums count them: in ranges only
|
|
18
|
+
* numbers, dates and times count (other text, `TRUE` / `FALSE` and empty cells are skipped);
|
|
19
|
+
* given directly, text that reads as a number and `TRUE` / `FALSE` count
|
|
20
|
+
* too. The first error found is returned instead.
|
|
21
|
+
*/
|
|
22
|
+
function numbersOf(args) {
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const arg of args) if (isRange(arg)) for (const [, , value] of arg.entries()) {
|
|
25
|
+
if (isSheetError(value)) return value;
|
|
26
|
+
if (typeof value === "number") out.push(value);
|
|
27
|
+
else {
|
|
28
|
+
const serial = serialIn(value);
|
|
29
|
+
if (serial != null) out.push(serial);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (arg != null) {
|
|
33
|
+
const n = toNumber(arg);
|
|
34
|
+
if (isSheetError(n)) return n;
|
|
35
|
+
out.push(n);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/** Every value in some arguments, ranges cell by cell (their empty cells left out). */
|
|
40
|
+
function* valuesOf(args) {
|
|
41
|
+
for (const arg of args) if (isRange(arg)) for (const [, , value] of arg.entries()) yield value;
|
|
42
|
+
else yield arg;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether all the numbers `numbersOf` takes from some arguments are dates,
|
|
46
|
+
* or all times, so `SUM` of times is a time and `MAX` of dates a date.
|
|
47
|
+
*/
|
|
48
|
+
function kindOf(args) {
|
|
49
|
+
let kind = null;
|
|
50
|
+
for (const value of valuesOf(args)) {
|
|
51
|
+
if (value == null || isSheetError(value)) continue;
|
|
52
|
+
const k = isDateText(value) ? "date" : isTimeText(value) ? "time" : null;
|
|
53
|
+
if (!k && typeof value === "string") continue;
|
|
54
|
+
if (!k || kind && kind !== k) return null;
|
|
55
|
+
kind = k;
|
|
56
|
+
}
|
|
57
|
+
return kind;
|
|
58
|
+
}
|
|
59
|
+
var num = (arg) => toNumber(scalarOf(arg));
|
|
60
|
+
var text = (arg) => toText(scalarOf(arg));
|
|
61
|
+
var bool = (arg) => toBoolean(scalarOf(arg));
|
|
62
|
+
var finite = (n) => Number.isFinite(n) ? n : NUM;
|
|
63
|
+
/** Rounds half away from zero, in decimal, so 1.005 rounds to 1.01. */
|
|
64
|
+
function roundTo(n, digits, mode = "round") {
|
|
65
|
+
const d = Math.trunc(digits);
|
|
66
|
+
const scaled = Math.abs(n) * 10 ** d;
|
|
67
|
+
const fixed = Number(scaled.toPrecision(15));
|
|
68
|
+
return Math.sign(n) * Number(((mode === "round" ? Math.round(fixed) : mode === "up" ? Math.ceil(fixed) : Math.floor(fixed)) / 10 ** d).toPrecision(15));
|
|
69
|
+
}
|
|
70
|
+
var firstError = (...values) => values.find(isSheetError);
|
|
71
|
+
/** Whether a lazy argument was given: not left out, nor left empty between commas. */
|
|
72
|
+
var given = (arg) => arg != null && arg() != null;
|
|
73
|
+
/** A branch's value as `IF` and `CHOOSE` give it: a range stays one, for `SUM(IF(…, A1:A3, B1:B3))`. */
|
|
74
|
+
var passOn = (arg) => isRange(arg) ? arg : arg ?? 0;
|
|
75
|
+
function math(name, run, minArgs, maxArgs = minArgs) {
|
|
76
|
+
return defineSheetFunction({
|
|
77
|
+
name,
|
|
78
|
+
minArgs,
|
|
79
|
+
maxArgs,
|
|
80
|
+
run: (args) => {
|
|
81
|
+
const ns = args.map((arg) => arg == null ? 0 : num(arg));
|
|
82
|
+
const error = firstError(...ns);
|
|
83
|
+
if (error) return error;
|
|
84
|
+
const out = run(...ns);
|
|
85
|
+
return typeof out === "number" ? finite(out) : out;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function textFn(name, run, minArgs = 1, maxArgs = minArgs) {
|
|
90
|
+
return defineSheetFunction({
|
|
91
|
+
name,
|
|
92
|
+
minArgs,
|
|
93
|
+
maxArgs,
|
|
94
|
+
run: (args) => {
|
|
95
|
+
const ss = args.map(text);
|
|
96
|
+
return firstError(...ss) ?? run(...ss);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Wildcards `*` and `?` (`~` before one keeps it) as a regular expression matching whole text. */
|
|
101
|
+
function wildcard(pattern) {
|
|
102
|
+
let source = "";
|
|
103
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
104
|
+
const c = pattern[i];
|
|
105
|
+
if (c === "~" && i + 1 < pattern.length) source += pattern[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
106
|
+
else if (c === "*") source += ".*";
|
|
107
|
+
else if (c === "?") source += ".";
|
|
108
|
+
else source += c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
109
|
+
}
|
|
110
|
+
return new RegExp(`^${source}$`, "is");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A condition as `SUMIF` and `COUNTIF` take it: a value to equal, or text
|
|
114
|
+
* such as `">5"`, `"<>完成"`, `"上海*"` (with `*` and `?`), `""` for empty.
|
|
115
|
+
*/
|
|
116
|
+
function criteriaOf(criteria) {
|
|
117
|
+
if (typeof criteria !== "string") return (value) => value != null && compareValues(value, criteria) === 0;
|
|
118
|
+
const op = /^(<>|<=|>=|=|<|>)?(.*)$/s.exec(criteria);
|
|
119
|
+
const how = op[1] ?? "=";
|
|
120
|
+
const rest = op[2];
|
|
121
|
+
const asNumber = rest.trim() === "" ? null : serialOf(rest.trim()) ?? (() => {
|
|
122
|
+
const n = toNumber(rest);
|
|
123
|
+
return typeof n === "number" ? n : null;
|
|
124
|
+
})();
|
|
125
|
+
if (asNumber != null) {
|
|
126
|
+
const test = (n) => how === "=" ? n === asNumber : how === "<>" ? n !== asNumber : how === "<" ? n < asNumber : how === ">" ? n > asNumber : how === "<=" ? n <= asNumber : n >= asNumber;
|
|
127
|
+
return (value) => {
|
|
128
|
+
if (typeof value === "number") return test(value);
|
|
129
|
+
if (typeof value === "string") {
|
|
130
|
+
const serial = serialOf(value);
|
|
131
|
+
if (serial != null) return test(serial);
|
|
132
|
+
}
|
|
133
|
+
return how === "<>";
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (how === "=" || how === "<>") {
|
|
137
|
+
const match = rest === "" ? (value) => value == null || value === "" : /[*?~]/.test(rest) ? ((re) => (value) => typeof value === "string" && re.test(value))(wildcard(rest)) : (value) => typeof value === "string" && value.toLowerCase() === rest.toLowerCase();
|
|
138
|
+
return how === "=" ? match : (value) => !match(value);
|
|
139
|
+
}
|
|
140
|
+
return (value) => {
|
|
141
|
+
if (typeof value !== "string") return false;
|
|
142
|
+
const c = compareValues(value, rest);
|
|
143
|
+
return how === "<" ? c < 0 : how === ">" ? c > 0 : how === "<=" ? c <= 0 : c >= 0;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** The ranges and conditions of `SUMIFS`-like functions, as the positions that meet all of them. */
|
|
147
|
+
function matching(ranges, criteria) {
|
|
148
|
+
const first = ranges[0];
|
|
149
|
+
for (const range of ranges) if (range.rows !== first.rows || range.cols !== first.cols) return VALUE;
|
|
150
|
+
const tests = criteria.map(criteriaOf);
|
|
151
|
+
const out = [];
|
|
152
|
+
for (let r = 0; r < first.rows; r++) for (let c = 0; c < first.cols; c++) if (tests.every((test, i) => test(ranges[i].at(r, c)))) out.push([r, c]);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
/** For `SUMIF` and the like on sparse data: only cells with data, unless the condition takes empty ones. */
|
|
156
|
+
function matchingOne(range, criteria) {
|
|
157
|
+
const test = criteriaOf(criteria);
|
|
158
|
+
if (test(null)) return matching([range], [criteria]);
|
|
159
|
+
const out = [];
|
|
160
|
+
for (const [r, c, value] of range.entries()) if (test(value)) out.push([r, c]);
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
function lookupIndex(value, length, at, mode, reverse = false) {
|
|
164
|
+
if (mode === "wildcard" && typeof value === "string") {
|
|
165
|
+
const re = wildcard(value);
|
|
166
|
+
for (let k = 0; k < length; k++) {
|
|
167
|
+
const i = reverse ? length - 1 - k : k;
|
|
168
|
+
const v = at(i);
|
|
169
|
+
if (typeof v === "string" && re.test(v)) return i;
|
|
170
|
+
}
|
|
171
|
+
return -1;
|
|
172
|
+
}
|
|
173
|
+
let best = -1;
|
|
174
|
+
let bestValue = null;
|
|
175
|
+
for (let k = 0; k < length; k++) {
|
|
176
|
+
const i = reverse ? length - 1 - k : k;
|
|
177
|
+
const v = at(i);
|
|
178
|
+
if (v == null || isSheetError(v)) continue;
|
|
179
|
+
const c = compareValues(v, value);
|
|
180
|
+
if (c === 0 && (typeof v === typeof value || v === "" && value == null)) return i;
|
|
181
|
+
if (mode === "below" && c < 0 && typeof v === typeof value && (best === -1 || compareValues(v, bestValue) > 0)) {
|
|
182
|
+
best = i;
|
|
183
|
+
bestValue = v;
|
|
184
|
+
}
|
|
185
|
+
if (mode === "above" && c > 0 && typeof v === typeof value && (best === -1 || compareValues(v, bestValue) < 0)) {
|
|
186
|
+
best = i;
|
|
187
|
+
bestValue = v;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return best;
|
|
191
|
+
}
|
|
192
|
+
/** The last position in sorted data whose value isn't above `value`, as approximate lookups find it. */
|
|
193
|
+
function sortedIndex(value, length, at) {
|
|
194
|
+
let lo = 0;
|
|
195
|
+
let hi = length - 1;
|
|
196
|
+
let found = -1;
|
|
197
|
+
while (lo <= hi) {
|
|
198
|
+
const mid = lo + hi >> 1;
|
|
199
|
+
const v = at(mid);
|
|
200
|
+
if (v == null) {
|
|
201
|
+
hi = mid - 1;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (compareValues(v, value) <= 0) {
|
|
205
|
+
found = mid;
|
|
206
|
+
lo = mid + 1;
|
|
207
|
+
} else hi = mid - 1;
|
|
208
|
+
}
|
|
209
|
+
return found;
|
|
210
|
+
}
|
|
211
|
+
/** `XMATCH` in sorted data, halving it each step: exact, or the nearest below or above. */
|
|
212
|
+
function halvingIndex(value, length, at, how, descending) {
|
|
213
|
+
const get = (i) => at(descending ? length - 1 - i : i);
|
|
214
|
+
const comparable = (v) => v != null && !isSheetError(v) && typeof v === typeof value;
|
|
215
|
+
let lo = 0;
|
|
216
|
+
let hi = length - 1;
|
|
217
|
+
let last = -1;
|
|
218
|
+
while (lo <= hi) {
|
|
219
|
+
const mid = lo + hi >> 1;
|
|
220
|
+
const v = get(mid);
|
|
221
|
+
if (comparable(v) && compareValues(v, value) <= 0) {
|
|
222
|
+
last = mid;
|
|
223
|
+
lo = mid + 1;
|
|
224
|
+
} else hi = mid - 1;
|
|
225
|
+
}
|
|
226
|
+
let i = -1;
|
|
227
|
+
if (last !== -1 && compareValues(get(last), value) === 0) i = last;
|
|
228
|
+
else if (how === "below") i = last;
|
|
229
|
+
else if (how === "above" && last + 1 < length) i = last + 1;
|
|
230
|
+
return i === -1 ? -1 : descending ? length - 1 - i : i;
|
|
231
|
+
}
|
|
232
|
+
var asRange = (arg) => isRange(arg) ? arg : {
|
|
233
|
+
kind: "range",
|
|
234
|
+
rows: 1,
|
|
235
|
+
cols: 1,
|
|
236
|
+
at: () => arg,
|
|
237
|
+
entries: () => arg == null ? [] : [[
|
|
238
|
+
0,
|
|
239
|
+
0,
|
|
240
|
+
arg
|
|
241
|
+
]]
|
|
242
|
+
};
|
|
243
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
244
|
+
function localDay(now) {
|
|
245
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
246
|
+
}
|
|
247
|
+
function daysInMonth(year, month) {
|
|
248
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
249
|
+
}
|
|
250
|
+
/** Keeps a result to 15 significant digits, so `CEILING(2.3, 0.1)` is 2.3, not 2.3000000000000003. */
|
|
251
|
+
var tidy = (n) => Number.isFinite(n) ? Number(n.toPrecision(15)) : NUM;
|
|
252
|
+
/** Numbers from ranges and values for the statistics, at least `least` of them, else `few`. */
|
|
253
|
+
function statsOf(args, least, few) {
|
|
254
|
+
const ns = numbersOf(args);
|
|
255
|
+
if (isSheetError(ns)) return ns;
|
|
256
|
+
return ns.length < least ? few : ns;
|
|
257
|
+
}
|
|
258
|
+
var mean = (ns) => sum(ns) / ns.length;
|
|
259
|
+
/** The variance of numbers: of a sample (divided by n - 1), or of all of them. */
|
|
260
|
+
function variance(ns, sample) {
|
|
261
|
+
const m = mean(ns);
|
|
262
|
+
let squares = 0;
|
|
263
|
+
for (const n of ns) squares += (n - m) ** 2;
|
|
264
|
+
return squares / (ns.length - (sample ? 1 : 0));
|
|
265
|
+
}
|
|
266
|
+
/** Sorted from smallest, without changing the list given. */
|
|
267
|
+
var ascending = (ns) => {
|
|
268
|
+
const out = ns.slice();
|
|
269
|
+
out.sort((a, b) => a - b);
|
|
270
|
+
return out;
|
|
271
|
+
};
|
|
272
|
+
/** The value `k` (0–1) of the way through sorted numbers, between the two nearest. */
|
|
273
|
+
function percentile(sorted, k) {
|
|
274
|
+
const at = (sorted.length - 1) * k;
|
|
275
|
+
const lo = Math.floor(at);
|
|
276
|
+
const hi = Math.min(lo + 1, sorted.length - 1);
|
|
277
|
+
return sorted[lo] + (sorted[hi] - sorted[lo]) * (at - lo);
|
|
278
|
+
}
|
|
279
|
+
/** A day number from a date argument; `null` when it isn't one. */
|
|
280
|
+
function serialArg(arg) {
|
|
281
|
+
const v = scalarOf(arg);
|
|
282
|
+
if (isSheetError(v)) return v;
|
|
283
|
+
if (typeof v === "number") return v;
|
|
284
|
+
if (typeof v === "string") return serialOf(v.trim());
|
|
285
|
+
return v == null ? 0 : null;
|
|
286
|
+
}
|
|
287
|
+
/** A day number as a date, without its time. */
|
|
288
|
+
var dayText = (serial) => dateText(Math.floor(serial));
|
|
289
|
+
/** The day number of a year, month and day, months past 12 or below 1 rolling over. */
|
|
290
|
+
var serialFor = (year, month, day) => (Date.UTC(year, month - 1, day) - Date.UTC(1899, 11, 30)) / 864e5;
|
|
291
|
+
/** 0 for Sunday to 6 for Saturday. */
|
|
292
|
+
var weekdayOf = (serial) => ((Math.floor(serial) + 6) % 7 + 7) % 7;
|
|
293
|
+
/** The holidays of `NETWORKDAYS` and `WORKDAY`, as day numbers. */
|
|
294
|
+
function holidaysOf(arg) {
|
|
295
|
+
const out = /* @__PURE__ */ new Set();
|
|
296
|
+
if (arg == null) return out;
|
|
297
|
+
for (const value of valuesOf([arg])) {
|
|
298
|
+
if (isSheetError(value)) return value;
|
|
299
|
+
if (value == null || value === "") continue;
|
|
300
|
+
const serial = typeof value === "number" ? value : serialOf(String(value).trim());
|
|
301
|
+
if (serial == null) return VALUE;
|
|
302
|
+
out.add(Math.floor(serial));
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
var isWeekday = (serial) => {
|
|
307
|
+
const day = weekdayOf(serial);
|
|
308
|
+
return day !== 0 && day !== 6;
|
|
309
|
+
};
|
|
310
|
+
/** The last day number dates can have, 9999-12-31. */
|
|
311
|
+
var LAST_DAY = 2958465;
|
|
312
|
+
/** Monday to Friday from `from` to `to`, both counted, by whole weeks: any seven days hold five. */
|
|
313
|
+
function weekdaysBetween(from, to) {
|
|
314
|
+
const weeks = Math.floor((to - from + 1) / 7);
|
|
315
|
+
let count = weeks * 5;
|
|
316
|
+
for (let day = from + weeks * 7; day <= to; day++) if (isWeekday(day)) count++;
|
|
317
|
+
return count;
|
|
318
|
+
}
|
|
319
|
+
/** The day `n` weekdays after `day` (before it when negative), by whole weeks. */
|
|
320
|
+
function weekdaysOn(day, n) {
|
|
321
|
+
const step = n < 0 ? -1 : 1;
|
|
322
|
+
let left = Math.abs(n);
|
|
323
|
+
const weeks = left > 0 ? Math.floor((left - 1) / 5) : 0;
|
|
324
|
+
day += step * weeks * 7;
|
|
325
|
+
left -= weeks * 5;
|
|
326
|
+
while (left > 0) {
|
|
327
|
+
day += step;
|
|
328
|
+
if (isWeekday(day)) left--;
|
|
329
|
+
}
|
|
330
|
+
return day;
|
|
331
|
+
}
|
|
332
|
+
/** Holidays on weekdays from `from` to `to`, both counted. */
|
|
333
|
+
function holidaysBetween(holidays, from, to) {
|
|
334
|
+
let count = 0;
|
|
335
|
+
for (const day of holidays) if (day >= from && day <= to && isWeekday(day)) count++;
|
|
336
|
+
return count;
|
|
337
|
+
}
|
|
338
|
+
/** The day `WEEKNUM`'s weeks start on for each type, 0 for Sunday. */
|
|
339
|
+
var WEEK_STARTS = {
|
|
340
|
+
1: 0,
|
|
341
|
+
2: 1,
|
|
342
|
+
11: 1,
|
|
343
|
+
12: 2,
|
|
344
|
+
13: 3,
|
|
345
|
+
14: 4,
|
|
346
|
+
15: 5,
|
|
347
|
+
16: 6,
|
|
348
|
+
17: 0
|
|
349
|
+
};
|
|
350
|
+
/** The ISO week number: weeks start on Monday, the first holds the year's first Thursday. */
|
|
351
|
+
function isoWeek(serial) {
|
|
352
|
+
const day = Math.floor(serial);
|
|
353
|
+
const thursday = day - (weekdayOf(day) + 6) % 7 + 3;
|
|
354
|
+
const year = new Date(thursday * 864e5 + Date.UTC(1899, 11, 30)).getUTCFullYear();
|
|
355
|
+
return Math.floor((thursday - serialFor(year, 1, 1)) / 7) + 1;
|
|
356
|
+
}
|
|
357
|
+
/** Where a delimiter is in text, every time, for `TEXTBEFORE` and `TEXTAFTER`. */
|
|
358
|
+
function positionsOf(t, delimiter, ignoreCase) {
|
|
359
|
+
const hay = ignoreCase ? t.toLowerCase() : t;
|
|
360
|
+
const needle = ignoreCase ? delimiter.toLowerCase() : delimiter;
|
|
361
|
+
const out = [];
|
|
362
|
+
if (!needle) {
|
|
363
|
+
for (let at = 0; at <= t.length; at++) out.push(at);
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
366
|
+
for (let at = hay.indexOf(needle); at !== -1; at = hay.indexOf(needle, at + needle.length)) out.push(at);
|
|
367
|
+
return out;
|
|
368
|
+
}
|
|
369
|
+
/** The payment of a loan or saving each period, as `PMT` works it out. */
|
|
370
|
+
function payment(rate, nper, pv, fv, type) {
|
|
371
|
+
if (rate === 0) return -(pv + fv) / nper;
|
|
372
|
+
const grow = (1 + rate) ** nper;
|
|
373
|
+
return -(pv * grow + fv) * rate / ((1 + rate * type) * (grow - 1));
|
|
374
|
+
}
|
|
375
|
+
/** One of the numbers taken from some arguments, written as they are: a date or a time when they all are. */
|
|
376
|
+
function asInputs(args, n) {
|
|
377
|
+
const kind = kindOf(args);
|
|
378
|
+
return kind === "date" ? dateText(n) : kind === "time" ? timeText(n) : n;
|
|
379
|
+
}
|
|
380
|
+
var sum = (ns) => ns.reduce((a, b) => a + b, 0);
|
|
381
|
+
var builtinFunctions = [
|
|
382
|
+
defineSheetFunction({
|
|
383
|
+
name: "SUM",
|
|
384
|
+
run: (args) => {
|
|
385
|
+
const ns = numbersOf(args);
|
|
386
|
+
if (isSheetError(ns)) return ns;
|
|
387
|
+
const total = finite(sum(ns));
|
|
388
|
+
return typeof total === "number" && kindOf(args) === "time" ? timeOrDays(total) : total;
|
|
389
|
+
}
|
|
390
|
+
}),
|
|
391
|
+
defineSheetFunction({
|
|
392
|
+
name: "PRODUCT",
|
|
393
|
+
run: (args) => {
|
|
394
|
+
const ns = numbersOf(args);
|
|
395
|
+
return isSheetError(ns) ? ns : finite(ns.reduce((a, b) => a * b, 1));
|
|
396
|
+
}
|
|
397
|
+
}),
|
|
398
|
+
defineSheetFunction({
|
|
399
|
+
name: "AVERAGE",
|
|
400
|
+
minArgs: 1,
|
|
401
|
+
run: (args) => {
|
|
402
|
+
const ns = numbersOf(args);
|
|
403
|
+
if (isSheetError(ns)) return ns;
|
|
404
|
+
return ns.length ? asInputs(args, sum(ns) / ns.length) : DIV0;
|
|
405
|
+
}
|
|
406
|
+
}),
|
|
407
|
+
defineSheetFunction({
|
|
408
|
+
name: "MIN",
|
|
409
|
+
run: (args) => {
|
|
410
|
+
const ns = numbersOf(args);
|
|
411
|
+
return isSheetError(ns) ? ns : ns.length ? asInputs(args, Math.min(...ns)) : 0;
|
|
412
|
+
}
|
|
413
|
+
}),
|
|
414
|
+
defineSheetFunction({
|
|
415
|
+
name: "MAX",
|
|
416
|
+
run: (args) => {
|
|
417
|
+
const ns = numbersOf(args);
|
|
418
|
+
return isSheetError(ns) ? ns : ns.length ? asInputs(args, Math.max(...ns)) : 0;
|
|
419
|
+
}
|
|
420
|
+
}),
|
|
421
|
+
defineSheetFunction({
|
|
422
|
+
name: "COUNT",
|
|
423
|
+
run: (args) => {
|
|
424
|
+
let n = 0;
|
|
425
|
+
for (const arg of args) if (isRange(arg)) {
|
|
426
|
+
for (const [, , v] of arg.entries()) if (typeof v === "number" || serialIn(v) != null) n++;
|
|
427
|
+
} else if (arg != null && !isSheetError(toNumber(arg))) n++;
|
|
428
|
+
return n;
|
|
429
|
+
}
|
|
430
|
+
}),
|
|
431
|
+
defineSheetFunction({
|
|
432
|
+
name: "COUNTA",
|
|
433
|
+
run: (args) => {
|
|
434
|
+
let n = 0;
|
|
435
|
+
for (const value of valuesOf(args)) if (value != null && value !== "") n++;
|
|
436
|
+
return n;
|
|
437
|
+
}
|
|
438
|
+
}),
|
|
439
|
+
defineSheetFunction({
|
|
440
|
+
name: "COUNTBLANK",
|
|
441
|
+
minArgs: 1,
|
|
442
|
+
maxArgs: 1,
|
|
443
|
+
run: ([arg]) => {
|
|
444
|
+
const range = asRange(arg);
|
|
445
|
+
let filled = 0;
|
|
446
|
+
for (const [, , v] of range.entries()) if (v != null && v !== "") filled++;
|
|
447
|
+
return range.rows * range.cols - filled;
|
|
448
|
+
}
|
|
449
|
+
}),
|
|
450
|
+
math("ABS", Math.abs, 1),
|
|
451
|
+
math("INT", Math.floor, 1),
|
|
452
|
+
math("SQRT", (n) => n < 0 ? NUM : Math.sqrt(n), 1),
|
|
453
|
+
math("POWER", (a, b) => a ** b, 2),
|
|
454
|
+
math("MOD", (a, b) => b === 0 ? DIV0 : a - b * Math.floor(a / b), 2),
|
|
455
|
+
math("ROUND", (n, d = 0) => roundTo(n, d), 1, 2),
|
|
456
|
+
math("ROUNDUP", (n, d = 0) => roundTo(n, d, "up"), 1, 2),
|
|
457
|
+
math("ROUNDDOWN", (n, d = 0) => roundTo(n, d, "down"), 1, 2),
|
|
458
|
+
defineSheetFunction({
|
|
459
|
+
name: "IF",
|
|
460
|
+
lazy: true,
|
|
461
|
+
minArgs: 2,
|
|
462
|
+
maxArgs: 3,
|
|
463
|
+
run: ([test, then, otherwise]) => {
|
|
464
|
+
const b = bool(test());
|
|
465
|
+
if (isSheetError(b)) return b;
|
|
466
|
+
if (b) return passOn(then());
|
|
467
|
+
return otherwise ? passOn(otherwise()) : false;
|
|
468
|
+
}
|
|
469
|
+
}),
|
|
470
|
+
defineSheetFunction({
|
|
471
|
+
name: "IFS",
|
|
472
|
+
lazy: true,
|
|
473
|
+
minArgs: 2,
|
|
474
|
+
run: (args) => {
|
|
475
|
+
if (args.length % 2) return VALUE;
|
|
476
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
477
|
+
const b = bool(args[i]());
|
|
478
|
+
if (isSheetError(b)) return b;
|
|
479
|
+
if (b) return passOn(args[i + 1]());
|
|
480
|
+
}
|
|
481
|
+
return NA;
|
|
482
|
+
}
|
|
483
|
+
}),
|
|
484
|
+
defineSheetFunction({
|
|
485
|
+
name: "IFERROR",
|
|
486
|
+
lazy: true,
|
|
487
|
+
minArgs: 2,
|
|
488
|
+
maxArgs: 2,
|
|
489
|
+
run: ([value, fallback]) => {
|
|
490
|
+
const v = scalarOf(value());
|
|
491
|
+
return isSheetError(v) ? scalarOf(fallback()) ?? 0 : v ?? 0;
|
|
492
|
+
}
|
|
493
|
+
}),
|
|
494
|
+
defineSheetFunction({
|
|
495
|
+
name: "IFNA",
|
|
496
|
+
lazy: true,
|
|
497
|
+
minArgs: 2,
|
|
498
|
+
maxArgs: 2,
|
|
499
|
+
run: ([value, fallback]) => {
|
|
500
|
+
const v = scalarOf(value());
|
|
501
|
+
return v === NA ? scalarOf(fallback()) ?? 0 : v ?? 0;
|
|
502
|
+
}
|
|
503
|
+
}),
|
|
504
|
+
...["AND", "OR"].map((name) => defineSheetFunction({
|
|
505
|
+
name,
|
|
506
|
+
minArgs: 1,
|
|
507
|
+
run: (args) => {
|
|
508
|
+
let seen = false;
|
|
509
|
+
let out = name === "AND";
|
|
510
|
+
for (const arg of args) {
|
|
511
|
+
const values = isRange(arg) ? arg.entries() : [[
|
|
512
|
+
0,
|
|
513
|
+
0,
|
|
514
|
+
arg
|
|
515
|
+
]];
|
|
516
|
+
for (const [, , value] of values) {
|
|
517
|
+
if (value == null || isRange(arg) && typeof value === "string") continue;
|
|
518
|
+
const b = toBoolean(value);
|
|
519
|
+
if (isSheetError(b)) return b;
|
|
520
|
+
seen = true;
|
|
521
|
+
if (name === "AND" && !b) out = false;
|
|
522
|
+
if (name === "OR" && b) out = true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return seen ? out : VALUE;
|
|
526
|
+
}
|
|
527
|
+
})),
|
|
528
|
+
defineSheetFunction({
|
|
529
|
+
name: "NOT",
|
|
530
|
+
minArgs: 1,
|
|
531
|
+
maxArgs: 1,
|
|
532
|
+
run: ([a]) => {
|
|
533
|
+
const b = bool(a);
|
|
534
|
+
return isSheetError(b) ? b : !b;
|
|
535
|
+
}
|
|
536
|
+
}),
|
|
537
|
+
...[
|
|
538
|
+
["ISBLANK", (v) => v == null],
|
|
539
|
+
["ISNUMBER", (v) => typeof v === "number"],
|
|
540
|
+
["ISTEXT", (v) => typeof v === "string"],
|
|
541
|
+
["ISERROR", (v) => isSheetError(v)],
|
|
542
|
+
["ISNA", (v) => v === NA]
|
|
543
|
+
].map(([name, test]) => defineSheetFunction({
|
|
544
|
+
name,
|
|
545
|
+
minArgs: 1,
|
|
546
|
+
maxArgs: 1,
|
|
547
|
+
run: ([a]) => test(scalarOf(a))
|
|
548
|
+
})),
|
|
549
|
+
defineSheetFunction({
|
|
550
|
+
name: "HYPERLINK",
|
|
551
|
+
minArgs: 1,
|
|
552
|
+
maxArgs: 2,
|
|
553
|
+
run: ([location, name], context) => {
|
|
554
|
+
const target = toText(scalarOf(location));
|
|
555
|
+
if (isSheetError(target)) return target;
|
|
556
|
+
const shown = name === void 0 ? target : scalarOf(name);
|
|
557
|
+
if (!isSheetError(shown)) context.link?.(target);
|
|
558
|
+
return shown ?? "";
|
|
559
|
+
}
|
|
560
|
+
}),
|
|
561
|
+
defineSheetFunction({
|
|
562
|
+
name: "CONCAT",
|
|
563
|
+
run: (args) => {
|
|
564
|
+
let out = "";
|
|
565
|
+
for (const value of valuesOf(args)) {
|
|
566
|
+
const t = toText(value);
|
|
567
|
+
if (isSheetError(t)) return t;
|
|
568
|
+
out += t;
|
|
569
|
+
}
|
|
570
|
+
return out;
|
|
571
|
+
}
|
|
572
|
+
}),
|
|
573
|
+
defineSheetFunction({
|
|
574
|
+
name: "CONCATENATE",
|
|
575
|
+
minArgs: 1,
|
|
576
|
+
run: (args) => {
|
|
577
|
+
let out = "";
|
|
578
|
+
for (const arg of args) {
|
|
579
|
+
const t = text(arg);
|
|
580
|
+
if (isSheetError(t)) return t;
|
|
581
|
+
out += t;
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
}),
|
|
586
|
+
defineSheetFunction({
|
|
587
|
+
name: "TEXTJOIN",
|
|
588
|
+
minArgs: 3,
|
|
589
|
+
run: ([delimiter, ignoreEmpty, ...rest]) => {
|
|
590
|
+
const d = text(delimiter);
|
|
591
|
+
const skip = bool(ignoreEmpty);
|
|
592
|
+
const error = firstError(d, skip);
|
|
593
|
+
if (error) return error;
|
|
594
|
+
const parts = [];
|
|
595
|
+
for (const value of valuesOf(rest)) {
|
|
596
|
+
const t = toText(value);
|
|
597
|
+
if (isSheetError(t)) return t;
|
|
598
|
+
if (t !== "" || !skip) parts.push(t);
|
|
599
|
+
}
|
|
600
|
+
return parts.join(d);
|
|
601
|
+
}
|
|
602
|
+
}),
|
|
603
|
+
defineSheetFunction({
|
|
604
|
+
name: "LEFT",
|
|
605
|
+
minArgs: 1,
|
|
606
|
+
maxArgs: 2,
|
|
607
|
+
run: ([s, n]) => {
|
|
608
|
+
const t = text(s);
|
|
609
|
+
const count = n == null ? 1 : num(n);
|
|
610
|
+
const error = firstError(t, count);
|
|
611
|
+
if (error) return error;
|
|
612
|
+
return count < 0 ? VALUE : [...t].slice(0, count).join("");
|
|
613
|
+
}
|
|
614
|
+
}),
|
|
615
|
+
defineSheetFunction({
|
|
616
|
+
name: "RIGHT",
|
|
617
|
+
minArgs: 1,
|
|
618
|
+
maxArgs: 2,
|
|
619
|
+
run: ([s, n]) => {
|
|
620
|
+
const t = text(s);
|
|
621
|
+
const count = n == null ? 1 : num(n);
|
|
622
|
+
const error = firstError(t, count);
|
|
623
|
+
if (error) return error;
|
|
624
|
+
const chars = [...t];
|
|
625
|
+
return count < 0 ? VALUE : chars.slice(Math.max(0, chars.length - count)).join("");
|
|
626
|
+
}
|
|
627
|
+
}),
|
|
628
|
+
defineSheetFunction({
|
|
629
|
+
name: "MID",
|
|
630
|
+
minArgs: 3,
|
|
631
|
+
maxArgs: 3,
|
|
632
|
+
run: ([s, start, n]) => {
|
|
633
|
+
const t = text(s);
|
|
634
|
+
const from = num(start);
|
|
635
|
+
const count = num(n);
|
|
636
|
+
const error = firstError(t, from, count);
|
|
637
|
+
if (error) return error;
|
|
638
|
+
if (from < 1 || count < 0) return VALUE;
|
|
639
|
+
return [...t].slice(from - 1, from - 1 + count).join("");
|
|
640
|
+
}
|
|
641
|
+
}),
|
|
642
|
+
textFn("LEN", (s) => [...s].length),
|
|
643
|
+
textFn("TRIM", (s) => s.trim().replace(/ {2,}/g, " ")),
|
|
644
|
+
textFn("UPPER", (s) => s.toUpperCase()),
|
|
645
|
+
textFn("LOWER", (s) => s.toLowerCase()),
|
|
646
|
+
textFn("EXACT", (a, b) => a === b, 2),
|
|
647
|
+
defineSheetFunction({
|
|
648
|
+
name: "REPT",
|
|
649
|
+
minArgs: 2,
|
|
650
|
+
maxArgs: 2,
|
|
651
|
+
run: ([s, n]) => {
|
|
652
|
+
const t = text(s);
|
|
653
|
+
const count = num(n);
|
|
654
|
+
const error = firstError(t, count);
|
|
655
|
+
if (error) return error;
|
|
656
|
+
return count < 0 ? VALUE : t.repeat(Math.floor(count));
|
|
657
|
+
}
|
|
658
|
+
}),
|
|
659
|
+
defineSheetFunction({
|
|
660
|
+
name: "SUBSTITUTE",
|
|
661
|
+
minArgs: 3,
|
|
662
|
+
maxArgs: 4,
|
|
663
|
+
run: ([s, from, to, which]) => {
|
|
664
|
+
const t = text(s);
|
|
665
|
+
const a = text(from);
|
|
666
|
+
const b = text(to);
|
|
667
|
+
const nth = which == null ? null : num(which);
|
|
668
|
+
const error = firstError(t, a, b, nth);
|
|
669
|
+
if (error) return error;
|
|
670
|
+
if (a === "") return t;
|
|
671
|
+
if (nth == null) return t.split(a).join(b);
|
|
672
|
+
let at = -1;
|
|
673
|
+
for (let i = 0; i < nth; i++) {
|
|
674
|
+
at = t.indexOf(a, at + 1);
|
|
675
|
+
if (at === -1) return t;
|
|
676
|
+
}
|
|
677
|
+
return t.slice(0, at) + b + t.slice(at + a.length);
|
|
678
|
+
}
|
|
679
|
+
}),
|
|
680
|
+
...["FIND", "SEARCH"].map((name) => defineSheetFunction({
|
|
681
|
+
name,
|
|
682
|
+
minArgs: 2,
|
|
683
|
+
maxArgs: 3,
|
|
684
|
+
run: ([what, within, start]) => {
|
|
685
|
+
const a = text(what);
|
|
686
|
+
const t = text(within);
|
|
687
|
+
const from = start == null ? 1 : num(start);
|
|
688
|
+
const error = firstError(a, t, from);
|
|
689
|
+
if (error) return error;
|
|
690
|
+
const at = name === "FIND" ? t.indexOf(a, from - 1) : t.toLowerCase().indexOf(a.toLowerCase(), from - 1);
|
|
691
|
+
return at === -1 ? VALUE : at + 1;
|
|
692
|
+
}
|
|
693
|
+
})),
|
|
694
|
+
defineSheetFunction({
|
|
695
|
+
name: "TEXT",
|
|
696
|
+
minArgs: 2,
|
|
697
|
+
maxArgs: 2,
|
|
698
|
+
run: ([value, format], context) => {
|
|
699
|
+
const v = scalarOf(value);
|
|
700
|
+
const f = text(format);
|
|
701
|
+
const error = firstError(v, f);
|
|
702
|
+
if (error) return error;
|
|
703
|
+
const shown = typeof v === "number" && isDateFormat(f) ? dateText(v) : v;
|
|
704
|
+
return formatValue(shown, f, context.locale);
|
|
705
|
+
}
|
|
706
|
+
}),
|
|
707
|
+
defineSheetFunction({
|
|
708
|
+
name: "VALUE",
|
|
709
|
+
minArgs: 1,
|
|
710
|
+
maxArgs: 1,
|
|
711
|
+
run: ([value]) => {
|
|
712
|
+
const v = scalarOf(value);
|
|
713
|
+
return typeof v === "boolean" ? VALUE : toNumber(v);
|
|
714
|
+
}
|
|
715
|
+
}),
|
|
716
|
+
defineSheetFunction({
|
|
717
|
+
name: "TODAY",
|
|
718
|
+
maxArgs: 0,
|
|
719
|
+
volatile: true,
|
|
720
|
+
run: (_, c) => localDay(c.now)
|
|
721
|
+
}),
|
|
722
|
+
defineSheetFunction({
|
|
723
|
+
name: "NOW",
|
|
724
|
+
maxArgs: 0,
|
|
725
|
+
volatile: true,
|
|
726
|
+
run: (_, c) => `${localDay(c.now)} ${pad(c.now.getHours())}:${pad(c.now.getMinutes())}`
|
|
727
|
+
}),
|
|
728
|
+
math("DATE", (y, m, d) => {
|
|
729
|
+
const year = Math.floor(y) < 1900 ? Math.floor(y) + 1900 : Math.floor(y);
|
|
730
|
+
const ms = Date.UTC(year, Math.floor(m) - 1, Math.floor(d));
|
|
731
|
+
return Number.isNaN(ms) ? NUM : dateText((ms - Date.UTC(1899, 11, 30)) / 864e5);
|
|
732
|
+
}, 3),
|
|
733
|
+
...[
|
|
734
|
+
"YEAR",
|
|
735
|
+
"MONTH",
|
|
736
|
+
"DAY"
|
|
737
|
+
].map((name) => defineSheetFunction({
|
|
738
|
+
name,
|
|
739
|
+
minArgs: 1,
|
|
740
|
+
maxArgs: 1,
|
|
741
|
+
run: ([value]) => {
|
|
742
|
+
const v = scalarOf(value);
|
|
743
|
+
if (isSheetError(v)) return v;
|
|
744
|
+
const parts = dateParts(v);
|
|
745
|
+
if (!parts) return VALUE;
|
|
746
|
+
return name === "YEAR" ? parts.year : name === "MONTH" ? parts.month : parts.day;
|
|
747
|
+
}
|
|
748
|
+
})),
|
|
749
|
+
defineSheetFunction({
|
|
750
|
+
name: "WEEKDAY",
|
|
751
|
+
minArgs: 1,
|
|
752
|
+
maxArgs: 2,
|
|
753
|
+
run: ([value, type]) => {
|
|
754
|
+
const parts = dateParts(scalarOf(value));
|
|
755
|
+
const t = type == null ? 1 : num(type);
|
|
756
|
+
if (isSheetError(t)) return t;
|
|
757
|
+
if (!parts) return VALUE;
|
|
758
|
+
const sunday0 = ((Math.floor(parts.serial) + 6) % 7 + 7) % 7;
|
|
759
|
+
if (t === 1) return sunday0 + 1;
|
|
760
|
+
if (t === 2) return (sunday0 + 6) % 7 + 1;
|
|
761
|
+
if (t === 3) return (sunday0 + 6) % 7;
|
|
762
|
+
return NUM;
|
|
763
|
+
}
|
|
764
|
+
}),
|
|
765
|
+
defineSheetFunction({
|
|
766
|
+
name: "DATEDIF",
|
|
767
|
+
minArgs: 3,
|
|
768
|
+
maxArgs: 3,
|
|
769
|
+
run: ([start, end, unit]) => {
|
|
770
|
+
const a = dateParts(scalarOf(start));
|
|
771
|
+
const b = dateParts(scalarOf(end));
|
|
772
|
+
const u = text(unit);
|
|
773
|
+
if (isSheetError(u)) return u;
|
|
774
|
+
if (!a || !b) return VALUE;
|
|
775
|
+
if (a.serial > b.serial) return NUM;
|
|
776
|
+
const months = (b.year - a.year) * 12 + (b.month - a.month) - (b.day < a.day ? 1 : 0);
|
|
777
|
+
switch (u.toUpperCase()) {
|
|
778
|
+
case "Y": return Math.floor(months / 12);
|
|
779
|
+
case "M": return months;
|
|
780
|
+
case "D": return Math.floor(b.serial) - Math.floor(a.serial);
|
|
781
|
+
case "MD": return b.day >= a.day ? b.day - a.day : daysInMonth(b.year, b.month - 1) - a.day + b.day;
|
|
782
|
+
case "YM": return months % 12;
|
|
783
|
+
case "YD": {
|
|
784
|
+
const year = b.month > a.month || b.month === a.month && b.day >= a.day ? b.year : b.year - 1;
|
|
785
|
+
const from = Date.UTC(year, a.month - 1, a.day);
|
|
786
|
+
const to = Date.UTC(b.year, b.month - 1, b.day);
|
|
787
|
+
return Math.round((to - from) / 864e5);
|
|
788
|
+
}
|
|
789
|
+
default: return NUM;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}),
|
|
793
|
+
...["SUMIF", "AVERAGEIF"].map((name) => defineSheetFunction({
|
|
794
|
+
name,
|
|
795
|
+
minArgs: 2,
|
|
796
|
+
maxArgs: 3,
|
|
797
|
+
run: ([range, criteria, sumRange]) => {
|
|
798
|
+
const where = asRange(range);
|
|
799
|
+
const c = scalarOf(criteria);
|
|
800
|
+
if (isSheetError(c)) return c;
|
|
801
|
+
const values = sumRange == null ? where : asRange(sumRange);
|
|
802
|
+
let total = 0;
|
|
803
|
+
let count = 0;
|
|
804
|
+
for (const [r, col] of matchingOne(where, c)) {
|
|
805
|
+
const v = r < values.rows && col < values.cols ? values.at(r, col) : null;
|
|
806
|
+
if (isSheetError(v)) return v;
|
|
807
|
+
if (typeof v === "number") {
|
|
808
|
+
total += v;
|
|
809
|
+
count++;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (name === "SUMIF") return total;
|
|
813
|
+
return count ? total / count : DIV0;
|
|
814
|
+
}
|
|
815
|
+
})),
|
|
816
|
+
defineSheetFunction({
|
|
817
|
+
name: "COUNTIF",
|
|
818
|
+
minArgs: 2,
|
|
819
|
+
maxArgs: 2,
|
|
820
|
+
run: ([range, criteria]) => {
|
|
821
|
+
const c = scalarOf(criteria);
|
|
822
|
+
if (isSheetError(c)) return c;
|
|
823
|
+
return matchingOne(asRange(range), c).length;
|
|
824
|
+
}
|
|
825
|
+
}),
|
|
826
|
+
...["SUMIFS", "AVERAGEIFS"].map((name) => defineSheetFunction({
|
|
827
|
+
name,
|
|
828
|
+
minArgs: 3,
|
|
829
|
+
run: ([target, ...pairs]) => {
|
|
830
|
+
if (pairs.length % 2) return VALUE;
|
|
831
|
+
const ranges = [asRange(target)];
|
|
832
|
+
const criteria = [null];
|
|
833
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
834
|
+
ranges.push(asRange(pairs[i]));
|
|
835
|
+
const c = scalarOf(pairs[i + 1]);
|
|
836
|
+
if (isSheetError(c)) return c;
|
|
837
|
+
criteria.push(c);
|
|
838
|
+
}
|
|
839
|
+
const found = matching(ranges.slice(1), criteria.slice(1));
|
|
840
|
+
if (isSheetError(found)) return found;
|
|
841
|
+
if (ranges[0].rows !== ranges[1].rows || ranges[0].cols !== ranges[1].cols) return VALUE;
|
|
842
|
+
let total = 0;
|
|
843
|
+
let count = 0;
|
|
844
|
+
for (const [r, c] of found) {
|
|
845
|
+
const v = ranges[0].at(r, c);
|
|
846
|
+
if (isSheetError(v)) return v;
|
|
847
|
+
if (typeof v === "number") {
|
|
848
|
+
total += v;
|
|
849
|
+
count++;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (name === "SUMIFS") return total;
|
|
853
|
+
return count ? total / count : DIV0;
|
|
854
|
+
}
|
|
855
|
+
})),
|
|
856
|
+
defineSheetFunction({
|
|
857
|
+
name: "COUNTIFS",
|
|
858
|
+
minArgs: 2,
|
|
859
|
+
run: (args) => {
|
|
860
|
+
if (args.length % 2) return VALUE;
|
|
861
|
+
const ranges = [];
|
|
862
|
+
const criteria = [];
|
|
863
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
864
|
+
ranges.push(asRange(args[i]));
|
|
865
|
+
const c = scalarOf(args[i + 1]);
|
|
866
|
+
if (isSheetError(c)) return c;
|
|
867
|
+
criteria.push(c);
|
|
868
|
+
}
|
|
869
|
+
const found = matching(ranges, criteria);
|
|
870
|
+
return isSheetError(found) ? found : found.length;
|
|
871
|
+
}
|
|
872
|
+
}),
|
|
873
|
+
...["VLOOKUP", "HLOOKUP"].map((name) => defineSheetFunction({
|
|
874
|
+
name,
|
|
875
|
+
minArgs: 3,
|
|
876
|
+
maxArgs: 4,
|
|
877
|
+
run: ([value, table, index, approximate]) => {
|
|
878
|
+
const v = scalarOf(value);
|
|
879
|
+
if (isSheetError(v)) return v;
|
|
880
|
+
const t = asRange(table);
|
|
881
|
+
const i = num(index);
|
|
882
|
+
const approx = approximate == null ? true : bool(approximate);
|
|
883
|
+
const error = firstError(i, approx);
|
|
884
|
+
if (error) return error;
|
|
885
|
+
const n = Math.floor(i);
|
|
886
|
+
const vertical = name === "VLOOKUP";
|
|
887
|
+
const length = vertical ? t.rows : t.cols;
|
|
888
|
+
if (n < 1 || n > (vertical ? t.cols : t.rows)) return n < 1 ? VALUE : SheetError.of("#REF!");
|
|
889
|
+
const key = (k) => vertical ? t.at(k, 0) : t.at(0, k);
|
|
890
|
+
const found = approx ? sortedIndex(v, length, key) : lookupIndex(v, length, key, /[*?]/.test(String(v)) ? "wildcard" : "exact");
|
|
891
|
+
if (found === -1) return NA;
|
|
892
|
+
return vertical ? t.at(found, n - 1) : t.at(n - 1, found);
|
|
893
|
+
}
|
|
894
|
+
})),
|
|
895
|
+
defineSheetFunction({
|
|
896
|
+
name: "MATCH",
|
|
897
|
+
minArgs: 2,
|
|
898
|
+
maxArgs: 3,
|
|
899
|
+
run: ([value, range, type]) => {
|
|
900
|
+
const v = scalarOf(value);
|
|
901
|
+
if (isSheetError(v)) return v;
|
|
902
|
+
const r = asRange(range);
|
|
903
|
+
if (r.rows > 1 && r.cols > 1) return NA;
|
|
904
|
+
const t = type == null ? 1 : num(type);
|
|
905
|
+
if (isSheetError(t)) return t;
|
|
906
|
+
const length = Math.max(r.rows, r.cols);
|
|
907
|
+
const at = (i) => r.rows > 1 ? r.at(i, 0) : r.at(0, i);
|
|
908
|
+
let found;
|
|
909
|
+
if (t === 0) found = lookupIndex(v, length, at, typeof v === "string" && /[*?]/.test(v) ? "wildcard" : "exact");
|
|
910
|
+
else if (t > 0) found = sortedIndex(v, length, at);
|
|
911
|
+
else {
|
|
912
|
+
found = -1;
|
|
913
|
+
for (let i = 0; i < length; i++) {
|
|
914
|
+
const x = at(i);
|
|
915
|
+
if (x == null) break;
|
|
916
|
+
if (compareValues(x, v) >= 0) found = i;
|
|
917
|
+
else break;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return found === -1 ? NA : found + 1;
|
|
921
|
+
}
|
|
922
|
+
}),
|
|
923
|
+
defineSheetFunction({
|
|
924
|
+
name: "INDEX",
|
|
925
|
+
minArgs: 2,
|
|
926
|
+
maxArgs: 3,
|
|
927
|
+
run: ([range, row, col]) => {
|
|
928
|
+
const r = asRange(range);
|
|
929
|
+
const ri = num(row);
|
|
930
|
+
const ci = col == null ? 1 : num(col);
|
|
931
|
+
const error = firstError(ri, ci);
|
|
932
|
+
if (error) return error;
|
|
933
|
+
let y = Math.floor(ri);
|
|
934
|
+
let x = Math.floor(ci);
|
|
935
|
+
if (col == null && r.rows === 1) [y, x] = [1, y];
|
|
936
|
+
if (y < 1 || x < 1) return VALUE;
|
|
937
|
+
if (y > r.rows || x > r.cols) return SheetError.of("#REF!");
|
|
938
|
+
return r.at(y - 1, x - 1);
|
|
939
|
+
}
|
|
940
|
+
}),
|
|
941
|
+
defineSheetFunction({
|
|
942
|
+
name: "XLOOKUP",
|
|
943
|
+
lazy: true,
|
|
944
|
+
minArgs: 3,
|
|
945
|
+
maxArgs: 6,
|
|
946
|
+
run: ([value, lookup, results, notFound, matchMode, searchMode]) => {
|
|
947
|
+
const v = scalarOf(value());
|
|
948
|
+
if (isSheetError(v)) return v;
|
|
949
|
+
const l = asRange(lookup());
|
|
950
|
+
const out = asRange(results());
|
|
951
|
+
if (l.rows > 1 && l.cols > 1) return VALUE;
|
|
952
|
+
const mode = matchMode ? num(matchMode()) : 0;
|
|
953
|
+
const search = searchMode ? num(searchMode()) : 1;
|
|
954
|
+
const error = firstError(mode, search);
|
|
955
|
+
if (error) return error;
|
|
956
|
+
const vertical = l.rows > 1 || l.cols === 1;
|
|
957
|
+
const length = vertical ? l.rows : l.cols;
|
|
958
|
+
if ((vertical ? out.rows : out.cols) !== length) return VALUE;
|
|
959
|
+
const at = (i) => vertical ? l.at(i, 0) : l.at(0, i);
|
|
960
|
+
const how = mode === 0 ? "exact" : mode === -1 ? "below" : mode === 1 ? "above" : mode === 2 ? "wildcard" : null;
|
|
961
|
+
if (!how) return VALUE;
|
|
962
|
+
const found = lookupIndex(v, length, at, how, search < 0);
|
|
963
|
+
if (found === -1) return notFound ? scalarOf(notFound()) ?? 0 : NA;
|
|
964
|
+
return vertical ? out.at(found, 0) : out.at(0, found);
|
|
965
|
+
}
|
|
966
|
+
}),
|
|
967
|
+
defineSheetFunction({
|
|
968
|
+
name: "SUMPRODUCT",
|
|
969
|
+
minArgs: 1,
|
|
970
|
+
run: (args) => {
|
|
971
|
+
const ranges = args.map(asRange);
|
|
972
|
+
const first = ranges[0];
|
|
973
|
+
if (ranges.some((r) => r.rows !== first.rows || r.cols !== first.cols)) return VALUE;
|
|
974
|
+
let total = 0;
|
|
975
|
+
for (let r = 0; r < first.rows; r++) for (let c = 0; c < first.cols; c++) {
|
|
976
|
+
let product = 1;
|
|
977
|
+
for (const range of ranges) {
|
|
978
|
+
const v = range.at(r, c);
|
|
979
|
+
if (isSheetError(v)) return v;
|
|
980
|
+
product *= typeof v === "number" ? v : 0;
|
|
981
|
+
}
|
|
982
|
+
total += product;
|
|
983
|
+
}
|
|
984
|
+
return finite(total);
|
|
985
|
+
}
|
|
986
|
+
}),
|
|
987
|
+
defineSheetFunction({
|
|
988
|
+
name: "MEDIAN",
|
|
989
|
+
minArgs: 1,
|
|
990
|
+
run: (args) => {
|
|
991
|
+
const ns = statsOf(args, 1, NUM);
|
|
992
|
+
return isSheetError(ns) ? ns : asInputs(args, percentile(ascending(ns), .5));
|
|
993
|
+
}
|
|
994
|
+
}),
|
|
995
|
+
...["LARGE", "SMALL"].map((name) => defineSheetFunction({
|
|
996
|
+
name,
|
|
997
|
+
minArgs: 2,
|
|
998
|
+
maxArgs: 2,
|
|
999
|
+
run: ([values, nth]) => {
|
|
1000
|
+
const ns = numbersOf([values]);
|
|
1001
|
+
const k = num(nth);
|
|
1002
|
+
const error = firstError(ns, k);
|
|
1003
|
+
if (error) return error;
|
|
1004
|
+
const list = ascending(ns);
|
|
1005
|
+
const i = Math.ceil(k);
|
|
1006
|
+
if (i < 1 || i > list.length) return NUM;
|
|
1007
|
+
return name === "SMALL" ? list[i - 1] : list[list.length - i];
|
|
1008
|
+
}
|
|
1009
|
+
})),
|
|
1010
|
+
...["RANK", "RANK.EQ"].map((name) => defineSheetFunction({
|
|
1011
|
+
name,
|
|
1012
|
+
minArgs: 2,
|
|
1013
|
+
maxArgs: 3,
|
|
1014
|
+
run: ([value, values, order]) => {
|
|
1015
|
+
const v = num(value);
|
|
1016
|
+
const ns = numbersOf([asRange(values)]);
|
|
1017
|
+
const ascendingOrder = order == null ? false : bool(order);
|
|
1018
|
+
const error = firstError(v, ns, ascendingOrder);
|
|
1019
|
+
if (error) return error;
|
|
1020
|
+
if (!ns.includes(v)) return NA;
|
|
1021
|
+
let rank = 1;
|
|
1022
|
+
for (const n of ns) if (ascendingOrder ? n < v : n > v) rank++;
|
|
1023
|
+
return rank;
|
|
1024
|
+
}
|
|
1025
|
+
})),
|
|
1026
|
+
...[
|
|
1027
|
+
[
|
|
1028
|
+
"STDEV",
|
|
1029
|
+
true,
|
|
1030
|
+
true
|
|
1031
|
+
],
|
|
1032
|
+
[
|
|
1033
|
+
"STDEV.S",
|
|
1034
|
+
true,
|
|
1035
|
+
true
|
|
1036
|
+
],
|
|
1037
|
+
[
|
|
1038
|
+
"STDEV.P",
|
|
1039
|
+
true,
|
|
1040
|
+
false
|
|
1041
|
+
],
|
|
1042
|
+
[
|
|
1043
|
+
"VAR",
|
|
1044
|
+
false,
|
|
1045
|
+
true
|
|
1046
|
+
],
|
|
1047
|
+
[
|
|
1048
|
+
"VAR.S",
|
|
1049
|
+
false,
|
|
1050
|
+
true
|
|
1051
|
+
],
|
|
1052
|
+
[
|
|
1053
|
+
"VAR.P",
|
|
1054
|
+
false,
|
|
1055
|
+
false
|
|
1056
|
+
]
|
|
1057
|
+
].map(([name, root, sample]) => defineSheetFunction({
|
|
1058
|
+
name,
|
|
1059
|
+
minArgs: 1,
|
|
1060
|
+
run: (args) => {
|
|
1061
|
+
const ns = statsOf(args, sample ? 2 : 1, DIV0);
|
|
1062
|
+
if (isSheetError(ns)) return ns;
|
|
1063
|
+
const v = variance(ns, sample);
|
|
1064
|
+
return root ? Math.sqrt(v) : v;
|
|
1065
|
+
}
|
|
1066
|
+
})),
|
|
1067
|
+
...["PERCENTILE", "PERCENTILE.INC"].map((name) => defineSheetFunction({
|
|
1068
|
+
name,
|
|
1069
|
+
minArgs: 2,
|
|
1070
|
+
maxArgs: 2,
|
|
1071
|
+
run: ([values, k]) => {
|
|
1072
|
+
const ns = numbersOf([values]);
|
|
1073
|
+
const at = num(k);
|
|
1074
|
+
const error = firstError(ns, at);
|
|
1075
|
+
if (error) return error;
|
|
1076
|
+
if (!ns.length || at < 0 || at > 1) return NUM;
|
|
1077
|
+
return tidy(percentile(ascending(ns), at));
|
|
1078
|
+
}
|
|
1079
|
+
})),
|
|
1080
|
+
...["MAXIFS", "MINIFS"].map((name) => defineSheetFunction({
|
|
1081
|
+
name,
|
|
1082
|
+
minArgs: 3,
|
|
1083
|
+
run: ([target, ...pairs]) => {
|
|
1084
|
+
if (pairs.length % 2) return VALUE;
|
|
1085
|
+
const values = asRange(target);
|
|
1086
|
+
const ranges = [];
|
|
1087
|
+
const criteria = [];
|
|
1088
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
1089
|
+
ranges.push(asRange(pairs[i]));
|
|
1090
|
+
const c = scalarOf(pairs[i + 1]);
|
|
1091
|
+
if (isSheetError(c)) return c;
|
|
1092
|
+
criteria.push(c);
|
|
1093
|
+
}
|
|
1094
|
+
const found = matching(ranges, criteria);
|
|
1095
|
+
if (isSheetError(found)) return found;
|
|
1096
|
+
if (values.rows !== ranges[0].rows || values.cols !== ranges[0].cols) return VALUE;
|
|
1097
|
+
let best = null;
|
|
1098
|
+
for (const [r, c] of found) {
|
|
1099
|
+
const v = values.at(r, c);
|
|
1100
|
+
if (isSheetError(v)) return v;
|
|
1101
|
+
if (typeof v !== "number") continue;
|
|
1102
|
+
if (best == null || (name === "MAXIFS" ? v > best : v < best)) best = v;
|
|
1103
|
+
}
|
|
1104
|
+
return best ?? 0;
|
|
1105
|
+
}
|
|
1106
|
+
})),
|
|
1107
|
+
math("CEILING", (n, step = 1) => {
|
|
1108
|
+
if (step === 0) return 0;
|
|
1109
|
+
if (n > 0 && step < 0) return NUM;
|
|
1110
|
+
return tidy(Math.ceil(Number((n / step).toPrecision(15))) * step);
|
|
1111
|
+
}, 1, 2),
|
|
1112
|
+
math("FLOOR", (n, step = 1) => {
|
|
1113
|
+
if (step === 0) return n === 0 ? 0 : DIV0;
|
|
1114
|
+
if (n > 0 && step < 0) return NUM;
|
|
1115
|
+
return tidy(Math.floor(Number((n / step).toPrecision(15))) * step);
|
|
1116
|
+
}, 1, 2),
|
|
1117
|
+
math("MROUND", (n, step) => {
|
|
1118
|
+
if (step === 0 || n === 0) return 0;
|
|
1119
|
+
if (Math.sign(n) !== Math.sign(step)) return NUM;
|
|
1120
|
+
return tidy(roundTo(n / step, 0) * step);
|
|
1121
|
+
}, 2),
|
|
1122
|
+
math("TRUNC", (n, d = 0) => roundTo(n, d, "down"), 1, 2),
|
|
1123
|
+
math("SIGN", Math.sign, 1),
|
|
1124
|
+
math("PI", () => Math.PI, 0),
|
|
1125
|
+
math("EXP", Math.exp, 1),
|
|
1126
|
+
math("LN", (n) => n <= 0 ? NUM : Math.log(n), 1),
|
|
1127
|
+
math("LOG10", (n) => n <= 0 ? NUM : Math.log10(n), 1),
|
|
1128
|
+
math("LOG", (n, base = 10) => {
|
|
1129
|
+
if (n <= 0 || base <= 0) return NUM;
|
|
1130
|
+
return base === 1 ? DIV0 : tidy(Math.log(n) / Math.log(base));
|
|
1131
|
+
}, 1, 2),
|
|
1132
|
+
defineSheetFunction({
|
|
1133
|
+
name: "RAND",
|
|
1134
|
+
maxArgs: 0,
|
|
1135
|
+
volatile: true,
|
|
1136
|
+
run: () => Math.random()
|
|
1137
|
+
}),
|
|
1138
|
+
defineSheetFunction({
|
|
1139
|
+
name: "RANDBETWEEN",
|
|
1140
|
+
minArgs: 2,
|
|
1141
|
+
maxArgs: 2,
|
|
1142
|
+
volatile: true,
|
|
1143
|
+
run: ([low, high]) => {
|
|
1144
|
+
const a = num(low);
|
|
1145
|
+
const b = num(high);
|
|
1146
|
+
const error = firstError(a, b);
|
|
1147
|
+
if (error) return error;
|
|
1148
|
+
const lo = Math.ceil(a);
|
|
1149
|
+
const hi = Math.floor(b);
|
|
1150
|
+
if (lo > hi) return NUM;
|
|
1151
|
+
return lo + Math.floor(Math.random() * (hi - lo + 1));
|
|
1152
|
+
}
|
|
1153
|
+
}),
|
|
1154
|
+
defineSheetFunction({
|
|
1155
|
+
name: "XOR",
|
|
1156
|
+
minArgs: 1,
|
|
1157
|
+
run: (args) => {
|
|
1158
|
+
let seen = false;
|
|
1159
|
+
let out = false;
|
|
1160
|
+
for (const arg of args) {
|
|
1161
|
+
const values = isRange(arg) ? arg.entries() : [[
|
|
1162
|
+
0,
|
|
1163
|
+
0,
|
|
1164
|
+
arg
|
|
1165
|
+
]];
|
|
1166
|
+
for (const [, , value] of values) {
|
|
1167
|
+
if (value == null || isRange(arg) && typeof value === "string") continue;
|
|
1168
|
+
const b = toBoolean(value);
|
|
1169
|
+
if (isSheetError(b)) return b;
|
|
1170
|
+
seen = true;
|
|
1171
|
+
if (b) out = !out;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return seen ? out : VALUE;
|
|
1175
|
+
}
|
|
1176
|
+
}),
|
|
1177
|
+
defineSheetFunction({
|
|
1178
|
+
name: "SWITCH",
|
|
1179
|
+
lazy: true,
|
|
1180
|
+
minArgs: 3,
|
|
1181
|
+
run: ([value, ...cases]) => {
|
|
1182
|
+
const v = scalarOf(value());
|
|
1183
|
+
if (isSheetError(v)) return v;
|
|
1184
|
+
for (let i = 0; i + 1 < cases.length; i += 2) {
|
|
1185
|
+
const c = scalarOf(cases[i]());
|
|
1186
|
+
if (isSheetError(c)) return c;
|
|
1187
|
+
if (compareValues(c, v) === 0) return passOn(cases[i + 1]());
|
|
1188
|
+
}
|
|
1189
|
+
return cases.length % 2 ? passOn(cases[cases.length - 1]()) : NA;
|
|
1190
|
+
}
|
|
1191
|
+
}),
|
|
1192
|
+
defineSheetFunction({
|
|
1193
|
+
name: "CHOOSE",
|
|
1194
|
+
lazy: true,
|
|
1195
|
+
minArgs: 2,
|
|
1196
|
+
run: ([index, ...values]) => {
|
|
1197
|
+
const i = num(index());
|
|
1198
|
+
if (isSheetError(i)) return i;
|
|
1199
|
+
const at = Math.floor(i);
|
|
1200
|
+
if (at < 1 || at > values.length) return VALUE;
|
|
1201
|
+
return passOn(values[at - 1]());
|
|
1202
|
+
}
|
|
1203
|
+
}),
|
|
1204
|
+
defineSheetFunction({
|
|
1205
|
+
name: "NA",
|
|
1206
|
+
maxArgs: 0,
|
|
1207
|
+
run: () => NA
|
|
1208
|
+
}),
|
|
1209
|
+
...[["ISLOGICAL", (v) => typeof v === "boolean"], ["ISERR", (v) => isSheetError(v) && v !== NA]].map(([name, test]) => defineSheetFunction({
|
|
1210
|
+
name,
|
|
1211
|
+
minArgs: 1,
|
|
1212
|
+
maxArgs: 1,
|
|
1213
|
+
run: ([a]) => test(scalarOf(a))
|
|
1214
|
+
})),
|
|
1215
|
+
...["ISEVEN", "ISODD"].map((name) => defineSheetFunction({
|
|
1216
|
+
name,
|
|
1217
|
+
minArgs: 1,
|
|
1218
|
+
maxArgs: 1,
|
|
1219
|
+
run: ([a]) => {
|
|
1220
|
+
const v = scalarOf(a);
|
|
1221
|
+
if (typeof v === "boolean") return VALUE;
|
|
1222
|
+
const n = toNumber(v);
|
|
1223
|
+
if (isSheetError(n)) return n;
|
|
1224
|
+
const even = Math.abs(Math.trunc(n)) % 2 === 0;
|
|
1225
|
+
return name === "ISEVEN" ? even : !even;
|
|
1226
|
+
}
|
|
1227
|
+
})),
|
|
1228
|
+
textFn("PROPER", (s) => s.toLowerCase().replace(/(^|[^\p{L}])(\p{L})/gu, (_, before, letter) => before + letter.toUpperCase())),
|
|
1229
|
+
textFn("CLEAN", (s) => s.replace(/[\u0000-\u001f]/g, "")),
|
|
1230
|
+
defineSheetFunction({
|
|
1231
|
+
name: "REPLACE",
|
|
1232
|
+
minArgs: 4,
|
|
1233
|
+
maxArgs: 4,
|
|
1234
|
+
run: ([old, start, count, replacement]) => {
|
|
1235
|
+
const t = text(old);
|
|
1236
|
+
const from = num(start);
|
|
1237
|
+
const n = num(count);
|
|
1238
|
+
const r = text(replacement);
|
|
1239
|
+
const error = firstError(t, from, n, r);
|
|
1240
|
+
if (error) return error;
|
|
1241
|
+
if (from < 1 || n < 0) return VALUE;
|
|
1242
|
+
const chars = [...t];
|
|
1243
|
+
const at = Math.floor(from) - 1;
|
|
1244
|
+
return [
|
|
1245
|
+
...chars.slice(0, at),
|
|
1246
|
+
r,
|
|
1247
|
+
...chars.slice(at + Math.floor(n))
|
|
1248
|
+
].join("");
|
|
1249
|
+
}
|
|
1250
|
+
}),
|
|
1251
|
+
...["TEXTBEFORE", "TEXTAFTER"].map((name) => defineSheetFunction({
|
|
1252
|
+
name,
|
|
1253
|
+
lazy: true,
|
|
1254
|
+
minArgs: 2,
|
|
1255
|
+
maxArgs: 6,
|
|
1256
|
+
run: ([value, delimiter, instance, matchMode, matchEnd, ifNotFound]) => {
|
|
1257
|
+
const t = text(value());
|
|
1258
|
+
const d = text(delimiter());
|
|
1259
|
+
const nth = given(instance) ? num(instance()) : 1;
|
|
1260
|
+
const mode = given(matchMode) ? num(matchMode()) : 0;
|
|
1261
|
+
const end = given(matchEnd) ? bool(matchEnd()) : false;
|
|
1262
|
+
const error = firstError(t, d, nth, mode, end);
|
|
1263
|
+
if (error) return error;
|
|
1264
|
+
const source = t;
|
|
1265
|
+
const k = Math.trunc(nth);
|
|
1266
|
+
if (k === 0 || Math.abs(k) > source.length + 1) return VALUE;
|
|
1267
|
+
const found = positionsOf(source, d, mode === 1);
|
|
1268
|
+
const starts = end ? [...found, source.length] : found;
|
|
1269
|
+
const ends = end ? [0, ...found] : found;
|
|
1270
|
+
const width = d.length;
|
|
1271
|
+
let at;
|
|
1272
|
+
let after;
|
|
1273
|
+
if (k > 0) {
|
|
1274
|
+
if (k > starts.length) return given(ifNotFound) ? scalarOf(ifNotFound()) ?? 0 : NA;
|
|
1275
|
+
at = starts[k - 1];
|
|
1276
|
+
after = at === source.length ? at : at + width;
|
|
1277
|
+
} else {
|
|
1278
|
+
if (-k > ends.length) return given(ifNotFound) ? scalarOf(ifNotFound()) ?? 0 : NA;
|
|
1279
|
+
at = ends[ends.length + k];
|
|
1280
|
+
after = end && at === 0 && ends.length + k === 0 ? 0 : at + width;
|
|
1281
|
+
}
|
|
1282
|
+
return name === "TEXTBEFORE" ? source.slice(0, at) : source.slice(after);
|
|
1283
|
+
}
|
|
1284
|
+
})),
|
|
1285
|
+
...[["CHAR", 255], ["UNICHAR", 1114111]].map(([name, most]) => math(name, (n) => {
|
|
1286
|
+
const code = Math.trunc(n);
|
|
1287
|
+
return code < 1 || code > most ? VALUE : String.fromCodePoint(code);
|
|
1288
|
+
}, 1)),
|
|
1289
|
+
...["CODE", "UNICODE"].map((name) => textFn(name, (s) => s === "" ? VALUE : s.codePointAt(0))),
|
|
1290
|
+
math("TIME", (h, m, s) => {
|
|
1291
|
+
const seconds = Math.trunc(h) * 3600 + Math.trunc(m) * 60 + Math.trunc(s);
|
|
1292
|
+
return seconds < 0 ? NUM : timeText(seconds / 86400);
|
|
1293
|
+
}, 3),
|
|
1294
|
+
...[
|
|
1295
|
+
"HOUR",
|
|
1296
|
+
"MINUTE",
|
|
1297
|
+
"SECOND"
|
|
1298
|
+
].map((name) => defineSheetFunction({
|
|
1299
|
+
name,
|
|
1300
|
+
minArgs: 1,
|
|
1301
|
+
maxArgs: 1,
|
|
1302
|
+
run: ([value]) => {
|
|
1303
|
+
const serial = serialArg(value);
|
|
1304
|
+
if (isSheetError(serial)) return serial;
|
|
1305
|
+
if (serial == null || serial < 0) return VALUE;
|
|
1306
|
+
const seconds = Math.round(serial % 1 * 86400) % 86400;
|
|
1307
|
+
if (name === "HOUR") return Math.floor(seconds / 3600);
|
|
1308
|
+
if (name === "MINUTE") return Math.floor(seconds % 3600 / 60);
|
|
1309
|
+
return seconds % 60;
|
|
1310
|
+
}
|
|
1311
|
+
})),
|
|
1312
|
+
...["EDATE", "EOMONTH"].map((name) => defineSheetFunction({
|
|
1313
|
+
name,
|
|
1314
|
+
minArgs: 2,
|
|
1315
|
+
maxArgs: 2,
|
|
1316
|
+
run: ([start, months]) => {
|
|
1317
|
+
const parts = dateParts(scalarOf(start));
|
|
1318
|
+
const n = num(months);
|
|
1319
|
+
if (isSheetError(n)) return n;
|
|
1320
|
+
if (!parts) return VALUE;
|
|
1321
|
+
const month = parts.month + Math.trunc(n);
|
|
1322
|
+
if (name === "EOMONTH") return dayText(serialFor(parts.year, month + 1, 0));
|
|
1323
|
+
const last = new Date(Date.UTC(parts.year, month, 0)).getUTCDate();
|
|
1324
|
+
return dayText(serialFor(parts.year, month, Math.min(parts.day, last)));
|
|
1325
|
+
}
|
|
1326
|
+
})),
|
|
1327
|
+
defineSheetFunction({
|
|
1328
|
+
name: "DAYS",
|
|
1329
|
+
minArgs: 2,
|
|
1330
|
+
maxArgs: 2,
|
|
1331
|
+
run: ([end, start]) => {
|
|
1332
|
+
const b = serialArg(end);
|
|
1333
|
+
const a = serialArg(start);
|
|
1334
|
+
const error = firstError(a, b);
|
|
1335
|
+
if (error) return error;
|
|
1336
|
+
if (a == null || b == null) return VALUE;
|
|
1337
|
+
return Math.floor(b) - Math.floor(a);
|
|
1338
|
+
}
|
|
1339
|
+
}),
|
|
1340
|
+
defineSheetFunction({
|
|
1341
|
+
name: "NETWORKDAYS",
|
|
1342
|
+
minArgs: 2,
|
|
1343
|
+
maxArgs: 3,
|
|
1344
|
+
run: ([start, end, holidays]) => {
|
|
1345
|
+
const a = serialArg(start);
|
|
1346
|
+
const b = serialArg(end);
|
|
1347
|
+
const off = holidaysOf(holidays);
|
|
1348
|
+
const error = firstError(a, b, off);
|
|
1349
|
+
if (error) return error;
|
|
1350
|
+
if (a == null || b == null) return VALUE;
|
|
1351
|
+
const from = Math.floor(Math.min(a, b));
|
|
1352
|
+
const to = Math.floor(Math.max(a, b));
|
|
1353
|
+
const count = weekdaysBetween(from, to) - holidaysBetween(off, from, to);
|
|
1354
|
+
return a > b ? -count : count;
|
|
1355
|
+
}
|
|
1356
|
+
}),
|
|
1357
|
+
defineSheetFunction({
|
|
1358
|
+
name: "WORKDAY",
|
|
1359
|
+
minArgs: 2,
|
|
1360
|
+
maxArgs: 3,
|
|
1361
|
+
run: ([start, days, holidays]) => {
|
|
1362
|
+
const a = serialArg(start);
|
|
1363
|
+
const n = num(days);
|
|
1364
|
+
const off = holidaysOf(holidays);
|
|
1365
|
+
const error = firstError(a, n, off);
|
|
1366
|
+
if (error) return error;
|
|
1367
|
+
if (a == null) return VALUE;
|
|
1368
|
+
const count = Math.trunc(n);
|
|
1369
|
+
if (!Number.isFinite(count) || Math.abs(count) > LAST_DAY) return NUM;
|
|
1370
|
+
const step = count < 0 ? -1 : 1;
|
|
1371
|
+
let from = Math.floor(a);
|
|
1372
|
+
let day = weekdaysOn(from, count);
|
|
1373
|
+
for (;;) {
|
|
1374
|
+
const lo = Math.min(from, day);
|
|
1375
|
+
const hi = Math.max(from, day);
|
|
1376
|
+
const passed = holidaysBetween(off, lo + (step > 0 ? 1 : 0), hi - (step < 0 ? 1 : 0));
|
|
1377
|
+
if (!passed) break;
|
|
1378
|
+
from = day;
|
|
1379
|
+
day = weekdaysOn(day, passed * step);
|
|
1380
|
+
}
|
|
1381
|
+
return day < 0 || day > LAST_DAY ? NUM : dayText(day);
|
|
1382
|
+
}
|
|
1383
|
+
}),
|
|
1384
|
+
defineSheetFunction({
|
|
1385
|
+
name: "WEEKNUM",
|
|
1386
|
+
minArgs: 1,
|
|
1387
|
+
maxArgs: 2,
|
|
1388
|
+
run: ([value, type]) => {
|
|
1389
|
+
const serial = serialArg(value);
|
|
1390
|
+
const t = type == null ? 1 : num(type);
|
|
1391
|
+
const error = firstError(serial, t);
|
|
1392
|
+
if (error) return error;
|
|
1393
|
+
if (serial == null) return VALUE;
|
|
1394
|
+
if (t === 21) return isoWeek(serial);
|
|
1395
|
+
const start = WEEK_STARTS[t];
|
|
1396
|
+
if (start == null) return NUM;
|
|
1397
|
+
const day = Math.floor(serial);
|
|
1398
|
+
const first = serialFor(new Date(day * 864e5 + Date.UTC(1899, 11, 30)).getUTCFullYear(), 1, 1);
|
|
1399
|
+
const shift = (weekdayOf(first) - start + 7) % 7;
|
|
1400
|
+
return Math.floor((day - first + shift) / 7) + 1;
|
|
1401
|
+
}
|
|
1402
|
+
}),
|
|
1403
|
+
defineSheetFunction({
|
|
1404
|
+
name: "ISOWEEKNUM",
|
|
1405
|
+
minArgs: 1,
|
|
1406
|
+
maxArgs: 1,
|
|
1407
|
+
run: ([value]) => {
|
|
1408
|
+
const serial = serialArg(value);
|
|
1409
|
+
if (isSheetError(serial)) return serial;
|
|
1410
|
+
return serial == null ? VALUE : isoWeek(serial);
|
|
1411
|
+
}
|
|
1412
|
+
}),
|
|
1413
|
+
...["DATEVALUE", "TIMEVALUE"].map((name) => defineSheetFunction({
|
|
1414
|
+
name,
|
|
1415
|
+
minArgs: 1,
|
|
1416
|
+
maxArgs: 1,
|
|
1417
|
+
run: ([value]) => {
|
|
1418
|
+
const t = text(value);
|
|
1419
|
+
if (isSheetError(t)) return t;
|
|
1420
|
+
const typed = parseInput(t.trim()).value;
|
|
1421
|
+
const serial = typeof typed === "string" ? serialOf(typed) : null;
|
|
1422
|
+
if (serial == null) return VALUE;
|
|
1423
|
+
return name === "DATEVALUE" ? dayText(serial) : timeText(serial % 1);
|
|
1424
|
+
}
|
|
1425
|
+
})),
|
|
1426
|
+
defineSheetFunction({
|
|
1427
|
+
name: "XMATCH",
|
|
1428
|
+
minArgs: 2,
|
|
1429
|
+
maxArgs: 4,
|
|
1430
|
+
run: ([value, values, matchMode, searchMode]) => {
|
|
1431
|
+
const v = scalarOf(value);
|
|
1432
|
+
if (isSheetError(v)) return v;
|
|
1433
|
+
const r = asRange(values);
|
|
1434
|
+
if (r.rows > 1 && r.cols > 1) return VALUE;
|
|
1435
|
+
const mode = matchMode == null ? 0 : num(matchMode);
|
|
1436
|
+
const search = searchMode == null ? 1 : num(searchMode);
|
|
1437
|
+
const error = firstError(mode, search);
|
|
1438
|
+
if (error) return error;
|
|
1439
|
+
const how = mode === 0 ? "exact" : mode === -1 ? "below" : mode === 1 ? "above" : mode === 2 ? "wildcard" : null;
|
|
1440
|
+
if (!how || ![
|
|
1441
|
+
1,
|
|
1442
|
+
-1,
|
|
1443
|
+
2,
|
|
1444
|
+
-2
|
|
1445
|
+
].includes(search)) return VALUE;
|
|
1446
|
+
const length = Math.max(r.rows, r.cols);
|
|
1447
|
+
const at = (i) => r.rows > 1 ? r.at(i, 0) : r.at(0, i);
|
|
1448
|
+
if (Math.abs(search) === 2 && how === "wildcard") return VALUE;
|
|
1449
|
+
const found = Math.abs(search) === 2 ? halvingIndex(v, length, at, how, search === -2) : lookupIndex(v, length, at, how, search === -1);
|
|
1450
|
+
return found === -1 ? NA : found + 1;
|
|
1451
|
+
}
|
|
1452
|
+
}),
|
|
1453
|
+
...["ROWS", "COLUMNS"].map((name) => defineSheetFunction({
|
|
1454
|
+
name,
|
|
1455
|
+
minArgs: 1,
|
|
1456
|
+
maxArgs: 1,
|
|
1457
|
+
run: ([range]) => {
|
|
1458
|
+
if (isSheetError(range)) return range;
|
|
1459
|
+
const r = asRange(range);
|
|
1460
|
+
return name === "ROWS" ? r.rows : r.cols;
|
|
1461
|
+
}
|
|
1462
|
+
})),
|
|
1463
|
+
math("PMT", (rate, nper, pv, fv = 0, type = 0) => nper === 0 ? NUM : payment(rate, nper, pv, fv, type ? 1 : 0), 3, 5),
|
|
1464
|
+
math("FV", (rate, nper, pmt, pv = 0, type = 0) => {
|
|
1465
|
+
if (rate === 0) return -(pv + pmt * nper);
|
|
1466
|
+
const grow = (1 + rate) ** nper;
|
|
1467
|
+
return -(pv * grow + pmt * (1 + rate * (type ? 1 : 0)) * (grow - 1) / rate);
|
|
1468
|
+
}, 3, 5),
|
|
1469
|
+
math("PV", (rate, nper, pmt, fv = 0, type = 0) => {
|
|
1470
|
+
if (rate === 0) return -(fv + pmt * nper);
|
|
1471
|
+
const grow = (1 + rate) ** nper;
|
|
1472
|
+
return -(fv + pmt * (1 + rate * (type ? 1 : 0)) * (grow - 1) / rate) / grow;
|
|
1473
|
+
}, 3, 5),
|
|
1474
|
+
defineSheetFunction({
|
|
1475
|
+
name: "NPV",
|
|
1476
|
+
minArgs: 2,
|
|
1477
|
+
run: ([rate, ...values]) => {
|
|
1478
|
+
const r = num(rate);
|
|
1479
|
+
const ns = numbersOf(values);
|
|
1480
|
+
const error = firstError(r, ns);
|
|
1481
|
+
if (error) return error;
|
|
1482
|
+
if (r === -1) return DIV0;
|
|
1483
|
+
let total = 0;
|
|
1484
|
+
ns.forEach((n, i) => {
|
|
1485
|
+
total += n / (1 + r) ** (i + 1);
|
|
1486
|
+
});
|
|
1487
|
+
return finite(total);
|
|
1488
|
+
}
|
|
1489
|
+
})
|
|
1490
|
+
];
|
|
1491
|
+
export { builtinFunctions, criteriaOf, defineSheetFunction, numbersOf, scalarOf };
|
|
1492
|
+
|
|
1493
|
+
//# sourceMappingURL=functions.js.map
|