@trebco/treb 30.1.2 → 30.2.4
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/dist/languages/treb-i18n-da.mjs +1 -0
- package/dist/languages/treb-i18n-de.mjs +1 -0
- package/dist/languages/treb-i18n-es.mjs +1 -0
- package/dist/languages/treb-i18n-fr.mjs +1 -0
- package/dist/languages/treb-i18n-it.mjs +1 -0
- package/dist/languages/treb-i18n-nl.mjs +1 -0
- package/dist/languages/treb-i18n-no.mjs +1 -0
- package/dist/languages/treb-i18n-pl.mjs +1 -0
- package/dist/languages/treb-i18n-pt.mjs +1 -0
- package/dist/languages/treb-i18n-sv.mjs +1 -0
- package/dist/treb-spreadsheet-light.mjs +11 -11
- package/dist/treb-spreadsheet.mjs +11 -11
- package/dist/treb.d.ts +15 -1
- package/esbuild-custom-element.mjs +23 -1
- package/esbuild-utils.mjs +1 -0
- package/package.json +1 -1
- package/treb-base-types/src/area.ts +2 -1
- package/treb-calculator/src/calculator.ts +4 -3
- package/treb-calculator/src/descriptors.ts +4 -2
- package/treb-calculator/src/expression-calculator.ts +76 -3
- package/treb-calculator/src/functions/base-functions.ts +319 -3
- package/treb-embed/src/embedded-spreadsheet.ts +90 -7
- package/treb-embed/src/options.ts +18 -0
- package/treb-embed/src/plugin.ts +31 -0
- package/treb-grid/src/editors/autocomplete_matcher.ts +8 -1
- package/treb-grid/src/types/grid.ts +8 -0
package/dist/treb.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! API v30.
|
|
1
|
+
/*! API v30.2. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* add our tag to the map
|
|
@@ -282,6 +282,17 @@ export interface EmbeddedSpreadsheetOptions {
|
|
|
282
282
|
* starting in 30.1.0. set to false to disable.
|
|
283
283
|
*/
|
|
284
284
|
spill?: boolean;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* language. at the moment this controls spreadsheet function names
|
|
288
|
+
* only; the plan is to expand to the rest of the interface over time.
|
|
289
|
+
* should be an ISO 639-1 language code, like "en", "fr" or "sv" (case
|
|
290
|
+
* insensitive). we only support a limited subset of languages at the
|
|
291
|
+
* moment.
|
|
292
|
+
*
|
|
293
|
+
* leave blank or set to "locale" to use the current locale.
|
|
294
|
+
*/
|
|
295
|
+
language?: string;
|
|
285
296
|
}
|
|
286
297
|
|
|
287
298
|
/**
|
|
@@ -393,6 +404,9 @@ export declare class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
393
404
|
*/
|
|
394
405
|
ExternalEditor(config?: Partial<ExternalEditorConfig>): void;
|
|
395
406
|
|
|
407
|
+
/** dynamically load language module */
|
|
408
|
+
LoadLanguage(language?: string): Promise<void>;
|
|
409
|
+
|
|
396
410
|
/**
|
|
397
411
|
* Use this function to batch multiple document changes. Essentially the
|
|
398
412
|
* grid stops broadcasting events for the duration of the function call,
|
|
@@ -5,7 +5,7 @@ import * as esbuild from 'esbuild';
|
|
|
5
5
|
import { SassPlugin, WorkerPlugin, NotifyPlugin, HTMLPlugin } from './esbuild-utils.mjs';
|
|
6
6
|
import { promises as fs } from 'fs';
|
|
7
7
|
|
|
8
|
-
import pkg from './package.json'
|
|
8
|
+
import pkg from './package.json' with { type: 'json' };
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -58,6 +58,26 @@ for (let i = 0; i < process.argv.length; i++) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* thanks to
|
|
63
|
+
* https://github.com/evanw/esbuild/issues/3337#issuecomment-2085394950
|
|
64
|
+
*/
|
|
65
|
+
function RewriteIgnoredImports() {
|
|
66
|
+
return {
|
|
67
|
+
name: 'RewriteIgnoredImports',
|
|
68
|
+
setup(build) {
|
|
69
|
+
build.onEnd(async (result) => {
|
|
70
|
+
if (result.outputFiles) {
|
|
71
|
+
for (const file of result.outputFiles) {
|
|
72
|
+
const { path, text } = file;
|
|
73
|
+
await fs.writeFile(path, text.replace(/esbuild-ignore-import:/, ''));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
/** @type esbuild.BuildOptions */
|
|
62
82
|
const build_options = {
|
|
63
83
|
entryPoints: [
|
|
@@ -78,7 +98,9 @@ const build_options = {
|
|
|
78
98
|
'process.env.BUILD_VERSION': `"${pkg.version}"`,
|
|
79
99
|
'process.env.BUILD_NAME': `"${pkg.name}"`,
|
|
80
100
|
},
|
|
101
|
+
write: false,
|
|
81
102
|
plugins: [
|
|
103
|
+
RewriteIgnoredImports(),
|
|
82
104
|
NotifyPlugin(),
|
|
83
105
|
WorkerPlugin(options),
|
|
84
106
|
HTMLPlugin(options),
|
package/esbuild-utils.mjs
CHANGED
package/package.json
CHANGED
|
@@ -200,7 +200,8 @@ export class Area implements IArea {
|
|
|
200
200
|
+ (address.absolute_column ? '$' : '')
|
|
201
201
|
+ this.ColumnToLabel(address.column)
|
|
202
202
|
+ (address.absolute_row ? '$' : '')
|
|
203
|
-
+ (address.row + 1)
|
|
203
|
+
+ (address.row + 1)
|
|
204
|
+
+ (address.spill ? '#' : '');
|
|
204
205
|
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -36,7 +36,8 @@ import * as Utilities from './utilities';
|
|
|
36
36
|
|
|
37
37
|
import { FunctionLibrary } from './function-library';
|
|
38
38
|
import type { FunctionMap } from './descriptors';
|
|
39
|
-
import
|
|
39
|
+
// import * as Utils from './utilities';
|
|
40
|
+
|
|
40
41
|
import { AltFunctionLibrary, BaseFunctionLibrary } from './functions/base-functions';
|
|
41
42
|
import { FinanceFunctionLibrary } from './functions/finance-functions';
|
|
42
43
|
import { TextFunctionLibrary, TextFunctionAliases } from './functions/text-functions';
|
|
@@ -666,7 +667,7 @@ export class Calculator extends Graph {
|
|
|
666
667
|
name: 'width', },
|
|
667
668
|
|
|
668
669
|
],
|
|
669
|
-
return_type:
|
|
670
|
+
return_type: 'reference',
|
|
670
671
|
volatile: true,
|
|
671
672
|
fn: ((reference: UnionValue, rows = 0, columns = 0, height?: number, width?: number): UnionValue => {
|
|
672
673
|
|
|
@@ -758,7 +759,7 @@ export class Calculator extends Graph {
|
|
|
758
759
|
arguments: [
|
|
759
760
|
{ name: 'reference', description: 'Cell reference (string)' },
|
|
760
761
|
],
|
|
761
|
-
return_type:
|
|
762
|
+
return_type: 'reference',
|
|
762
763
|
volatile: true, // necessary?
|
|
763
764
|
fn: ((reference: string): UnionValue => {
|
|
764
765
|
|
|
@@ -23,9 +23,11 @@ import type { RenderFunction, ClickFunction, UnionValue } from 'treb-base-types'
|
|
|
23
23
|
|
|
24
24
|
// FIXME: at least some of this could move to base types
|
|
25
25
|
|
|
26
|
+
/*
|
|
26
27
|
export enum ReturnType {
|
|
27
28
|
value, reference
|
|
28
29
|
}
|
|
30
|
+
*/
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* descriptor for an individual argument
|
|
@@ -161,9 +163,9 @@ export interface CompositeFunctionDescriptor {
|
|
|
161
163
|
xlfn?: boolean;
|
|
162
164
|
|
|
163
165
|
/**
|
|
164
|
-
* support returning references
|
|
166
|
+
* support returning references. 'value' is default. FIXME: could just be a bool?
|
|
165
167
|
*/
|
|
166
|
-
return_type?:
|
|
168
|
+
return_type?: 'reference'|'value';
|
|
167
169
|
|
|
168
170
|
/**
|
|
169
171
|
* @internal
|
|
@@ -32,7 +32,6 @@ import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
|
|
|
32
32
|
UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference } from 'treb-parser';
|
|
33
33
|
import type { DataModel, MacroFunction, Sheet } from 'treb-data-model';
|
|
34
34
|
import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError } from './function-error';
|
|
35
|
-
import { ReturnType } from './descriptors';
|
|
36
35
|
|
|
37
36
|
import * as Primitives from './primitives';
|
|
38
37
|
|
|
@@ -194,7 +193,27 @@ export class ExpressionCalculator {
|
|
|
194
193
|
|
|
195
194
|
switch (arg.type) {
|
|
196
195
|
case 'address':
|
|
197
|
-
|
|
196
|
+
|
|
197
|
+
if (arg.spill) {
|
|
198
|
+
let sheet: Sheet|undefined;
|
|
199
|
+
if (arg.sheet_id) {
|
|
200
|
+
sheet = this.data_model.sheets.Find(arg.sheet_id);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!sheet) {
|
|
204
|
+
console.error('missing sheet [da6]');
|
|
205
|
+
return ReferenceError();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const cell_data = sheet.CellData(arg);
|
|
209
|
+
if (cell_data.spill) {
|
|
210
|
+
range = cell_data.spill;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
address = arg;
|
|
216
|
+
}
|
|
198
217
|
break;
|
|
199
218
|
|
|
200
219
|
case 'range':
|
|
@@ -541,7 +560,7 @@ export class ExpressionCalculator {
|
|
|
541
560
|
return argument_error;
|
|
542
561
|
}
|
|
543
562
|
|
|
544
|
-
if (func.return_type ===
|
|
563
|
+
if (func.return_type === 'reference') {
|
|
545
564
|
|
|
546
565
|
const result = func.fn.apply(null, mapped_args);
|
|
547
566
|
|
|
@@ -714,6 +733,41 @@ export class ExpressionCalculator {
|
|
|
714
733
|
|
|
715
734
|
}
|
|
716
735
|
|
|
736
|
+
/**
|
|
737
|
+
* convert expr to a cell address, possibly calculating the contents.
|
|
738
|
+
* returns single address only (ranges with len > 1 will fail)
|
|
739
|
+
*/
|
|
740
|
+
protected AsReference(expr: ExpressionUnit): ICellAddress|undefined {
|
|
741
|
+
|
|
742
|
+
switch (expr.type) {
|
|
743
|
+
case 'address':
|
|
744
|
+
return expr;
|
|
745
|
+
|
|
746
|
+
case 'range':
|
|
747
|
+
if (expr.start.row === expr.end.row && expr.start.column === expr.end.column) {
|
|
748
|
+
return expr.start;
|
|
749
|
+
}
|
|
750
|
+
break;
|
|
751
|
+
|
|
752
|
+
default:
|
|
753
|
+
{
|
|
754
|
+
const union = this.CalculateExpression(expr as ExtendedExpressionUnit, true);
|
|
755
|
+
if (UnionIsExpressionUnit(union)) {
|
|
756
|
+
if (union.value.type === 'address') {
|
|
757
|
+
return union.value;
|
|
758
|
+
}
|
|
759
|
+
if (union.value.type === 'range' && union.value.start.row === union.value.end.row && union.value.start.column === union.value.end.column) {
|
|
760
|
+
return union.value.start;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return undefined;
|
|
768
|
+
|
|
769
|
+
}
|
|
770
|
+
|
|
717
771
|
protected BinaryExpression(x: UnitBinary): (expr: UnitBinary) => UnionValue /*UnionOrArray*/ {
|
|
718
772
|
|
|
719
773
|
// we are constructing and caching functions for binary expressions.
|
|
@@ -731,6 +785,25 @@ export class ExpressionCalculator {
|
|
|
731
785
|
const fn = Primitives.MapOperator(x.operator);
|
|
732
786
|
|
|
733
787
|
if (!fn) {
|
|
788
|
+
|
|
789
|
+
// support dynamically-constructed ranges, as long as the
|
|
790
|
+
// arguments are both addresses (we might see ranges, but
|
|
791
|
+
// if they are 1x1 then we can accept them)
|
|
792
|
+
|
|
793
|
+
if (x.operator === ':') {
|
|
794
|
+
return (expr: UnitBinary) => {
|
|
795
|
+
|
|
796
|
+
const start = this.AsReference(expr.left);
|
|
797
|
+
const end = this.AsReference(expr.right);
|
|
798
|
+
|
|
799
|
+
if (start && end) {
|
|
800
|
+
return this.CellFunction4(start, end);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return ExpressionError();
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
734
807
|
return () => { // expr: UnitBinary) => {
|
|
735
808
|
console.info(`(unexpected binary operator: ${x.operator})`);
|
|
736
809
|
return ExpressionError();
|
|
@@ -24,7 +24,7 @@ import * as Utils from '../utilities';
|
|
|
24
24
|
import { ReferenceError, NotImplError, NAError, ArgumentError, DivideByZeroError, ValueError } from '../function-error';
|
|
25
25
|
import type { UnionValue,
|
|
26
26
|
RenderFunctionResult, RenderFunctionOptions, Complex, CellValue } from 'treb-base-types';
|
|
27
|
-
import { Box, ValueType, GetValueType, ComplexOrReal, IsComplex } from 'treb-base-types';
|
|
27
|
+
import { Box, ValueType, GetValueType, ComplexOrReal, IsComplex, Area } from 'treb-base-types';
|
|
28
28
|
import { Sparkline } from './sparkline';
|
|
29
29
|
import { LotusDate, UnlotusDate } from 'treb-format';
|
|
30
30
|
|
|
@@ -33,6 +33,7 @@ import { UnionIsMetadata } from '../expression-calculator';
|
|
|
33
33
|
|
|
34
34
|
import { Exp as ComplexExp, Power as ComplexPower, Multiply as ComplexMultply } from '../complex-math';
|
|
35
35
|
import { CoerceComplex } from './function-utilities';
|
|
36
|
+
import type { UnitAddress, UnitRange } from 'treb-parser';
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* BaseFunctionLibrary is a static object that has basic spreadsheet
|
|
@@ -1180,7 +1181,11 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1180
1181
|
},
|
|
1181
1182
|
*/
|
|
1182
1183
|
|
|
1184
|
+
|
|
1183
1185
|
/*
|
|
1186
|
+
* rewrite of xlookup to return a reference. better compatibility.
|
|
1187
|
+
* ---
|
|
1188
|
+
*
|
|
1184
1189
|
* unsaid anywhere (that I can locate) aboud XLOOKUP is that lookup
|
|
1185
1190
|
* array must be one-dimensional. it can be either a row or a column,
|
|
1186
1191
|
* but one dimension must be one. that simplifies things quite a bit.
|
|
@@ -1193,21 +1198,331 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1193
1198
|
arguments: [
|
|
1194
1199
|
{ name: 'Lookup value', },
|
|
1195
1200
|
{ name: 'Lookup array', },
|
|
1196
|
-
{ name: 'Return array',
|
|
1201
|
+
{ name: 'Return array', metadata: true, },
|
|
1202
|
+
{ name: 'Not found', boxed: true },
|
|
1203
|
+
{ name: 'Match mode', default: 0, },
|
|
1204
|
+
{ name: 'Search mode', default: 1, },
|
|
1205
|
+
],
|
|
1206
|
+
return_type: 'reference',
|
|
1207
|
+
xlfn: true,
|
|
1208
|
+
fn: (
|
|
1209
|
+
lookup_value: IntrinsicValue,
|
|
1210
|
+
lookup_array: IntrinsicValue[][],
|
|
1211
|
+
return_array: UnionValue,
|
|
1212
|
+
not_found?: UnionValue,
|
|
1213
|
+
match_mode = 0,
|
|
1214
|
+
search_mode = 1,
|
|
1215
|
+
): UnionValue => {
|
|
1216
|
+
|
|
1217
|
+
////////
|
|
1218
|
+
|
|
1219
|
+
if (!return_array) {
|
|
1220
|
+
return ArgumentError();
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// const parse_result = this.parser.Parse(reference);
|
|
1224
|
+
// if (parse_result.error || !parse_result.expression) {
|
|
1225
|
+
// return ReferenceError;
|
|
1226
|
+
//}
|
|
1227
|
+
|
|
1228
|
+
let rng: Area|undefined;
|
|
1229
|
+
|
|
1230
|
+
if (return_array.type === ValueType.array) {
|
|
1231
|
+
// console.info({return_array});
|
|
1232
|
+
|
|
1233
|
+
const arr = return_array.value;
|
|
1234
|
+
const r = arr.length;
|
|
1235
|
+
const c = arr[0].length;
|
|
1236
|
+
|
|
1237
|
+
const start = arr[0][0];
|
|
1238
|
+
const end = arr[r-1][c-1];
|
|
1239
|
+
|
|
1240
|
+
if (UnionIsMetadata(start) && UnionIsMetadata(end)) {
|
|
1241
|
+
rng = new Area(start.value.address, end.value.address);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (!rng) {
|
|
1247
|
+
console.info('invalid range');
|
|
1248
|
+
return ReferenceError();
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// console.info({rng});
|
|
1252
|
+
|
|
1253
|
+
/*
|
|
1254
|
+
if (return_array.type === ValueType.array) {
|
|
1255
|
+
|
|
1256
|
+
// subset array. this is constructed, so we can take ownership
|
|
1257
|
+
// and modify it, although it would be safer to copy. also, what's
|
|
1258
|
+
// the cost of functional vs imperative loops these days?
|
|
1259
|
+
|
|
1260
|
+
const end_row = typeof height === 'number' ? (rows + height) : undefined;
|
|
1261
|
+
const end_column = typeof width === 'number' ? (columns + width) : undefined;
|
|
1262
|
+
|
|
1263
|
+
const result: UnionValue = {
|
|
1264
|
+
type: ValueType.array,
|
|
1265
|
+
value: reference.value.slice(rows, end_row).map(row => row.slice(columns, end_column)),
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
return result;
|
|
1269
|
+
|
|
1270
|
+
}
|
|
1271
|
+
*/
|
|
1272
|
+
|
|
1273
|
+
// FIXME: we could I suppose be more graceful about single values
|
|
1274
|
+
// if passed instead of arrays
|
|
1275
|
+
|
|
1276
|
+
if (!Array.isArray(lookup_array)) {
|
|
1277
|
+
console.info("lookup is not an array");
|
|
1278
|
+
return ValueError();
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
const first = lookup_array[0];
|
|
1282
|
+
if (!Array.isArray(first)) {
|
|
1283
|
+
console.info("lookup is not a 2d array");
|
|
1284
|
+
return ValueError();
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (lookup_array.length !== 1 && first.length !== 1) {
|
|
1288
|
+
console.info("lookup array has invalid dimensions");
|
|
1289
|
+
return ValueError();
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// FIXME: is it required that the return array be (at least) the
|
|
1293
|
+
// same size? we can return undefineds, but maybe we should error
|
|
1294
|
+
|
|
1295
|
+
/*
|
|
1296
|
+
if (!Array.isArray(return_array)) {
|
|
1297
|
+
console.info("return array is not an array");
|
|
1298
|
+
return ValueError();
|
|
1299
|
+
}
|
|
1300
|
+
*/
|
|
1301
|
+
|
|
1302
|
+
const transpose = (lookup_array.length === 1);
|
|
1303
|
+
if (transpose) {
|
|
1304
|
+
lookup_array = Utils.TransposeArray(lookup_array);
|
|
1305
|
+
// return_array = Utils.TransposeArray(return_array);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// maybe reverse...
|
|
1309
|
+
|
|
1310
|
+
if (search_mode < 0) {
|
|
1311
|
+
lookup_array.reverse();
|
|
1312
|
+
// return_array.reverse();
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
//
|
|
1316
|
+
// return value at index, transpose if necessary, and return
|
|
1317
|
+
// an array. we might prefer to return a scalar if there's only
|
|
1318
|
+
// one value, not sure what's the intended behavior
|
|
1319
|
+
//
|
|
1320
|
+
const ReturnIndex = (rng: Area, index: number): UnionValue => {
|
|
1321
|
+
|
|
1322
|
+
// console.info("transpose?", transpose, {rng}, 'shape', rng.rows, rng.columns);
|
|
1323
|
+
|
|
1324
|
+
let start: UnitAddress|undefined;
|
|
1325
|
+
let end: UnitAddress|undefined;
|
|
1326
|
+
|
|
1327
|
+
// I guess "transpose" in this context means "return a row from column(s)"? rename
|
|
1328
|
+
|
|
1329
|
+
if (transpose) {
|
|
1330
|
+
|
|
1331
|
+
if (search_mode < 0) {
|
|
1332
|
+
index = rng.rows - 1 - index; // invert FIXME: test
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if (index >= 0 && index < rng.rows) {
|
|
1336
|
+
start = {
|
|
1337
|
+
type: 'address',
|
|
1338
|
+
position: 0, id: 1, label: '',
|
|
1339
|
+
row: rng.start.row + index,
|
|
1340
|
+
column: rng.start.column,
|
|
1341
|
+
sheet_id: rng.start.sheet_id,
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
end = {
|
|
1345
|
+
type: 'address',
|
|
1346
|
+
position: 0, id: 2, label: '',
|
|
1347
|
+
row: rng.start.row + index,
|
|
1348
|
+
column: rng.end.column,
|
|
1349
|
+
sheet_id: rng.start.sheet_id,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
}
|
|
1354
|
+
else {
|
|
1355
|
+
|
|
1356
|
+
if (search_mode < 0) {
|
|
1357
|
+
index = rng.columns - 1 - index; // invert FIXME: test
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
if (index >= 0 && index < rng.columns) {
|
|
1361
|
+
start = {
|
|
1362
|
+
type: 'address',
|
|
1363
|
+
position: 0, id: 1, label: '',
|
|
1364
|
+
row: rng.start.row,
|
|
1365
|
+
column: rng.start.column + index,
|
|
1366
|
+
sheet_id: rng.start.sheet_id,
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
end = {
|
|
1370
|
+
type: 'address',
|
|
1371
|
+
position: 0, id: 2, label: '',
|
|
1372
|
+
row: rng.end.row,
|
|
1373
|
+
column: rng.start.column + index,
|
|
1374
|
+
sheet_id: rng.start.sheet_id,
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
if (start && end) {
|
|
1380
|
+
|
|
1381
|
+
const expr: UnitRange = {
|
|
1382
|
+
type: 'range',
|
|
1383
|
+
position: 0,
|
|
1384
|
+
id: 0,
|
|
1385
|
+
label: '',
|
|
1386
|
+
start, end,
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
// console.info({expr});
|
|
1390
|
+
|
|
1391
|
+
return {
|
|
1392
|
+
type: ValueType.object,
|
|
1393
|
+
value: expr,
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
return { type: ValueType.undefined };
|
|
1399
|
+
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
// if value is not a string, then we can ignore wildcards.
|
|
1403
|
+
// in that case convert to exact match.
|
|
1404
|
+
|
|
1405
|
+
if (match_mode === 2 && typeof lookup_value !== 'string') {
|
|
1406
|
+
match_mode = 0;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// what does inexact matching mean in this case if the lookup
|
|
1410
|
+
// value is a string or boolean? (...)
|
|
1411
|
+
|
|
1412
|
+
if ((match_mode === 1 || match_mode === -1) && typeof lookup_value === 'number') {
|
|
1413
|
+
|
|
1414
|
+
let min_delta = 0;
|
|
1415
|
+
let index = -1;
|
|
1416
|
+
|
|
1417
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1418
|
+
const value = lookup_array[i][0];
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
if (typeof value === 'number') {
|
|
1422
|
+
|
|
1423
|
+
// check for exact match first, just in case
|
|
1424
|
+
if (value === lookup_value) {
|
|
1425
|
+
return ReturnIndex(rng, i);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const delta = Math.abs(value - lookup_value);
|
|
1429
|
+
|
|
1430
|
+
if ((match_mode === 1 && value > lookup_value) || (match_mode === -1 && value < lookup_value)){
|
|
1431
|
+
if (index < 0 || delta < min_delta) {
|
|
1432
|
+
min_delta = delta;
|
|
1433
|
+
index = i;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (index >= 0) {
|
|
1441
|
+
return ReturnIndex(rng, index);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
switch (match_mode) {
|
|
1447
|
+
|
|
1448
|
+
case 2:
|
|
1449
|
+
{
|
|
1450
|
+
// wildcard string match. we only handle strings for
|
|
1451
|
+
// this case (see above).
|
|
1452
|
+
|
|
1453
|
+
const pattern = Utils.ParseWildcards(lookup_value?.toString() || '');
|
|
1454
|
+
const regex = new RegExp('^' + pattern + '$', 'i'); //.exec(lookup_value);
|
|
1455
|
+
|
|
1456
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1457
|
+
const value = lookup_array[i][0];
|
|
1458
|
+
if (typeof value === 'string' && regex.exec(value)) {
|
|
1459
|
+
return ReturnIndex(rng, i);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
}
|
|
1464
|
+
break;
|
|
1465
|
+
|
|
1466
|
+
case 0:
|
|
1467
|
+
if (typeof lookup_value === 'string') {
|
|
1468
|
+
lookup_value = lookup_value.toLowerCase();
|
|
1469
|
+
}
|
|
1470
|
+
for (let i = 0; i < lookup_array.length; i++) {
|
|
1471
|
+
let value = lookup_array[i][0];
|
|
1472
|
+
|
|
1473
|
+
if (typeof value === 'string') {
|
|
1474
|
+
value = value.toLowerCase();
|
|
1475
|
+
}
|
|
1476
|
+
if (value === lookup_value) {
|
|
1477
|
+
return ReturnIndex(rng, i);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
// FIXME: if we're expecting to return an array maybe we should
|
|
1486
|
+
// pack it up as an array? if it's not already an array? (...)
|
|
1487
|
+
|
|
1488
|
+
return (not_found && not_found.type !== ValueType.undefined) ? not_found : NAError();
|
|
1489
|
+
|
|
1490
|
+
},
|
|
1491
|
+
},
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
/*
|
|
1495
|
+
* unsaid anywhere (that I can locate) aboud XLOOKUP is that lookup
|
|
1496
|
+
* array must be one-dimensional. it can be either a row or a column,
|
|
1497
|
+
* but one dimension must be one. that simplifies things quite a bit.
|
|
1498
|
+
*
|
|
1499
|
+
* there's a note in the docs about binary search over the data --
|
|
1500
|
+
* that might explain how inexact VLOOKUP works as well. seems an odd
|
|
1501
|
+
* choice but maybe back in the day it made sense
|
|
1502
|
+
* /
|
|
1503
|
+
XLOOKUP: {
|
|
1504
|
+
arguments: [
|
|
1505
|
+
{ name: 'Lookup value', },
|
|
1506
|
+
{ name: 'Lookup array', },
|
|
1507
|
+
{ name: 'Return array', address: true, },
|
|
1197
1508
|
{ name: 'Not found', boxed: true },
|
|
1198
1509
|
{ name: 'Match mode', default: 0, },
|
|
1199
1510
|
{ name: 'Search mode', default: 1, },
|
|
1200
1511
|
],
|
|
1512
|
+
return_type: 'reference',
|
|
1201
1513
|
xlfn: true,
|
|
1202
1514
|
fn: (
|
|
1203
1515
|
lookup_value: IntrinsicValue,
|
|
1204
1516
|
lookup_array: IntrinsicValue[][],
|
|
1205
|
-
return_array:
|
|
1517
|
+
return_array: string,
|
|
1206
1518
|
not_found?: UnionValue,
|
|
1207
1519
|
match_mode = 0,
|
|
1208
1520
|
search_mode = 1,
|
|
1209
1521
|
): UnionValue => {
|
|
1210
1522
|
|
|
1523
|
+
console.info({return_array});
|
|
1524
|
+
|
|
1525
|
+
|
|
1211
1526
|
// FIXME: we could I suppose be more graceful about single values
|
|
1212
1527
|
// if passed instead of arrays
|
|
1213
1528
|
|
|
@@ -1368,6 +1683,7 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1368
1683
|
|
|
1369
1684
|
},
|
|
1370
1685
|
},
|
|
1686
|
+
*/
|
|
1371
1687
|
|
|
1372
1688
|
/**
|
|
1373
1689
|
* copied from HLOOKUP, fix that one first
|