@trebco/treb 27.7.6 → 27.11.1
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 +28 -26
- package/notes/conditional-fomratring.md +29 -0
- package/package.json +1 -1
- package/treb-base-types/src/area.ts +181 -0
- package/{treb-grid/src/util/dom_utilities.ts → treb-base-types/src/dom-utilities.ts} +29 -20
- 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 +3 -0
- package/treb-base-types/src/theme.ts +3 -5
- package/treb-calculator/src/calculator.ts +190 -28
- 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/custom-element/global.d.ts +3 -1
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +13 -19
- package/treb-embed/src/embedded-spreadsheet.ts +378 -132
- package/treb-embed/src/spinner.ts +3 -3
- package/treb-embed/style/layout.scss +1 -1
- package/treb-export/src/drawing2/chart2.ts +11 -2
- package/treb-export/src/export-worker/export-worker.ts +0 -13
- package/treb-export/src/export2.ts +197 -2
- package/treb-export/src/import2.ts +169 -4
- package/treb-export/src/workbook-style2.ts +59 -10
- package/treb-export/src/workbook2.ts +10 -1
- package/treb-grid/src/editors/autocomplete.ts +28 -24
- package/treb-grid/src/editors/editor.ts +3 -4
- package/treb-grid/src/editors/formula_bar.ts +1 -1
- package/treb-grid/src/index.ts +2 -1
- package/treb-grid/src/layout/base_layout.ts +34 -31
- package/treb-grid/src/layout/grid_layout.ts +17 -28
- package/treb-grid/src/render/selection-renderer.ts +2 -3
- package/treb-grid/src/render/svg_header_overlay.ts +4 -11
- package/treb-grid/src/render/svg_selection_block.ts +27 -34
- package/treb-grid/src/render/tile_renderer.ts +8 -6
- package/treb-grid/src/types/conditional_format.ts +168 -0
- package/treb-grid/src/types/grid.ts +37 -47
- package/treb-grid/src/types/grid_base.ts +188 -33
- package/treb-grid/src/types/scale-control.ts +2 -2
- package/treb-grid/src/types/sheet.ts +332 -28
- package/treb-grid/src/types/sheet_types.ts +4 -0
- package/treb-grid/src/types/tab_bar.ts +4 -8
- package/treb-utils/src/index.ts +0 -1
- package/treb-utils/src/resizable.ts +26 -27
- package/treb-utils/src/template.ts +0 -70
- /package/{README-shadow-DOM.md → notes/shadow-DOM.md} +0 -0
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
*
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { DOMUtilities } from 'treb-base-types';
|
|
23
|
+
|
|
22
24
|
export class Spinner {
|
|
23
25
|
|
|
24
26
|
private node: HTMLDivElement;
|
|
25
27
|
private visible = false;
|
|
26
28
|
|
|
27
29
|
constructor(public container: HTMLElement) {
|
|
28
|
-
this.node =
|
|
29
|
-
this.node.classList.add('treb-spinner');
|
|
30
|
+
this.node = DOMUtilities.Div('treb-spinner', container);
|
|
30
31
|
this.node.innerHTML = `<div><div></div><div></div><div></div><div></div></div>`;
|
|
31
|
-
container.appendChild(this.node);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
public Show(): void {
|
|
@@ -169,7 +169,11 @@ export class Chart {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
// "accent7" will break
|
|
173
|
+
|
|
174
|
+
if (i < 6) {
|
|
175
|
+
series['c:spPr']['a:ln']['a:solidFill']['a:schemeClr'].a$['val'] = `accent${i+1}`;
|
|
176
|
+
}
|
|
173
177
|
|
|
174
178
|
series['c:yVal']['c:numRef']['c:f'] = this.options.data[i]?.label;
|
|
175
179
|
|
|
@@ -255,7 +259,12 @@ export class Chart {
|
|
|
255
259
|
|
|
256
260
|
series['c:idx'] = { a$: { val: i.toString() }};
|
|
257
261
|
series['c:order'] = { a$: { val: i.toString() }};
|
|
258
|
-
|
|
262
|
+
|
|
263
|
+
// "accent7" will break
|
|
264
|
+
|
|
265
|
+
if (i < 6) {
|
|
266
|
+
series['c:spPr']['a:solidFill']['a:schemeClr'].a$['val'] = `accent${i+1}`;
|
|
267
|
+
}
|
|
259
268
|
|
|
260
269
|
if (!i && this.options.labels) {
|
|
261
270
|
series['c:cat'] = {
|
|
@@ -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
|
// ------------
|
|
@@ -586,6 +642,9 @@ export class Exporter {
|
|
|
586
642
|
// is this backwards, vis a vis our rendering? I think it might be...
|
|
587
643
|
// YES: should be row pattern -> row -> column -> cell [corrected]
|
|
588
644
|
|
|
645
|
+
// FIXME: can't we just ask the sheet? (A: no, because we don't have
|
|
646
|
+
// an actual sheet, although we could?)
|
|
647
|
+
|
|
589
648
|
// if (sheet.row_style && sheet.row_style[row]) {
|
|
590
649
|
// list.push(sheet.row_style[row]);
|
|
591
650
|
// }
|
|
@@ -1214,6 +1273,7 @@ export class Exporter {
|
|
|
1214
1273
|
},
|
|
1215
1274
|
dataValidations: {},
|
|
1216
1275
|
hyperlinks: {},
|
|
1276
|
+
conditionalFormatting: {},
|
|
1217
1277
|
pageMargins: {
|
|
1218
1278
|
a$: {
|
|
1219
1279
|
left: 0.7,
|
|
@@ -1981,6 +2041,135 @@ export class Exporter {
|
|
|
1981
2041
|
|
|
1982
2042
|
}
|
|
1983
2043
|
|
|
2044
|
+
// --- conditional formats -----------------------------------------------
|
|
2045
|
+
|
|
2046
|
+
if (sheet.conditional_formats?.length) {
|
|
2047
|
+
const conditionalFormatting: any[] = [];
|
|
2048
|
+
let priority_index = 1;
|
|
2049
|
+
|
|
2050
|
+
const reverse_operator_map: Record<string, string> = {};
|
|
2051
|
+
const operator_list: string[] = Object.entries(ConditionalFormatOperators).map(entry => {
|
|
2052
|
+
reverse_operator_map[entry[1]] = entry[0];
|
|
2053
|
+
return entry[1];
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
operator_list.sort((a, b) => b.length - a.length);
|
|
2057
|
+
|
|
2058
|
+
for (const format of sheet.conditional_formats) {
|
|
2059
|
+
|
|
2060
|
+
let dxf_index = 0;
|
|
2061
|
+
|
|
2062
|
+
if (format.type !== 'gradient') {
|
|
2063
|
+
|
|
2064
|
+
// these are zero-based? I thought everything in there
|
|
2065
|
+
// was 1-based. [A: yes, these are indexed from 0].
|
|
2066
|
+
|
|
2067
|
+
dxf_index = style_cache.dxf_styles.length;
|
|
2068
|
+
style_cache.dxf_styles.push(format.style);
|
|
2069
|
+
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
switch (format.type) {
|
|
2073
|
+
case 'cell-match':
|
|
2074
|
+
{
|
|
2075
|
+
let operator = '';
|
|
2076
|
+
let formula = '';
|
|
2077
|
+
|
|
2078
|
+
for (const test of operator_list) {
|
|
2079
|
+
if (new RegExp('^' + test + '\\s').test(format.expression)) {
|
|
2080
|
+
operator = reverse_operator_map[test];
|
|
2081
|
+
formula = format.expression.substring(test.length).trim();
|
|
2082
|
+
break;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
if (operator) {
|
|
2086
|
+
|
|
2087
|
+
conditionalFormatting.push({
|
|
2088
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2089
|
+
cfRule: {
|
|
2090
|
+
a$: { type: 'cellIs', dxfId: dxf_index, operator, priority: priority_index++ },
|
|
2091
|
+
formula,
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
break;
|
|
2097
|
+
|
|
2098
|
+
case 'expression':
|
|
2099
|
+
conditionalFormatting.push({
|
|
2100
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2101
|
+
cfRule: {
|
|
2102
|
+
a$: { type: 'expression', dxfId: dxf_index, priority: priority_index++ },
|
|
2103
|
+
formula: format.expression,
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
break;
|
|
2107
|
+
|
|
2108
|
+
case 'duplicate-values':
|
|
2109
|
+
conditionalFormatting.push({
|
|
2110
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2111
|
+
cfRule: {
|
|
2112
|
+
a$: { type: format.unique ? 'uniqueValues' : 'duplicateValues', dxfId: dxf_index, priority: priority_index++ },
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
break;
|
|
2116
|
+
|
|
2117
|
+
case 'gradient':
|
|
2118
|
+
{
|
|
2119
|
+
let cfvo: any[] = [];
|
|
2120
|
+
let color: any[] = [];
|
|
2121
|
+
|
|
2122
|
+
for (const stop of format.stops) {
|
|
2123
|
+
if (stop.value === 0) {
|
|
2124
|
+
cfvo.push({ a$: { type: 'min' }});
|
|
2125
|
+
}
|
|
2126
|
+
else if (stop.value === 1) {
|
|
2127
|
+
cfvo.push({ a$: { type: 'max' }});
|
|
2128
|
+
}
|
|
2129
|
+
else {
|
|
2130
|
+
cfvo.push({ a$: { type: 'percentile', val: stop.value * 100 }});
|
|
2131
|
+
}
|
|
2132
|
+
const stop_color: any = { a$: {}};
|
|
2133
|
+
if (stop.color.text) {
|
|
2134
|
+
stop_color.a$.rgb = 'FF' + stop.color.text.substring(1);
|
|
2135
|
+
}
|
|
2136
|
+
else if (stop.color.theme) {
|
|
2137
|
+
stop_color.a$.theme = stop.color.theme;
|
|
2138
|
+
stop_color.a$.tint = stop.color.tint || undefined;
|
|
2139
|
+
}
|
|
2140
|
+
color.push(stop_color);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
const generated = {
|
|
2144
|
+
a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
|
|
2145
|
+
cfRule: {
|
|
2146
|
+
a$: { type: 'colorScale', priority: priority_index++ },
|
|
2147
|
+
colorScale: {
|
|
2148
|
+
cfvo,
|
|
2149
|
+
color,
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
|
|
2154
|
+
conditionalFormatting.push(generated);
|
|
2155
|
+
|
|
2156
|
+
}
|
|
2157
|
+
break;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
if (conditionalFormatting.length) {
|
|
2162
|
+
dom.worksheet.conditionalFormatting = (conditionalFormatting.length > 1) ? conditionalFormatting : conditionalFormatting[0];
|
|
2163
|
+
}
|
|
2164
|
+
else {
|
|
2165
|
+
delete dom.worksheet.conditionalFormatting;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
}
|
|
2169
|
+
else {
|
|
2170
|
+
delete dom.worksheet.conditionalFormatting;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
1984
2173
|
// --- merges ------------------------------------------------------------
|
|
1985
2174
|
|
|
1986
2175
|
if (merges.length) {
|
|
@@ -2152,6 +2341,12 @@ export class Exporter {
|
|
|
2152
2341
|
delete dom.worksheet.drawing;
|
|
2153
2342
|
}
|
|
2154
2343
|
|
|
2344
|
+
// --- move page margins -------------------------------------------------
|
|
2345
|
+
|
|
2346
|
+
// const margins = dom.worksheet.pageMargins;
|
|
2347
|
+
// delete dom.worksheet.pageMargins;
|
|
2348
|
+
// dom.worksheet.pageMargins = margins;
|
|
2349
|
+
|
|
2155
2350
|
// --- end? --------------------------------------------------------------
|
|
2156
2351
|
|
|
2157
2352
|
// 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
|
|
|
@@ -423,17 +424,18 @@ export class StyleCache {
|
|
|
423
424
|
fill.pattern_type = 'solid';
|
|
424
425
|
if (composite.fill.text) {
|
|
425
426
|
fill.fg_color = { argb: composite.fill.text };
|
|
427
|
+
options.fill = fill;
|
|
426
428
|
}
|
|
427
429
|
else if (typeof composite.fill.theme === 'number') {
|
|
428
430
|
fill.fg_color = { theme: composite.fill.theme };
|
|
429
431
|
if (composite.fill.tint) {
|
|
430
432
|
fill.fg_color.tint = composite.fill.tint;
|
|
431
433
|
}
|
|
434
|
+
options.fill = fill;
|
|
432
435
|
}
|
|
433
436
|
else {
|
|
434
|
-
fill.fg_color = { theme: 1 };
|
|
437
|
+
// fill.fg_color = { theme: 1 };
|
|
435
438
|
}
|
|
436
|
-
options.fill = fill;
|
|
437
439
|
}
|
|
438
440
|
|
|
439
441
|
if (composite.wrap) {
|
|
@@ -1197,9 +1199,7 @@ export class StyleCache {
|
|
|
1197
1199
|
|
|
1198
1200
|
// ---
|
|
1199
1201
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
this.fills = composite.map(element => {
|
|
1202
|
+
const ParseFill = (element: any) => {
|
|
1203
1203
|
|
|
1204
1204
|
const fill: Fill = { pattern_type: 'none' };
|
|
1205
1205
|
if (element.patternFill) {
|
|
@@ -1236,13 +1236,15 @@ export class StyleCache {
|
|
|
1236
1236
|
|
|
1237
1237
|
return fill;
|
|
1238
1238
|
|
|
1239
|
-
}
|
|
1239
|
+
};
|
|
1240
1240
|
|
|
1241
|
-
|
|
1241
|
+
composite = FindAll('styleSheet/fills/fill');
|
|
1242
1242
|
|
|
1243
|
-
|
|
1243
|
+
this.fills = composite.map(ParseFill);
|
|
1244
1244
|
|
|
1245
|
-
|
|
1245
|
+
// ---
|
|
1246
|
+
|
|
1247
|
+
const ParseFont = (element: any) => {
|
|
1246
1248
|
|
|
1247
1249
|
const font: Font = {};
|
|
1248
1250
|
|
|
@@ -1278,8 +1280,55 @@ export class StyleCache {
|
|
|
1278
1280
|
|
|
1279
1281
|
return font;
|
|
1280
1282
|
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
composite = FindAll('styleSheet/fonts/font');
|
|
1286
|
+
this.fonts = composite.map(ParseFont);
|
|
1287
|
+
|
|
1288
|
+
// dxfs (differential formats) are inline. because reasons? not sure
|
|
1289
|
+
// what's allowed in there, atm we're just looking at font color and
|
|
1290
|
+
// background color.
|
|
1291
|
+
|
|
1292
|
+
const ParseDXFColor = (element: any) => {
|
|
1293
|
+
const color: Color = {};
|
|
1294
|
+
if (element.a$.rgb) {
|
|
1295
|
+
color.text = '#' + element.a$.rgb.substring(2);
|
|
1296
|
+
}
|
|
1297
|
+
else if (element.a$.theme) {
|
|
1298
|
+
color.theme = Number(element.a$.theme) || 0;
|
|
1299
|
+
if (element.a$.tint) {
|
|
1300
|
+
color.tint = Math.round(element.a$.tint * 1000) / 1000;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return color;
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
const dxfs = FindAll('styleSheet/dxfs/dxf');
|
|
1307
|
+
this.dxf_styles = dxfs.map(dxf => {
|
|
1308
|
+
|
|
1309
|
+
const style: CellStyle = {};
|
|
1310
|
+
|
|
1311
|
+
// dxf fonts are different too? this is irritating
|
|
1312
|
+
|
|
1313
|
+
if (dxf.font) {
|
|
1314
|
+
style.bold = !!dxf.font.b;
|
|
1315
|
+
style.italic = !!dxf.font.i && dxf.font.i.a$.val !== '0';
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// dxfs fills are different? anyway we can't reuse the above code for fill, just grab the color
|
|
1319
|
+
|
|
1320
|
+
if (dxf.font?.color?.a$) {
|
|
1321
|
+
style.text = ParseDXFColor(dxf.font.color);
|
|
1322
|
+
}
|
|
1323
|
+
if (dxf.fill?.patternFill?.bgColor?.a$) {
|
|
1324
|
+
style.fill = ParseDXFColor(dxf.fill.patternFill.bgColor);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
return style;
|
|
1281
1328
|
});
|
|
1282
1329
|
|
|
1330
|
+
// console.info({dxfs: this.dxf_styles});
|
|
1331
|
+
|
|
1283
1332
|
}
|
|
1284
1333
|
|
|
1285
1334
|
}
|
|
@@ -54,6 +54,15 @@ const XMLTypeMap = {
|
|
|
54
54
|
};
|
|
55
55
|
*/
|
|
56
56
|
|
|
57
|
+
export const ConditionalFormatOperators: Record<string, string> = {
|
|
58
|
+
greaterThan: '>',
|
|
59
|
+
greaterThanOrEquals: '>=',
|
|
60
|
+
lessThan: '<',
|
|
61
|
+
lessThanOrEquals: '<=',
|
|
62
|
+
equal: '=',
|
|
63
|
+
notEqual: '<>',
|
|
64
|
+
};
|
|
65
|
+
|
|
57
66
|
export enum ChartType {
|
|
58
67
|
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie
|
|
59
68
|
}
|
|
@@ -180,7 +189,7 @@ export class Workbook {
|
|
|
180
189
|
xml = xmlparser2.parse(data);
|
|
181
190
|
this.style_cache.FromXML(xml, this.theme);
|
|
182
191
|
|
|
183
|
-
console.info({c: this.style_cache});
|
|
192
|
+
// console.info({c: this.style_cache});
|
|
184
193
|
|
|
185
194
|
// read workbook
|
|
186
195
|
data = await this.zip.file('xl/workbook.xml')?.async('text') as string;
|