@trebco/treb 32.4.1 → 32.6.4
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/README.md +0 -6
- package/dist/treb-spreadsheet.mjs +12 -12
- package/package.json +2 -2
- package/treb-base-types/src/import.ts +14 -10
- package/treb-calculator/src/calculator.ts +5 -0
- package/treb-charts/src/chart-types.ts +2 -0
- package/treb-charts/src/chart-utils.ts +45 -4
- package/treb-charts/src/default-chart-renderer.ts +84 -24
- package/treb-data-model/src/sheet.ts +9 -0
- package/treb-embed/src/embedded-spreadsheet.ts +2 -2
- package/treb-embed/style/tab-bar.scss +1 -0
- package/treb-export/src/{drawing2/drawing2.ts → drawing/drawing.ts} +2 -2
- package/treb-export/src/export.ts +11 -11
- package/treb-export/src/import.ts +216 -20
- package/treb-export/src/metadata.ts +187 -0
- package/treb-export/src/{workbook-sheet2.ts → workbook-sheet.ts} +2 -2
- package/treb-export/src/{workbook-style2.ts → workbook-style.ts} +1 -1
- package/treb-export/src/{workbook-theme2.ts → workbook-theme.ts} +0 -2
- package/treb-export/src/{workbook2.ts → workbook.ts} +157 -11
- package/treb-grid/src/types/grid.ts +9 -9
- package/treb-grid/src/types/grid_base.ts +2 -1
- package/treb-grid/src/types/grid_command.ts +3 -0
- package/treb-grid/src/types/tab_bar.ts +36 -7
- /package/treb-export/src/{drawing2 → drawing}/bubble-chart-template.ts +0 -0
- /package/treb-export/src/{drawing2 → drawing}/chart-template-components2.ts +0 -0
- /package/treb-export/src/{drawing2/chart2.ts → drawing/chart.ts} +0 -0
- /package/treb-export/src/{drawing2 → drawing}/column-chart-template2.ts +0 -0
- /package/treb-export/src/{drawing2 → drawing}/donut-chart-template2.ts +0 -0
- /package/treb-export/src/{drawing2 → drawing}/embedded-image.ts +0 -0
- /package/treb-export/src/{drawing2 → drawing}/scatter-chart-template2.ts +0 -0
- /package/treb-export/src/{shared-strings2.ts → shared-strings.ts} +0 -0
|
@@ -24,17 +24,17 @@
|
|
|
24
24
|
// import UZip from 'uzip';
|
|
25
25
|
import Base64JS from 'base64-js';
|
|
26
26
|
|
|
27
|
-
import type { AnchoredChartDescription, AnchoredImageDescription, AnchoredTextBoxDescription} from './
|
|
28
|
-
import { ChartType, ConditionalFormatOperators, Workbook } from './
|
|
29
|
-
import type { ParseResult } from 'treb-parser';
|
|
27
|
+
import type { AnchoredChartDescription, AnchoredImageDescription, AnchoredTextBoxDescription} from './workbook';
|
|
28
|
+
import { ChartType, ConditionalFormatOperators, Workbook } from './workbook';
|
|
29
|
+
import type { ExpressionUnit, ParseResult, UnitCall } from 'treb-parser';
|
|
30
30
|
import { DecimalMarkType, Parser } from 'treb-parser';
|
|
31
31
|
import type { RangeType, AddressType, HyperlinkType } from './address-type';
|
|
32
32
|
import { is_range, ShiftRange, InRange, is_address } from './address-type';
|
|
33
33
|
import { type ImportedSheetData, type AnchoredAnnotation, type CellParseResult, type AnnotationLayout, type Corner as LayoutCorner, type IArea, type GradientStop, type Color, type HTMLColor, type ThemeColor, Area } from 'treb-base-types';
|
|
34
34
|
import type { SerializedValueType } from 'treb-base-types';
|
|
35
|
-
import type { Sheet} from './workbook-
|
|
36
|
-
import { VisibleState } from './workbook-
|
|
37
|
-
import type { CellAnchor } from './
|
|
35
|
+
import type { Sheet} from './workbook-sheet';
|
|
36
|
+
import { VisibleState } from './workbook-sheet';
|
|
37
|
+
import type { CellAnchor } from './drawing/drawing';
|
|
38
38
|
import { type GenericDOMElement, XMLUtils } from './xml-utils';
|
|
39
39
|
|
|
40
40
|
// import { one_hundred_pixels } from './constants';
|
|
@@ -42,6 +42,7 @@ import { ColumnWidthToPixels } from './column-width';
|
|
|
42
42
|
import type { DataValidation, AnnotationType } from 'treb-data-model';
|
|
43
43
|
import { ZipWrapper } from './zip-wrapper';
|
|
44
44
|
import type { ConditionalFormat } from 'treb-data-model';
|
|
45
|
+
import { LookupMetadata, type MetadataFlags } from './metadata';
|
|
45
46
|
|
|
46
47
|
interface SharedFormula {
|
|
47
48
|
row: number;
|
|
@@ -57,6 +58,7 @@ interface CellElementType {
|
|
|
57
58
|
r?: string;
|
|
58
59
|
t?: string;
|
|
59
60
|
s?: string;
|
|
61
|
+
cm?: string;
|
|
60
62
|
};
|
|
61
63
|
v?: string|number|{
|
|
62
64
|
t$: string;
|
|
@@ -79,7 +81,26 @@ interface ConditionalFormatRule {
|
|
|
79
81
|
priority?: string;
|
|
80
82
|
operator?: string;
|
|
81
83
|
};
|
|
84
|
+
|
|
82
85
|
formula?: string|[number,number]|{t$: string};
|
|
86
|
+
|
|
87
|
+
dataBar?: {
|
|
88
|
+
a$?: {
|
|
89
|
+
showValue?: string;
|
|
90
|
+
},
|
|
91
|
+
color?: {
|
|
92
|
+
a$: {
|
|
93
|
+
rgb?: string;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
extLst?: {
|
|
99
|
+
ext?: {
|
|
100
|
+
'x14:id'?: string;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
colorScale?: {
|
|
84
105
|
cfvo?: {
|
|
85
106
|
a$: {
|
|
@@ -128,6 +149,7 @@ export class Importer {
|
|
|
128
149
|
element: CellElementType,
|
|
129
150
|
shared_formulae: SharedFormulaMap,
|
|
130
151
|
arrays: RangeType[],
|
|
152
|
+
dynamic_arrays: RangeType[],
|
|
131
153
|
merges: RangeType[],
|
|
132
154
|
links: HyperlinkType[],
|
|
133
155
|
// validations: Array<{ address: ICellAddress, validation: DataValidation }>,
|
|
@@ -146,6 +168,15 @@ export class Importer {
|
|
|
146
168
|
return undefined;
|
|
147
169
|
}
|
|
148
170
|
|
|
171
|
+
// new (to us) metadata
|
|
172
|
+
let metadata_flags: MetadataFlags = {};
|
|
173
|
+
if (element.a$?.cm) {
|
|
174
|
+
const cm_index = Number(element.a$?.cm);
|
|
175
|
+
if (this.workbook?.metadata) {
|
|
176
|
+
metadata_flags = LookupMetadata(this.workbook.metadata, 'cell', cm_index).flags;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
149
180
|
// console.info(element);
|
|
150
181
|
|
|
151
182
|
let value: undefined | number | boolean | string;
|
|
@@ -194,6 +225,8 @@ export class Importer {
|
|
|
194
225
|
|
|
195
226
|
if (formula) {
|
|
196
227
|
|
|
228
|
+
// console.info("F", formula);
|
|
229
|
+
|
|
197
230
|
// doing it like this is sloppy (also does not work properly).
|
|
198
231
|
value = '=' + formula.replace(/^_xll\./g, '');
|
|
199
232
|
|
|
@@ -202,10 +235,66 @@ export class Importer {
|
|
|
202
235
|
value = formula;
|
|
203
236
|
}
|
|
204
237
|
else {
|
|
238
|
+
|
|
205
239
|
const parse_result = this.parser.Parse(formula); // l10n?
|
|
206
240
|
if (parse_result.expression) {
|
|
207
|
-
|
|
241
|
+
|
|
242
|
+
const TrimPrefixes = (name: string) => {
|
|
243
|
+
|
|
244
|
+
if (/^_xll\./.test(name)) {
|
|
245
|
+
name = name.substring(5);
|
|
246
|
+
}
|
|
247
|
+
if (/^_xlfn\./.test(name)) {
|
|
248
|
+
console.info("xlfn:", name);
|
|
249
|
+
name = name.substring(6);
|
|
250
|
+
}
|
|
251
|
+
if (/^_xlws\./.test(name)) {
|
|
252
|
+
console.info("xlws:", name);
|
|
253
|
+
name = name.substring(6);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return name;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const TreeWalker = (unit: ExpressionUnit) => {
|
|
260
|
+
|
|
208
261
|
if (unit.type === 'call') {
|
|
262
|
+
|
|
263
|
+
// if we see _xlfn.SINGLE, translate that into
|
|
264
|
+
// @ + the name of the first parameter...
|
|
265
|
+
//
|
|
266
|
+
// this is solving the case where single appears, but it
|
|
267
|
+
// doesn't solve the case where it does not appear -- they
|
|
268
|
+
// may be using the array flag of the cell as an indicator?
|
|
269
|
+
// but how then do they know it _should_ be an array? not
|
|
270
|
+
// sure, and I don't see any other indication.
|
|
271
|
+
|
|
272
|
+
if (/^_xlfn\.single/i.test(unit.name)) {
|
|
273
|
+
|
|
274
|
+
const first = unit.args[0];
|
|
275
|
+
if (first.type === 'call') {
|
|
276
|
+
|
|
277
|
+
// we could do this in place, we don't need to copy...
|
|
278
|
+
// although it seems like a good idea. also watch out,
|
|
279
|
+
// these SINGLEs could be nested.
|
|
280
|
+
|
|
281
|
+
const replacement: UnitCall = JSON.parse(JSON.stringify(first));
|
|
282
|
+
replacement.name = '@' + TrimPrefixes(replacement.name);
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < replacement.args.length; i++) {
|
|
285
|
+
replacement.args[i] = this.parser.Walk2(replacement.args[i], TreeWalker);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return replacement;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
console.info("_xlfn.SINGLE unexpected argument", unit.args[0]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
unit.name = TrimPrefixes(unit.name);
|
|
296
|
+
|
|
297
|
+
/*
|
|
209
298
|
if (/^_xll\./.test(unit.name)) {
|
|
210
299
|
unit.name = unit.name.substring(5);
|
|
211
300
|
}
|
|
@@ -217,9 +306,15 @@ export class Importer {
|
|
|
217
306
|
console.info("xlws:", unit.name);
|
|
218
307
|
unit.name = unit.name.substring(6);
|
|
219
308
|
}
|
|
309
|
+
*/
|
|
310
|
+
|
|
220
311
|
}
|
|
221
312
|
return true;
|
|
222
|
-
|
|
313
|
+
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
parse_result.expression = this.parser.Walk2(parse_result.expression, TreeWalker);
|
|
317
|
+
|
|
223
318
|
value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
|
|
224
319
|
}
|
|
225
320
|
}
|
|
@@ -255,10 +350,38 @@ export class Importer {
|
|
|
255
350
|
}
|
|
256
351
|
}
|
|
257
352
|
|
|
353
|
+
//
|
|
354
|
+
// arrays and spill/dynamic arrays
|
|
355
|
+
//
|
|
356
|
+
|
|
258
357
|
if (typeof element.f !== 'string' && element.f.a$?.t === 'array') {
|
|
259
358
|
const translated = sheet.TranslateAddress(element.f.a$.ref || '');
|
|
260
|
-
|
|
261
|
-
|
|
359
|
+
|
|
360
|
+
// why are we checking "is_range" here? this should be valid
|
|
361
|
+
// even if the ref attribute is one cell, if it explicitly
|
|
362
|
+
// says t="array"
|
|
363
|
+
|
|
364
|
+
// we will need to adjust it, though? yes, because the lists
|
|
365
|
+
// only accept ranges. note that range type has a superfluous
|
|
366
|
+
// sheet parameter? ...
|
|
367
|
+
|
|
368
|
+
let range = translated;
|
|
369
|
+
if (!is_range(range)) {
|
|
370
|
+
range = {
|
|
371
|
+
to: { ...range },
|
|
372
|
+
from: { ...range },
|
|
373
|
+
sheet: range.sheet,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// if (is_range(translated))
|
|
378
|
+
{
|
|
379
|
+
if (metadata_flags['dynamic-array']) {
|
|
380
|
+
dynamic_arrays.push(ShiftRange(range, -1, -1));
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
arrays.push(ShiftRange(range, -1, -1));
|
|
384
|
+
}
|
|
262
385
|
}
|
|
263
386
|
}
|
|
264
387
|
|
|
@@ -298,12 +421,14 @@ export class Importer {
|
|
|
298
421
|
// but perhaps we should check that... although at this point we have
|
|
299
422
|
// already added the array so we need to check for root
|
|
300
423
|
|
|
301
|
-
for (const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
424
|
+
for (const set of [arrays, dynamic_arrays]) {
|
|
425
|
+
for (const array of set) {
|
|
426
|
+
if (InRange(array, shifted) && (shifted.row !== array.from.row || shifted.col !== array.from.col)) {
|
|
427
|
+
calculated_type = type;
|
|
428
|
+
calculated_value = value;
|
|
429
|
+
value = undefined;
|
|
430
|
+
type = 'undefined'; // ValueType.undefined;
|
|
431
|
+
}
|
|
307
432
|
}
|
|
308
433
|
}
|
|
309
434
|
|
|
@@ -350,6 +475,20 @@ export class Importer {
|
|
|
350
475
|
}
|
|
351
476
|
}
|
|
352
477
|
|
|
478
|
+
for (const range of dynamic_arrays) {
|
|
479
|
+
if (InRange(range, shifted)) {
|
|
480
|
+
result.spill = {
|
|
481
|
+
start: {
|
|
482
|
+
row: range.from.row,
|
|
483
|
+
column: range.from.col,
|
|
484
|
+
}, end: {
|
|
485
|
+
row: range.to.row,
|
|
486
|
+
column: range.to.col,
|
|
487
|
+
},
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
353
492
|
for (const range of arrays) {
|
|
354
493
|
if (InRange(range, shifted)) {
|
|
355
494
|
result.area = {
|
|
@@ -383,7 +522,7 @@ export class Importer {
|
|
|
383
522
|
|
|
384
523
|
}
|
|
385
524
|
|
|
386
|
-
public ParseConditionalFormat(address: RangeType|AddressType, rule: ConditionalFormatRule): ConditionalFormat|ConditionalFormat[]|undefined {
|
|
525
|
+
public ParseConditionalFormat(address: RangeType|AddressType, rule: ConditionalFormatRule, extensions?: any[]): ConditionalFormat|ConditionalFormat[]|undefined {
|
|
387
526
|
|
|
388
527
|
const area = this.AddressToArea(address);
|
|
389
528
|
const operators = ConditionalFormatOperators;
|
|
@@ -543,6 +682,44 @@ export class Importer {
|
|
|
543
682
|
}
|
|
544
683
|
break;
|
|
545
684
|
|
|
685
|
+
case 'dataBar':
|
|
686
|
+
{
|
|
687
|
+
const hide_values = (rule.dataBar?.a$?.showValue === '0');
|
|
688
|
+
let extension: any = undefined;
|
|
689
|
+
|
|
690
|
+
if (rule.extLst?.ext?.['x14:id']) {
|
|
691
|
+
for (const test of (extensions || [])) {
|
|
692
|
+
if (test['x14:cfRule']?.a$?.id === rule.extLst.ext['x14:id']) {
|
|
693
|
+
extension = test;
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!extension) {
|
|
698
|
+
console.info("conditional format extension not found");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (rule.dataBar?.color?.a$?.rgb) {
|
|
703
|
+
|
|
704
|
+
let negative: Color|undefined = undefined;
|
|
705
|
+
|
|
706
|
+
if (extension?.['x14:cfRule']?.['x14:dataBar']?.['x14:negativeFillColor']?.a$?.rgb) {
|
|
707
|
+
const rgb = extension['x14:cfRule']['x14:dataBar']['x14:negativeFillColor'].a$.rgb;
|
|
708
|
+
negative = { text: '#' + rgb.toString().substring(2) };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const fill: Color = { text: '#' + rule.dataBar.color.a$.rgb.substring(2) };
|
|
712
|
+
return {
|
|
713
|
+
type: 'data-bar',
|
|
714
|
+
area,
|
|
715
|
+
fill,
|
|
716
|
+
hide_values,
|
|
717
|
+
negative,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
|
|
546
723
|
case 'colorScale':
|
|
547
724
|
if (rule.colorScale && Array.isArray(rule.colorScale.cfvo) && Array.isArray(rule.colorScale.color)) {
|
|
548
725
|
|
|
@@ -618,6 +795,7 @@ export class Importer {
|
|
|
618
795
|
const data: CellParseResult[] = [];
|
|
619
796
|
const shared_formulae: {[index: string]: SharedFormula} = {};
|
|
620
797
|
const arrays: RangeType[] = [];
|
|
798
|
+
const dynamic_arrays: RangeType[] = [];
|
|
621
799
|
const merges: RangeType[] = [];
|
|
622
800
|
const conditional_formats: ConditionalFormat[] = [];
|
|
623
801
|
const links: HyperlinkType[] = [];
|
|
@@ -665,6 +843,11 @@ export class Importer {
|
|
|
665
843
|
// conditionals
|
|
666
844
|
|
|
667
845
|
const conditional_formatting = FindAll('worksheet/conditionalFormatting');
|
|
846
|
+
|
|
847
|
+
// we might need extensions as well? TODO
|
|
848
|
+
|
|
849
|
+
const conditional_formattings = FindAll('worksheet/extLst/ext/x14:conditionalFormattings/x14:conditionalFormatting');
|
|
850
|
+
|
|
668
851
|
for (const element of conditional_formatting) {
|
|
669
852
|
if (element.a$?.sqref ){
|
|
670
853
|
|
|
@@ -678,7 +861,7 @@ export class Importer {
|
|
|
678
861
|
if (element.cfRule) {
|
|
679
862
|
const rules = Array.isArray(element.cfRule) ? element.cfRule : [element.cfRule];
|
|
680
863
|
for (const rule of rules) {
|
|
681
|
-
const format = this.ParseConditionalFormat(area, rule as unknown as ConditionalFormatRule);
|
|
864
|
+
const format = this.ParseConditionalFormat(area, rule as unknown as ConditionalFormatRule, conditional_formattings);
|
|
682
865
|
if (format) {
|
|
683
866
|
if (Array.isArray(format)) {
|
|
684
867
|
conditional_formats.push(...format);
|
|
@@ -905,7 +1088,7 @@ export class Importer {
|
|
|
905
1088
|
const cells = row.c ? Array.isArray(row.c) ? row.c : [row.c] : [];
|
|
906
1089
|
|
|
907
1090
|
for (const element of cells) {
|
|
908
|
-
const cell = this.ParseCell(sheet, element as unknown as CellElementType, shared_formulae, arrays, merges, links); // , validations);
|
|
1091
|
+
const cell = this.ParseCell(sheet, element as unknown as CellElementType, shared_formulae, arrays, dynamic_arrays, merges, links); // , validations);
|
|
909
1092
|
if (cell) {
|
|
910
1093
|
data.push(cell);
|
|
911
1094
|
}
|
|
@@ -1192,12 +1375,21 @@ export class Importer {
|
|
|
1192
1375
|
|
|
1193
1376
|
break;
|
|
1194
1377
|
|
|
1378
|
+
case ChartType.Histogram:
|
|
1379
|
+
type = 'treb-chart';
|
|
1380
|
+
func = 'Histogram.Plot';
|
|
1381
|
+
if (series?.length) {
|
|
1382
|
+
// ...
|
|
1383
|
+
}
|
|
1384
|
+
args[1] = descriptor.chart.title;
|
|
1385
|
+
break;
|
|
1386
|
+
|
|
1195
1387
|
case ChartType.Box:
|
|
1196
1388
|
type = 'treb-chart';
|
|
1197
1389
|
func = 'Box.Plot';
|
|
1198
1390
|
if (series?.length) {
|
|
1199
1391
|
args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
|
|
1200
|
-
console.info("S?", {series}, args[0])
|
|
1392
|
+
// console.info("S?", {series}, args[0])
|
|
1201
1393
|
}
|
|
1202
1394
|
args[1] = descriptor.chart.title;
|
|
1203
1395
|
break;
|
|
@@ -1255,6 +1447,10 @@ export class Importer {
|
|
|
1255
1447
|
args[1] = series[0]?.categories || '';
|
|
1256
1448
|
}
|
|
1257
1449
|
|
|
1450
|
+
if (descriptor.chart.type === ChartType.Column && descriptor.chart.flags?.includes('stacked')) {
|
|
1451
|
+
args[3] = '"stacked"';
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1258
1454
|
break;
|
|
1259
1455
|
}
|
|
1260
1456
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { XMLUtils } from './xml-utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.metadatarecord?view=openxml-3.0.1
|
|
5
|
+
*
|
|
6
|
+
* ah, mixing 0-based and 1-based indexes. that's great. not at all confusing.
|
|
7
|
+
*/
|
|
8
|
+
export interface MetadataRecord {
|
|
9
|
+
|
|
10
|
+
//
|
|
11
|
+
// A 1-based index to the metadata record type in metadataTypes.
|
|
12
|
+
//
|
|
13
|
+
t: number;
|
|
14
|
+
|
|
15
|
+
//
|
|
16
|
+
// A zero based index to a specific metadata record. If the corresponding
|
|
17
|
+
// metadataType has name="XLMDX", then this is an index to a record in
|
|
18
|
+
// mdxMetadata, otherwise this is an index to a record in the futureMetadata
|
|
19
|
+
// section whose name matches the name of the metadataType.
|
|
20
|
+
//
|
|
21
|
+
v: number;
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.metadatatype?view=openxml-3.0.1
|
|
27
|
+
*
|
|
28
|
+
* punting on all these attributes for now, except the ones we specifically
|
|
29
|
+
* want.
|
|
30
|
+
*/
|
|
31
|
+
export interface MetadataType {
|
|
32
|
+
|
|
33
|
+
//
|
|
34
|
+
// Represents the name of this particular metadata type. This name shall be
|
|
35
|
+
// unique amongst all other metadataTypes.
|
|
36
|
+
//
|
|
37
|
+
name: string;
|
|
38
|
+
|
|
39
|
+
//
|
|
40
|
+
// A Boolean flag indicating whether metadata is cell metadata. True when
|
|
41
|
+
// the metadata is cell metadata, false otherwise - in the false case it
|
|
42
|
+
// is considered to be value metadata.
|
|
43
|
+
//
|
|
44
|
+
cell_meta?: boolean;
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* the type `Future Feature Data Storage Area` is not actually defined? not
|
|
50
|
+
* sure. maybe it's just a standard extension type and I need to look that up?
|
|
51
|
+
*
|
|
52
|
+
* for the time being we'll use a list of flags...
|
|
53
|
+
*/
|
|
54
|
+
export interface MetadataFlags {
|
|
55
|
+
'dynamic-array'?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.futuremetadata?view=openxml-3.0.1
|
|
60
|
+
* https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.futuremetadatablock?view=openxml-3.0.1
|
|
61
|
+
*
|
|
62
|
+
* these are named, matching the names in "MetadataType"; we'll use those
|
|
63
|
+
* as indexes, and not store them in the object.
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
export interface FutureMetadata {
|
|
67
|
+
flags: MetadataFlags;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* we're just reflecting the actual data at this point, not simplifying
|
|
72
|
+
* (although we should simplify). will require some lookups.
|
|
73
|
+
*/
|
|
74
|
+
export interface Metadata {
|
|
75
|
+
cell_metadata: MetadataRecord[];
|
|
76
|
+
metadata_types: MetadataType[];
|
|
77
|
+
|
|
78
|
+
// TODO
|
|
79
|
+
// value_metadata: MetadataRecord[];
|
|
80
|
+
|
|
81
|
+
future_metadata: Record<string, FutureMetadata[]>;
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const LookupMetadata = (source: Metadata, type: 'cell'|'value', index: number): FutureMetadata => {
|
|
86
|
+
|
|
87
|
+
if (type === 'cell') {
|
|
88
|
+
|
|
89
|
+
//
|
|
90
|
+
// the docs say "zero based", but this looks to be one based -- there's
|
|
91
|
+
// only one entry when we create a doc, but the cm index in cells is "1".
|
|
92
|
+
//
|
|
93
|
+
// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell?view=openxml-3.0.1&redirectedfrom=MSDN
|
|
94
|
+
//
|
|
95
|
+
|
|
96
|
+
const metadata_type = source.cell_metadata[index - 1];
|
|
97
|
+
|
|
98
|
+
if (metadata_type) {
|
|
99
|
+
const record_type = source.metadata_types[metadata_type.t - 1]; // 1-based
|
|
100
|
+
if (record_type) {
|
|
101
|
+
if (record_type.name === 'XLMDX') {
|
|
102
|
+
// ...
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const future_metadata_list = source.future_metadata[record_type.name];
|
|
106
|
+
if (future_metadata_list) {
|
|
107
|
+
return future_metadata_list[metadata_type.v] || {}; // 0-based
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.warn('value metadata not implemented')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {flags: {}}; // null, essentially
|
|
118
|
+
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
export const ParseMetadataXML = (xml: any): Metadata => {
|
|
123
|
+
|
|
124
|
+
const metadata: Metadata = {
|
|
125
|
+
metadata_types: [],
|
|
126
|
+
cell_metadata: [],
|
|
127
|
+
future_metadata: {},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const metadata_types = XMLUtils.FindAll(xml, 'metadata/metadataTypes/metadataType');
|
|
131
|
+
for (const entry of metadata_types) {
|
|
132
|
+
|
|
133
|
+
const name: string = entry.a$?.name || '';
|
|
134
|
+
const value = entry.a$?.cellMeta;
|
|
135
|
+
const cell_meta: boolean = (value === '1' || value === 'true');
|
|
136
|
+
|
|
137
|
+
metadata.metadata_types.push({
|
|
138
|
+
name, cell_meta
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const future_metadata_blocks = XMLUtils.FindAll(xml, 'metadata/futureMetadata');
|
|
144
|
+
for (const entry of future_metadata_blocks) {
|
|
145
|
+
|
|
146
|
+
const name: string = entry.a$?.name || '';
|
|
147
|
+
if (name) {
|
|
148
|
+
|
|
149
|
+
const future_metadata_list: FutureMetadata[] = [];
|
|
150
|
+
|
|
151
|
+
// `extLst` entries can be inside of `bk` entries, but aparently not
|
|
152
|
+
// required? what's the case where they are _not_ in `bk` elements?
|
|
153
|
+
|
|
154
|
+
for (const block of XMLUtils.FindAll(entry, 'bk')) {
|
|
155
|
+
|
|
156
|
+
const future_metadata: FutureMetadata = { flags: {} };
|
|
157
|
+
|
|
158
|
+
// I guess metadata attributes are elements? we'll probably
|
|
159
|
+
// have to look them up individually
|
|
160
|
+
|
|
161
|
+
for (const child of XMLUtils.FindAll(block, 'extLst/ext/xda:dynamicArrayProperties')) {
|
|
162
|
+
if (child?.a$.fDynamic === '1') {
|
|
163
|
+
future_metadata.flags['dynamic-array'] = true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
future_metadata_list.push(future_metadata);
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
metadata.future_metadata[name] = future_metadata_list;
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const entry of XMLUtils.FindAll(xml, 'metadata/cellMetadata/bk/rc')) {
|
|
177
|
+
metadata.cell_metadata.push({
|
|
178
|
+
t: Number(entry.a$?.t || -1),
|
|
179
|
+
v: Number(entry.a$?.v || -1),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// console.info({metadata});
|
|
184
|
+
|
|
185
|
+
return metadata;
|
|
186
|
+
|
|
187
|
+
}
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
|
|
44
44
|
import type { AddressType, RangeType} from './address-type';
|
|
45
45
|
import { is_range } from './address-type';
|
|
46
|
-
import type { SharedStrings } from './shared-
|
|
47
|
-
import type { Drawing } from './
|
|
46
|
+
import type { SharedStrings } from './shared-strings';
|
|
47
|
+
import type { Drawing } from './drawing/drawing';
|
|
48
48
|
import type { RelationshipMap } from './relationship';
|
|
49
49
|
|
|
50
50
|
export interface SheetOptions {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// import { Element, ElementTree as Tree } from 'elementtree';
|
|
24
24
|
|
|
25
25
|
import { type CompositeBorderEdge, Style, type CellStyle, type PropertyKeys, type Color, IsHTMLColor, IsThemeColor, type ThemeColor, type HTMLColor, ThemeColorIndex } from 'treb-base-types';
|
|
26
|
-
import { Theme } from './workbook-
|
|
26
|
+
import { Theme } from './workbook-theme';
|
|
27
27
|
import { NumberFormatCache } from 'treb-format';
|
|
28
28
|
import { XMLUtils } from './xml-utils';
|
|
29
29
|
|