@trebco/treb 29.1.6 → 29.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  }
@@ -136,10 +115,12 @@ export class NamedRangeManager {
136
115
 
137
116
  const validated = this.ValidateNamed(name);
138
117
  if (!validated) {
139
- console.warn('invalid name');
118
+ console.warn('invalid name', {name});
140
119
  return false;
141
120
  }
142
121
 
122
+ /*
123
+
143
124
  if (named.type === 'range') {
144
125
 
145
126
  // why is this considered invalid here? I've seen it done.
@@ -148,12 +129,14 @@ export class NamedRangeManager {
148
129
 
149
130
  if (named.area.entire_column || named.area.entire_row) {
150
131
  console.info({named});
151
- console.warn(`invalid range`);
132
+ console.warn('invalid range');
152
133
  return false;
153
134
  }
154
135
 
155
136
  }
156
137
 
138
+ */
139
+
157
140
  // this.named.set(name.toLowerCase(), named);
158
141
  this.named.set(this.ScopedName(name, named.scope), named);
159
142
 
@@ -226,14 +209,26 @@ export class NamedRangeManager {
226
209
  * - must start with letter or underscore (not a number or dot).
227
210
  * - cannot look like a spreadsheet address, which is 1-3 letters followed by numbers.
228
211
  *
212
+ * - apparently questuon marks are legal, but not in first position. atm
213
+ * our parser will reject.
214
+ *
229
215
  * returns a normalized name (just caps, atm)
230
216
  */
231
217
  public ValidateNamed(name: string): string|false {
232
218
  name = name.trim();
219
+
220
+ // can't be empty
233
221
  if (!name.length) return false;
222
+
223
+ // can't look like a spreadsheet address
234
224
  if (/^[A-Za-z]{1,3}\d+$/.test(name)) return false;
235
- if (/[^A-Za-z\d_.]/.test(name)) return false;
225
+
226
+ // can only contain legal characters
227
+ if (/[^A-Za-z\d_.?]/.test(name)) return false;
228
+
229
+ // must start with ascii letter or underscore
236
230
  if (/^[^A-Za-z_]/.test(name)) return false;
231
+
237
232
  return name.toUpperCase();
238
233
  }
239
234
 
@@ -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);
@@ -54,7 +54,7 @@ import { AddRel } from './relationship';
54
54
  import { type DOMContent, XMLOptions2 } from './xml-utils';
55
55
 
56
56
  import type { UnitAddress, UnitRange, ExpressionUnit} from 'treb-parser';
57
- import { Parser, QuotedSheetNameRegex } from 'treb-parser';
57
+ import { Parser } from 'treb-parser';
58
58
 
59
59
  // FIXME: move
60
60
  import type { ChartOptions } from './drawing2/chart2';
@@ -2464,92 +2464,13 @@ export class Exporter {
2464
2464
  }
2465
2465
  }
2466
2466
 
2467
- if (entry.area) {
2468
- let sheet_name = '';
2469
- const area = new Area(entry.area.start, entry.area.end);
2470
- area.start.absolute_column = area.start.absolute_row = true;
2471
- area.end.absolute_column = area.end.absolute_row = true;
2472
-
2473
- if (entry.area.start.sheet) {
2474
- sheet_name = entry.area.start.sheet;
2475
- }
2476
- else if (entry.area.start.sheet_id) {
2477
- for (const sheet of source.sheet_data) {
2478
- if (sheet.id === area.start.sheet_id) {
2479
- sheet_name = sheet.name || '';
2480
- break;
2481
- }
2482
- }
2483
- }
2484
-
2485
- if (sheet_name) {
2486
- if (QuotedSheetNameRegex.test(sheet_name)) {
2487
- sheet_name = `'${sheet_name}'`;
2488
- }
2489
- sheet_name += '!';
2490
- }
2491
-
2492
- // console.info({key, area, lx: area.spreadsheet_label, sheet_name });
2493
- definedNames.definedName.push({
2494
- a$: { name: entry.name, localSheetId: scope },
2495
- t$: sheet_name + area.spreadsheet_label,
2496
- });
2497
-
2498
- }
2499
- else if (entry.expression) {
2500
- definedNames.definedName.push({
2501
- a$: { name: entry.name, localSheetId: scope },
2502
- t$: entry.expression,
2503
- });
2504
- }
2505
-
2506
- }
2507
- }
2508
-
2509
- console.info("DB", definedNames);
2510
-
2511
- /*
2512
- if (source.named_ranges) {
2513
- const keys = Object.keys(source.named_ranges);
2514
- for (const key of keys) {
2515
- let sheet_name = '';
2516
- const area = new Area(source.named_ranges[key].start, source.named_ranges[key].end);
2517
- area.start.absolute_column = area.start.absolute_row = true;
2518
- area.end.absolute_column = area.end.absolute_row = true;
2519
-
2520
- if (area.start.sheet_id) {
2521
- for (const sheet of source.sheet_data) {
2522
- if (sheet.id === area.start.sheet_id) {
2523
- sheet_name = sheet.name || '';
2524
- break;
2525
- }
2526
- }
2527
- }
2528
-
2529
- if (sheet_name) {
2530
- if (QuotedSheetNameRegex.test(sheet_name)) {
2531
- sheet_name = `'${sheet_name}'`;
2532
- }
2533
- sheet_name += '!';
2534
- }
2535
-
2536
- // console.info({key, area, lx: area.spreadsheet_label, sheet_name });
2537
2467
  definedNames.definedName.push({
2538
- a$: { name: key },
2539
- t$: sheet_name + area.spreadsheet_label,
2540
- });
2541
-
2542
- }
2543
- }
2544
- if (source.named_expressions) {
2545
- for (const entry of source.named_expressions) {
2546
- definedNames.definedName.push({
2547
- a$: { name: entry.name },
2468
+ a$: { name: entry.name, localSheetId: scope },
2548
2469
  t$: entry.expression,
2549
2470
  });
2471
+
2550
2472
  }
2551
2473
  }
2552
- */
2553
2474
 
2554
2475
  if (!definedNames.definedName.length) {
2555
2476
  definedNames = undefined;
@@ -348,21 +348,23 @@ export class Importer {
348
348
  case 'duplicateValues':
349
349
  case 'uniqueValues':
350
350
 
351
- let style = {};
351
+ {
352
+ let style = {};
352
353
 
353
- if (rule.a$.dxfId) {
354
- const index = Number(rule.a$.dxfId);
355
- if (!isNaN(index)) {
356
- style = this.workbook?.style_cache.dxf_styles[index] || {};
354
+ if (rule.a$.dxfId) {
355
+ const index = Number(rule.a$.dxfId);
356
+ if (!isNaN(index)) {
357
+ style = this.workbook?.style_cache.dxf_styles[index] || {};
358
+ }
357
359
  }
358
- }
359
360
 
360
- return {
361
- type: 'duplicate-values',
362
- area,
363
- style,
364
- unique: (rule.a$.type === 'uniqueValues'),
365
- };
361
+ return {
362
+ type: 'duplicate-values',
363
+ area,
364
+ style,
365
+ unique: (rule.a$.type === 'uniqueValues'),
366
+ };
367
+ }
366
368
 
367
369
  case 'cellIs':
368
370
  if (rule.a$.operator && rule.formula) {
@@ -392,7 +394,10 @@ export class Importer {
392
394
  }
393
395
  break;
394
396
 
397
+ case 'containsErrors':
398
+ case 'notContainsErrors':
395
399
  case 'expression':
400
+
396
401
  if (rule.formula) {
397
402
 
398
403
  if (typeof rule.formula !== 'string') {
@@ -436,7 +441,7 @@ export class Importer {
436
441
  const stops: GradientStop[] = [];
437
442
  for (const [index, entry] of rule.colorScale.cfvo.entries()) {
438
443
  let value = 0;
439
- let color: Color = {};
444
+ const color: Color = {};
440
445
 
441
446
  const color_element = rule.colorScale.color[index];
442
447
  if (color_element.a$.rgb) {
@@ -39,7 +39,7 @@ import { Sheet, VisibleState } from './workbook-sheet2';
39
39
  import type { RelationshipMap } from './relationship';
40
40
  import { ZipWrapper } from './zip-wrapper';
41
41
  import type { CellStyle } from 'treb-base-types';
42
- import type { CompositeNamed } from 'treb-data-model';
42
+ import type { SerializedNamed } from 'treb-data-model';
43
43
 
44
44
 
45
45
  /*
@@ -155,7 +155,7 @@ export class Workbook {
155
155
  /* * defined names. these can be ranges or expressions. */
156
156
  // public defined_names: Record<string, string> = {};
157
157
 
158
- public named: Array<CompositeNamed & {local_scope?: number}> = [];
158
+ public named: Array<SerializedNamed & {local_scope?: number}> = [];
159
159
 
160
160
  /** the workbook "rels" */
161
161
  public rels: RelationshipMap = {};
@@ -70,15 +70,17 @@ export const UnlotusDate = (value: number, local = true): number => {
70
70
  if (local) {
71
71
 
72
72
  const local_date = new Date(value);
73
- const utc_date = new Date();
74
-
75
- utc_date.setUTCMilliseconds(local_date.getUTCMilliseconds());
76
- utc_date.setUTCSeconds(local_date.getUTCSeconds());
77
- utc_date.setUTCMinutes(local_date.getUTCMinutes());
78
- utc_date.setUTCHours(local_date.getHours());
79
- utc_date.setUTCDate(local_date.getDate());
80
- utc_date.setUTCMonth(local_date.getMonth());
81
- utc_date.setUTCFullYear(local_date.getFullYear());
73
+ const utc_date = new Date(
74
+ local_date.getFullYear(),
75
+ local_date.getMonth(),
76
+ local_date.getDate(),
77
+ local_date.getHours(),
78
+ local_date.getMinutes(),
79
+ local_date.getSeconds(),
80
+ local_date.getMilliseconds(),
81
+ );
82
+
83
+ // console.info("Converting local", utc_date.toUTCString());
82
84
 
83
85
  value = utc_date.getTime();
84
86
 
@@ -322,7 +322,7 @@ class ValueParserType {
322
322
  }
323
323
 
324
324
  return {
325
- value: UnlotusDate(date),
325
+ value: UnlotusDate(date, true),
326
326
  type: ValueType.number,
327
327
  hints,
328
328
  };
@@ -83,7 +83,7 @@ import type { GridEvent } from './grid_events';
83
83
  import { ErrorCode } from './grid_events';
84
84
 
85
85
  import type {
86
- CompositeNamed,
86
+ SerializedNamed,
87
87
  DataModel,
88
88
  GridSelection,
89
89
  LegacySerializedSheet,
@@ -890,7 +890,7 @@ export class Grid extends GridBase {
890
890
  import_data: {
891
891
  sheets: ImportedSheetData[],
892
892
  // names?: Record<string, string|number>,
893
- named?: CompositeNamed[],
893
+ named?: SerializedNamed[],
894
894
  active_tab?: number,
895
895
  },
896
896
  render = false,
@@ -970,79 +970,11 @@ export class Grid extends GridBase {
970
970
  this.model.sheets.UpdateIndexes();
971
971
 
972
972
  this.model.named.Reset();
973
- // this.model.named_ranges.Reset();
974
- // this.model.named_expressions.clear();
975
- // console.info({IDX: import_data.named});
976
973
 
977
974
  if (import_data.named) {
978
- this.model.UnserializeComposite(import_data.named, this.active_sheet);
975
+ this.model.UnserializeNames(import_data.named, this.active_sheet);
979
976
  }
980
977
 
981
- /*
982
- if (import_data.names) {
983
-
984
- console.info("IDN", { names: import_data.names })
985
-
986
- for (const name of Object.keys(import_data.names)) {
987
-
988
- const validated = this.model.named.ValidateNamed(name);
989
-
990
- if (!validated) {
991
- console.warn(`invalid name: ${name}`, import_data.names[name]);
992
- continue;
993
- }
994
-
995
- const label = import_data.names[name];
996
- if (typeof label === 'number') {
997
- // console.info('dropping (temp)', name, label);
998
- }
999
- else {
1000
- const parse_result = this.parser.Parse(label);
1001
-
1002
- if (parse_result.expression) {
1003
- if (parse_result.expression.type === 'range') {
1004
- const sheet_id = name_map[parse_result.expression.start.sheet || ''];
1005
- if (sheet_id) {
1006
- parse_result.expression.start.sheet_id = sheet_id;
1007
- this.model.named.SetNamedRange(validated, new Area(parse_result.expression.start, parse_result.expression.end));
1008
- }
1009
- }
1010
- else if (parse_result.expression.type === 'address') {
1011
- const sheet_id = name_map[parse_result.expression.sheet || ''];
1012
- if (sheet_id) {
1013
- parse_result.expression.sheet_id = sheet_id;
1014
- this.model.named.SetNamedRange(validated, new Area(parse_result.expression));
1015
- }
1016
- }
1017
- else {
1018
-
1019
- // we need to map sheet names to sheet IDs in named
1020
- // expressions, if they have any addresses/references.
1021
-
1022
- const expr = parse_result.expression;
1023
- this.parser.Walk(expr, unit => {
1024
- if (unit.type === 'address' || unit.type === 'range') {
1025
- if (unit.type === 'range') {
1026
- unit = unit.start;
1027
- }
1028
- if (!unit.sheet_id) {
1029
- unit.sheet_id = name_map[unit.sheet || ''] || 0; // default is bad here -- do we have an active sheet yet?
1030
- }
1031
- return false;
1032
- }
1033
- return true;
1034
- });
1035
-
1036
- this.model.named.SetNamedExpression(validated, expr);
1037
-
1038
- }
1039
- }
1040
- }
1041
- }
1042
- // this.model.named_ranges.RebuildList();
1043
- }
1044
- */
1045
-
1046
978
  // FIXME: do we need to rebuild autocomplete here (A: yes)
1047
979
  // ...
1048
980
 
@@ -1597,7 +1529,7 @@ export class Grid extends GridBase {
1597
1529
  */
1598
1530
  public SetName(name: string, range?: ICellAddress | Area, expression?: string, scope?: number, overwrite = false): void {
1599
1531
 
1600
- // console.info('setname', name, range, expression, scope, overwrite);
1532
+ // console.info('setname', { name, range, expression, scope, overwrite });
1601
1533
 
1602
1534
  // validate/translate name first
1603
1535
 
@@ -89,6 +89,7 @@ const CLOSE_BRACE = 0x7d;
89
89
  const OPEN_SQUARE_BRACKET = 0x5b;
90
90
  const CLOSE_SQUARE_BRACKET = 0x5d;
91
91
 
92
+ const QUESTION_MARK = 0x3f;
92
93
  const EXCLAMATION_MARK = 0x21;
93
94
  // const COLON = 0x3a; // became an operator
94
95
  const SEMICOLON = 0x3b;
@@ -2100,6 +2101,11 @@ export class Parser {
2100
2101
 
2101
2102
  // I think that's all the rules for structured references.
2102
2103
 
2104
+ // testing question marks, which are legal in defined names
2105
+ // (but I think not in table names or column names)
2106
+
2107
+ || (char === QUESTION_MARK && square_bracket === 0)
2108
+
2103
2109
  /*
2104
2110
 
2105
2111
  || (this.flags.r1c1 && (