@trebco/treb 29.2.0 → 29.3.2

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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v29.2. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v29.3. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
@@ -1596,25 +1596,36 @@ export interface SerializedMacroFunction {
1596
1596
  argument_names?: string[];
1597
1597
  description?: string;
1598
1598
  }
1599
+
1600
+ /**
1601
+ * this type is no longer in use, but we retain it to parse old documents
1602
+ * that use it.
1603
+ *
1604
+ * @deprecated
1605
+ */
1599
1606
  export interface SerializedNamedExpression {
1600
1607
  name: string;
1601
1608
  expression: string;
1602
1609
  }
1603
1610
 
1604
1611
  /**
1605
- * serialized type
1612
+ * serialized type is a composite of expression/range. we determine
1613
+ * what it is when parsing the expression. this simplifies passing these
1614
+ * things around.
1615
+ *
1616
+ * (named expressions and ranges they have slightly different behavior,
1617
+ * which is why we have a distinction at all).
1618
+ *
1606
1619
  */
1607
1620
  export interface SerializedNamed {
1608
1621
  name: string;
1609
- area?: SerializedArea;
1610
- expression?: string;
1622
+
1623
+ /** expression or address/area */
1624
+ expression: string;
1625
+
1626
+ /** scope is a sheet name (not ID) */
1611
1627
  scope?: string;
1612
1628
  }
1613
- export type SerializedArea = IArea & {
1614
- start: ICellAddress & {
1615
- sheet: string;
1616
- };
1617
- };
1618
1629
  export interface SerializedSheet {
1619
1630
 
1620
1631
  /** cell data */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "29.2.0",
3
+ "version": "29.3.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -102,6 +102,9 @@ export interface Dimensions {
102
102
  * start/end value for row/column/both, so watch out on loops. the
103
103
  * sheet class has a method for reducing infinite ranges to actual
104
104
  * populated ranges.
105
+ *
106
+ * infinitiy is turning into a headache because it doesn't serialize
107
+ * to json properly. should we switch to a flag, or -1, or something?
105
108
  */
106
109
  export class Area implements IArea {
107
110
 
@@ -166,6 +169,10 @@ export class Area implements IArea {
166
169
 
167
170
  public static CellAddressToLabel(address: ICellAddress, sheet_id = false): string {
168
171
 
172
+ if (address.row === Infinity || address.column === Infinity) {
173
+ throw new Error('this is going to break something');
174
+ }
175
+
169
176
  const prefix = sheet_id ? `${address.sheet_id || 0}!` : '';
170
177
 
171
178
  return prefix
@@ -1930,12 +1930,15 @@ export class Calculator extends Graph {
1930
1930
  }
1931
1931
  }
1932
1932
 
1933
+ /*
1933
1934
  if (area.count > 1) {
1934
1935
  range = Area.CellAddressToLabel(area.start) + ':' + Area.CellAddressToLabel(area.end);
1935
1936
  }
1936
1937
  else {
1937
1938
  range = Area.CellAddressToLabel(area.start);
1938
1939
  }
1940
+ */
1941
+ range = area.spreadsheet_label;
1939
1942
 
1940
1943
  if (!qualified) {
1941
1944
  return range;
@@ -2514,21 +2517,20 @@ export class Calculator extends Graph {
2514
2517
  const unit = dependencies.ranges[key];
2515
2518
  const range = new Area(unit.start, unit.end);
2516
2519
 
2517
- for (const address of range) {
2518
- this.AddLeafVertexEdge(address, vertex);
2520
+ if (range.entire_column || range.entire_row || range.count > 1) {
2521
+ // this.AddLeafVertexEdge(range.start, vertex);
2522
+ this.AddLeafVertexArrayEdge(range, vertex);
2523
+ }
2524
+ else {
2525
+ this.AddLeafVertexEdge(range.start, vertex);
2519
2526
  }
2520
-
2521
- /*
2522
- range.Iterate((address: ICellAddress) => {
2523
- this.AddLeafVertexEdge(address, vertex);
2524
- });
2525
- */
2526
2527
 
2527
2528
  /*
2528
2529
  for (const address of range) {
2529
2530
  this.AddLeafVertexEdge(address, vertex);
2530
2531
  }
2531
2532
  */
2533
+
2532
2534
  }
2533
2535
 
2534
2536
  for (const key of Object.keys(dependencies.addresses)){
@@ -101,11 +101,9 @@ export class ArrayVertex extends SpreadsheetVertexBase {
101
101
  * returns a list of arrays that contain this address
102
102
  */
103
103
  public static GetContainingArrays(address: ICellAddress2): ArrayVertex[] {
104
- // console.info('av2 get arrays:', address.row, address.column);
105
104
  const list: ArrayVertex[] = [];
106
105
  for (const entry of this.list) {
107
106
  if ((entry.area.start.sheet_id === address.sheet_id) && entry.area.Contains(address)) {
108
- // console.info("match", entry.area.spreadsheet_label);
109
107
  list.push(entry);
110
108
  }
111
109
  }
@@ -613,9 +613,91 @@ export abstract class Graph implements GraphCallbacks {
613
613
  /**
614
614
  * new array vertices
615
615
  */
616
- public AddArrayEdge(u: Area, v: ICellAddress): void {
616
+ protected CompositeAddArrayEdge(u: Area, vertex: Vertex): void {
617
+
618
+ if (!u.start.sheet_id) {
619
+ throw new Error('AddArrayEdge called without sheet ID');
620
+ }
621
+
622
+ // create or use existing
623
+ const [array_vertex, created] = ArrayVertex.GetVertex(u);
624
+
625
+ // add an edge
626
+ vertex.DependsOn(array_vertex);
627
+
628
+ // force a check on next calculation pass
629
+ this.loop_check_required = true;
630
+
631
+ if (!created) {
632
+ // console.info('reusing, so not adding edges');
633
+ return;
634
+ }
635
+
636
+ // now add edges from/to nodes THAT ALREADY EXIST
637
+
638
+ // range can't span sheets, so we only need one set to look up
639
+
640
+ const map = this.vertices[u.start.sheet_id];
641
+
642
+ // this might happen on create, we can let it go because the
643
+ // references will be added when the relevant sheet is added
644
+
645
+ if (!map) {
646
+ return;
647
+ }
648
+
649
+ // ...
650
+
651
+ if (u.entire_row) {
652
+ // console.group('entire row(s)')
653
+ for (let column = 0; column < map.length; column++) {
654
+ if (map[column]) {
655
+ for (let row = u.start.row; row <= u.end.row; row++ ) {
656
+ const vertex = map[column][row];
657
+ if (vertex) {
658
+ // console.info('add', column, row);
659
+ array_vertex.DependsOn(vertex);
660
+ }
661
+ }
662
+ }
663
+ }
664
+ // console.groupEnd();
665
+ }
666
+ else if (u.entire_column) {
667
+ // console.group('entire column(s)');
668
+ for (let column = u.start.column; column <= u.end.column; column++) {
669
+ if(map[column]) {
670
+ for (const vertex of map[column]) {
671
+ if (vertex?.address) {
672
+ // console.info('add', vertex.address);
673
+ array_vertex.DependsOn(vertex);
674
+ }
675
+ }
676
+ }
677
+ }
678
+ // console.groupEnd();
679
+ }
680
+ else {
681
+ for (let row = u.start.row; row <= u.end.row; row++) {
682
+ for (let column = u.start.column; column <= u.end.column; column++) {
683
+ const vertex = map[column][row];
684
+ if (vertex) {
685
+ array_vertex.DependsOn(vertex);
686
+ }
687
+ }
688
+ }
689
+ }
617
690
 
618
- // console.info('add array edge', u, v);
691
+ }
692
+
693
+ public AddLeafVertexArrayEdge(u: Area, vertex: LeafVertex) {
694
+ this.CompositeAddArrayEdge(u, vertex);
695
+ }
696
+
697
+ /**
698
+ * new array vertices
699
+ */
700
+ public AddArrayEdge(u: Area, v: ICellAddress): void {
619
701
 
620
702
  if (!u.start.sheet_id) {
621
703
  throw new Error('AddArrayEdge called without sheet ID');
@@ -624,6 +706,9 @@ export abstract class Graph implements GraphCallbacks {
624
706
  // this should have already been added...
625
707
  const v_v = this.GetVertex(v, true);
626
708
 
709
+ this.CompositeAddArrayEdge(u, v_v);
710
+
711
+ /*
627
712
  // create or use existing
628
713
  const [array_vertex, created] = ArrayVertex.GetVertex(u);
629
714
 
@@ -692,6 +777,7 @@ export abstract class Graph implements GraphCallbacks {
692
777
  }
693
778
  }
694
779
  }
780
+ */
695
781
 
696
782
  }
697
783
 
@@ -226,71 +226,13 @@ export class SpreadsheetVertex extends SpreadsheetVertexBase {
226
226
  // handle it properly regardless.
227
227
 
228
228
  const single = (this.result.type === ValueType.array) ? this.result.value[0][0] : this.result;
229
- /*
230
- if (single.type === ValueType.object) {
231
- this.reference.SetCalculationError('OBJECT');
232
- }
233
- else */
234
229
 
235
230
  // we are using object type in the returned value for sparklines...
236
231
  // so we can't drop it here. we could change rendering though. or
237
232
  // whitelist types. or blacklist types. or something.
238
233
 
239
- {
240
- this.reference.SetCalculatedValue(single.value as CellValue, single.type);
241
- }
242
-
243
- /*
244
- console.info("T2", t2);
245
-
246
- // because we let sloppy data filter through, it's possible
247
- // that we get some random stuff at this point. generally this
248
- // shoudl not happen but if you use (e.g.) one of the chart
249
- // functions in a spreadsheet cell, you'll get a 1d array.
250
-
251
- // so we need to validate that what we have is either a UnionValue
252
- // or a 2d UnionValue[][] array.
253
-
254
- // don't know the performance cost of this.
255
-
256
- // FIXME: don't let sloppy data through.
257
-
258
- let test: any = this.result;
259
-
260
- if (Array.isArray(test)) {
261
- if (test[0] && Array.isArray(test[0])) {
262
- test = test[0][0];
263
- }
264
- else {
265
- // console.warn('error 1');
266
- test = undefined;
267
- }
268
- }
269
-
270
- if (!test || typeof (test as any).type === undefined) {
271
- // console.warn('error 2/3');
272
- this.reference.SetCalculationError('UNK');
273
- }
274
- else {
275
- this.reference.SetCalculatedValue((test as UnionValue).value, (test as UnionValue).type);
276
- }
277
- */
278
-
279
- /*
280
- const single = Array.isArray(this.result) ? this.result[0][0] : this.result;
281
-
282
- // error is implicit
283
- this.reference.SetCalculatedValue(single.value, single.type);
284
- */
234
+ this.reference.SetCalculatedValue(single.value as CellValue, single.type);
285
235
 
286
- /*
287
- if (typeof this.result === 'object' && this.result.error) {
288
- this.reference.SetCalculationError(this.result.error);
289
- }
290
- else {
291
- this.reference.SetCalculatedValue(this.result);
292
- }
293
- */
294
236
  }
295
237
 
296
238
  }
@@ -318,7 +260,6 @@ export class SpreadsheetVertex extends SpreadsheetVertexBase {
318
260
  //
319
261
 
320
262
  for (const edge of this.edges_out as Set<SpreadsheetVertexBase>){
321
- // (edge as SpreadsheetVertex).Calculate(graph);
322
263
  if (edge.dirty) {
323
264
  graph.calculation_list.push(edge);
324
265
  }
@@ -502,7 +502,9 @@ export class ExpressionCalculator {
502
502
 
503
503
  if (!func) {
504
504
 
505
- // console.info('missing function', outer.name);
505
+ if (process.env.NODE_ENV !== 'production') {
506
+ console.info('(dev) missing function', outer.name);
507
+ }
506
508
 
507
509
  return () => NameError();
508
510
  }
@@ -416,7 +416,12 @@ export const BaseFunctionLibrary: FunctionMap = {
416
416
  case 1: // feb, special
417
417
  {
418
418
  const year = date.getUTCFullYear();
419
- if (year % 4 === 0 && (year === 1900 || (year % 100 !== 0))) {
419
+
420
+ // it's a leap year if it is divisible by 4, unless it's also
421
+ // divisible by 100 AND NOT divisible by 400. 1900 is grand-
422
+ // fathered in via an error in Lotus.
423
+
424
+ if (year % 4 === 0 && (year === 1900 || (year % 400 === 0) || (year % 100 !== 0))) {
420
425
  date.setUTCDate(29);
421
426
  }
422
427
  else {
@@ -25,7 +25,7 @@ import type { IArea, ICellAddress, Table, CellStyle } from 'treb-base-types';
25
25
  import type { SerializedSheet } from './sheet_types';
26
26
  import { type ExpressionUnit, type UnitAddress, type UnitStructuredReference, type UnitRange, Parser, QuotedSheetNameRegex, DecimalMarkType, ArgumentSeparatorType } from 'treb-parser';
27
27
  import { Area, IsCellAddress, Style } from 'treb-base-types';
28
- import type { CompositeNamed, SerializedNamed } from './named';
28
+ import type { SerializedNamed } from './named';
29
29
  import { NamedRangeManager } from './named';
30
30
 
31
31
  export interface ConnectedElementType {
@@ -51,7 +51,7 @@ export interface MacroFunction extends SerializedMacroFunction {
51
51
  }
52
52
 
53
53
  /**
54
- * FIXME: this should move out of the grid module, grid should be focused on view
54
+ *
55
55
  */
56
56
  export class DataModel {
57
57
 
@@ -128,51 +128,46 @@ export class DataModel {
128
128
  }
129
129
 
130
130
  /**
131
- * from import, we get ranges and expressions in the same format. we
132
- * need to check if something is a range -- if so, it will just be
133
- * a single address or range. in that case store it as a named range.
131
+ *
134
132
  */
135
- public UnserializeComposite(names: CompositeNamed[], active_sheet?: Sheet) {
133
+ public UnserializeNames(names: SerializedNamed[], active_sheet?: Sheet) {
136
134
 
137
135
  this.parser.Save();
138
136
  this.parser.SetLocaleSettings(DecimalMarkType.Period, ArgumentSeparatorType.Comma);
139
137
 
140
- const sorted = names.map(named => {
141
- const parse_result = this.parser.Parse(named.expression);
142
- if (parse_result.expression) {
143
- if (parse_result.expression.type === 'address' || parse_result.expression.type === 'range') {
144
- return {
145
- ...named,
146
- expression: undefined,
147
- area: parse_result.expression.type === 'address' ? {
148
- start: parse_result.expression,
149
- end: parse_result.expression,
150
- } : parse_result.expression,
151
- } as SerializedNamed;
152
- }
153
- return {
154
- ...named
155
- } as SerializedNamed;
156
- }
157
- return undefined;
158
- }).filter((test): test is SerializedNamed => !!test);
138
+ //const sorted = names.map(named => {
139
+ for (const named of names) {
159
140
 
160
- this.parser.Restore();
161
- this.UnserializeNames(sorted, active_sheet);
141
+ if (!named.expression) { continue; }
162
142
 
163
- }
143
+ const parse_result = this.parser.Parse(named.expression);
144
+ if (parse_result.expression) {
164
145
 
165
- public UnserializeNames(names: SerializedNamed[], active_sheet?: Sheet) {
146
+ const scope = (typeof named.scope === 'string') ? this.sheets.ID(named.scope) : undefined;
166
147
 
167
- for (const entry of names) {
148
+ if (parse_result.expression.type === 'address' || parse_result.expression.type === 'range') {
168
149
 
169
- const scope = (typeof entry.scope === 'string') ? this.sheets.ID(entry.scope) : undefined;
150
+ const [start, end] = parse_result.expression.type === 'range' ?
151
+ [ parse_result.expression.start, parse_result.expression.end, ] :
152
+ [ parse_result.expression, parse_result.expression ];
170
153
 
171
- if (entry.expression) {
154
+ if (start.sheet) {
155
+ const area = new Area({...start, sheet_id: this.sheets.ID(start.sheet), }, end);
156
+
157
+ if (area.start.sheet_id) {
158
+ this.named.SetNamedRange(named.name, area, scope);
159
+ }
160
+ else {
161
+ console.warn("missing sheet ID?", start);
162
+ }
172
163
 
173
- const parse_result = this.parser.Parse(entry.expression);
164
+ }
165
+ else {
166
+ console.warn("missing sheet name?", start);
167
+ }
174
168
 
175
- if (parse_result.valid && parse_result.expression) {
169
+ }
170
+ else {
176
171
  this.parser.Walk(parse_result.expression, unit => {
177
172
  if (unit.type === 'address' || unit.type === 'range') {
178
173
  if (unit.type === 'range') {
@@ -191,31 +186,14 @@ export class DataModel {
191
186
  return true;
192
187
  });
193
188
 
194
- this.named.SetNamedExpression(entry.name, parse_result.expression, scope);
195
-
189
+ this.named.SetNamedExpression(named.name, parse_result.expression, scope);
196
190
  }
197
191
 
198
192
  }
199
- else if (entry.area) {
200
- if (entry.area.start.sheet) {
201
-
202
- const area = new Area({
203
- ...entry.area.start,
204
- sheet_id: this.sheets.ID(entry.area.start.sheet),
205
- }, entry.area.end);
206
-
207
- if (area.start.sheet_id) {
208
- this.named.SetNamedRange(entry.name, area, scope);
209
- }
210
- else {
211
- console.warn("missing sheet ID?", entry.area.start.sheet);
212
- }
213
- }
214
- else {
215
- console.warn("missing sheet name?");
216
- }
217
- }
193
+ return undefined;
218
194
  }
195
+
196
+ this.parser.Restore();
219
197
 
220
198
  }
221
199
 
@@ -231,7 +209,7 @@ export class DataModel {
231
209
 
232
210
  const named: SerializedNamed = {
233
211
  name: entry.name,
234
- // scope: entry.scope,
212
+ expression: '',
235
213
  };
236
214
 
237
215
  if (entry.scope) {
@@ -278,11 +256,10 @@ export class DataModel {
278
256
 
279
257
  }
280
258
  else {
281
- named.area = {
259
+
260
+ const area = {
282
261
  start: {
283
262
  ...entry.area.start,
284
- sheet: this.sheets.Name(entry.area.start.sheet_id || 0) || '',
285
- sheet_id: undefined, // in favor of name
286
263
  absolute_column: true,
287
264
  absolute_row: true,
288
265
  },
@@ -292,6 +269,9 @@ export class DataModel {
292
269
  absolute_row: true,
293
270
  },
294
271
  };
272
+
273
+ named.expression = this.AddressToLabel(area);
274
+
295
275
  }
296
276
 
297
277
  list.push(named);
@@ -570,6 +550,12 @@ export interface ViewModel {
570
550
  view_index: number;
571
551
  }
572
552
 
553
+ /**
554
+ * this type is no longer in use, but we retain it to parse old documents
555
+ * that use it.
556
+ *
557
+ * @deprecated
558
+ */
573
559
  export interface SerializedNamedExpression {
574
560
  name: string;
575
561
  expression: string;
@@ -30,7 +30,7 @@ export type {
30
30
  SerializedMacroFunction,
31
31
  } from './data_model';
32
32
 
33
- export type { SerializedNamed, CompositeNamed } from './named';
33
+ export type { SerializedNamed } from './named';
34
34
 
35
35
  export { Sheet } from './sheet';
36
36
  export type { SerializedSheet, FreezePane, LegacySerializedSheet } from './sheet_types';
@@ -20,15 +20,15 @@
20
20
  */
21
21
 
22
22
  import { Area } from 'treb-base-types';
23
- import type { SerializedArea, IArea } from 'treb-base-types';
23
+ import type { IArea } from 'treb-base-types';
24
24
  import type { ExpressionUnit } from 'treb-parser';
25
25
 
26
- export interface NamedExpression {
26
+ interface NamedExpression {
27
27
  type: 'expression';
28
28
  expression: ExpressionUnit;
29
29
  }
30
30
 
31
- export interface NamedRange {
31
+ interface NamedRange {
32
32
  type: 'range';
33
33
  area: Area;
34
34
  }
@@ -38,44 +38,23 @@ export type Named = (NamedExpression | NamedRange) & {
38
38
  scope?: number; // scope to sheet by ID
39
39
  };
40
40
 
41
- /**
42
- * serialized type
43
- *
44
- * @privateRemarks
45
- *
46
- * for the external type we switch on the presence of the area
47
- * or the expression. area uses a type that includes sheet names
48
- * (IArea should allow that?). expression here is a string.
41
+ /**
42
+ * serialized type is a composite of expression/range. we determine
43
+ * what it is when parsing the expression. this simplifies passing these
44
+ * things around.
49
45
  *
50
- * we could theoretically switch the internal type the same way
51
- * and drop the string keys. something to think about.
46
+ * (named expressions and ranges they have slightly different behavior,
47
+ * which is why we have a distinction at all).
52
48
  *
53
- * when serialized, scope is either the sheet name or nothing
54
- * (implicit global scope).
55
49
  */
56
50
  export interface SerializedNamed {
57
- name: string;
58
- area?: SerializedArea;
59
- expression?: string;
60
- scope?: string;
61
- }
62
-
63
- /**
64
- * this is a type we're using in imports. it consolidates the
65
- * two types. we should maybe switch as well, at least for
66
- * serialized representation? something to think about.
67
- */
68
- export interface CompositeNamed {
69
51
 
70
52
  name: string;
71
53
 
72
- /**
73
- * could be a address/range or a function expression. we'll distinguish
74
- * when we parse it.
75
- */
54
+ /** expression or address/area */
76
55
  expression: string;
77
56
 
78
- /** resolved sheet name */
57
+ /** scope is a sheet name (not ID) */
79
58
  scope?: string;
80
59
 
81
60
  }
@@ -3282,9 +3282,20 @@ export class Sheet {
3282
3282
  // stop rule. if you go forwards, you need some sort of indicator
3283
3283
  // or flag).
3284
3284
 
3285
+ const area = JSON.parse(JSON.stringify(format.area));
3286
+
3287
+ if (area.start.row === null || area.end.row === null) {
3288
+ area.start.row = 0;
3289
+ area.end.row = this.cells.rows - 1;
3290
+ }
3291
+ if (area.start.column === null || area.end.column === null) {
3292
+ area.start.column = 0;
3293
+ area.end.column = this.cells.columns - 1;
3294
+ }
3295
+
3296
+ const result = format.internal?.vertex?.result;
3297
+
3285
3298
  if (format.type === 'gradient') {
3286
- const area = JSON.parse(JSON.stringify(format.area));
3287
- const result = format.internal?.vertex?.result;
3288
3299
 
3289
3300
  if (result && format.internal?.gradient) {
3290
3301
  const property: 'fill'|'text' = format.property ?? 'fill';
@@ -3322,16 +3333,13 @@ export class Sheet {
3322
3333
 
3323
3334
  // handle types expression, cell-match and duplicate-values
3324
3335
 
3325
- const area = JSON.parse(JSON.stringify(format.area));
3326
- const result = format.internal?.vertex?.result;
3327
-
3328
3336
  if (result) {
3329
3337
 
3330
3338
  if (result.type === ValueType.array) {
3331
3339
  for (let row = area.start.row; row <= area.end.row; row++) {
3332
3340
  for (let column = area.start.column; column <= area.end.column; column++) {
3333
3341
  const value = result.value[column - area.start.column][row - area.start.row];
3334
- if ((value.type === ValueType.boolean || value.type === ValueType.number) && !!value.value) {
3342
+ if (value && (value.type === ValueType.boolean || value.type === ValueType.number) && !!value.value) {
3335
3343
  if (!temp[row]) { temp[row] = []; }
3336
3344
  if (!temp[row][column] ) { temp[row][column] = []; }
3337
3345
  temp[row][column].push(format.style);