@trebco/treb 30.6.2 → 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/esbuild-utils.mjs +3 -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 -2
- package/treb-calculator/src/functions/sparkline.ts +20 -0
- package/treb-calculator/src/functions/statistics-functions.ts +2 -0
- package/treb-charts/src/chart-functions.ts +1 -0
- package/treb-charts/src/chart-utils.ts +81 -2
- package/treb-charts/src/quicksort.ts +54 -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 +58 -2
- 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
|
@@ -246,8 +246,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
246
246
|
/* * @internal */
|
|
247
247
|
// public static export_worker_text = '';
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
public static treb_embedded_script_path = '';
|
|
249
|
+
/* * @internal */
|
|
250
|
+
// public static treb_embedded_script_path = '';
|
|
251
251
|
|
|
252
252
|
/**
|
|
253
253
|
* @internal
|
|
@@ -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
|
|
@@ -136,9 +136,10 @@ const binary_operators_precendence: PrecedenceList = {
|
|
|
136
136
|
':': 13, // range operator
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
/* *
|
|
140
140
|
* binary ops are sorted by length so we can compare long ops first
|
|
141
|
-
|
|
141
|
+
switching to a composite w/ unary operators
|
|
142
|
+
* /
|
|
142
143
|
const binary_operators = Object.keys(binary_operators_precendence).sort(
|
|
143
144
|
(a, b) => b.length - a.length,
|
|
144
145
|
);
|
|
@@ -147,8 +148,17 @@ const binary_operators = Object.keys(binary_operators_precendence).sort(
|
|
|
147
148
|
* unary operators. atm we have no precedence issues, unary operators
|
|
148
149
|
* always have absolute precedence. (for numbers, these are properly part
|
|
149
150
|
* of the number, but consider `=-SUM(1,2)` -- this is an operator).
|
|
151
|
+
*
|
|
152
|
+
* implicit intersection operator should now have precedence over +/-.
|
|
153
|
+
*/
|
|
154
|
+
const unary_operators: PrecedenceList = { '@': 50, '-': 100, '+': 100 };
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* to avoid the double - and +, we're just adding our one extra unary
|
|
158
|
+
* operator. doing this dynamically would be silly, although this does
|
|
159
|
+
* make this code more fragile.
|
|
150
160
|
*/
|
|
151
|
-
const
|
|
161
|
+
const composite_operators: string[] = [...Object.keys(binary_operators_precendence), '@'].sort((a, b) => b.length - a.length);
|
|
152
162
|
|
|
153
163
|
/**
|
|
154
164
|
* parser for spreadsheet language.
|
|
@@ -2092,7 +2102,7 @@ export class Parser {
|
|
|
2092
2102
|
}
|
|
2093
2103
|
|
|
2094
2104
|
protected ConsumeOperator(): ExpressionUnit | null {
|
|
2095
|
-
for (const operator of
|
|
2105
|
+
for (const operator of composite_operators) {
|
|
2096
2106
|
if (this.expression.substr(this.index, operator.length) === operator) {
|
|
2097
2107
|
const position = this.index;
|
|
2098
2108
|
this.index += operator.length;
|