@trebco/treb 30.6.3 → 30.8.2
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-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +9 -9
- package/dist/treb.d.ts +1 -1
- package/package.json +1 -1
- package/treb-calculator/src/calculator.ts +29 -6
- package/treb-calculator/src/expression-calculator.ts +107 -3
- package/treb-calculator/src/functions/base-functions.ts +78 -1
- package/treb-calculator/src/functions/sparkline.ts +20 -0
- package/treb-calculator/src/functions/statistics-functions.ts +2 -0
- package/treb-data-model/src/conditional_format.ts +4 -1
- package/treb-data-model/src/sheet.ts +4 -3
- package/treb-embed/src/embedded-spreadsheet.ts +56 -0
- package/treb-export/src/import2.ts +75 -6
- package/treb-export/src/workbook2.ts +5 -0
- package/treb-grid/src/editors/editor.ts +1 -1
- package/treb-parser/src/parser.ts +14 -4
package/dist/treb.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -2383,6 +2383,9 @@ export class Calculator extends Graph {
|
|
|
2383
2383
|
// is it also (3) adding unecessary calculations (building the expression,
|
|
2384
2384
|
// below)?
|
|
2385
2385
|
|
|
2386
|
+
// NOTE: moving all conditional formats into EN-US (i.e. dot-separated).
|
|
2387
|
+
// make sure to evaluate them in this format.
|
|
2388
|
+
|
|
2386
2389
|
if (!list) {
|
|
2387
2390
|
|
|
2388
2391
|
// we could in theory remove all of the leaves (the ones we know to
|
|
@@ -2416,8 +2419,15 @@ export class Calculator extends Graph {
|
|
|
2416
2419
|
let expression = '';
|
|
2417
2420
|
|
|
2418
2421
|
switch (entry.type) {
|
|
2422
|
+
|
|
2419
2423
|
case 'cell-match':
|
|
2420
|
-
|
|
2424
|
+
if (entry.between) {
|
|
2425
|
+
const addr = this.Unresolve(entry.area, context, true, false);
|
|
2426
|
+
expression = `BETWEEN(${[addr, ...entry.between].join(', ')})`;
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
|
|
2430
|
+
}
|
|
2421
2431
|
break;
|
|
2422
2432
|
|
|
2423
2433
|
case 'expression':
|
|
@@ -2440,7 +2450,7 @@ export class Calculator extends Graph {
|
|
|
2440
2450
|
entry.min ?? '',
|
|
2441
2451
|
entry.max ?? '',
|
|
2442
2452
|
|
|
2443
|
-
].join(
|
|
2453
|
+
].join(',')
|
|
2444
2454
|
})`;
|
|
2445
2455
|
break;
|
|
2446
2456
|
|
|
@@ -2464,13 +2474,17 @@ export class Calculator extends Graph {
|
|
|
2464
2474
|
|
|
2465
2475
|
entry.internal.vertex = vertex;
|
|
2466
2476
|
|
|
2467
|
-
let options: EvaluateOptions
|
|
2477
|
+
let options: EvaluateOptions = {
|
|
2478
|
+
argument_separator: ',',
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2468
2481
|
if (entry.type !== 'gradient' && entry.type !== 'duplicate-values') {
|
|
2469
|
-
options = entry.options;
|
|
2482
|
+
options = {...entry.options, ...options};
|
|
2470
2483
|
}
|
|
2471
2484
|
|
|
2472
2485
|
// first pass, run the calculation
|
|
2473
2486
|
const check = this.Evaluate(expression, context, options, true);
|
|
2487
|
+
|
|
2474
2488
|
entry.internal.vertex.result = check;
|
|
2475
2489
|
entry.internal.vertex.updated = true;
|
|
2476
2490
|
|
|
@@ -2478,7 +2492,7 @@ export class Calculator extends Graph {
|
|
|
2478
2492
|
|
|
2479
2493
|
const vertex = entry.internal.vertex as LeafVertex;
|
|
2480
2494
|
this.AddLeafVertex(vertex);
|
|
2481
|
-
this.UpdateLeafVertex(vertex, expression, context);
|
|
2495
|
+
this.UpdateLeafVertex(vertex, expression, context, DecimalMarkType.Period); // force en-us
|
|
2482
2496
|
|
|
2483
2497
|
}
|
|
2484
2498
|
|
|
@@ -2846,10 +2860,15 @@ export class Calculator extends Graph {
|
|
|
2846
2860
|
return dependencies;
|
|
2847
2861
|
}
|
|
2848
2862
|
|
|
2849
|
-
protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet): void {
|
|
2863
|
+
protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet, decimal_mark?: DecimalMarkType): void {
|
|
2850
2864
|
|
|
2851
2865
|
vertex.Reset();
|
|
2852
2866
|
|
|
2867
|
+
if (decimal_mark) {
|
|
2868
|
+
this.parser.Save();
|
|
2869
|
+
this.parser.SetLocaleSettings(decimal_mark);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2853
2872
|
const parse_result = this.parser.Parse(formula);
|
|
2854
2873
|
if (parse_result.expression) {
|
|
2855
2874
|
const dependencies =
|
|
@@ -2895,6 +2914,10 @@ export class Calculator extends Graph {
|
|
|
2895
2914
|
|
|
2896
2915
|
// vertex.UpdateState();
|
|
2897
2916
|
|
|
2917
|
+
if (decimal_mark) {
|
|
2918
|
+
this.parser.Restore();
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2898
2921
|
}
|
|
2899
2922
|
|
|
2900
2923
|
/**
|
|
@@ -31,7 +31,7 @@ import { ValueType, GetValueType, Area } from 'treb-base-types';
|
|
|
31
31
|
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
|
-
import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError } from './function-error';
|
|
34
|
+
import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError, ValueError } from './function-error';
|
|
35
35
|
|
|
36
36
|
import * as Primitives from './primitives';
|
|
37
37
|
|
|
@@ -633,7 +633,7 @@ export class ExpressionCalculator {
|
|
|
633
633
|
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
protected UnaryExpression(x: UnitUnary): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
636
|
+
protected UnaryExpression(x: UnitUnary, return_reference = false): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
637
637
|
|
|
638
638
|
// there are basically three code paths here: negate, identity, and error.
|
|
639
639
|
// they have very different semantics so we're going to do them completely
|
|
@@ -663,6 +663,110 @@ export class ExpressionCalculator {
|
|
|
663
663
|
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
+
case '@':
|
|
667
|
+
return (expr: UnitUnary) => {
|
|
668
|
+
|
|
669
|
+
// if the operand is a range, then we need to do implicit intersection.
|
|
670
|
+
// otherwise, calculate the expression and return the first value
|
|
671
|
+
// if it's an array.
|
|
672
|
+
|
|
673
|
+
let address: UnitAddress|undefined;
|
|
674
|
+
|
|
675
|
+
switch (expr.operand.type) {
|
|
676
|
+
case 'address':
|
|
677
|
+
if (expr.operand.spill) {
|
|
678
|
+
|
|
679
|
+
// we need to calculate the result so we know how large the
|
|
680
|
+
// range is... perhaps there's a way to look this up without
|
|
681
|
+
// calculating? (TODO/FIXME)
|
|
682
|
+
|
|
683
|
+
const calculated = this.CellFunction2(expr.operand)();
|
|
684
|
+
if (calculated.type === ValueType.array) {
|
|
685
|
+
|
|
686
|
+
const row = this.context.address.row ?? -1;
|
|
687
|
+
const column = this.context.address.column ?? -1;
|
|
688
|
+
|
|
689
|
+
// for this verison we already have the result, so unless
|
|
690
|
+
// we're trying to preserve the address, we could just
|
|
691
|
+
// return it
|
|
692
|
+
|
|
693
|
+
if (row >= expr.operand.row && row < expr.operand.row + calculated.value[0]?.length && calculated.value.length === 1) {
|
|
694
|
+
|
|
695
|
+
if (!return_reference) {
|
|
696
|
+
return calculated.value[0][row - expr.operand.row];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
address = {
|
|
700
|
+
...expr.operand,
|
|
701
|
+
row,
|
|
702
|
+
spill: false,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
else if (column >= expr.operand.column && column < expr.operand.column + calculated.value.length && calculated.value[0]?.length === 1) {
|
|
706
|
+
|
|
707
|
+
if (!return_reference) {
|
|
708
|
+
return calculated.value[column - expr.operand.column][0];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
address = {
|
|
712
|
+
...expr.operand,
|
|
713
|
+
column,
|
|
714
|
+
spill: false,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
return ValueError(); // out of range
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// return { type: ValueType.string, value: 'implicit (spill)' };
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
|
|
727
|
+
case 'range':
|
|
728
|
+
{
|
|
729
|
+
// how do we intersect, if at all?
|
|
730
|
+
|
|
731
|
+
const row = this.context.address.row ?? -1;
|
|
732
|
+
const column = this.context.address.column ?? -1;
|
|
733
|
+
if (row >= expr.operand.start.row && row <= expr.operand.end.row && expr.operand.start.column === expr.operand.end.column) {
|
|
734
|
+
address = {
|
|
735
|
+
...expr.operand.start,
|
|
736
|
+
row,
|
|
737
|
+
spill: false,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
else if (column >= expr.operand.start.column && column <= expr.operand.end.column && expr.operand.start.row === expr.operand.end.row) {
|
|
741
|
+
address = {
|
|
742
|
+
...expr.operand.start,
|
|
743
|
+
column,
|
|
744
|
+
spill: false,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
return ValueError(); // out of range
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (address) {
|
|
754
|
+
if (return_reference) {
|
|
755
|
+
return { type: ValueType.object, value: address,
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return this.CellFunction2(address)();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const operand = this.CalculateExpression(expr.operand as ExtendedExpressionUnit);
|
|
762
|
+
|
|
763
|
+
if (operand.type === ValueType.array) {
|
|
764
|
+
return operand.value[0][0];
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return operand;
|
|
768
|
+
};
|
|
769
|
+
|
|
666
770
|
default:
|
|
667
771
|
return () => {
|
|
668
772
|
console.warn('unexpected unary operator:', x.operator);
|
|
@@ -977,7 +1081,7 @@ export class ExpressionCalculator {
|
|
|
977
1081
|
return (expr.fn = this.BinaryExpression(expr))(expr); // check
|
|
978
1082
|
|
|
979
1083
|
case 'unary':
|
|
980
|
-
return (expr.fn = this.UnaryExpression(expr))(expr); // check
|
|
1084
|
+
return (expr.fn = this.UnaryExpression(expr, return_reference))(expr); // check
|
|
981
1085
|
|
|
982
1086
|
case 'identifier':
|
|
983
1087
|
return (expr.fn = this.Identifier(expr))(); // check
|
|
@@ -244,6 +244,23 @@ const ZLookup = (value: number|string|boolean|undefined, table: (number|string|b
|
|
|
244
244
|
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
+
const NumberArgument = (argument?: UnionValue, default_value: number|false = false) => {
|
|
248
|
+
|
|
249
|
+
if (!argument) {
|
|
250
|
+
return default_value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
switch (argument.type) {
|
|
254
|
+
case ValueType.number:
|
|
255
|
+
return argument.value;
|
|
256
|
+
case ValueType.undefined:
|
|
257
|
+
return default_value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return false;
|
|
261
|
+
|
|
262
|
+
};
|
|
263
|
+
|
|
247
264
|
/**
|
|
248
265
|
* alternate functions. these are used (atm) only for changing complex
|
|
249
266
|
* behavior.
|
|
@@ -1923,6 +1940,17 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1923
1940
|
},
|
|
1924
1941
|
},
|
|
1925
1942
|
|
|
1943
|
+
Ceiling: {
|
|
1944
|
+
arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
|
|
1945
|
+
|
|
1946
|
+
fn: (a: number) => {
|
|
1947
|
+
return {
|
|
1948
|
+
type: ValueType.number,
|
|
1949
|
+
value: Math.ceil(a),
|
|
1950
|
+
};
|
|
1951
|
+
},
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1926
1954
|
Round: {
|
|
1927
1955
|
arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
|
|
1928
1956
|
|
|
@@ -2358,6 +2386,21 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2358
2386
|
},
|
|
2359
2387
|
},
|
|
2360
2388
|
|
|
2389
|
+
Between: {
|
|
2390
|
+
arguments: [
|
|
2391
|
+
{ name: 'target', boxed: true, unroll: true },
|
|
2392
|
+
{ name: 'min' },
|
|
2393
|
+
{ name: 'max' },
|
|
2394
|
+
],
|
|
2395
|
+
visibility: 'internal',
|
|
2396
|
+
fn: (target: UnionValue, min = 0, max = 1) => {
|
|
2397
|
+
return {
|
|
2398
|
+
type: ValueType.boolean,
|
|
2399
|
+
value: (target.type === ValueType.number) && (target.value >= min && target.value <= max),
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
|
|
2361
2404
|
Gradient: {
|
|
2362
2405
|
arguments: [
|
|
2363
2406
|
{ name: 'range', boxed: true },
|
|
@@ -2509,7 +2552,41 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2509
2552
|
return ArgumentError();
|
|
2510
2553
|
|
|
2511
2554
|
},
|
|
2512
|
-
}
|
|
2555
|
+
},
|
|
2556
|
+
|
|
2557
|
+
Sequence: {
|
|
2558
|
+
arguments:[
|
|
2559
|
+
{ name: 'rows', boxed: true },
|
|
2560
|
+
{ name: 'columns', default: 1, boxed: true },
|
|
2561
|
+
{ name: 'start', default: 1, boxed: true },
|
|
2562
|
+
{ name: 'step', default: 1, boxed: true }
|
|
2563
|
+
],
|
|
2564
|
+
fn: (rows: UnionValue, columns: UnionValue, start: UnionValue, step: UnionValue) => {
|
|
2565
|
+
|
|
2566
|
+
const rx = NumberArgument(rows, 1);
|
|
2567
|
+
const cx = NumberArgument(columns, 1);
|
|
2568
|
+
const step_ = NumberArgument(step, 1);
|
|
2569
|
+
const start_ = NumberArgument(start, 1);
|
|
2570
|
+
|
|
2571
|
+
if (rx === false || cx === false || step_ === false || start_ === false) {
|
|
2572
|
+
return ArgumentError();
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
const value: UnionValue[][] = [];
|
|
2576
|
+
for (let c = 0; c < cx; c++) {
|
|
2577
|
+
const col: UnionValue[] = [];
|
|
2578
|
+
for (let r = 0; r < rx; r++) {
|
|
2579
|
+
col.push({ type: ValueType.number, value: start_ + r * step_ * cx + c * step_ });
|
|
2580
|
+
}
|
|
2581
|
+
value.push(col);
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
return { type: ValueType.array, value };
|
|
2585
|
+
|
|
2586
|
+
},
|
|
2587
|
+
|
|
2588
|
+
},
|
|
2589
|
+
|
|
2513
2590
|
|
|
2514
2591
|
};
|
|
2515
2592
|
|
|
@@ -173,6 +173,16 @@ export class Sparkline {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
if (min === max) {
|
|
177
|
+
if (min) {
|
|
178
|
+
min -= 1;
|
|
179
|
+
max += 1;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
max += 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
176
186
|
if (min !== max) {
|
|
177
187
|
|
|
178
188
|
const step = (width * (1 - 2 * x_margin)) / (values.length - 1);
|
|
@@ -251,6 +261,16 @@ export class Sparkline {
|
|
|
251
261
|
}
|
|
252
262
|
}
|
|
253
263
|
|
|
264
|
+
if (min === max) {
|
|
265
|
+
if (min) {
|
|
266
|
+
min -= 1;
|
|
267
|
+
max += 1;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
max += 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
254
274
|
if (values.length) {
|
|
255
275
|
|
|
256
276
|
// const step = (width - 2 * x_margin - 2) / (values.length-1);
|
|
@@ -685,6 +685,8 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
685
685
|
export const StatisticsFunctionAliases: {[index: string]: string} = {
|
|
686
686
|
Mean: 'Average',
|
|
687
687
|
'StDev': 'StDev.S',
|
|
688
|
+
'StDevA': 'StDev.S',
|
|
689
|
+
'StDevPA': 'StDev.P',
|
|
688
690
|
'Var': 'Var.S',
|
|
689
691
|
'Quartile': 'Quartile.Inc',
|
|
690
692
|
};
|
|
@@ -139,6 +139,7 @@ export interface ConditionalFormatCellMatch extends ConditionalFormatCellMatchOp
|
|
|
139
139
|
export interface ConditionalFormatCellMatchOptions {
|
|
140
140
|
style: CellStyle;
|
|
141
141
|
expression: string;
|
|
142
|
+
between?: [number, number];
|
|
142
143
|
options?: EvaluateOptions;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -178,8 +179,10 @@ export interface ConditionalFormatDuplicateValues extends ConditionalFormatDupli
|
|
|
178
179
|
*
|
|
179
180
|
* ...everybody has a vertex now, we could standardize it
|
|
180
181
|
*
|
|
182
|
+
* update: adding a priority field, optional
|
|
183
|
+
*
|
|
181
184
|
*/
|
|
182
|
-
export type ConditionalFormat = { internal?: unknown } & (
|
|
185
|
+
export type ConditionalFormat = { internal?: unknown, priority?: number } & (
|
|
183
186
|
ConditionalFormatDuplicateValues |
|
|
184
187
|
ConditionalFormatExpression |
|
|
185
188
|
ConditionalFormatCellMatch |
|
|
@@ -3338,9 +3338,6 @@ export class Sheet {
|
|
|
3338
3338
|
for (const format of this.conditional_formats) {
|
|
3339
3339
|
|
|
3340
3340
|
if (format.internal?.vertex?.updated) {
|
|
3341
|
-
|
|
3342
|
-
// console.info('updated');
|
|
3343
|
-
|
|
3344
3341
|
format.internal.vertex.updated = false;
|
|
3345
3342
|
}
|
|
3346
3343
|
|
|
@@ -3354,6 +3351,10 @@ export class Sheet {
|
|
|
3354
3351
|
// stop rule. if you go forwards, you need some sort of indicator
|
|
3355
3352
|
// or flag).
|
|
3356
3353
|
|
|
3354
|
+
// there's more to this, because there are rules that apply to areas,
|
|
3355
|
+
// which might stop, and there's priority. so we probably need those
|
|
3356
|
+
// flags eventually.
|
|
3357
|
+
|
|
3357
3358
|
const area = JSON.parse(JSON.stringify(format.area));
|
|
3358
3359
|
|
|
3359
3360
|
if (area.start.row === null || area.end.row === null) {
|
|
@@ -4840,6 +4840,53 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4840
4840
|
return reject(event.data.error || 'unknown error');
|
|
4841
4841
|
}
|
|
4842
4842
|
|
|
4843
|
+
if (this.parser.decimal_mark !== DecimalMarkType.Period) {
|
|
4844
|
+
|
|
4845
|
+
// console.info("IMPORT WARNING 2", event.data);
|
|
4846
|
+
|
|
4847
|
+
// FIXME: unify w/ the convert locale method
|
|
4848
|
+
|
|
4849
|
+
const target_decimal_mark = this.parser.decimal_mark;
|
|
4850
|
+
const target_argument_separator = this.parser.argument_separator;
|
|
4851
|
+
this.parser.Save();
|
|
4852
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
4853
|
+
|
|
4854
|
+
const translate = (formula: string): string | undefined => {
|
|
4855
|
+
const parse_result = this.parser.Parse(formula);
|
|
4856
|
+
if (!parse_result.expression) { return undefined; }
|
|
4857
|
+
return '=' + this.parser.Render(
|
|
4858
|
+
parse_result.expression, {
|
|
4859
|
+
missing: '',
|
|
4860
|
+
convert_decimal: target_decimal_mark,
|
|
4861
|
+
convert_argument_separator: target_argument_separator,
|
|
4862
|
+
});
|
|
4863
|
+
};
|
|
4864
|
+
|
|
4865
|
+
for (const named of event.data.results) {
|
|
4866
|
+
named.expression = translate(named.expression);
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
for (const sheet of event.data.results.sheets || []) {
|
|
4870
|
+
|
|
4871
|
+
for (const cell of sheet.cells || []) {
|
|
4872
|
+
if (cell.type === 'formula' && cell.value) {
|
|
4873
|
+
cell.value = translate(cell.value);
|
|
4874
|
+
}
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
if (sheet.annotations){
|
|
4878
|
+
for (const annotation of sheet.annotations) {
|
|
4879
|
+
if (annotation.formula) {
|
|
4880
|
+
annotation.formula = translate(annotation.formula);
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4886
|
+
this.parser.Restore();
|
|
4887
|
+
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4843
4890
|
this.grid.FromImportData(event.data.results);
|
|
4844
4891
|
|
|
4845
4892
|
this.ResetInternal();
|
|
@@ -5914,6 +5961,15 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
5914
5961
|
}
|
|
5915
5962
|
}
|
|
5916
5963
|
|
|
5964
|
+
if (data.named) {
|
|
5965
|
+
for (const named of data.named) {
|
|
5966
|
+
const translated = translate(named.expression);
|
|
5967
|
+
if (translated) {
|
|
5968
|
+
named.expression = translated;
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
|
|
5917
5973
|
if (data.sheet_data) {
|
|
5918
5974
|
|
|
5919
5975
|
const sheets = Array.isArray(data.sheet_data) ? data.sheet_data : [data.sheet_data];
|
|
@@ -27,10 +27,10 @@ import Base64JS from 'base64-js';
|
|
|
27
27
|
import type { AnchoredChartDescription, AnchoredImageDescription, AnchoredTextBoxDescription} from './workbook2';
|
|
28
28
|
import { ChartType, ConditionalFormatOperators, Workbook } from './workbook2';
|
|
29
29
|
import type { ParseResult } from 'treb-parser';
|
|
30
|
-
import { Parser } from 'treb-parser';
|
|
30
|
+
import { DecimalMarkType, Parser } from 'treb-parser';
|
|
31
31
|
import type { RangeType, AddressType, HyperlinkType } from './address-type';
|
|
32
32
|
import { is_range, ShiftRange, InRange, is_address } from './address-type';
|
|
33
|
-
import type
|
|
33
|
+
import { type ImportedSheetData, type AnchoredAnnotation, type CellParseResult, type AnnotationLayout, type Corner as LayoutCorner, type IArea, type GradientStop, type Color, type HTMLColor, type ThemeColor, Area } from 'treb-base-types';
|
|
34
34
|
import type { SerializedValueType } from 'treb-base-types';
|
|
35
35
|
import type { Sheet} from './workbook-sheet2';
|
|
36
36
|
import { VisibleState } from './workbook-sheet2';
|
|
@@ -341,7 +341,7 @@ export class Importer {
|
|
|
341
341
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|undefined {
|
|
344
|
+
public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|ConditionalFormat[]|undefined {
|
|
345
345
|
|
|
346
346
|
const area = this.AddressToArea(address);
|
|
347
347
|
const operators = ConditionalFormatOperators;
|
|
@@ -365,11 +365,13 @@ export class Importer {
|
|
|
365
365
|
area,
|
|
366
366
|
style,
|
|
367
367
|
unique: (rule.a$.type === 'uniqueValues'),
|
|
368
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
369
|
+
|
|
368
370
|
};
|
|
369
371
|
}
|
|
370
372
|
|
|
371
373
|
case 'cellIs':
|
|
372
|
-
if (rule.a$.operator && rule.formula) {
|
|
374
|
+
if (rule.a$.operator && (rule.formula || typeof rule.formula === 'number')) {
|
|
373
375
|
let style = {};
|
|
374
376
|
|
|
375
377
|
if (rule.a$.dxfId) {
|
|
@@ -379,10 +381,26 @@ export class Importer {
|
|
|
379
381
|
}
|
|
380
382
|
}
|
|
381
383
|
|
|
384
|
+
if (rule.a$.operator === 'between') {
|
|
385
|
+
if (Array.isArray(rule.formula) && rule.formula.length === 2
|
|
386
|
+
&& typeof rule.formula[0] === 'number' && typeof rule.formula[1] === 'number') {
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
type: 'cell-match',
|
|
390
|
+
expression: '',
|
|
391
|
+
between: rule.formula, // special case? ugh
|
|
392
|
+
area,
|
|
393
|
+
style,
|
|
394
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
382
400
|
const operator = operators[rule.a$.operator || ''];
|
|
383
401
|
|
|
384
402
|
if (!operator) {
|
|
385
|
-
console.info('unhandled cellIs operator:', rule.a$.operator);
|
|
403
|
+
console.info('unhandled cellIs operator:', rule.a$.operator, {rule});
|
|
386
404
|
}
|
|
387
405
|
else {
|
|
388
406
|
return {
|
|
@@ -390,10 +408,14 @@ export class Importer {
|
|
|
390
408
|
expression: operator + ' ' + rule.formula,
|
|
391
409
|
area,
|
|
392
410
|
style,
|
|
411
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
393
412
|
};
|
|
394
413
|
}
|
|
395
414
|
|
|
396
415
|
}
|
|
416
|
+
else {
|
|
417
|
+
console.info("miss?", rule);
|
|
418
|
+
}
|
|
397
419
|
break;
|
|
398
420
|
|
|
399
421
|
case 'containsErrors':
|
|
@@ -427,11 +449,51 @@ export class Importer {
|
|
|
427
449
|
}
|
|
428
450
|
}
|
|
429
451
|
|
|
452
|
+
if (rule.a$.type === 'expression' && (area.start.row !== area.end.row || area.start.column !== area.end.column)) {
|
|
453
|
+
|
|
454
|
+
// (1) this is only required if there are relative references
|
|
455
|
+
// in the formula. so we could check and short-circuit.
|
|
456
|
+
//
|
|
457
|
+
// (2) I'd like to find a way to apply this as a single formula,
|
|
458
|
+
// so there's only one rule required.
|
|
459
|
+
|
|
460
|
+
this.parser.Save();
|
|
461
|
+
this.parser.SetLocaleSettings(DecimalMarkType.Period);
|
|
462
|
+
|
|
463
|
+
const list: ConditionalFormat[] = [];
|
|
464
|
+
const a2 = new Area(area.start, area.end);
|
|
465
|
+
|
|
466
|
+
const parse_result = this.parser.Parse(rule.formula);
|
|
467
|
+
if (parse_result.expression) {
|
|
468
|
+
for (const cell of a2) {
|
|
469
|
+
const f = this.parser.Render(parse_result.expression, {
|
|
470
|
+
missing: '',
|
|
471
|
+
offset: { rows: cell.row - area.start.row, columns: cell.column - area.start.column }
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
list.push({
|
|
475
|
+
type: 'expression',
|
|
476
|
+
expression: f,
|
|
477
|
+
style,
|
|
478
|
+
area: { start: cell, end: cell },
|
|
479
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// console.info(f);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
this.parser.Restore();
|
|
487
|
+
return list;
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
|
|
430
491
|
return {
|
|
431
492
|
type: 'expression',
|
|
432
493
|
expression: rule.formula,
|
|
433
494
|
area,
|
|
434
495
|
style,
|
|
496
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
435
497
|
};
|
|
436
498
|
|
|
437
499
|
}
|
|
@@ -479,6 +541,8 @@ export class Importer {
|
|
|
479
541
|
stops,
|
|
480
542
|
color_space: 'RGB',
|
|
481
543
|
area,
|
|
544
|
+
priority: rule.a$.priority ? Number(rule.a$.priority) : undefined,
|
|
545
|
+
|
|
482
546
|
};
|
|
483
547
|
|
|
484
548
|
}
|
|
@@ -545,7 +609,12 @@ export class Importer {
|
|
|
545
609
|
for (const rule of rules) {
|
|
546
610
|
const format = this.ParseConditionalFormat(area, rule);
|
|
547
611
|
if (format) {
|
|
548
|
-
|
|
612
|
+
if (Array.isArray(format)) {
|
|
613
|
+
conditional_formats.push(...format);
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
conditional_formats.push(format);
|
|
617
|
+
}
|
|
549
618
|
}
|
|
550
619
|
}
|
|
551
620
|
}
|
|
@@ -39,10 +39,15 @@ import { ZipWrapper } from './zip-wrapper';
|
|
|
39
39
|
import type { CellStyle, ThemeColor } from 'treb-base-types';
|
|
40
40
|
import type { SerializedNamed } from 'treb-data-model';
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @privateRemarks -- FIXME: not sure about the equal/equals thing. need to check.
|
|
44
|
+
*/
|
|
42
45
|
export const ConditionalFormatOperators: Record<string, string> = {
|
|
43
46
|
greaterThan: '>',
|
|
47
|
+
greaterThanOrEqual: '>=',
|
|
44
48
|
greaterThanOrEquals: '>=',
|
|
45
49
|
lessThan: '<',
|
|
50
|
+
lessThanOrEqual: '<=',
|
|
46
51
|
lessThanOrEquals: '<=',
|
|
47
52
|
equal: '=',
|
|
48
53
|
notEqual: '<>',
|
|
@@ -163,7 +163,7 @@ export interface NodeDescriptor {
|
|
|
163
163
|
|
|
164
164
|
export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorEvent> {
|
|
165
165
|
|
|
166
|
-
protected static readonly FormulaChars = ('$^&*(-+={[
|
|
166
|
+
protected static readonly FormulaChars = ('$^&*(-+={[<>/~%@' + Localization.argument_separator).split(''); // FIXME: i18n
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
169
|
* the current edit cell. in the event we're editing a merged or
|