@trebco/treb 29.3.4 → 29.5.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 (44) hide show
  1. package/dist/treb-spreadsheet-light.mjs +12 -12
  2. package/dist/treb-spreadsheet.mjs +12 -12
  3. package/dist/treb.d.ts +36 -41
  4. package/package.json +1 -1
  5. package/treb-base-types/src/area.ts +7 -0
  6. package/treb-base-types/src/cell.ts +2 -46
  7. package/treb-base-types/src/cells.ts +14 -8
  8. package/treb-base-types/src/gradient.ts +2 -2
  9. package/treb-base-types/src/import.ts +2 -2
  10. package/treb-base-types/src/style.ts +79 -6
  11. package/treb-base-types/src/theme.ts +24 -15
  12. package/treb-calculator/src/calculator.ts +22 -12
  13. package/treb-calculator/src/dag/graph.ts +12 -3
  14. package/treb-calculator/src/expression-calculator.ts +66 -74
  15. package/treb-calculator/src/functions/base-functions.ts +2 -2
  16. package/treb-calculator/src/functions/sparkline.ts +2 -2
  17. package/treb-calculator/src/functions/statistics-functions.ts +31 -1
  18. package/treb-data-model/src/data-validation.ts +44 -0
  19. package/treb-data-model/src/data_model.ts +11 -7
  20. package/treb-data-model/src/index.ts +1 -1
  21. package/treb-data-model/src/named.ts +35 -10
  22. package/treb-data-model/src/sheet.ts +75 -15
  23. package/treb-data-model/src/sheet_types.ts +4 -0
  24. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
  25. package/treb-embed/src/embedded-spreadsheet.ts +50 -28
  26. package/treb-embed/src/progress-dialog.ts +4 -1
  27. package/treb-embed/src/types.ts +9 -0
  28. package/treb-export/src/drawing2/chart2.ts +20 -38
  29. package/treb-export/src/drawing2/drawing2.ts +2 -107
  30. package/treb-export/src/export-worker/export-worker.ts +1 -1
  31. package/treb-export/src/{export2.ts → export.ts} +439 -628
  32. package/treb-export/src/import2.ts +63 -26
  33. package/treb-export/src/workbook-style2.ts +16 -14
  34. package/treb-export/src/workbook2.ts +2 -18
  35. package/treb-export/src/xml-utils.ts +50 -2
  36. package/treb-export/src/zip-wrapper.ts +1 -1
  37. package/treb-grid/src/editors/overlay_editor.ts +3 -3
  38. package/treb-grid/src/layout/base_layout.ts +5 -14
  39. package/treb-grid/src/render/tile_renderer.ts +49 -48
  40. package/treb-grid/src/types/grid.ts +164 -26
  41. package/treb-grid/src/types/grid_base.ts +93 -17
  42. package/treb-grid/src/types/grid_command.ts +2 -1
  43. package/treb-parser/src/parser-types.ts +10 -0
  44. package/treb-parser/src/parser.ts +55 -17
@@ -36,13 +36,13 @@ const XMLDeclaration = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
36
36
  import { template } from './template-2';
37
37
  import type { SerializedModel, SerializedSheet } from 'treb-data-model';
38
38
 
39
- import type { IArea, ICellAddress, CellValue, DataValidation, CellStyle,
40
- AnnotationLayout, Corner as LayoutCorner, Cell, Rectangle } from 'treb-base-types';
41
- import { Area, Cells, ValueType, Style, ValidationType } from 'treb-base-types';
39
+ import type { IArea, ICellAddress, CellStyle,
40
+ AnnotationLayout, Corner as LayoutCorner, Cell, Rectangle, Color } from 'treb-base-types';
41
+ import { Area, Cells, ValueType, Style, IsHTMLColor, IsThemeColor, ThemeColorIndex } from 'treb-base-types';
42
42
 
43
43
  // import * as xmlparser from 'fast-xml-parser';
44
44
  import type { XmlBuilderOptions} from 'fast-xml-parser';
45
- import { XMLParser, XMLBuilder } from 'fast-xml-parser';
45
+ import { XMLParser } from 'fast-xml-parser';
46
46
 
47
47
  import { SharedStrings } from './shared-strings2';
48
48
  import type { XlColor, BorderEdge } from './workbook-style2';
@@ -51,7 +51,7 @@ import { Theme } from './workbook-theme2';
51
51
 
52
52
  import type { RelationshipMap} from './relationship';
53
53
  import { AddRel } from './relationship';
54
- import { type DOMContent, XMLOptions2 } from './xml-utils';
54
+ import { type DOMContent, XMLOptions2, PatchXMLBuilder } from './xml-utils';
55
55
 
56
56
  import type { UnitAddress, UnitRange, ExpressionUnit} from 'treb-parser';
57
57
  import { Parser } from 'treb-parser';
@@ -72,6 +72,39 @@ interface NestedDOMType {
72
72
  }
73
73
  */
74
74
 
75
+ /**
76
+ * utility function. given a Color object (our Color, from Style) returns
77
+ * an XML structure like
78
+ *
79
+ * { a$: { rgb: '123456 }}
80
+ *
81
+ * or
82
+ *
83
+ * { a$: { theme: 1, tint: .5 }}
84
+ *
85
+ */
86
+ const ColorAttrs = (color?: Color): DOMContent|undefined => {
87
+
88
+ if (IsHTMLColor(color)) {
89
+ return {
90
+ a$: {
91
+ rgb: `FF` + color.text.substring(1),
92
+ },
93
+ };
94
+ }
95
+ if (IsThemeColor(color)) {
96
+ return {
97
+ a$: {
98
+ theme: ThemeColorIndex(color),
99
+ tint: color.tint,
100
+ },
101
+ };
102
+ }
103
+
104
+ return undefined;
105
+
106
+ };
107
+
75
108
  export class Exporter {
76
109
 
77
110
  // public zip?: JSZip;
@@ -119,7 +152,7 @@ export class Exporter {
119
152
  };
120
153
 
121
154
  // public xmlparser = new xmlparser.j2xParser(this.xmloptions);
122
- public xmlbuilder1 = new XMLBuilder(this.xmloptions);
155
+ public xmlbuilder1 = PatchXMLBuilder(this.xmloptions);
123
156
  public xmlparser2 = new XMLParser(XMLOptions2);
124
157
 
125
158
  // FIXME: need a way to share/pass parser flags
@@ -217,7 +250,8 @@ export class Exporter {
217
250
  if (color.argb !== undefined) {
218
251
  attrs.rgb = color.argb;
219
252
  }
220
- return attrs;
253
+
254
+ return attrs as DOMContent;
221
255
 
222
256
  };
223
257
 
@@ -255,7 +289,7 @@ export class Exporter {
255
289
  return block;
256
290
  });
257
291
 
258
- const BorderColorAttributes = (edge: BorderEdge) => {
292
+ const BorderColorAttributes= (edge: BorderEdge): DOMContent|undefined => {
259
293
  if (edge.color) {
260
294
  return { indexed: edge.color };
261
295
  }
@@ -263,131 +297,174 @@ export class Exporter {
263
297
  return { rgb: edge.rgba };
264
298
  }
265
299
  if (edge.theme) {
266
- const attrs: any = {
300
+ return {
267
301
  theme: edge.theme,
268
- }
269
- if (edge.tint) {
270
- attrs.tint = edge.tint;
271
- }
272
- return attrs;
302
+ tint: edge.tint,
303
+ };
273
304
  }
274
305
  return undefined;
275
306
  };
276
307
 
277
308
  const borders = style_cache.borders.map(border => {
278
- const block: any = {
279
- left: {},
280
- right: {},
281
- top: {},
282
- bottom: {},
283
- diagonal: {},
284
- };
309
+
310
+ const top: DOMContent = {};
311
+ const left: DOMContent = {};
312
+ const right: DOMContent = {};
313
+ const bottom: DOMContent = {};
314
+ const diagonal: DOMContent = {};
285
315
 
286
316
  if (border.top.style) {
287
- block.top.a$ = {
317
+ top.a$ = {
288
318
  style: border.top.style,
289
319
  };
290
320
  const attrs = BorderColorAttributes(border.top);
291
- if (attrs) { block.top.color = {a$: attrs}; }
321
+ if (attrs) { top.color = {a$: attrs}; }
292
322
  }
293
323
 
294
324
  if (border.left.style) {
295
- block.left.a$ = {
325
+ left.a$ = {
296
326
  style: border.left.style,
297
327
  };
298
328
  const attrs = BorderColorAttributes(border.left);
299
- if (attrs) { block.left.color = {a$: attrs}; }
329
+ if (attrs) { left.color = {a$: attrs}; }
300
330
  }
301
331
 
302
332
  if (border.bottom.style) {
303
- block.bottom.a$ = {
333
+ bottom.a$ = {
304
334
  style: border.bottom.style,
305
335
  };
306
336
  const attrs = BorderColorAttributes(border.bottom);
307
- if (attrs) { block.bottom.color = {a$: attrs}; }
337
+ if (attrs) { bottom.color = {a$: attrs}; }
308
338
  }
309
339
 
310
340
  if (border.right.style) {
311
- block.right.a$ = {
341
+ right.a$ = {
312
342
  style: border.right.style,
313
343
  };
314
344
  const attrs = BorderColorAttributes(border.right);
315
- if (attrs) { block.right.color = {a$: attrs}; }
345
+ if (attrs) { right.color = {a$: attrs}; }
316
346
  }
317
347
 
318
348
  if (border.diagonal.style) {
319
- block.diagonal.a$ = {
349
+ diagonal.a$ = {
320
350
  style: border.diagonal.style,
321
351
  };
322
352
  const attrs = BorderColorAttributes(border.diagonal);
323
- if (attrs) { block.diagonal.color = {a$: attrs}; }
353
+ if (attrs) { diagonal.color = {a$: attrs}; }
324
354
  }
325
355
 
326
- return block;
356
+ return {
357
+ left,
358
+ right,
359
+ top,
360
+ bottom,
361
+ diagonal,
362
+ };
363
+
327
364
  });
328
365
 
329
- // console.info("SC", style_cache);
366
+ const fills: DOMContent[] = style_cache.fills.map(fill => ({
367
+ patternFill: {
368
+ a$: {
369
+ patternType: (fill.pattern_gray !== undefined) ? `gray${fill.pattern_gray}` : fill.pattern_type,
370
+ },
330
371
 
331
- const fills = style_cache.fills.map(fill => {
332
- const block: any = {
333
- a$: { patternType: fill.pattern_type },
334
- };
372
+ bgColor: fill.bg_color ? {
373
+ a$: ColorAttributes(fill.bg_color),
374
+ } : undefined,
335
375
 
336
- if (fill.pattern_gray !== undefined) {
337
- block.a$.patternType = `gray${fill.pattern_gray}`;
338
- }
339
- if (fill.bg_color) {
340
- block.bgColor = { a$: ColorAttributes(fill.bg_color) };
341
- }
342
- if (fill.fg_color) {
343
- block.fgColor = { a$: ColorAttributes(fill.fg_color) };
376
+ fgColor: fill.fg_color ? {
377
+ a$: ColorAttributes(fill.fg_color),
378
+ } : undefined,
379
+ },
380
+ }));
381
+
382
+ const ValProp = (prop: string|number|undefined) => {
383
+
384
+ if (typeof prop === 'undefined') {
385
+ return undefined;
344
386
  }
345
387
 
346
- return {patternFill: block};
347
- });
388
+ return {
389
+ a$: {
390
+ val: prop,
391
+ },
392
+ };
348
393
 
349
- const fonts = style_cache.fonts.map(font => {
350
- const block: any = {};
394
+ };
351
395
 
352
- // flags
396
+ const fonts: DOMContent[] = style_cache.fonts.map(font => {
353
397
 
354
- if (font.bold) { block.b = ''; }
355
- if (font.italic) { block.i = ''; }
356
- if (font.underline) { block.u = ''; }
357
- if (font.strike) { block.strike = ''; }
398
+ return {
358
399
 
359
- // "val" props
400
+ // flags
360
401
 
361
- if (font.size !== undefined) {
362
- block.sz = { a$: { val: font.size }};
363
- }
364
- if (font.family !== undefined) {
365
- block.family = { a$: { val: font.family }};
366
- }
367
- if (font.name !== undefined) {
368
- block.name = { a$: { val: font.name }};
369
- }
370
- if (font.scheme !== undefined) {
371
- block.scheme = { a$: { val: font.scheme }};
372
- }
402
+ b: font.bold ? '' : undefined,
403
+ i: font.italic ? '' : undefined,
404
+ u: font.underline ? '' : undefined,
405
+ strike: font.strike ? '' : undefined,
406
+
407
+ // 'val' props
373
408
 
374
- // color
409
+ sz: ValProp(font.size),
410
+ family: ValProp(font.family),
411
+ name: ValProp(font.name),
412
+ scheme: ValProp(font.scheme),
413
+
414
+ color: font.color_argb ? {
415
+ a$: { rgb: font.color_argb },
416
+ } : font.color_theme ? {
417
+ a$: {
418
+ theme: font.color_theme,
419
+ tint: font.color_tint,
420
+ },
421
+ } : undefined,
422
+
423
+ };
424
+
425
+ });
375
426
 
376
- if (font.color_argb !== undefined) {
377
- block.color = { a$: { rgb: font.color_argb }};
427
+ const WithCount = (key: string, source: DOMContent[]) => {
428
+ if (source.length) {
429
+ return {
430
+ a$: { count: source.length },
431
+ [key]: source,
432
+ };
378
433
  }
379
- else if (font.color_theme !== undefined) {
380
- block.color = { a$: { theme: font.color_theme }};
381
- if (font.color_tint) {
382
- block.color.a$.tint = font.color_tint;
383
- }
434
+ return undefined;
435
+ };
436
+
437
+
438
+ const dxf: DOMContent[] = style_cache.dxf_styles.map(style => {
439
+
440
+ const entry: DOMContent = {
441
+ fill: style.fill ? {
442
+ patternFill: {
443
+ bgColor: ColorAttrs(style.fill),
444
+ },
445
+ } : undefined,
446
+ };
447
+
448
+ if (style.text || style.bold || style.italic || style.underline) {
449
+ entry.font = {
450
+ b: style.bold ? {} : undefined,
451
+ i: style.italic ? {} : undefined,
452
+ u: style.underline ? {} : undefined,
453
+ strike: style.strike ? {} : undefined,
454
+ color: ColorAttrs(style.text),
455
+ };
384
456
  }
385
457
 
386
- return block;
458
+ return entry;
459
+
387
460
  });
388
461
 
462
+ // console.info({dxf});
463
+
389
464
  const dom: DOMContent = {
465
+
390
466
  styleSheet: {
467
+
391
468
  a$: {
392
469
  'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
393
470
  'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
@@ -396,131 +473,34 @@ export class Exporter {
396
473
  'xmlns:x16r2': 'http://schemas.microsoft.com/office/spreadsheetml/2015/02/main',
397
474
  'xmlns:xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision',
398
475
  },
399
- },
400
- };
401
-
402
- // we're only adding elements here if they are not empty, but in
403
- // practice only numFmts can be empty (because there are implicit
404
- // formats); everything else has a default 0 entry
405
-
406
- if (style_cache.number_formats.length) {
407
-
408
- (dom.styleSheet as DOMContent).numFmts = {
409
- a$: { count: style_cache.number_formats.length },
410
- numFmt: style_cache.number_formats.map(format => {
411
- return {
412
- a$: {
413
- numFmtId: format.id,
414
- formatCode: format.format,
415
- } as DOMContent,
416
- };
417
- }),
418
- };
419
-
420
- // console.info(style_cache.number_formats);
421
- // console.info(dom.styleSheet.numFmts);
422
476
 
423
- }
424
-
425
- if (fonts.length) {
426
- (dom.styleSheet as DOMContent).fonts = {
427
- a$: { count: fonts.length },
428
- font: fonts,
429
- };
430
- }
431
-
432
- if (fills.length) {
433
- (dom.styleSheet as DOMContent).fills = {
434
- a$: { count: fills.length },
435
- fill: fills,
436
- };
437
- }
438
-
439
- if (borders.length) {
440
- (dom.styleSheet as DOMContent).borders = {
441
- a$: { count: borders.length },
442
- border: borders,
443
- };
444
- }
445
-
446
- // console.info("B", borders, JSON.stringify(dom.styleSheet.borders, undefined, 2))
447
-
448
- if (xfs.length) {
449
- (dom.styleSheet as DOMContent).cellXfs = {
450
- a$: { count: xfs.length },
451
- xf: xfs,
452
- };
453
- }
477
+ // we're only adding elements here if they are not empty, but in
478
+ // practice only numFmts can be empty (because there are implicit
479
+ // formats); everything else has a default 0 entry
454
480
 
455
- if (style_cache.dxf_styles.length) {
456
- const dxf: DOMContent[] = [];
457
- for (const style of style_cache.dxf_styles) {
458
- const entry: DOMContent = {};
459
- if (style.text || style.bold || style.italic || style.underline) {
460
- const font: DOMContent = {};
461
- if (style.text) {
462
- font.color = { a$: {}};
463
- if (style.text.text) {
464
- (font.color.a$ as DOMContent).rgb = `FF` + style.text.text.substring(1);
465
- }
466
- else if (style.text.theme) {
467
- (font.color.a$ as DOMContent).theme = style.text.theme;
468
- if (style.text.tint) {
469
- (font.color.a$ as DOMContent).tint = style.text.tint;
470
- }
471
- }
472
- }
473
- if (style.bold) {
474
- font.b = {};
475
- }
476
- if (style.italic) {
477
- font.i = {};
478
- }
479
- if (style.underline) {
480
- font.u = {};
481
- }
482
- if (style.strike) {
483
- font.strike = {};
484
- }
485
- entry.font = font;
486
- }
487
- if (style.fill) {
488
- const attrs: DOMContent = {};
489
- if (style.fill.text) {
490
- attrs.rgb = `FF` + style.fill.text.substring(1);
491
- }
492
- else if (style.fill.theme) {
493
- attrs.theme = style.fill.theme;
494
- if (style.fill.tint) {
495
- attrs.tint = style.fill.tint;
496
- }
497
- }
498
- const color: DOMContent = { a$: attrs };
499
- entry.fill = { patternFill: { bgColor: color }};
500
- }
501
- dxf.push(entry);
502
- }
503
-
504
- if (dxf.length) {
505
- (dom.styleSheet as DOMContent).dxfs = {
506
- a$: { count: dxf.length },
507
- dxf,
508
- };
509
- }
481
+ numFmts: style_cache.number_formats.length ? {
482
+ a$: { count: style_cache.number_formats.length },
483
+ numFmt: style_cache.number_formats.map(format => {
484
+ return {
485
+ a$: {
486
+ numFmtId: format.id,
487
+ formatCode: format.format,
488
+ } as DOMContent,
489
+ };
490
+ }),
491
+ } : undefined,
492
+
493
+ fonts: WithCount('font', fonts),
494
+ fills: WithCount('fill', fills),
495
+ borders: WithCount('border', borders),
496
+ cellXfs: WithCount('xf', xfs),
497
+ dxfs: WithCount('dxf', dxf),
510
498
 
511
- }
499
+ },
512
500
 
513
- // not used:
514
- //
515
- // cellStyleXfs
516
- // cellStyles
517
- // tableStyles
518
-
519
- // ------------
501
+ };
520
502
 
521
503
  const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
522
- // console.info(xml);
523
-
524
504
  this.zip?.Set('xl/styles.xml', xml);
525
505
 
526
506
  }
@@ -558,34 +538,6 @@ export class Exporter {
558
538
 
559
539
  }
560
540
 
561
- /**
562
- * FIXME: merge with workbook function (put somewhere else)
563
- * /
564
- public async ReadRels(zip?: JSZip, path = ''): Promise<RelationshipMap> {
565
-
566
- const rels: RelationshipMap = {};
567
- const data = await zip?.file(path)?.async('text') as string;
568
- //
569
- // force array on <Relationship/> elements, but be slack on the rest
570
- // (we know they are single elements)
571
- //
572
- const xml = this.xmlparser2.parse(data || '');
573
- console.info(path, xml);
574
-
575
- for (const relationship of xml.Relationships?.Relationship || []) {
576
- const id = relationship.a$.Id;
577
- rels[id] = {
578
- id,
579
- type: relationship.a$.Type,
580
- target: relationship.a$.Target,
581
- };
582
- }
583
-
584
- return rels;
585
-
586
- }
587
- */
588
-
589
541
  /**
590
542
  * FIXME: we might not always need this.
591
543
  */
@@ -1292,80 +1244,6 @@ export class Exporter {
1292
1244
 
1293
1245
  const default_row_height = sheet.default_row_height ? (sheet.default_row_height / 20 * 15) : 15;
1294
1246
 
1295
- const dom: any = {
1296
- worksheet: {
1297
- a$: {
1298
- ...sheet_attributes,
1299
- },
1300
- dimension: {
1301
- a$: {
1302
- ref: 'A1',
1303
- },
1304
- },
1305
- sheetViews: {
1306
- sheetView: {
1307
- a$: {
1308
- // tabSelected: (sheet_index === active_sheet ? 1 : 0),
1309
- workbookViewId: 0,
1310
- },
1311
- },
1312
- },
1313
- sheetFormatPr: {
1314
- a$: default_row_height === 15 ? {
1315
- 'x14ac:dyDescent': 0.25,
1316
- } : {
1317
- defaultRowHeight: default_row_height,
1318
- customHeight: 1,
1319
- 'x14ac:dyDescent': 0.25,
1320
- },
1321
- },
1322
- cols: {},
1323
- sheetData: {},
1324
- mergeCells: {
1325
- a$: { count: 0 },
1326
- },
1327
- dataValidations: {},
1328
- hyperlinks: {},
1329
- conditionalFormatting: {},
1330
- pageMargins: {
1331
- a$: {
1332
- left: 0.7,
1333
- right: 0.7,
1334
- top: 0.75,
1335
- bottom: 0.75,
1336
- header: 0.3,
1337
- footer: 0.3,
1338
- },
1339
- },
1340
- drawing: {},
1341
- tableParts: {
1342
- a$: {
1343
- count: 0,
1344
- },
1345
- },
1346
- extLst: {
1347
- ext: {
1348
- a$: {
1349
- uri: '{05C60535-1F16-4fd2-B633-F4F36F0B64E0}',
1350
- 'xmlns:x14': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main',
1351
- },
1352
- },
1353
- },
1354
-
1355
- },
1356
- };
1357
-
1358
- /*
1359
- const column_styles = (sheet.column_style as any) || {};
1360
-
1361
- for (const key of Object.keys(column_styles)) {
1362
- console.info(key, '=>', column_styles[key]);
1363
-
1364
- }
1365
-
1366
- return;
1367
- */
1368
-
1369
1247
  // data has different representations. it is either blocked into rows or
1370
1248
  // columns, or a set of individual cells. we could theoretically guarantee
1371
1249
  // a particular encoding if we wanted to (by row would be optimal for excel).
@@ -1400,7 +1278,8 @@ export class Exporter {
1400
1278
 
1401
1279
  // cells data is row-major, and sparse.
1402
1280
 
1403
- const sheet_data: any = { row: [] };
1281
+ // const sheet_data: any = { row: [] };
1282
+ const sheet_rows: DOMContent[] = [];
1404
1283
 
1405
1284
  const hyperlinks: Array<{
1406
1285
  rel: string,
@@ -1417,11 +1296,6 @@ export class Exporter {
1417
1296
  const merges: Area[] = [];
1418
1297
  const tables: TableDescription[] = [];
1419
1298
 
1420
- const validations: Array<{
1421
- address: ICellAddress,
1422
- validation: DataValidation,
1423
- }> = [];
1424
-
1425
1299
  // --
1426
1300
 
1427
1301
  //
@@ -1447,7 +1321,7 @@ export class Exporter {
1447
1321
 
1448
1322
  // row span
1449
1323
  const span = {start: -1, end: -1};
1450
- const row: any = [];
1324
+ const row: DOMContent[] = [];
1451
1325
 
1452
1326
  for (let c = 0; c < cells.data[r].length; c++) {
1453
1327
 
@@ -1606,13 +1480,6 @@ export class Exporter {
1606
1480
  });
1607
1481
  }
1608
1482
 
1609
- if (cell.validation && (cell.validation.type === ValidationType.List || cell.validation.type === ValidationType.Range)) {
1610
- validations.push({
1611
- address: {row: r, column: c},
1612
- validation: cell.validation,
1613
- });
1614
- }
1615
-
1616
1483
  // short-circuit here
1617
1484
  if (cell.type === ValueType.formula && /^=?sparkline\./i.test(cell.value as string)) {
1618
1485
  sparklines.push({
@@ -1652,21 +1519,22 @@ export class Exporter {
1652
1519
  }
1653
1520
 
1654
1521
  // v (child element) is the value
1655
- let v: CellValue = undefined;
1522
+
1523
+ let v: string|number|undefined;
1656
1524
  let t: string|undefined;
1657
- let f: any; // string|undefined;
1525
+ let f: DOMContent|string|undefined; // string|undefined;
1658
1526
 
1659
1527
  switch (cell.type) {
1660
1528
  case ValueType.formula:
1661
1529
  f = this.FormulaText(cell.value as string, cell);
1662
1530
  switch (cell.calculated_type) {
1663
1531
  case ValueType.string:
1664
- v = cell.calculated;
1532
+ v = cell.calculated as string;
1665
1533
  t = 'str';
1666
1534
  break;
1667
1535
 
1668
1536
  case ValueType.number:
1669
- v = cell.calculated;
1537
+ v = cell.calculated as number;
1670
1538
  break;
1671
1539
 
1672
1540
  case ValueType.boolean:
@@ -1682,7 +1550,7 @@ export class Exporter {
1682
1550
  break;
1683
1551
 
1684
1552
  case ValueType.number:
1685
- v = cell.value;
1553
+ v = cell.value as number;
1686
1554
  break;
1687
1555
 
1688
1556
  case ValueType.boolean:
@@ -1706,63 +1574,63 @@ export class Exporter {
1706
1574
  }
1707
1575
  }
1708
1576
 
1709
- // zerp
1710
- const element: any = {
1577
+ row.push({
1711
1578
  a$: {
1579
+
1712
1580
  r: Area.CellAddressToLabel({row: r, column: c}),
1713
- // t,
1714
- // s,
1715
- },
1716
- // v,
1717
- };
1581
+ t,
1718
1582
 
1719
- if (t !== undefined) {
1720
- element.a$.t = t;
1721
- }
1722
- if (s !== undefined) {
1583
+ // old comment regarding `s`:
1723
1584
 
1724
- // we could skip this if it's equal to row style,
1725
- // or there is no row style and it's equal to column style
1726
- // or there is no column style and it's equal to sheet style
1585
+ // we could skip this if it's equal to row style,
1586
+ // or there is no row style and it's equal to column style
1587
+ // or there is no column style and it's equal to sheet style
1727
1588
 
1728
- element.a$.s = s;
1589
+ s,
1590
+ },
1729
1591
 
1730
- }
1731
- if (f !== undefined) {
1732
- element.f = f;
1733
- }
1734
- if (v !== undefined) {
1735
- element.v = v;
1736
- }
1592
+ f,
1593
+ v,
1737
1594
 
1738
- row.push(element);
1595
+ });
1739
1596
 
1740
1597
  }
1741
1598
  }
1742
1599
 
1743
1600
  if (row.length || (row_style && row_style !== sheet_style)) {
1744
1601
 
1745
- const row_data: any = {
1746
- a$: {
1747
- r: r + 1,
1748
- spans: `${span.start + 1}:${span.end + 1}`, // this works out to 0:0 for an empty row, will that work?
1749
- },
1750
- c: row,
1751
- };
1602
+ let customHeight: number|undefined = undefined;
1603
+ let ht: number|undefined = undefined;
1604
+
1605
+ let s: number|undefined = undefined;
1606
+ let customFormat: number|undefined = undefined;
1607
+
1752
1608
  if (sheet.row_height
1753
- && (typeof sheet.row_height[r] === 'number')
1754
- && sheet.row_height[r] !== sheet.default_row_height) {
1755
-
1756
- row_data.a$.customHeight = 1;
1757
- row_data.a$.ht = sheet.row_height[r] * 3 / 4;
1609
+ && (typeof sheet.row_height[r] === 'number')
1610
+ && sheet.row_height[r] !== sheet.default_row_height) {
1611
+
1612
+ customHeight = 1;
1613
+ ht = sheet.row_height[r] * 3 / 4;
1758
1614
  }
1759
1615
 
1760
1616
  if (row_style && row_style !== sheet_style) {
1761
- row_data.a$.s = row_style;
1762
- row_data.a$.customFormat = 1;
1617
+ s = row_style;
1618
+ customFormat = 1;
1763
1619
  }
1764
1620
 
1765
- sheet_data.row.push(row_data);
1621
+ // sheet_data.row.
1622
+ sheet_rows.push({
1623
+ a$: {
1624
+ r: r + 1,
1625
+ spans: `${span.start + 1}:${span.end + 1}`, // this works out to 0:0 for an empty row, will that work?
1626
+ customHeight,
1627
+ ht,
1628
+ s,
1629
+ customFormat,
1630
+ },
1631
+ c: row,
1632
+ });
1633
+
1766
1634
  }
1767
1635
 
1768
1636
  }
@@ -1783,11 +1651,6 @@ export class Exporter {
1783
1651
  // because we'll have a default entry for columns that have the
1784
1652
  // sheet style. this is only for columns that are different.
1785
1653
 
1786
- if (sheet.default_column_width) {
1787
- dom.worksheet.sheetFormatPr.a$.defaultColWidth = // sheet.default_column_width * one_hundred_pixels / 100;
1788
- PixelsToColumnWidth(sheet.default_column_width);
1789
- }
1790
-
1791
1654
  for (let c = 0; c < sheet.columns; c++) {
1792
1655
  const entry: { style?: number, width?: number, index: number } = { index: c };
1793
1656
  if (sheet.column_width
@@ -1795,36 +1658,14 @@ export class Exporter {
1795
1658
  && (typeof sheet.column_width[c] === 'number')
1796
1659
  && sheet.column_width[c] !== sheet.default_column_width) {
1797
1660
 
1798
- entry.width = // sheet.column_width[c] * one_hundred_pixels / 100;
1799
- PixelsToColumnWidth(sheet.column_width[c]);
1800
-
1801
- // console.info("COLUMN", c, 'width', sheet.column_width[c], 'calc?', entry.width, '100p', one_hundred_pixels);
1802
-
1661
+ entry.width = PixelsToColumnWidth(sheet.column_width[c]);
1803
1662
  }
1804
1663
 
1805
- let style = column_style_map[c];
1664
+ const style = column_style_map[c];
1806
1665
  if (style && style !== sheet_style) {
1807
1666
  entry.style = style;
1808
1667
  }
1809
1668
 
1810
- /*
1811
- let style = sheet.column_style[c];
1812
-
1813
- if (typeof style === 'number') {
1814
- style = cell_style_refs[style];
1815
- if (style) {
1816
- entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(style));
1817
- }
1818
- }
1819
- else if (style) {
1820
- entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(style));
1821
- }
1822
- */
1823
-
1824
- //if (sheet.column_style[c]) {
1825
- // entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(sheet.column_style[c]));
1826
- //}
1827
-
1828
1669
  if (entry.style !== undefined || entry.width !== undefined) {
1829
1670
  column_entries[c] = entry;
1830
1671
  }
@@ -1833,9 +1674,11 @@ export class Exporter {
1833
1674
  // we're short-cutting here, these should be arranged in blocks if
1834
1675
  // there's overlap. not sure how much of an issue that is though.
1835
1676
 
1677
+ let dom_cols: DOMContent|undefined;
1678
+
1836
1679
  if (column_entries.length || sheet_style) {
1837
1680
 
1838
- const filled: any[] = [];
1681
+ const filled: DOMContent[] = [];
1839
1682
  const default_column_width = PixelsToColumnWidth(sheet.default_column_width || 90);
1840
1683
 
1841
1684
  // FIXME: can merge these two branches
@@ -1859,25 +1702,13 @@ export class Exporter {
1859
1702
  });
1860
1703
  }
1861
1704
 
1862
- const a$: any = {
1705
+ filled.push({a$: {
1863
1706
  min: entry.index + 1,
1864
1707
  max: entry.index + 1,
1865
- };
1866
- if (entry.style === undefined) {
1867
- a$.style = sheet_style;
1868
- }
1869
- else {
1870
- a$.style = entry.style;
1871
- }
1872
- if (entry.width !== undefined) {
1873
- a$.width = entry.width;
1874
- a$.customWidth = 1;
1875
- }
1876
- else {
1877
- a$.width = default_column_width;
1878
- }
1879
-
1880
- filled.push({a$});
1708
+ style: entry.style === undefined ? sheet_style : entry.style,
1709
+ width: entry.width === undefined ? default_column_width : entry.width,
1710
+ customWidth: entry.width === undefined ? undefined : 1,
1711
+ }});
1881
1712
 
1882
1713
  start_index = entry.index;
1883
1714
 
@@ -1894,112 +1725,90 @@ export class Exporter {
1894
1725
  });
1895
1726
  }
1896
1727
 
1897
- dom.worksheet.cols.col = filled;
1728
+ dom_cols = { col: filled };
1898
1729
 
1899
1730
  }
1900
1731
 
1901
- /*
1902
- else {
1903
- dom.worksheet.cols.col = column_entries.map(entry => {
1904
- const a$: any = {
1905
- min: entry.index + 1,
1906
- max: entry.index + 1,
1907
- };
1908
- if (entry.style !== undefined) {
1909
- a$.style = entry.style;
1910
- }
1911
- if (entry.width !== undefined) {
1912
- a$.width = entry.width;
1913
- a$.customWidth = 1;
1914
- }
1915
- else {
1916
- a$.width = // (sheet.default_column_width || 100) / 100 * one_hundred_pixels;
1917
- default_column_width; // PixelsToColumnWidth(sheet.default_column_width || 90);
1918
- }
1919
- return {a$};
1920
- });
1921
- }
1922
- console.info({cols: dom.worksheet.cols});
1923
- */
1924
- }
1925
-
1926
- else {
1927
- delete dom.worksheet.cols;
1928
1732
  }
1929
1733
 
1930
1734
  // --- validation ----------------------------------------------------------
1931
1735
 
1932
- if (validations.length) {
1736
+ let dataValidations: DOMContent|undefined;
1933
1737
 
1934
- dom.worksheet.dataValidations = {
1935
- a$: { count: validations.length },
1936
- dataValidation: validations.map(validation => {
1937
- const entry: any = {
1938
- a$: {
1939
- type: 'list',
1940
- allowBlank: 1,
1941
- showInputMessage: 1,
1942
- showErrorMessage: 1,
1943
- sqref: new Area(validation.address).spreadsheet_label,
1944
- },
1945
- };
1946
- if (validation.validation.type === ValidationType.Range) {
1738
+ if (sheet.data_validations?.length) {
1739
+
1740
+ dataValidations = {
1741
+
1742
+ a$: { count: sheet.data_validations.length },
1743
+ dataValidation: sheet.data_validations.map(validation => {
1744
+
1745
+ const sqref = validation.target.map(target => {
1746
+ return new Area(target.start, target.end).spreadsheet_label;
1747
+ }).join(' ');
1748
+
1749
+ let formula1: string|undefined = undefined;
1750
+
1751
+ if (validation.type === 'range') {
1947
1752
 
1948
1753
  const range: UnitRange = {
1949
1754
  id: 0,
1950
1755
  type: 'range',
1951
1756
  label: '', position: 0,
1952
1757
  start:
1953
- {...validation.validation.area.start, absolute_column: true, absolute_row: true, id: 0, label: '', position: 0, type: 'address', },
1758
+ {...validation.area.start, absolute_column: true, absolute_row: true, id: 0, label: '', position: 0, type: 'address', },
1954
1759
  end:
1955
- {...validation.validation.area.end, absolute_column: true, absolute_row: true, id: 0, label: '', position: 0, type: 'address', }
1760
+ {...validation.area.end, absolute_column: true, absolute_row: true, id: 0, label: '', position: 0, type: 'address', }
1956
1761
  ,
1957
1762
  }
1958
1763
 
1959
- if (typeof validation.validation.area.start.sheet_id !== 'undefined') {
1960
- range.start.sheet = sheet_name_map[validation.validation.area.start.sheet_id];
1764
+ if (typeof validation.area.start.sheet_id !== 'undefined') {
1765
+ range.start.sheet = sheet_name_map[validation.area.start.sheet_id];
1961
1766
  }
1962
1767
 
1963
- /*
1964
- const area = new Area(
1965
- {...validation.validation.area.start, absolute_column: true, absolute_row: true},
1966
- {...validation.validation.area.end, absolute_column: true, absolute_row: true},
1967
- );
1968
-
1969
- entry.formula1 = `${area.spreadsheet_label}`;
1970
- */
1971
- entry.formula1 = this.parser.Render(range);
1768
+ formula1 = this.parser.Render(range);
1972
1769
 
1973
1770
  }
1974
- else if (validation.validation.type === ValidationType.List) {
1975
- entry.formula1 = `"${validation.validation.list.join(',')}"`;
1771
+ else if (validation.type === 'list') {
1772
+ formula1 = `"${validation.list.join(',')}"`;
1976
1773
  }
1977
- return entry;
1774
+
1775
+ return {
1776
+ a$: {
1777
+ type: 'list',
1778
+ allowBlank: 1,
1779
+ showInputMessage: 1,
1780
+ showErrorMessage: 1,
1781
+ sqref, // : new Area(validation.address).spreadsheet_label,
1782
+ },
1783
+ formula1,
1784
+ };
1785
+
1978
1786
  }),
1979
1787
  };
1980
1788
 
1981
1789
  }
1982
- else {
1983
- delete dom.worksheet.dataValidations;
1984
- }
1985
1790
 
1986
1791
  // --- tables ------------------------------------------------------------
1987
1792
 
1793
+ let tableParts: DOMContent|undefined;
1794
+
1988
1795
  if (tables.length) {
1989
- dom.worksheet.tableParts.a$.count = tables.length;
1990
- dom.worksheet.tableParts.tablePart = tables.map(table => {
1991
- return {
1992
- a$: {
1993
- 'r:id': table.rel || '',
1994
- }
1995
- };
1996
- });
1997
- }
1998
- else {
1999
- delete dom.worksheet.tableParts;
1796
+
1797
+ tableParts = {
1798
+ a$: {
1799
+ count: tables.length,
1800
+ },
1801
+ tablePart: tables.map(table => {
1802
+ return {
1803
+ a$: {
1804
+ 'r:id': table.rel || '',
1805
+ }
1806
+ };
1807
+ }),
1808
+ };
1809
+
2000
1810
  }
2001
1811
 
2002
- // const GUID = () => '{' + uuidv4().toUpperCase() + '}';
2003
1812
 
2004
1813
  for (const table of tables) {
2005
1814
 
@@ -2008,47 +1817,23 @@ export class Exporter {
2008
1817
  totals_attributes.totalsRowCount = 1;
2009
1818
  }
2010
1819
 
2011
- const tableColumns: any = {
1820
+ const tableColumns: DOMContent = {
2012
1821
  a$: {
2013
1822
  count: (table.columns || []).length,
2014
1823
  },
2015
- tableColumn: [],
2016
- /*
2017
- tableColumn: (table.columns||[]).map((column, index) => ({
2018
- a$: {
2019
- id: index + 1,
2020
- // 'xr3:uid': GUID(),
2021
- name: column || ('Column' + (index + 1)),
2022
- },
2023
- })),
2024
- */
2025
- };
2026
-
2027
- if (table.columns) {
2028
- for (let i = 0; i < table.columns.length; i++) {
2029
- const column = table.columns[i];
1824
+ tableColumn: (table.columns||[]).map((column, i) => {
2030
1825
  const footer = (table.footers || [])[i];
2031
- const obj: any = {
1826
+ return {
2032
1827
  a$: {
2033
1828
  id: i + 1,
2034
1829
  name: column || `Column${i + 1}`,
1830
+ totalsRowLabel: footer?.type === 'label' ? footer.value : undefined,
1831
+ totalsRowFunction: footer?.type === 'formula' ? 'custom' : undefined,
2035
1832
  },
1833
+ totalsRowFormula: footer?.type === 'formula' ? footer.value : undefined,
2036
1834
  };
2037
-
2038
- if (footer) {
2039
- if (footer.type === 'label') {
2040
- obj.a$.totalsRowLabel = footer.value;
2041
- }
2042
- else if (footer.type === 'formula') {
2043
- obj.a$.totalsRowFunction = 'custom';
2044
- obj.totalsRowFormula = footer.value;
2045
- }
2046
- }
2047
-
2048
- tableColumns.tableColumn.push(obj);
2049
-
2050
- }
2051
- }
1835
+ }),
1836
+ };
2052
1837
 
2053
1838
  const table_dom = {
2054
1839
  table: {
@@ -2059,18 +1844,15 @@ export class Exporter {
2059
1844
  'xmlns:xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision',
2060
1845
  'xmlns:xr3': 'http://schemas.microsoft.com/office/spreadsheetml/2016/revision3',
2061
1846
  id: table.index || 0,
2062
- // 'xr:uid': '{676B775D-AA84-41B6-8450-8515A94D2D7B}',
2063
1847
  name: table.name,
2064
1848
  displayName: table.display_name,
2065
1849
  ...totals_attributes,
2066
1850
  ref: table.ref,
2067
- // 'xr:uid': GUID(),
2068
1851
  },
2069
1852
 
2070
1853
  autoFilter: {
2071
1854
  a$: {
2072
1855
  ref: table.filterRef || table.ref,
2073
- // 'xr:uid': GUID(),
2074
1856
  },
2075
1857
  },
2076
1858
 
@@ -2090,14 +1872,19 @@ export class Exporter {
2090
1872
  };
2091
1873
 
2092
1874
  const xml = XMLDeclaration + this.xmlbuilder1.build(table_dom);
1875
+ // console.info(xml);
1876
+
2093
1877
  this.zip?.Set(`xl/tables/table${table.index}.xml`, xml);
2094
1878
 
2095
1879
  }
2096
1880
 
2097
1881
  // --- conditional formats -----------------------------------------------
2098
1882
 
1883
+ let conditionalFormatting: DOMContent|DOMContent[]|undefined;
1884
+
2099
1885
  if (sheet.conditional_formats?.length) {
2100
- const conditionalFormatting: any[] = [];
1886
+
1887
+ const format_list: DOMContent[] = [];
2101
1888
  let priority_index = 1;
2102
1889
 
2103
1890
  const reverse_operator_map: Record<string, string> = {};
@@ -2137,7 +1924,7 @@ export class Exporter {
2137
1924
  }
2138
1925
  if (operator) {
2139
1926
 
2140
- conditionalFormatting.push({
1927
+ format_list.push({
2141
1928
  a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
2142
1929
  cfRule: {
2143
1930
  a$: { type: 'cellIs', dxfId: dxf_index, operator, priority: priority_index++ },
@@ -2149,7 +1936,7 @@ export class Exporter {
2149
1936
  break;
2150
1937
 
2151
1938
  case 'expression':
2152
- conditionalFormatting.push({
1939
+ format_list.push({
2153
1940
  a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
2154
1941
  cfRule: {
2155
1942
  a$: { type: 'expression', dxfId: dxf_index, priority: priority_index++ },
@@ -2159,7 +1946,7 @@ export class Exporter {
2159
1946
  break;
2160
1947
 
2161
1948
  case 'duplicate-values':
2162
- conditionalFormatting.push({
1949
+ format_list.push({
2163
1950
  a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
2164
1951
  cfRule: {
2165
1952
  a$: { type: format.unique ? 'uniqueValues' : 'duplicateValues', dxfId: dxf_index, priority: priority_index++ },
@@ -2169,10 +1956,11 @@ export class Exporter {
2169
1956
 
2170
1957
  case 'gradient':
2171
1958
  {
2172
- let cfvo: any[] = [];
2173
- let color: any[] = [];
1959
+ const cfvo: DOMContent[] = [];
1960
+ const color: DOMContent[] = [];
2174
1961
 
2175
1962
  for (const stop of format.stops) {
1963
+
2176
1964
  if (stop.value === 0) {
2177
1965
  cfvo.push({ a$: { type: 'min' }});
2178
1966
  }
@@ -2182,18 +1970,13 @@ export class Exporter {
2182
1970
  else {
2183
1971
  cfvo.push({ a$: { type: 'percentile', val: stop.value * 100 }});
2184
1972
  }
2185
- const stop_color: any = { a$: {}};
2186
- if (stop.color.text) {
2187
- stop_color.a$.rgb = 'FF' + stop.color.text.substring(1);
2188
- }
2189
- else if (stop.color.theme) {
2190
- stop_color.a$.theme = stop.color.theme;
2191
- stop_color.a$.tint = stop.color.tint || undefined;
2192
- }
2193
- color.push(stop_color);
1973
+
1974
+ const attrs = ColorAttrs(stop.color);
1975
+ if (attrs) { color.push(attrs); }
1976
+
2194
1977
  }
2195
1978
 
2196
- const generated = {
1979
+ const generated: DOMContent = {
2197
1980
  a$: { sqref: new Area(format.area.start, format.area.end).spreadsheet_label },
2198
1981
  cfRule: {
2199
1982
  a$: { type: 'colorScale', priority: priority_index++ },
@@ -2204,60 +1987,60 @@ export class Exporter {
2204
1987
  }
2205
1988
  };
2206
1989
 
2207
- conditionalFormatting.push(generated);
1990
+ format_list.push(generated);
2208
1991
 
2209
1992
  }
2210
1993
  break;
2211
1994
  }
2212
1995
  }
2213
1996
 
2214
- if (conditionalFormatting.length) {
2215
- dom.worksheet.conditionalFormatting = (conditionalFormatting.length > 1) ? conditionalFormatting : conditionalFormatting[0];
2216
- }
2217
- else {
2218
- delete dom.worksheet.conditionalFormatting;
1997
+ if (format_list.length) {
1998
+ conditionalFormatting = (format_list.length > 1) ? format_list : format_list[0];
2219
1999
  }
2220
2000
 
2221
2001
  }
2222
- else {
2223
- delete dom.worksheet.conditionalFormatting;
2224
- }
2225
2002
 
2226
2003
  // --- merges ------------------------------------------------------------
2227
2004
 
2005
+ let mergeCells: DOMContent|undefined;
2228
2006
  if (merges.length) {
2229
- dom.worksheet.mergeCells.a$.count = merges.length;
2230
- dom.worksheet.mergeCells.mergeCell = merges.map(merge => {
2231
- return {
2232
- a$: { ref: merge.spreadsheet_label }
2233
- };
2234
- });
2235
- }
2236
- else {
2237
- delete dom.worksheet.mergeCells;
2007
+
2008
+ mergeCells = {
2009
+ a$: { count: merges.length },
2010
+ mergeCell: merges.map(merge => {
2011
+ return {
2012
+ a$: { ref: merge.spreadsheet_label }
2013
+ };
2014
+ }),
2015
+ };
2016
+
2238
2017
  }
2239
2018
 
2240
2019
  // --- hyperlinks --------------------------------------------------------
2241
2020
 
2021
+ let dom_hyperlinks: DOMContent|undefined;
2022
+
2242
2023
  if (hyperlinks.length) {
2243
- dom.worksheet.hyperlinks.hyperlink = hyperlinks.map(link => {
2244
- return {
2245
- a$: {
2246
- 'r:id': link.rel,
2247
- ref: new Area(link.address).spreadsheet_label,
2248
- 'xr:uid': '{0C6B7792-7EA0-4932-BF15-D49C453C565D}',
2249
- },
2250
- };
2251
- });
2252
- }
2253
- else {
2254
- delete dom.worksheet.hyperlinks;
2024
+ dom_hyperlinks = {
2025
+ hyperlink: hyperlinks.map(link => {
2026
+ return {
2027
+ a$: {
2028
+ 'r:id': link.rel,
2029
+ ref: new Area(link.address).spreadsheet_label,
2030
+ 'xr:uid': '{0C6B7792-7EA0-4932-BF15-D49C453C565D}',
2031
+ },
2032
+ };
2033
+ }),
2034
+ };
2255
2035
  }
2256
2036
 
2257
2037
  // --- sparklines --------------------------------------------------------
2258
2038
 
2039
+ let extLst: DOMContent|undefined;
2040
+
2259
2041
  if (sparklines.length) {
2260
- dom.worksheet.extLst.ext['x14:sparklineGroups'] = {
2042
+
2043
+ const groups: DOMContent = {
2261
2044
  a$: {
2262
2045
  'xmlns:xm': 'http://schemas.microsoft.com/office/excel/2006/main',
2263
2046
  },
@@ -2285,16 +2068,11 @@ export class Exporter {
2285
2068
  }
2286
2069
  }
2287
2070
 
2288
- const a$: any = {
2289
- displayEmptyCellsAs: 'gap',
2290
- };
2291
-
2292
- if (/column/i.test(sparkline.formula)) {
2293
- a$.type = 'column';
2294
- }
2295
-
2296
2071
  return {
2297
- a$,
2072
+ a$: {
2073
+ displayEmptyCellsAs: 'gap',
2074
+ type: /column/i.test(sparkline.formula) ? 'column' : undefined,
2075
+ },
2298
2076
  'x14:colorSeries': { a$: { rgb: 'FF376092' }},
2299
2077
  'x14:sparklines': {
2300
2078
  'x14:sparkline': {
@@ -2305,15 +2083,25 @@ export class Exporter {
2305
2083
  }
2306
2084
  }),
2307
2085
  };
2308
- }
2309
- else {
2310
- delete dom.worksheet.extLst;
2086
+
2087
+ extLst = {
2088
+ ext: {
2089
+ a$: {
2090
+ uri: '{05C60535-1F16-4fd2-B633-F4F36F0B64E0}',
2091
+ 'xmlns:x14': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main',
2092
+ },
2093
+ 'x14:sparklineGroups': groups
2094
+ }
2095
+ };
2096
+
2097
+
2311
2098
  }
2312
2099
 
2313
- dom.worksheet.sheetData = sheet_data;
2314
2100
 
2315
2101
  // --- charts ------------------------------------------------------------
2316
2102
 
2103
+ let dom_drawing: DOMContent|undefined;
2104
+
2317
2105
  const charts = this.ParseCharts(sheet);
2318
2106
  const images = this.ParseImages(sheet);
2319
2107
 
@@ -2385,7 +2173,8 @@ export class Exporter {
2385
2173
  `http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing`,
2386
2174
  `../drawings/drawing${drawing.index}.xml`);
2387
2175
 
2388
- dom.worksheet.drawing = {
2176
+ // dom.worksheet.drawing = {
2177
+ dom_drawing = {
2389
2178
  a$: {
2390
2179
  'r:id': drawing_rel,
2391
2180
  },
@@ -2393,7 +2182,7 @@ export class Exporter {
2393
2182
 
2394
2183
  }
2395
2184
  else {
2396
- delete dom.worksheet.drawing;
2185
+ // delete dom.worksheet.drawing;
2397
2186
  }
2398
2187
 
2399
2188
  // --- move page margins -------------------------------------------------
@@ -2404,13 +2193,75 @@ export class Exporter {
2404
2193
 
2405
2194
  // --- end? --------------------------------------------------------------
2406
2195
 
2196
+ const sheetFormatPr: DOMContent = {
2197
+ a$: {
2198
+ 'x14ac:dyDescent': 0.25,
2199
+ defaultRowHeight: default_row_height === 15 ? undefined : default_row_height,
2200
+ customHeight: default_row_height === 15 ? undefined : 1,
2201
+ defaultColWidth: sheet.default_column_width ? PixelsToColumnWidth(sheet.default_column_width) : undefined,
2202
+ },
2203
+ }
2204
+
2205
+ //------------------------------------------------------------------------
2206
+ //
2207
+ // NOTE: order matters. that's why we define the layout here. we
2208
+ // can't just append entries to the worksheet object.
2209
+ //
2210
+ //------------------------------------------------------------------------
2211
+
2212
+ const dom: DOMContent = {
2213
+
2214
+ worksheet: {
2215
+ a$: { ...sheet_attributes },
2216
+ dimension: {
2217
+ a$: {
2218
+ ref: new Area(extent.start, extent.end).spreadsheet_label,
2219
+ },
2220
+ },
2221
+ sheetViews: {
2222
+ sheetView: {
2223
+ a$: {
2224
+ workbookViewId: 0,
2225
+ },
2226
+ },
2227
+ },
2228
+
2229
+ sheetFormatPr,
2230
+ cols: dom_cols,
2231
+ sheetData: { row: sheet_rows },
2232
+
2233
+ mergeCells,
2234
+ conditionalFormatting,
2235
+ dataValidations,
2236
+ hyperlinks: dom_hyperlinks,
2237
+
2238
+ pageMargins: {
2239
+ a$: {
2240
+ left: 0.7,
2241
+ right: 0.7,
2242
+ top: 0.75,
2243
+ bottom: 0.75,
2244
+ header: 0.3,
2245
+ footer: 0.3,
2246
+ },
2247
+ },
2248
+ drawing: dom_drawing,
2249
+ tableParts,
2250
+ extLst,
2251
+
2252
+ },
2253
+ };
2254
+
2255
+ // -----------------------------------------------------------------------
2256
+
2407
2257
  // it seems like chrome, at least, will maintain order. but this is
2408
2258
  // not gauranteed and we can't rely on it. the best thing to do might
2409
2259
  // be to use the renderer on blocks and then assemble the blocks ourselves.
2410
-
2411
- dom.worksheet.dimension.a$.ref = new Area(extent.start, extent.end).spreadsheet_label;
2260
+
2412
2261
  const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
2413
2262
 
2263
+ // console.info(xml);
2264
+
2414
2265
  // write this into the file
2415
2266
 
2416
2267
  this.zip?.Set(`xl/worksheets/sheet${sheet_index + 1}.xml`, xml);
@@ -2446,15 +2297,12 @@ export class Exporter {
2446
2297
 
2447
2298
  this.WriteRels(workbook_rels, `xl/_rels/workbook.xml.rels`);
2448
2299
 
2449
- let definedNames: any = {definedName: []};
2450
-
2451
- if (source.named) {
2452
- for (const entry of source.named) {
2300
+ const definedNames: DOMContent|undefined = source.named?.length ? {
2301
+ definedName: (source.named||[]).map(entry => {
2453
2302
 
2454
2303
  let scope: string|undefined = undefined;
2455
2304
 
2456
2305
  if (entry.scope) {
2457
-
2458
2306
  const test = entry.scope.toLowerCase();
2459
2307
  for (const [index, sheet] of source.sheet_data.entries()) {
2460
2308
  if (sheet.name?.toLowerCase() === test) {
@@ -2464,20 +2312,16 @@ export class Exporter {
2464
2312
  }
2465
2313
  }
2466
2314
 
2467
- definedNames.definedName.push({
2315
+ return {
2468
2316
  a$: { name: entry.name, localSheetId: scope },
2469
2317
  t$: entry.expression,
2470
- });
2471
-
2472
- }
2473
- }
2318
+ };
2474
2319
 
2475
- if (!definedNames.definedName.length) {
2476
- definedNames = undefined;
2477
- }
2478
- // else { console.info({definedNames}); }
2320
+ }),
2321
+ } : undefined;
2322
+
2479
2323
 
2480
- const workbook_dom: any = {
2324
+ const workbook_dom: DOMContent = {
2481
2325
  workbook: {
2482
2326
  a$: {
2483
2327
  'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
@@ -2503,17 +2347,14 @@ export class Exporter {
2503
2347
  },
2504
2348
  },
2505
2349
  sheets: {
2506
- sheet: source.sheet_data.map((sheet, index) => {
2507
- const a$: any = {
2350
+ sheet: source.sheet_data.map((sheet, index) => ({
2351
+ a$: {
2508
2352
  name: sheet.name || `Sheet${index + 1}`,
2509
2353
  sheetId: index + 1,
2510
2354
  'r:id': worksheet_rels_map[index],
2511
- };
2512
- if (sheet.visible === false) {
2513
- a$.state = 'hidden';
2355
+ state: (sheet.visible === false) ? 'hidden' : undefined,
2514
2356
  }
2515
- return { a$ };
2516
- }),
2357
+ })),
2517
2358
  },
2518
2359
  definedNames,
2519
2360
  },
@@ -2541,7 +2382,7 @@ export class Exporter {
2541
2382
  }
2542
2383
  }
2543
2384
 
2544
- const content_types_dom: any = {
2385
+ const content_types_dom: DOMContent = {
2545
2386
  Types: {
2546
2387
  a$: {
2547
2388
  'xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types',
@@ -2565,7 +2406,7 @@ export class Exporter {
2565
2406
  }),
2566
2407
 
2567
2408
  // charts and drawings
2568
- ...drawings.reduce((a: any, drawing) => {
2409
+ ...drawings.reduce((a: DOMContent[], drawing) => {
2569
2410
  return a.concat([
2570
2411
  ...drawing.charts.map(chart => {
2571
2412
  return { a$: {
@@ -2620,34 +2461,4 @@ export class Exporter {
2620
2461
  return new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
2621
2462
  }
2622
2463
 
2623
- /* * zip -> binary string * /
2624
- public async AsBinaryString(compression_level?: number) {
2625
- if (!this.zip) {
2626
- throw new Error('missing zip');
2627
- }
2628
- const opts: JSZip.JSZipGeneratorOptions = { type: 'binarystring' };
2629
- if (typeof compression_level !== 'undefined') {
2630
- opts.compression = 'DEFLATE';
2631
- opts.compressionOptions = {level: compression_level };
2632
- }
2633
- const output = await this.zip.generateAsync(opts);
2634
- return output;
2635
- }
2636
-
2637
- /* * zip -> blob * /
2638
- public async AsBlob(compression_level?: number) {
2639
- if (!this.zip) {
2640
- throw new Error('missing zip');
2641
- }
2642
- const opts: JSZip.JSZipGeneratorOptions = { type: 'blob' };
2643
- if (typeof compression_level !== 'undefined') {
2644
- opts.compression = 'DEFLATE';
2645
- opts.compressionOptions = {level: compression_level };
2646
- }
2647
- const output = await this.zip.generateAsync(opts);
2648
- return output;
2649
- }
2650
- */
2651
-
2652
-
2653
- }
2464
+ }