@trebco/treb 27.5.3 → 27.9.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/dist/treb-spreadsheet.mjs +14 -14
- package/dist/treb.d.ts +37 -23
- package/notes/conditional-fomratring.md +29 -0
- package/package.json +3 -3
- package/treb-base-types/src/area.ts +181 -0
- package/treb-base-types/src/evaluate-options.ts +21 -0
- package/treb-base-types/src/gradient.ts +97 -0
- package/treb-base-types/src/import.ts +2 -1
- package/treb-base-types/src/index.ts +2 -0
- package/treb-calculator/src/calculator.ts +205 -132
- package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
- package/treb-calculator/src/dag/graph.ts +10 -22
- package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
- package/treb-calculator/src/descriptors.ts +10 -3
- package/treb-calculator/src/expression-calculator.ts +1 -1
- package/treb-calculator/src/function-library.ts +25 -22
- package/treb-calculator/src/functions/base-functions.ts +166 -5
- package/treb-calculator/src/index.ts +6 -6
- package/treb-calculator/src/notifier-types.ts +1 -1
- package/treb-calculator/src/utilities.ts +2 -2
- package/treb-charts/src/util.ts +2 -2
- package/treb-embed/src/embedded-spreadsheet.ts +382 -71
- package/treb-embed/style/formula-bar.scss +2 -0
- package/treb-embed/style/theme-defaults.scss +46 -15
- package/treb-export/src/export-worker/export-worker.ts +0 -13
- package/treb-export/src/export2.ts +187 -2
- package/treb-export/src/import2.ts +169 -4
- package/treb-export/src/workbook-style2.ts +56 -8
- package/treb-export/src/workbook2.ts +10 -1
- package/treb-grid/src/editors/editor.ts +1276 -0
- package/treb-grid/src/editors/external_editor.ts +113 -0
- package/treb-grid/src/editors/formula_bar.ts +450 -474
- package/treb-grid/src/editors/overlay_editor.ts +437 -512
- package/treb-grid/src/index.ts +2 -1
- package/treb-grid/src/layout/base_layout.ts +24 -16
- package/treb-grid/src/render/tile_renderer.ts +2 -1
- package/treb-grid/src/types/conditional_format.ts +168 -0
- package/treb-grid/src/types/data_model.ts +130 -3
- package/treb-grid/src/types/external_editor_config.ts +47 -0
- package/treb-grid/src/types/grid.ts +96 -45
- package/treb-grid/src/types/grid_base.ts +187 -35
- package/treb-grid/src/types/scale-control.ts +1 -1
- package/treb-grid/src/types/sheet.ts +330 -26
- package/treb-grid/src/types/sheet_types.ts +4 -0
- package/treb-grid/src/util/dom_utilities.ts +58 -25
- package/treb-grid/src/editors/formula_editor_base.ts +0 -912
- package/treb-grid/src/types/external_editor.ts +0 -27
- /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
|
@@ -31,27 +31,43 @@
|
|
|
31
31
|
$primary-selection-color: #4caaf1;
|
|
32
32
|
$primary-selection-color-unfocused: #acc0cf;
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
/*
|
|
35
35
|
$alternate-selection-color-1: rgb(251, 177, 60);
|
|
36
|
-
//$alternate-selection-color-2: rgb(87, 184, 255);
|
|
37
36
|
$alternate-selection-color-2: rgb(64, 192, 64);
|
|
38
37
|
$alternate-selection-color-3: rgb(182, 109, 13);
|
|
39
38
|
$alternate-selection-color-4: rgb(33, 118, 174);
|
|
40
39
|
$alternate-selection-color-5: rgb(254, 104, 71);
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
/ * *
|
|
43
42
|
* slightly darkening colors for text highlighting
|
|
44
43
|
* algo: convert to HSL; if L > .5, regenerate with L = .5; back to RGB (why?)
|
|
45
|
-
|
|
44
|
+
* /
|
|
46
45
|
$text-reference-color-1: rgb(250, 155, 5);
|
|
47
|
-
//$text-reference-color-2: rgb(0, 147, 255);
|
|
48
46
|
$text-reference-color-2: rgb(58, 173, 58);
|
|
49
47
|
$text-reference-color-3: rgb(182, 109, 13);
|
|
50
48
|
$text-reference-color-4: rgb(33, 118, 174);
|
|
51
49
|
$text-reference-color-5: rgb(254, 47, 1);
|
|
50
|
+
*/
|
|
52
51
|
|
|
53
52
|
.treb-main.treb-main {
|
|
54
53
|
|
|
54
|
+
--alternate-selection-color-1: rgb(251, 177, 60);
|
|
55
|
+
--alternate-selection-color-2: rgb(64, 192, 64);
|
|
56
|
+
--alternate-selection-color-3: rgb(182, 109, 13);
|
|
57
|
+
--alternate-selection-color-4: rgb(33, 118, 174);
|
|
58
|
+
--alternate-selection-color-5: rgb(254, 104, 71);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* slightly darkening colors for text highlighting
|
|
62
|
+
* algo: convert to HSL; if L > .5, regenerate with L = .5; back to RGB (why?)
|
|
63
|
+
*/
|
|
64
|
+
--text-reference-color-1: rgb(224, 138, 0);
|
|
65
|
+
--text-reference-color-2: rgb(58, 173, 58);
|
|
66
|
+
--text-reference-color-3: rgb(182, 109, 13);
|
|
67
|
+
--text-reference-color-4: rgb(33, 118, 174);
|
|
68
|
+
--text-reference-color-5: rgb(254, 47, 1);
|
|
69
|
+
|
|
70
|
+
|
|
55
71
|
/**
|
|
56
72
|
* move primary selection focus color to focus-within on the top-level
|
|
57
73
|
* node, and use a (new) unfocused selection color. name?
|
|
@@ -284,29 +300,39 @@ $text-reference-color-5: rgb(254, 47, 1);
|
|
|
284
300
|
.treb-editor-container>div,
|
|
285
301
|
.treb-overlay-editor {
|
|
286
302
|
|
|
303
|
+
[data-highlight-index="1"] { color: var(--text-reference-color-1); }
|
|
304
|
+
[data-highlight-index="2"] { color: var(--text-reference-color-2); }
|
|
305
|
+
[data-highlight-index="3"] { color: var(--text-reference-color-3); }
|
|
306
|
+
[data-highlight-index="4"] { color: var(--text-reference-color-4); }
|
|
307
|
+
[data-highlight-index="5"] { color: var(--text-reference-color-5); }
|
|
308
|
+
|
|
287
309
|
/* span:nth-of-type(1n) { */
|
|
288
310
|
span.highlight-1 {
|
|
289
|
-
color:
|
|
311
|
+
color: var(--text-reference-color-1);
|
|
290
312
|
}
|
|
291
313
|
|
|
292
314
|
/* span:nth-of-type(2n) { */
|
|
293
315
|
span.highlight-2 {
|
|
294
|
-
color: $text-reference-color-2;
|
|
316
|
+
// color: $text-reference-color-2;
|
|
317
|
+
color: var(--text-reference-color-2);
|
|
295
318
|
}
|
|
296
319
|
|
|
297
320
|
/* span:nth-of-type(3n) { */
|
|
298
321
|
span.highlight-3 {
|
|
299
|
-
color: $text-reference-color-3;
|
|
322
|
+
// color: $text-reference-color-3;
|
|
323
|
+
color: var(--text-reference-color-3);
|
|
300
324
|
}
|
|
301
325
|
|
|
302
326
|
/* span:nth-of-type(4n) { */
|
|
303
327
|
span.highlight-4 {
|
|
304
|
-
color: $text-reference-color-4;
|
|
328
|
+
// color: $text-reference-color-4;
|
|
329
|
+
color: var(--text-reference-color-4);
|
|
305
330
|
}
|
|
306
331
|
|
|
307
332
|
/* span:nth-of-type(5n) { */
|
|
308
333
|
span.highlight-5 {
|
|
309
|
-
color: $text-reference-color-5;
|
|
334
|
+
// color: $text-reference-color-5;
|
|
335
|
+
color: var(--text-reference-color-5);
|
|
310
336
|
}
|
|
311
337
|
|
|
312
338
|
}
|
|
@@ -339,23 +365,28 @@ $text-reference-color-5: rgb(254, 47, 1);
|
|
|
339
365
|
}
|
|
340
366
|
|
|
341
367
|
.alternate-selection:nth-of-type(1n) {
|
|
342
|
-
color: $alternate-selection-color-1;
|
|
368
|
+
// color: $alternate-selection-color-1;
|
|
369
|
+
color: var(--alternate-selection-color-1);
|
|
343
370
|
}
|
|
344
371
|
|
|
345
372
|
.alternate-selection:nth-of-type(2n) {
|
|
346
|
-
color: $alternate-selection-color-2;
|
|
373
|
+
// color: $alternate-selection-color-2;
|
|
374
|
+
color: var(--alternate-selection-color-2);
|
|
347
375
|
}
|
|
348
376
|
|
|
349
377
|
.alternate-selection:nth-of-type(3n) {
|
|
350
|
-
color: $alternate-selection-color-3;
|
|
378
|
+
// color: $alternate-selection-color-3;
|
|
379
|
+
color: var(--alternate-selection-color-3);
|
|
351
380
|
}
|
|
352
381
|
|
|
353
382
|
.alternate-selection:nth-of-type(4n) {
|
|
354
|
-
color: $alternate-selection-color-4;
|
|
383
|
+
// color: $alternate-selection-color-4;
|
|
384
|
+
color: var(--alternate-selection-color-4);
|
|
355
385
|
}
|
|
356
386
|
|
|
357
387
|
.alternate-selection:nth-of-type(5n) {
|
|
358
|
-
color: $alternate-selection-color-5;
|
|
388
|
+
// color: $alternate-selection-color-5;
|
|
389
|
+
color: var(--alternate-selection-color-5);
|
|
359
390
|
}
|
|
360
391
|
|
|
361
392
|
/**
|
|
@@ -40,19 +40,6 @@ const ExportSheets = async (data: any) => {
|
|
|
40
40
|
ctx.postMessage({ status: 'complete', blob: corrected });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/*
|
|
44
|
-
if (data.sheet) {
|
|
45
|
-
await exporter.Init();
|
|
46
|
-
await exporter.ExportSheets(data.sheet);
|
|
47
|
-
const blob = await exporter.AsBlob(1);
|
|
48
|
-
|
|
49
|
-
// correct the mime type for firefox
|
|
50
|
-
const corrected = (blob as Blob).slice(0, (blob as Blob).size,
|
|
51
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
52
|
-
|
|
53
|
-
ctx.postMessage({ status: 'complete', blob: corrected });
|
|
54
|
-
}
|
|
55
|
-
*/
|
|
56
43
|
};
|
|
57
44
|
|
|
58
45
|
const ImportSheet = async (data: any) => {
|
|
@@ -60,7 +60,7 @@ import { Chart } from './drawing2/chart2';
|
|
|
60
60
|
import type { ImageOptions } from './drawing2/embedded-image';
|
|
61
61
|
import type { TwoCellAnchor } from './drawing2/drawing2';
|
|
62
62
|
import { Drawing } from './drawing2/drawing2';
|
|
63
|
-
import type
|
|
63
|
+
import { ConditionalFormatOperators, type TableDescription, type TableFooterType } from './workbook2';
|
|
64
64
|
import type { AnnotationData } from 'treb-grid/src/types/annotation';
|
|
65
65
|
|
|
66
66
|
export class Exporter {
|
|
@@ -433,11 +433,67 @@ export class Exporter {
|
|
|
433
433
|
};
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
+
if (style_cache.dxf_styles.length) {
|
|
437
|
+
const dxf: any[] = [];
|
|
438
|
+
for (const style of style_cache.dxf_styles) {
|
|
439
|
+
const entry: any = {};
|
|
440
|
+
if (style.text || style.bold || style.italic || style.underline) {
|
|
441
|
+
const font: any = {};
|
|
442
|
+
if (style.text) {
|
|
443
|
+
font.color = { a$: {}};
|
|
444
|
+
if (style.text.text) {
|
|
445
|
+
font.color.a$.rgb = `FF` + style.text.text.substring(1);
|
|
446
|
+
}
|
|
447
|
+
else if (style.text.theme) {
|
|
448
|
+
font.color.a$.theme = style.text.theme;
|
|
449
|
+
if (style.text.tint) {
|
|
450
|
+
font.color.a$.tint = style.text.tint;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (style.bold) {
|
|
455
|
+
font.b = {};
|
|
456
|
+
}
|
|
457
|
+
if (style.italic) {
|
|
458
|
+
font.i = {};
|
|
459
|
+
}
|
|
460
|
+
if (style.underline) {
|
|
461
|
+
font.u = {};
|
|
462
|
+
}
|
|
463
|
+
if (style.strike) {
|
|
464
|
+
font.strike = {};
|
|
465
|
+
}
|
|
466
|
+
entry.font = font;
|
|
467
|
+
}
|
|
468
|
+
if (style.fill) {
|
|
469
|
+
const color: any = { a$: {}};
|
|
470
|
+
if (style.fill.text) {
|
|
471
|
+
color.a$.rgb = `FF` + style.fill.text.substring(1);
|
|
472
|
+
}
|
|
473
|
+
else if (style.fill.theme) {
|
|
474
|
+
color.a$.theme = style.fill.theme;
|
|
475
|
+
if (style.fill.tint) {
|
|
476
|
+
color.a$.tint = style.fill.tint;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
entry.fill = { patternFill: { bgColor: color }};
|
|
480
|
+
}
|
|
481
|
+
dxf.push(entry);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (dxf.length) {
|
|
485
|
+
dom.styleSheet.dxfs = {
|
|
486
|
+
a$: { count: dxf.length },
|
|
487
|
+
dxf,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
}
|
|
492
|
+
|
|
436
493
|
// not used:
|
|
437
494
|
//
|
|
438
495
|
// cellStyleXfs
|
|
439
496
|
// cellStyles
|
|
440
|
-
// dxfs
|
|
441
497
|
// tableStyles
|
|
442
498
|
|
|
443
499
|
// ------------
|
|
@@ -1981,6 +2037,129 @@ export class Exporter {
|
|
|
1981
2037
|
|
|
1982
2038
|
}
|
|
1983
2039
|
|
|
2040
|
+
// --- conditional formats -----------------------------------------------
|
|
2041
|
+
|
|
2042
|
+
if (sheet.conditional_formats?.length) {
|
|
2043
|
+
const conditionalFormatting: any[] = [];
|
|
2044
|
+
let priority_index = 1;
|
|
2045
|
+
|
|
2046
|
+
const reverse_operator_map: Record<string, string> = {};
|
|
2047
|
+
const operator_list: string[] = Object.entries(ConditionalFormatOperators).map(entry => {
|
|
2048
|
+
reverse_operator_map[entry[1]] = entry[0];
|
|
2049
|
+
return entry[1];
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
operator_list.sort((a, b) => b.length - a.length);
|
|
2053
|
+
|
|
2054
|
+
for (const format of sheet.conditional_formats) {
|
|
2055
|
+
|
|
2056
|
+
let dxf_index = 0;
|
|
2057
|
+
|
|
2058
|
+
if (format.type !== 'gradient') {
|
|
2059
|
+
|
|
2060
|
+
// these are zero-based? I thought everything in there
|
|
2061
|
+
// was 1-based. [A: yes, these are indexed from 0].
|
|
2062
|
+
|
|
2063
|
+
dxf_index = style_cache.dxf_styles.length;
|
|
2064
|
+
style_cache.dxf_styles.push(format.style);
|
|
2065
|
+
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
switch (format.type) {
|
|
2069
|
+
case 'cell-match':
|
|
2070
|
+
{
|
|
2071
|
+
let operator = '';
|
|
2072
|
+
let formula = '';
|
|
2073
|
+
|
|
2074
|
+
for (const test of operator_list) {
|
|
2075
|
+
if (new RegExp('^' + test + '\\s').test(format.expression)) {
|
|
2076
|
+
operator = reverse_operator_map[test];
|
|
2077
|
+
formula = format.expression.substring(test.length).trim();
|
|
2078
|
+
break;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
if (operator) {
|
|
2082
|
+
|
|
2083
|
+
conditionalFormatting.push({
|
|
2084
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2085
|
+
cfRule: {
|
|
2086
|
+
a$: { type: 'cellIs', dxfId: dxf_index, operator, priority: priority_index++ },
|
|
2087
|
+
formula,
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
break;
|
|
2093
|
+
|
|
2094
|
+
case 'expression':
|
|
2095
|
+
conditionalFormatting.push({
|
|
2096
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2097
|
+
cfRule: {
|
|
2098
|
+
a$: { type: 'expression', dxfId: dxf_index, priority: priority_index++ },
|
|
2099
|
+
formula: format.expression,
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
break;
|
|
2103
|
+
|
|
2104
|
+
case 'duplicate-values':
|
|
2105
|
+
conditionalFormatting.push({
|
|
2106
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2107
|
+
cfRule: {
|
|
2108
|
+
a$: { type: format.unique ? 'uniqueValues' : 'duplicateValues', dxfId: dxf_index, priority: priority_index++ },
|
|
2109
|
+
}
|
|
2110
|
+
});
|
|
2111
|
+
break;
|
|
2112
|
+
|
|
2113
|
+
case 'gradient':
|
|
2114
|
+
{
|
|
2115
|
+
let cfvo: any[] = [];
|
|
2116
|
+
let color: any[] = [];
|
|
2117
|
+
|
|
2118
|
+
for (const stop of format.stops) {
|
|
2119
|
+
if (stop.value === 0) {
|
|
2120
|
+
cfvo.push({ a$: { type: 'min' }});
|
|
2121
|
+
}
|
|
2122
|
+
else if (stop.value === 1) {
|
|
2123
|
+
cfvo.push({ a$: { type: 'max' }});
|
|
2124
|
+
}
|
|
2125
|
+
else {
|
|
2126
|
+
cfvo.push({ a$: { type: 'percentile', val: stop.value * 100 }});
|
|
2127
|
+
}
|
|
2128
|
+
const stop_color: any = { a$: {}};
|
|
2129
|
+
if (stop.color.text) {
|
|
2130
|
+
stop_color.a$.rgb = 'FF' + stop.color.text.substring(1);
|
|
2131
|
+
}
|
|
2132
|
+
else if (stop.color.theme) {
|
|
2133
|
+
stop_color.a$.theme = stop.color.theme;
|
|
2134
|
+
stop_color.a$.tint = stop.color.tint || undefined;
|
|
2135
|
+
}
|
|
2136
|
+
color.push(stop_color);
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
const generated = {
|
|
2140
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2141
|
+
cfRule: {
|
|
2142
|
+
a$: { type: 'colorScale', priority: priority_index++ },
|
|
2143
|
+
colorScale: {
|
|
2144
|
+
cfvo,
|
|
2145
|
+
color,
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
conditionalFormatting.push(generated);
|
|
2151
|
+
|
|
2152
|
+
}
|
|
2153
|
+
break;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
if (conditionalFormatting.length) {
|
|
2158
|
+
dom.worksheet.conditionalFormatting = (conditionalFormatting.length > 1) ? conditionalFormatting : conditionalFormatting[0];
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
}
|
|
2162
|
+
|
|
1984
2163
|
// --- merges ------------------------------------------------------------
|
|
1985
2164
|
|
|
1986
2165
|
if (merges.length) {
|
|
@@ -2152,6 +2331,12 @@ export class Exporter {
|
|
|
2152
2331
|
delete dom.worksheet.drawing;
|
|
2153
2332
|
}
|
|
2154
2333
|
|
|
2334
|
+
// --- move page margins -------------------------------------------------
|
|
2335
|
+
|
|
2336
|
+
const margins = dom.worksheet.pageMargins;
|
|
2337
|
+
delete dom.worksheet.pageMargins;
|
|
2338
|
+
dom.worksheet.pageMargins = margins;
|
|
2339
|
+
|
|
2155
2340
|
// --- end? --------------------------------------------------------------
|
|
2156
2341
|
|
|
2157
2342
|
// it seems like chrome, at least, will maintain order. but this is
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
import JSZip from 'jszip';
|
|
24
24
|
|
|
25
25
|
import type { AnchoredChartDescription} from './workbook2';
|
|
26
|
-
import { ChartType, Workbook } from './workbook2';
|
|
26
|
+
import { ChartType, ConditionalFormatOperators, Workbook } from './workbook2';
|
|
27
27
|
import type { ParseResult } from 'treb-parser';
|
|
28
28
|
import { Parser } from 'treb-parser';
|
|
29
29
|
import type { RangeType, AddressType, HyperlinkType } from './address-type';
|
|
30
30
|
import { is_range, ShiftRange, InRange, is_address } from './address-type';
|
|
31
|
-
import type { ImportedSheetData, AnchoredAnnotation, CellParseResult, AnnotationLayout, Corner as LayoutCorner, ICellAddress, DataValidation, IArea } from 'treb-base-types/src';
|
|
31
|
+
import type { ImportedSheetData, AnchoredAnnotation, CellParseResult, AnnotationLayout, Corner as LayoutCorner, ICellAddress, DataValidation, IArea, GradientStop, Color } from 'treb-base-types/src';
|
|
32
32
|
import { type ValueType, ValidationType, type SerializedValueType } from 'treb-base-types/src';
|
|
33
33
|
import type { Sheet} from './workbook-sheet2';
|
|
34
34
|
import { VisibleState } from './workbook-sheet2';
|
|
@@ -37,7 +37,7 @@ import { XMLUtils } from './xml-utils';
|
|
|
37
37
|
|
|
38
38
|
// import { one_hundred_pixels } from './constants';
|
|
39
39
|
import { ColumnWidthToPixels } from './column-width';
|
|
40
|
-
import type { AnnotationType } from 'treb-grid';
|
|
40
|
+
import type { AnnotationType, ConditionalFormat } from 'treb-grid';
|
|
41
41
|
|
|
42
42
|
interface SharedFormula {
|
|
43
43
|
row: number;
|
|
@@ -320,6 +320,151 @@ export class Importer {
|
|
|
320
320
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
public AddressToArea(address: RangeType|AddressType): IArea {
|
|
324
|
+
|
|
325
|
+
const area: IArea = is_address(address) ? {
|
|
326
|
+
start: { row: address.row - 1, column: address.col - 1 },
|
|
327
|
+
end: { row: address.row - 1, column: address.col - 1 },
|
|
328
|
+
} : {
|
|
329
|
+
start: { row: address.from.row - 1, column: address.from.col - 1 },
|
|
330
|
+
end: { row: address.to.row - 1, column: address.to.col - 1 },
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
return area;
|
|
334
|
+
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public ParseConditionalFormat(address: RangeType|AddressType, rule: any): ConditionalFormat|undefined {
|
|
338
|
+
|
|
339
|
+
const area = this.AddressToArea(address);
|
|
340
|
+
const operators = ConditionalFormatOperators;
|
|
341
|
+
|
|
342
|
+
switch (rule.a$.type) {
|
|
343
|
+
case 'duplicateValues':
|
|
344
|
+
case 'uniqueValues':
|
|
345
|
+
|
|
346
|
+
let style = {};
|
|
347
|
+
|
|
348
|
+
if (rule.a$.dxfId) {
|
|
349
|
+
const index = Number(rule.a$.dxfId);
|
|
350
|
+
if (!isNaN(index)) {
|
|
351
|
+
style = this.workbook?.style_cache.dxf_styles[index] || {};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
type: 'duplicate-values',
|
|
357
|
+
area,
|
|
358
|
+
style,
|
|
359
|
+
unique: (rule.a$.type === 'uniqueValues'),
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
case 'cellIs':
|
|
363
|
+
if (rule.a$.operator && rule.formula) {
|
|
364
|
+
let style = {};
|
|
365
|
+
|
|
366
|
+
if (rule.a$.dxfId) {
|
|
367
|
+
const index = Number(rule.a$.dxfId);
|
|
368
|
+
if (!isNaN(index)) {
|
|
369
|
+
style = this.workbook?.style_cache.dxf_styles[index] || {};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const operator = operators[rule.a$.operator || ''];
|
|
374
|
+
|
|
375
|
+
if (!operator) {
|
|
376
|
+
console.info('unhandled cellIs operator:', rule.a$.operator);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
return {
|
|
380
|
+
type: 'cell-match',
|
|
381
|
+
expression: operator + ' ' + rule.formula,
|
|
382
|
+
area,
|
|
383
|
+
style,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
case 'expression':
|
|
391
|
+
if (rule.formula) {
|
|
392
|
+
let style = {};
|
|
393
|
+
|
|
394
|
+
if (rule.a$.dxfId) {
|
|
395
|
+
const index = Number(rule.a$.dxfId);
|
|
396
|
+
if (!isNaN(index)) {
|
|
397
|
+
style = this.workbook?.style_cache.dxf_styles[index] || {};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
type: 'expression',
|
|
403
|
+
expression: rule.formula,
|
|
404
|
+
area,
|
|
405
|
+
style,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
|
|
411
|
+
case 'colorScale':
|
|
412
|
+
if (rule.colorScale && Array.isArray(rule.colorScale.cfvo) && Array.isArray(rule.colorScale.color)) {
|
|
413
|
+
|
|
414
|
+
const stops: GradientStop[] = [];
|
|
415
|
+
for (const [index, entry] of rule.colorScale.cfvo.entries()) {
|
|
416
|
+
let value = 0;
|
|
417
|
+
let color: Color = {};
|
|
418
|
+
|
|
419
|
+
const color_element = rule.colorScale.color[index];
|
|
420
|
+
if (color_element.a$.rgb) {
|
|
421
|
+
color.text = '#' + color_element.a$.rgb.substring(2);
|
|
422
|
+
}
|
|
423
|
+
else if (color_element.a$.theme) {
|
|
424
|
+
color.theme = Number(color_element.a$.theme) || 0;
|
|
425
|
+
if (color_element.a$.tint) {
|
|
426
|
+
color.tint = Math.round(color_element.a$.tint * 1000) / 1000;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
switch (entry.a$.type) {
|
|
431
|
+
case 'min':
|
|
432
|
+
value = 0;
|
|
433
|
+
break;
|
|
434
|
+
|
|
435
|
+
case 'max':
|
|
436
|
+
value = 1;
|
|
437
|
+
break;
|
|
438
|
+
|
|
439
|
+
case 'percentile':
|
|
440
|
+
value = (Number(entry.a$.val) || 0) / 100;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
stops.push({ color, value });
|
|
445
|
+
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
type: 'gradient',
|
|
450
|
+
stops,
|
|
451
|
+
color_space: 'RGB',
|
|
452
|
+
area,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
console.info('unexpected colorScale', {rule});
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
460
|
+
|
|
461
|
+
default:
|
|
462
|
+
console.info('unhandled cf type:', {rule});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
|
|
323
468
|
public async GetSheet(index = 0): Promise<ImportedSheetData> {
|
|
324
469
|
|
|
325
470
|
if (!this.workbook) {
|
|
@@ -337,6 +482,7 @@ export class Importer {
|
|
|
337
482
|
const shared_formulae: {[index: string]: SharedFormula} = {};
|
|
338
483
|
const arrays: RangeType[] = [];
|
|
339
484
|
const merges: RangeType[] = [];
|
|
485
|
+
const conditional_formats: ConditionalFormat[] = [];
|
|
340
486
|
const links: HyperlinkType[] = [];
|
|
341
487
|
const validations: Array<{
|
|
342
488
|
address: ICellAddress,
|
|
@@ -344,8 +490,26 @@ export class Importer {
|
|
|
344
490
|
}> = [];
|
|
345
491
|
const annotations: AnchoredAnnotation[] = [];
|
|
346
492
|
|
|
347
|
-
const FindAll = XMLUtils.FindAll.bind(XMLUtils, sheet.sheet_data);
|
|
493
|
+
const FindAll: (path: string) => any[] = XMLUtils.FindAll.bind(XMLUtils, sheet.sheet_data);
|
|
494
|
+
|
|
495
|
+
// conditionals
|
|
348
496
|
|
|
497
|
+
const conditional_formatting = FindAll('worksheet/conditionalFormatting');
|
|
498
|
+
for (const element of conditional_formatting) {
|
|
499
|
+
if (element.a$?.sqref ){
|
|
500
|
+
const area = sheet.TranslateAddress(element.a$.sqref);
|
|
501
|
+
if (element.cfRule) {
|
|
502
|
+
const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
|
|
503
|
+
for (const rule of rules) {
|
|
504
|
+
const format = this.ParseConditionalFormat(area, rule);
|
|
505
|
+
if (format) {
|
|
506
|
+
conditional_formats.push(format);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
349
513
|
// merges
|
|
350
514
|
|
|
351
515
|
const merge_cells = FindAll('worksheet/mergeCells/mergeCell');
|
|
@@ -878,6 +1042,7 @@ export class Importer {
|
|
|
878
1042
|
column_widths,
|
|
879
1043
|
row_heights,
|
|
880
1044
|
annotations,
|
|
1045
|
+
conditional_formats,
|
|
881
1046
|
styles: this.workbook?.style_cache?.CellXfToStyles() || [],
|
|
882
1047
|
};
|
|
883
1048
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// import * as ElementTree from 'elementtree';
|
|
23
23
|
// import { Element, ElementTree as Tree } from 'elementtree';
|
|
24
24
|
|
|
25
|
-
import { type CompositeBorderEdge, Style, type CellStyle, type PropertyKeys } from 'treb-base-types';
|
|
25
|
+
import { type CompositeBorderEdge, Style, type CellStyle, type PropertyKeys, type Color } from 'treb-base-types';
|
|
26
26
|
import { Theme } from './workbook-theme2';
|
|
27
27
|
import { NumberFormatCache } from 'treb-format';
|
|
28
28
|
import { XMLUtils } from './xml-utils';
|
|
@@ -202,6 +202,7 @@ export class StyleCache {
|
|
|
202
202
|
public fills: Fill[] = [];
|
|
203
203
|
public number_formats: NumberFormat[] = [];
|
|
204
204
|
public base_number_format_id = 200; // ?
|
|
205
|
+
public dxf_styles: CellStyle[] = [];
|
|
205
206
|
|
|
206
207
|
// public dom?: Tree;
|
|
207
208
|
|
|
@@ -1197,9 +1198,7 @@ export class StyleCache {
|
|
|
1197
1198
|
|
|
1198
1199
|
// ---
|
|
1199
1200
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
this.fills = composite.map(element => {
|
|
1201
|
+
const ParseFill = (element: any) => {
|
|
1203
1202
|
|
|
1204
1203
|
const fill: Fill = { pattern_type: 'none' };
|
|
1205
1204
|
if (element.patternFill) {
|
|
@@ -1236,13 +1235,15 @@ export class StyleCache {
|
|
|
1236
1235
|
|
|
1237
1236
|
return fill;
|
|
1238
1237
|
|
|
1239
|
-
}
|
|
1238
|
+
};
|
|
1240
1239
|
|
|
1241
|
-
|
|
1240
|
+
composite = FindAll('styleSheet/fills/fill');
|
|
1242
1241
|
|
|
1243
|
-
|
|
1242
|
+
this.fills = composite.map(ParseFill);
|
|
1244
1243
|
|
|
1245
|
-
|
|
1244
|
+
// ---
|
|
1245
|
+
|
|
1246
|
+
const ParseFont = (element: any) => {
|
|
1246
1247
|
|
|
1247
1248
|
const font: Font = {};
|
|
1248
1249
|
|
|
@@ -1278,8 +1279,55 @@ export class StyleCache {
|
|
|
1278
1279
|
|
|
1279
1280
|
return font;
|
|
1280
1281
|
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
composite = FindAll('styleSheet/fonts/font');
|
|
1285
|
+
this.fonts = composite.map(ParseFont);
|
|
1286
|
+
|
|
1287
|
+
// dxfs (differential formats) are inline. because reasons? not sure
|
|
1288
|
+
// what's allowed in there, atm we're just looking at font color and
|
|
1289
|
+
// background color.
|
|
1290
|
+
|
|
1291
|
+
const ParseDXFColor = (element: any) => {
|
|
1292
|
+
const color: Color = {};
|
|
1293
|
+
if (element.a$.rgb) {
|
|
1294
|
+
color.text = '#' + element.a$.rgb.substring(2);
|
|
1295
|
+
}
|
|
1296
|
+
else if (element.a$.theme) {
|
|
1297
|
+
color.theme = Number(element.a$.theme) || 0;
|
|
1298
|
+
if (element.a$.tint) {
|
|
1299
|
+
color.tint = Math.round(element.a$.tint * 1000) / 1000;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return color;
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
const dxfs = FindAll('styleSheet/dxfs/dxf');
|
|
1306
|
+
this.dxf_styles = dxfs.map(dxf => {
|
|
1307
|
+
|
|
1308
|
+
const style: CellStyle = {};
|
|
1309
|
+
|
|
1310
|
+
// dxf fonts are different too? this is irritating
|
|
1311
|
+
|
|
1312
|
+
if (dxf.font) {
|
|
1313
|
+
style.bold = !!dxf.font.b;
|
|
1314
|
+
style.italic = !!dxf.font.i && dxf.font.i.a$.val !== '0';
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// dxfs fills are different? anyway we can't reuse the above code for fill, just grab the color
|
|
1318
|
+
|
|
1319
|
+
if (dxf.font?.color?.a$) {
|
|
1320
|
+
style.text = ParseDXFColor(dxf.font.color);
|
|
1321
|
+
}
|
|
1322
|
+
if (dxf.fill?.patternFill?.bgColor?.a$) {
|
|
1323
|
+
style.fill = ParseDXFColor(dxf.fill.patternFill.bgColor);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
return style;
|
|
1281
1327
|
});
|
|
1282
1328
|
|
|
1329
|
+
// console.info({dxfs: this.dxf_styles});
|
|
1330
|
+
|
|
1283
1331
|
}
|
|
1284
1332
|
|
|
1285
1333
|
}
|