@trebco/treb 30.2.4 → 30.2.10
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/treb-spreadsheet-light.mjs +11 -11
- package/dist/treb-spreadsheet.mjs +11 -11
- package/package.json +1 -1
- package/treb-calculator/src/calculator.ts +41 -0
- package/treb-calculator/src/functions/base-functions.ts +164 -1
- package/treb-calculator/src/functions/statistics-functions.ts +134 -0
- package/treb-calculator/src/functions/text-functions.ts +45 -0
- package/treb-calculator/src/utilities.ts +5 -1
- package/treb-data-model/src/data_model.ts +227 -2
- package/treb-data-model/src/index.ts +1 -0
- package/{treb-embed → treb-data-model}/src/language-model.ts +3 -0
- package/treb-data-model/src/sheet.ts +0 -13
- package/treb-embed/src/embedded-spreadsheet.ts +12 -36
- package/treb-grid/src/render/tile_renderer.ts +14 -0
- package/treb-grid/src/types/grid.ts +25 -139
- package/treb-parser/src/parser-types.ts +12 -0
- package/treb-parser/src/parser.ts +43 -2
- package/tsproject.json +1 -1
package/package.json
CHANGED
|
@@ -33,6 +33,7 @@ import type { CalculationResult } from './dag/spreadsheet_vertex_base';
|
|
|
33
33
|
|
|
34
34
|
import { ExpressionCalculator, UnionIsMetadata } from './expression-calculator';
|
|
35
35
|
import * as Utilities from './utilities';
|
|
36
|
+
import { StringUnion } from './utilities';
|
|
36
37
|
|
|
37
38
|
import { FunctionLibrary } from './function-library';
|
|
38
39
|
import type { FunctionMap } from './descriptors';
|
|
@@ -567,6 +568,46 @@ export class Calculator extends Graph {
|
|
|
567
568
|
},
|
|
568
569
|
},
|
|
569
570
|
|
|
571
|
+
Address: {
|
|
572
|
+
arguments: [
|
|
573
|
+
{ name: 'row' },
|
|
574
|
+
{ name: 'column' },
|
|
575
|
+
{ name: 'absolute' },
|
|
576
|
+
{ name: 'a1' },
|
|
577
|
+
{ name: 'sheet name'}
|
|
578
|
+
],
|
|
579
|
+
fn: (row = 1, column = 1, absolute = 1, a1 = true, sheet_name?: string): UnionValue => {
|
|
580
|
+
|
|
581
|
+
const address: UnitAddress = {
|
|
582
|
+
type: 'address',
|
|
583
|
+
id: 0, position: 0, label: '',
|
|
584
|
+
row: row-1,
|
|
585
|
+
column: column-1,
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
switch (absolute) {
|
|
589
|
+
case 2:
|
|
590
|
+
address.absolute_row = true;
|
|
591
|
+
break;
|
|
592
|
+
case 3:
|
|
593
|
+
address.absolute_column = true;
|
|
594
|
+
break;
|
|
595
|
+
case 4:
|
|
596
|
+
break;
|
|
597
|
+
default:
|
|
598
|
+
address.absolute_column = true;
|
|
599
|
+
address.absolute_row = true;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (sheet_name) {
|
|
603
|
+
address.sheet = sheet_name;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return StringUnion(this.parser.Render(address, { r1c1: !a1 }));
|
|
607
|
+
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
|
|
570
611
|
/**
|
|
571
612
|
* anything I said about COUNTIF applies here, but worse.
|
|
572
613
|
* COUNTIFS is an AND operation across separate COUNTIFs.
|
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
|
|
22
22
|
import type { FunctionMap, IntrinsicValue } from '../descriptors';
|
|
23
23
|
import * as Utils from '../utilities';
|
|
24
|
+
// import { StringUnion, NumberUnion } from '../utilities';
|
|
24
25
|
import { ReferenceError, NotImplError, NAError, ArgumentError, DivideByZeroError, ValueError } from '../function-error';
|
|
25
26
|
import type { UnionValue,
|
|
26
|
-
RenderFunctionResult, RenderFunctionOptions, Complex, CellValue
|
|
27
|
+
RenderFunctionResult, RenderFunctionOptions, Complex, CellValue,
|
|
28
|
+
// ICellAddress
|
|
29
|
+
} from 'treb-base-types';
|
|
27
30
|
import { Box, ValueType, GetValueType, ComplexOrReal, IsComplex, Area } from 'treb-base-types';
|
|
28
31
|
import { Sparkline } from './sparkline';
|
|
29
32
|
import { LotusDate, UnlotusDate } from 'treb-format';
|
|
@@ -318,6 +321,15 @@ export const AltFunctionLibrary: FunctionMap = {
|
|
|
318
321
|
|
|
319
322
|
export const BaseFunctionLibrary: FunctionMap = {
|
|
320
323
|
|
|
324
|
+
// not sure why there are functions for booleans, but there are
|
|
325
|
+
True: {
|
|
326
|
+
fn: () => ({ type: ValueType.boolean, value: true }),
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
False: {
|
|
330
|
+
fn: () => ({ type: ValueType.boolean, value: false }),
|
|
331
|
+
},
|
|
332
|
+
|
|
321
333
|
Int: {
|
|
322
334
|
fn: (value: number) => {
|
|
323
335
|
return {type: ValueType.number, value: Math.floor(value) };
|
|
@@ -585,6 +597,37 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
585
597
|
},
|
|
586
598
|
},
|
|
587
599
|
|
|
600
|
+
IsErr: {
|
|
601
|
+
description: 'Checks if another cell contains an error',
|
|
602
|
+
arguments: [{ name: 'reference', allow_error: true, boxed: true }],
|
|
603
|
+
fn: (...args: UnionValue[]): UnionValue => {
|
|
604
|
+
|
|
605
|
+
const values = Utils.FlattenBoxed(args);
|
|
606
|
+
for (const value of values) {
|
|
607
|
+
if (value.type === ValueType.error && value.value !== 'N/A') {
|
|
608
|
+
return { type: ValueType.boolean, value: true };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/*
|
|
613
|
+
if (Array.isArray(ref)) {
|
|
614
|
+
const values = Utils.Flatten(ref) as UnionValue[];
|
|
615
|
+
for (const value of values) {
|
|
616
|
+
if (value.type === ValueType.error) {
|
|
617
|
+
return { type: ValueType.boolean, value: true };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else if (ref) {
|
|
622
|
+
return { type: ValueType.boolean, value: ref.type === ValueType.error };
|
|
623
|
+
}
|
|
624
|
+
*/
|
|
625
|
+
|
|
626
|
+
return { type: ValueType.boolean, value: false };
|
|
627
|
+
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
|
|
588
631
|
IsError: {
|
|
589
632
|
description: 'Checks if another cell contains an error',
|
|
590
633
|
arguments: [{ name: 'reference', allow_error: true, boxed: true }],
|
|
@@ -1182,6 +1225,126 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1182
1225
|
*/
|
|
1183
1226
|
|
|
1184
1227
|
|
|
1228
|
+
Row: {
|
|
1229
|
+
arguments: [{ name: 'reference', metadata: true }],
|
|
1230
|
+
fn: (ref: UnionValue): UnionValue => {
|
|
1231
|
+
if (ref.type === ValueType.array) {
|
|
1232
|
+
const arr = ref.value;
|
|
1233
|
+
const first = arr[0][0];
|
|
1234
|
+
|
|
1235
|
+
if (UnionIsMetadata(first)) {
|
|
1236
|
+
return {
|
|
1237
|
+
type: ValueType.array,
|
|
1238
|
+
value: [arr[0].map((row, index) => ({
|
|
1239
|
+
type: ValueType.number,
|
|
1240
|
+
value: index + first.value.address.row + 1
|
|
1241
|
+
}))],
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
}
|
|
1246
|
+
else if (UnionIsMetadata(ref)) {
|
|
1247
|
+
return {
|
|
1248
|
+
type: ValueType.number, value: ref.value.address.row + 1,
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return ArgumentError();
|
|
1252
|
+
},
|
|
1253
|
+
},
|
|
1254
|
+
|
|
1255
|
+
Column: {
|
|
1256
|
+
arguments: [{ name: 'reference', metadata: true }],
|
|
1257
|
+
fn: (ref: UnionValue): UnionValue => {
|
|
1258
|
+
if (ref.type === ValueType.array) {
|
|
1259
|
+
const arr = ref.value;
|
|
1260
|
+
const first = arr[0][0];
|
|
1261
|
+
|
|
1262
|
+
if (UnionIsMetadata(first)) {
|
|
1263
|
+
return {
|
|
1264
|
+
type: ValueType.array,
|
|
1265
|
+
value: arr.map((row, index) => [{
|
|
1266
|
+
type: ValueType.number,
|
|
1267
|
+
value: index + first.value.address.column + 1
|
|
1268
|
+
}]),
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
}
|
|
1273
|
+
else if (UnionIsMetadata(ref)) {
|
|
1274
|
+
return {
|
|
1275
|
+
type: ValueType.number, value: ref.value.address.row + 1,
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return ArgumentError();
|
|
1279
|
+
},
|
|
1280
|
+
},
|
|
1281
|
+
|
|
1282
|
+
Choose: {
|
|
1283
|
+
arguments: [
|
|
1284
|
+
{ name: 'Selected index', },
|
|
1285
|
+
{ name: 'Choice 1...', metadata: true },
|
|
1286
|
+
],
|
|
1287
|
+
return_type: 'reference',
|
|
1288
|
+
description: 'Returns one of a list of choices',
|
|
1289
|
+
fn: (selected: number, ...choices: UnionValue[]): UnionValue => {
|
|
1290
|
+
|
|
1291
|
+
if (selected < 1 || selected > choices.length) {
|
|
1292
|
+
return ValueError();
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const value = choices[selected - 1];
|
|
1296
|
+
|
|
1297
|
+
// this should be metadata. is there a different object we
|
|
1298
|
+
// might run into? maybe we should refactor how metadata works
|
|
1299
|
+
|
|
1300
|
+
if (UnionIsMetadata(value)) {
|
|
1301
|
+
return {
|
|
1302
|
+
type: ValueType.object,
|
|
1303
|
+
value: value.value.address,
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// check if array is metadata. if it's a literal array
|
|
1308
|
+
// we just want to return it.
|
|
1309
|
+
|
|
1310
|
+
if (value.type === ValueType.array) {
|
|
1311
|
+
const arr = value.value;
|
|
1312
|
+
const rows = arr.length;
|
|
1313
|
+
const cols = arr[0].length;
|
|
1314
|
+
const first = arr[0][0];
|
|
1315
|
+
const last = arr[rows - 1][cols - 1];
|
|
1316
|
+
|
|
1317
|
+
if (rows === 1 && cols === 1) {
|
|
1318
|
+
if (UnionIsMetadata(first)) {
|
|
1319
|
+
return {
|
|
1320
|
+
type: ValueType.object,
|
|
1321
|
+
value: first.value.address,
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
if (UnionIsMetadata(first) && UnionIsMetadata(last)) {
|
|
1327
|
+
return {
|
|
1328
|
+
type: ValueType.object,
|
|
1329
|
+
value: {
|
|
1330
|
+
type: 'range',
|
|
1331
|
+
position: 0, id: 0, label: '',
|
|
1332
|
+
start: first.value.address,
|
|
1333
|
+
end: last.value.address,
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
return {
|
|
1342
|
+
...value, // should we deep-copy in case of an array?
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
},
|
|
1346
|
+
},
|
|
1347
|
+
|
|
1185
1348
|
/*
|
|
1186
1349
|
* rewrite of xlookup to return a reference. better compatibility.
|
|
1187
1350
|
* ---
|
|
@@ -260,6 +260,140 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
260
260
|
},
|
|
261
261
|
},
|
|
262
262
|
|
|
263
|
+
LCM: {
|
|
264
|
+
description: 'Retuns the least common mulitple of the arguments',
|
|
265
|
+
arguments: [{ boxed: true }],
|
|
266
|
+
fn: (...args: UnionValue[]): UnionValue => {
|
|
267
|
+
args = Utils.FlattenBoxed(args);
|
|
268
|
+
const list: number[] = [];
|
|
269
|
+
|
|
270
|
+
for (const ref of args as UnionValue[]) {
|
|
271
|
+
if (ref.type === ValueType.error) {
|
|
272
|
+
return ref;
|
|
273
|
+
}
|
|
274
|
+
if (ref.type === ValueType.number) {
|
|
275
|
+
list.push(ref.value);
|
|
276
|
+
}
|
|
277
|
+
else if (ref.type !== ValueType.undefined ) {
|
|
278
|
+
console.info("RT", ref.type);
|
|
279
|
+
return ArgumentError();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const gcd = (x: number, y: number): number => {
|
|
284
|
+
if (x % y == 0) {
|
|
285
|
+
return y; // Base case
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
return gcd(y, x % y); // Recusrsive case
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (list.length === 0) {
|
|
293
|
+
return { type: ValueType.number, value: 0 };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let lcm = list[0];
|
|
297
|
+
|
|
298
|
+
for (let i = 1; i < list.length; i++) {
|
|
299
|
+
lcm = (lcm * list[i]) / gcd(lcm, list[i]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
type: ValueType.number,
|
|
304
|
+
value: lcm,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
GCD: {
|
|
311
|
+
description: 'Finds the greatest common divisor of the arguments',
|
|
312
|
+
arguments: [{ boxed: true }],
|
|
313
|
+
fn: (...args: UnionValue[]): UnionValue => {
|
|
314
|
+
args = Utils.FlattenBoxed(args);
|
|
315
|
+
const list: number[] = [];
|
|
316
|
+
|
|
317
|
+
for (const ref of args as UnionValue[]) {
|
|
318
|
+
if (ref.type === ValueType.error) {
|
|
319
|
+
return ref;
|
|
320
|
+
}
|
|
321
|
+
if (ref.type === ValueType.number) {
|
|
322
|
+
list.push(ref.value);
|
|
323
|
+
}
|
|
324
|
+
else if (ref.type !== ValueType.undefined ) {
|
|
325
|
+
console.info("RT", ref.type);
|
|
326
|
+
return ArgumentError();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const gcd = (x: number, y: number): number => {
|
|
331
|
+
if (x % y == 0) {
|
|
332
|
+
return y; // Base case
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
return gcd(y, x % y); // Recusrsive case
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
if (list.length === 0) {
|
|
340
|
+
return ValueError();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (list.length === 1) {
|
|
344
|
+
return {
|
|
345
|
+
type: ValueType.number,
|
|
346
|
+
value: list[0],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let value = list[0];
|
|
351
|
+
for (let i = 1; i < list.length; i++) {
|
|
352
|
+
value = gcd(value, list[i]);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
type: ValueType.number,
|
|
357
|
+
value,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
HarMean: {
|
|
365
|
+
description: 'Returns the harmonic mean of the arguments',
|
|
366
|
+
arguments: [{ boxed: true }],
|
|
367
|
+
fn: (...args: UnionValue[]): UnionValue => {
|
|
368
|
+
args = Utils.FlattenBoxed(args);
|
|
369
|
+
const result = { real: 0, imaginary: 0 };
|
|
370
|
+
let count = 0;
|
|
371
|
+
|
|
372
|
+
for (const ref of args as UnionValue[]) {
|
|
373
|
+
if (ref.type === ValueType.error) {
|
|
374
|
+
return ref;
|
|
375
|
+
}
|
|
376
|
+
if (ref.type === ValueType.number) {
|
|
377
|
+
result.real += (1/ref.value);
|
|
378
|
+
count++;
|
|
379
|
+
}
|
|
380
|
+
if (ref.type === ValueType.complex) {
|
|
381
|
+
const reciprocal = ComplexMath.Divide({real: 1, imaginary: 0}, ref.value);
|
|
382
|
+
result.real += reciprocal.real;
|
|
383
|
+
result.imaginary += reciprocal.imaginary;
|
|
384
|
+
count++;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (result.imaginary) {
|
|
389
|
+
return { type: ValueType.complex, value: ComplexMath.Divide({real: count, imaginary: 0}, result), };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { type: ValueType.number, value: count/result.real };
|
|
393
|
+
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
|
|
263
397
|
Average: {
|
|
264
398
|
|
|
265
399
|
description: 'Returns the arithmetic mean of all numeric arguments',
|
|
@@ -120,6 +120,51 @@ export const TextFunctionLibrary: FunctionMap = {
|
|
|
120
120
|
},
|
|
121
121
|
},
|
|
122
122
|
|
|
123
|
+
Len: {
|
|
124
|
+
arguments: [
|
|
125
|
+
{ name: 'string', unroll: true },
|
|
126
|
+
],
|
|
127
|
+
fn: (str: string): UnionValue => {
|
|
128
|
+
return {
|
|
129
|
+
type: ValueType.number,
|
|
130
|
+
value: str.toString().length,
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
Substitute: {
|
|
136
|
+
arguments: [
|
|
137
|
+
{ name: 'text' },
|
|
138
|
+
{ name: 'search' },
|
|
139
|
+
{ name: 'replacement' },
|
|
140
|
+
{ name: 'index' },
|
|
141
|
+
],
|
|
142
|
+
fn: (text: string, search: string, replacement: string, index?: number): UnionValue => {
|
|
143
|
+
if (typeof index === 'number') {
|
|
144
|
+
if (index < 1) {
|
|
145
|
+
return ValueError();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let counter = 1;
|
|
149
|
+
return {
|
|
150
|
+
type: ValueType.string,
|
|
151
|
+
value: text.replaceAll(search, (...args) => {
|
|
152
|
+
console.info(args);
|
|
153
|
+
return (counter++) === index ? replacement : search;
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
return {
|
|
160
|
+
type: ValueType.string,
|
|
161
|
+
value: text.replaceAll(search, replacement),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
|
|
123
168
|
Left: {
|
|
124
169
|
arguments: [
|
|
125
170
|
{ name: 'string' },
|
|
@@ -352,4 +352,8 @@ export const ParseWildcards = (text: string): string => {
|
|
|
352
352
|
|
|
353
353
|
return result.join('');
|
|
354
354
|
|
|
355
|
-
};
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export const StringUnion = (value: string): UnionValue => ({ type: ValueType.string, value });
|
|
358
|
+
export const NumberUnion = (value: number): UnionValue => ({ type: ValueType.number, value });
|
|
359
|
+
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
import type { Sheet } from './sheet';
|
|
23
23
|
import { SheetCollection } from './sheet_collection';
|
|
24
24
|
import { type UnitAddress, type UnitStructuredReference, type UnitRange, Parser, QuotedSheetNameRegex, DecimalMarkType, ArgumentSeparatorType } from 'treb-parser';
|
|
25
|
-
import type { IArea, ICellAddress, Table, CellStyle } from 'treb-base-types';
|
|
25
|
+
import type { IArea, ICellAddress, Table, CellStyle, CellValue } from 'treb-base-types';
|
|
26
|
+
import { Is2DArray } from 'treb-base-types';
|
|
26
27
|
import { Area, IsCellAddress, Style } from 'treb-base-types';
|
|
27
28
|
import type { SerializedNamed } from './named';
|
|
28
29
|
import { NamedRangeManager } from './named';
|
|
29
30
|
import type { ConnectedElementType, MacroFunction } from './types';
|
|
30
|
-
|
|
31
|
+
import type { LanguageModel } from './language-model';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
*
|
|
@@ -36,6 +37,9 @@ export class DataModel {
|
|
|
36
37
|
|
|
37
38
|
public readonly parser: Parser = new Parser();
|
|
38
39
|
|
|
40
|
+
/** moved from embedded spreadsheet */
|
|
41
|
+
public language_model?: LanguageModel;
|
|
42
|
+
|
|
39
43
|
/** document metadata */
|
|
40
44
|
public document_name?: string;
|
|
41
45
|
|
|
@@ -523,5 +527,226 @@ export class DataModel {
|
|
|
523
527
|
*/
|
|
524
528
|
public connected_elements: Map<number, ConnectedElementType> = new Map();
|
|
525
529
|
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
// --- moving translation here ----------------------------------------------
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* maps common language (english) -> local language. this should
|
|
537
|
+
* be passed in (actually set via a function).
|
|
538
|
+
*/
|
|
539
|
+
private language_map?: Record<string, string>;
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* maps local language -> common (english). this should be constructed
|
|
543
|
+
* when the forward function is passed in, so there's a 1-1 correspondence.
|
|
544
|
+
*/
|
|
545
|
+
private reverse_language_map?: Record<string, string>;
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* set the language translation map. this is a set of function names
|
|
550
|
+
* (in english) -> the local equivalent. both should be in canonical form,
|
|
551
|
+
* as that will be used when we translate one way or the other.
|
|
552
|
+
*/
|
|
553
|
+
public SetLanguageMap(language_map?: Record<string, string>) {
|
|
554
|
+
|
|
555
|
+
if (!language_map) {
|
|
556
|
+
this.language_map = this.reverse_language_map = undefined;
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
|
|
560
|
+
const keys = Object.keys(language_map);
|
|
561
|
+
|
|
562
|
+
// normalize forward
|
|
563
|
+
this.language_map = {};
|
|
564
|
+
for (const key of keys) {
|
|
565
|
+
this.language_map[key.toUpperCase()] = language_map[key];
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// normalize backward
|
|
569
|
+
this.reverse_language_map = {};
|
|
570
|
+
for (const key of keys) {
|
|
571
|
+
const value = language_map[key];
|
|
572
|
+
this.reverse_language_map[value.toUpperCase()] = key;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/*
|
|
578
|
+
// we might need to update the current displayed selection. depends
|
|
579
|
+
// on when we expect languages to be set.
|
|
580
|
+
|
|
581
|
+
if (!this.primary_selection.empty) {
|
|
582
|
+
this.Select(this.primary_selection, this.primary_selection.area, this.primary_selection.target);
|
|
583
|
+
}
|
|
584
|
+
*/
|
|
585
|
+
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* translate function from common (english) -> local language. this could
|
|
590
|
+
* be inlined (assuming it's only called in one place), but we are breaking
|
|
591
|
+
* it out so we can develop/test/manage it.
|
|
592
|
+
*/
|
|
593
|
+
public TranslateFunction(value: string): string {
|
|
594
|
+
if (this.language_map) {
|
|
595
|
+
return this.TranslateInternal(value, this.language_map, this.language_model?.boolean_true, this.language_model?.boolean_false);
|
|
596
|
+
}
|
|
597
|
+
return value;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* translate from local language -> common (english).
|
|
602
|
+
* @see TranslateFunction
|
|
603
|
+
*/
|
|
604
|
+
public UntranslateFunction(value: string): string {
|
|
605
|
+
if (this.reverse_language_map) {
|
|
606
|
+
return this.TranslateInternal(value, this.reverse_language_map, 'TRUE', 'FALSE');
|
|
607
|
+
}
|
|
608
|
+
return value;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
public UntranslateData(value: CellValue|CellValue[]|CellValue[][]): CellValue|CellValue[]|CellValue[][] {
|
|
612
|
+
|
|
613
|
+
if (Array.isArray(value)) {
|
|
614
|
+
|
|
615
|
+
// could be 1d or 2d. typescript is complaining, not sure why...
|
|
616
|
+
|
|
617
|
+
if (Is2DArray(value)) {
|
|
618
|
+
return value.map(row => row.map(entry => {
|
|
619
|
+
if (entry && typeof entry === 'string' && entry[0] === '=') {
|
|
620
|
+
return this.UntranslateFunction(entry);
|
|
621
|
+
}
|
|
622
|
+
return entry;
|
|
623
|
+
}));
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
return value.map(entry => {
|
|
627
|
+
if (entry && typeof entry === 'string' && entry[0] === '=') {
|
|
628
|
+
return this.UntranslateFunction(entry);
|
|
629
|
+
}
|
|
630
|
+
return entry;
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
}
|
|
635
|
+
else if (value && typeof value === 'string' && value[0] === '=') {
|
|
636
|
+
|
|
637
|
+
// single value
|
|
638
|
+
value = this.UntranslateFunction(value);
|
|
639
|
+
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return value;
|
|
643
|
+
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* translation back and forth is the same operation, with a different
|
|
649
|
+
* (inverted) map. although it still might be worth inlining depending
|
|
650
|
+
* on cost.
|
|
651
|
+
*
|
|
652
|
+
* FIXME: it's about time we started using proper maps, we dropped
|
|
653
|
+
* support for IE11 some time ago.
|
|
654
|
+
*/
|
|
655
|
+
private TranslateInternal(value: string, map: Record<string, string>, boolean_true?: string, boolean_false?: string): string {
|
|
656
|
+
|
|
657
|
+
const parse_result = this.parser.Parse(value);
|
|
658
|
+
|
|
659
|
+
if (parse_result.expression) {
|
|
660
|
+
|
|
661
|
+
let modified = false;
|
|
662
|
+
this.parser.Walk(parse_result.expression, unit => {
|
|
663
|
+
if (unit.type === 'call') {
|
|
664
|
+
const replacement = map[unit.name.toUpperCase()];
|
|
665
|
+
if (replacement) {
|
|
666
|
+
modified = true;
|
|
667
|
+
unit.name = replacement;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else if (unit.type === 'literal' && typeof unit.value === 'boolean') {
|
|
671
|
+
|
|
672
|
+
// to/from english/locale depends on the direction, but we're not
|
|
673
|
+
// passing that in? FIXME, pass it as a parameter. it doesn't matter
|
|
674
|
+
// here, but later when we render.
|
|
675
|
+
|
|
676
|
+
modified = true;
|
|
677
|
+
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return true;
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (modified) {
|
|
684
|
+
return '=' + this.parser.Render(parse_result.expression, {
|
|
685
|
+
missing: '', boolean_true, boolean_false,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return value;
|
|
691
|
+
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* this is not public _yet_
|
|
697
|
+
*
|
|
698
|
+
* @internal
|
|
699
|
+
*/
|
|
700
|
+
public SetLanguage(model?: LanguageModel): void {
|
|
701
|
+
|
|
702
|
+
this.language_model = model;
|
|
703
|
+
|
|
704
|
+
if (!model) {
|
|
705
|
+
this.SetLanguageMap(); // clear
|
|
706
|
+
|
|
707
|
+
// set defaults for parsing.
|
|
708
|
+
|
|
709
|
+
this.parser.flags.boolean_true = 'TRUE';
|
|
710
|
+
this.parser.flags.boolean_false = 'FALSE';
|
|
711
|
+
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
|
|
715
|
+
// create a name map for grid
|
|
716
|
+
|
|
717
|
+
const map: Record< string, string > = {};
|
|
718
|
+
|
|
719
|
+
if (model.functions) {
|
|
720
|
+
for (const entry of model.functions || []) {
|
|
721
|
+
map[entry.base.toUpperCase()] = entry.name; // toUpperCase because of a data error -- fix at the source
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
this.SetLanguageMap(map);
|
|
726
|
+
|
|
727
|
+
// console.info({map});
|
|
728
|
+
|
|
729
|
+
if (!model.boolean_false) {
|
|
730
|
+
model.boolean_false = map['FALSE'];
|
|
731
|
+
}
|
|
732
|
+
if (!model.boolean_true) {
|
|
733
|
+
model.boolean_true = map['TRUE'];
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// set defaults for parsing.
|
|
737
|
+
|
|
738
|
+
this.parser.flags.boolean_true = model.boolean_true || 'TRUE';
|
|
739
|
+
this.parser.flags.boolean_false = model.boolean_false || 'FALSE';
|
|
740
|
+
|
|
741
|
+
// console.info("booleans:", this.model.parser.flags.boolean_true, ",", this.model.parser.flags.boolean_false)
|
|
742
|
+
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
for (const sheet of this.sheets.list) {
|
|
746
|
+
sheet.FlushCellStyles();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
}
|
|
750
|
+
|
|
526
751
|
}
|
|
527
752
|
|