@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.
Files changed (48) hide show
  1. package/dist/treb-spreadsheet.mjs +14 -14
  2. package/dist/treb.d.ts +37 -23
  3. package/notes/conditional-fomratring.md +29 -0
  4. package/package.json +3 -3
  5. package/treb-base-types/src/area.ts +181 -0
  6. package/treb-base-types/src/evaluate-options.ts +21 -0
  7. package/treb-base-types/src/gradient.ts +97 -0
  8. package/treb-base-types/src/import.ts +2 -1
  9. package/treb-base-types/src/index.ts +2 -0
  10. package/treb-calculator/src/calculator.ts +205 -132
  11. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +97 -0
  12. package/treb-calculator/src/dag/graph.ts +10 -22
  13. package/treb-calculator/src/dag/{leaf_vertex.ts → state_leaf_vertex.ts} +3 -3
  14. package/treb-calculator/src/descriptors.ts +10 -3
  15. package/treb-calculator/src/expression-calculator.ts +1 -1
  16. package/treb-calculator/src/function-library.ts +25 -22
  17. package/treb-calculator/src/functions/base-functions.ts +166 -5
  18. package/treb-calculator/src/index.ts +6 -6
  19. package/treb-calculator/src/notifier-types.ts +1 -1
  20. package/treb-calculator/src/utilities.ts +2 -2
  21. package/treb-charts/src/util.ts +2 -2
  22. package/treb-embed/src/embedded-spreadsheet.ts +382 -71
  23. package/treb-embed/style/formula-bar.scss +2 -0
  24. package/treb-embed/style/theme-defaults.scss +46 -15
  25. package/treb-export/src/export-worker/export-worker.ts +0 -13
  26. package/treb-export/src/export2.ts +187 -2
  27. package/treb-export/src/import2.ts +169 -4
  28. package/treb-export/src/workbook-style2.ts +56 -8
  29. package/treb-export/src/workbook2.ts +10 -1
  30. package/treb-grid/src/editors/editor.ts +1276 -0
  31. package/treb-grid/src/editors/external_editor.ts +113 -0
  32. package/treb-grid/src/editors/formula_bar.ts +450 -474
  33. package/treb-grid/src/editors/overlay_editor.ts +437 -512
  34. package/treb-grid/src/index.ts +2 -1
  35. package/treb-grid/src/layout/base_layout.ts +24 -16
  36. package/treb-grid/src/render/tile_renderer.ts +2 -1
  37. package/treb-grid/src/types/conditional_format.ts +168 -0
  38. package/treb-grid/src/types/data_model.ts +130 -3
  39. package/treb-grid/src/types/external_editor_config.ts +47 -0
  40. package/treb-grid/src/types/grid.ts +96 -45
  41. package/treb-grid/src/types/grid_base.ts +187 -35
  42. package/treb-grid/src/types/scale-control.ts +1 -1
  43. package/treb-grid/src/types/sheet.ts +330 -26
  44. package/treb-grid/src/types/sheet_types.ts +4 -0
  45. package/treb-grid/src/util/dom_utilities.ts +58 -25
  46. package/treb-grid/src/editors/formula_editor_base.ts +0 -912
  47. package/treb-grid/src/types/external_editor.ts +0 -27
  48. /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
@@ -127,6 +127,8 @@
127
127
  overflow-y: hidden;
128
128
  overflow-x: hidden;
129
129
 
130
+ white-space: pre-wrap;
131
+
130
132
  /**
131
133
  * 1.35 is better on safari, seems like a wash on windows.
132
134
  * can we browser-limit this? (...)
@@ -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: $text-reference-color-1;
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 { TableDescription, TableFooterType } from './workbook2';
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
- composite = FindAll('styleSheet/fills/fill');
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
- composite = FindAll('styleSheet/fonts/font');
1242
+ this.fills = composite.map(ParseFill);
1244
1243
 
1245
- this.fonts = composite.map(element => {
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
  }