@trebco/treb 23.6.5 → 25.0.0-rc2

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 (217) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +164 -0
  3. package/README-shadow-DOM.md +88 -0
  4. package/README.md +37 -130
  5. package/api-config.json +29 -0
  6. package/api-generator/api-generator-types.ts +82 -0
  7. package/api-generator/api-generator.ts +1172 -0
  8. package/api-generator/package.json +3 -0
  9. package/build/treb-spreadsheet.mjs +14 -0
  10. package/{treb.d.ts → build/treb.d.ts} +323 -271
  11. package/esbuild-custom-element.mjs +336 -0
  12. package/esbuild.js +305 -0
  13. package/package.json +49 -14
  14. package/treb-base-types/package.json +5 -0
  15. package/treb-base-types/src/api_types.ts +36 -0
  16. package/treb-base-types/src/area.ts +583 -0
  17. package/treb-base-types/src/basic_types.ts +45 -0
  18. package/treb-base-types/src/cell.ts +612 -0
  19. package/treb-base-types/src/cells.ts +1066 -0
  20. package/treb-base-types/src/color.ts +124 -0
  21. package/treb-base-types/src/import.ts +71 -0
  22. package/treb-base-types/src/index-standalone.ts +29 -0
  23. package/treb-base-types/src/index.ts +42 -0
  24. package/treb-base-types/src/layout.ts +47 -0
  25. package/treb-base-types/src/localization.ts +187 -0
  26. package/treb-base-types/src/rectangle.ts +145 -0
  27. package/treb-base-types/src/render_text.ts +72 -0
  28. package/treb-base-types/src/style.ts +545 -0
  29. package/treb-base-types/src/table.ts +109 -0
  30. package/treb-base-types/src/text_part.ts +54 -0
  31. package/treb-base-types/src/theme.ts +608 -0
  32. package/treb-base-types/src/union.ts +152 -0
  33. package/treb-base-types/src/value-type.ts +164 -0
  34. package/treb-base-types/style/resizable.css +59 -0
  35. package/treb-calculator/modern.tsconfig.json +11 -0
  36. package/treb-calculator/package.json +5 -0
  37. package/treb-calculator/src/calculator.ts +2546 -0
  38. package/treb-calculator/src/complex-math.ts +558 -0
  39. package/treb-calculator/src/dag/array-vertex.ts +198 -0
  40. package/treb-calculator/src/dag/graph.ts +951 -0
  41. package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
  42. package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
  43. package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
  44. package/treb-calculator/src/dag/vertex.ts +352 -0
  45. package/treb-calculator/src/descriptors.ts +162 -0
  46. package/treb-calculator/src/expression-calculator.ts +1069 -0
  47. package/treb-calculator/src/function-error.ts +103 -0
  48. package/treb-calculator/src/function-library.ts +103 -0
  49. package/treb-calculator/src/functions/base-functions.ts +1214 -0
  50. package/treb-calculator/src/functions/checkbox.ts +164 -0
  51. package/treb-calculator/src/functions/complex-functions.ts +253 -0
  52. package/treb-calculator/src/functions/finance-functions.ts +399 -0
  53. package/treb-calculator/src/functions/information-functions.ts +102 -0
  54. package/treb-calculator/src/functions/matrix-functions.ts +182 -0
  55. package/treb-calculator/src/functions/sparkline.ts +335 -0
  56. package/treb-calculator/src/functions/statistics-functions.ts +350 -0
  57. package/treb-calculator/src/functions/text-functions.ts +298 -0
  58. package/treb-calculator/src/index.ts +27 -0
  59. package/treb-calculator/src/notifier-types.ts +59 -0
  60. package/treb-calculator/src/primitives.ts +428 -0
  61. package/treb-calculator/src/utilities.ts +305 -0
  62. package/treb-charts/package.json +5 -0
  63. package/treb-charts/src/chart-functions.ts +156 -0
  64. package/treb-charts/src/chart-types.ts +230 -0
  65. package/treb-charts/src/chart.ts +1288 -0
  66. package/treb-charts/src/index.ts +24 -0
  67. package/treb-charts/src/main.ts +37 -0
  68. package/treb-charts/src/rectangle.ts +52 -0
  69. package/treb-charts/src/renderer.ts +1841 -0
  70. package/treb-charts/src/util.ts +122 -0
  71. package/treb-charts/style/charts.scss +221 -0
  72. package/treb-charts/style/old-charts.scss +250 -0
  73. package/treb-embed/markup/layout.html +137 -0
  74. package/treb-embed/markup/toolbar.html +175 -0
  75. package/treb-embed/modern.tsconfig.json +25 -0
  76. package/treb-embed/src/custom-element/content-types.d.ts +18 -0
  77. package/treb-embed/src/custom-element/global.d.ts +11 -0
  78. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1228 -0
  79. package/treb-embed/src/custom-element/treb-global.ts +44 -0
  80. package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
  81. package/treb-embed/src/embedded-spreadsheet.ts +5358 -0
  82. package/treb-embed/src/index.ts +16 -0
  83. package/treb-embed/src/language-model.ts +41 -0
  84. package/treb-embed/src/options.ts +298 -0
  85. package/treb-embed/src/progress-dialog.ts +228 -0
  86. package/treb-embed/src/selection-state.ts +16 -0
  87. package/treb-embed/src/spinner.ts +42 -0
  88. package/treb-embed/src/toolbar-message.ts +96 -0
  89. package/treb-embed/src/types.ts +167 -0
  90. package/treb-embed/style/autocomplete.scss +103 -0
  91. package/treb-embed/style/dark-theme.scss +114 -0
  92. package/treb-embed/style/defaults.scss +36 -0
  93. package/treb-embed/style/dialog.scss +181 -0
  94. package/treb-embed/style/dropdown-select.scss +101 -0
  95. package/treb-embed/style/formula-bar.scss +193 -0
  96. package/treb-embed/style/grid.scss +374 -0
  97. package/treb-embed/style/layout.scss +424 -0
  98. package/treb-embed/style/mouse-mask.scss +67 -0
  99. package/treb-embed/style/note.scss +92 -0
  100. package/treb-embed/style/overlay-editor.scss +102 -0
  101. package/treb-embed/style/spinner.scss +92 -0
  102. package/treb-embed/style/tab-bar.scss +228 -0
  103. package/treb-embed/style/table.scss +80 -0
  104. package/treb-embed/style/theme-defaults.scss +444 -0
  105. package/treb-embed/style/toolbar.scss +416 -0
  106. package/treb-embed/style/tooltip.scss +68 -0
  107. package/treb-embed/style/treb-icons.scss +130 -0
  108. package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
  109. package/treb-embed/style/z-index.scss +43 -0
  110. package/treb-export/docs/charts.md +68 -0
  111. package/treb-export/modern.tsconfig.json +19 -0
  112. package/treb-export/package.json +4 -0
  113. package/treb-export/src/address-type.ts +77 -0
  114. package/treb-export/src/base-template.ts +22 -0
  115. package/treb-export/src/column-width.ts +85 -0
  116. package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
  117. package/treb-export/src/drawing2/chart2.ts +282 -0
  118. package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
  119. package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
  120. package/treb-export/src/drawing2/drawing2.ts +355 -0
  121. package/treb-export/src/drawing2/embedded-image.ts +71 -0
  122. package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
  123. package/treb-export/src/export-worker/export-worker.ts +99 -0
  124. package/treb-export/src/export-worker/index-modern.ts +22 -0
  125. package/treb-export/src/export2.ts +2204 -0
  126. package/treb-export/src/import2.ts +882 -0
  127. package/treb-export/src/relationship.ts +36 -0
  128. package/treb-export/src/shared-strings2.ts +128 -0
  129. package/treb-export/src/template-2.ts +22 -0
  130. package/treb-export/src/unescape_xml.ts +47 -0
  131. package/treb-export/src/workbook-sheet2.ts +182 -0
  132. package/treb-export/src/workbook-style2.ts +1285 -0
  133. package/treb-export/src/workbook-theme2.ts +88 -0
  134. package/treb-export/src/workbook2.ts +491 -0
  135. package/treb-export/src/xml-utils.ts +201 -0
  136. package/treb-export/template/base/[Content_Types].xml +2 -0
  137. package/treb-export/template/base/_rels/.rels +2 -0
  138. package/treb-export/template/base/docProps/app.xml +2 -0
  139. package/treb-export/template/base/docProps/core.xml +12 -0
  140. package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
  141. package/treb-export/template/base/xl/sharedStrings.xml +2 -0
  142. package/treb-export/template/base/xl/styles.xml +2 -0
  143. package/treb-export/template/base/xl/theme/theme1.xml +2 -0
  144. package/treb-export/template/base/xl/workbook.xml +2 -0
  145. package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
  146. package/treb-export/template/base.xlsx +0 -0
  147. package/treb-format/package.json +8 -0
  148. package/treb-format/src/format.test.ts +213 -0
  149. package/treb-format/src/format.ts +942 -0
  150. package/treb-format/src/format_cache.ts +199 -0
  151. package/treb-format/src/format_parser.ts +723 -0
  152. package/treb-format/src/index.ts +25 -0
  153. package/treb-format/src/number_format_section.ts +100 -0
  154. package/treb-format/src/value_parser.ts +337 -0
  155. package/treb-grid/package.json +5 -0
  156. package/treb-grid/src/editors/autocomplete.ts +394 -0
  157. package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
  158. package/treb-grid/src/editors/formula_bar.ts +473 -0
  159. package/treb-grid/src/editors/formula_editor_base.ts +910 -0
  160. package/treb-grid/src/editors/overlay_editor.ts +511 -0
  161. package/treb-grid/src/index.ts +37 -0
  162. package/treb-grid/src/layout/base_layout.ts +2618 -0
  163. package/treb-grid/src/layout/grid_layout.ts +299 -0
  164. package/treb-grid/src/layout/rectangle_cache.ts +86 -0
  165. package/treb-grid/src/render/selection-renderer.ts +414 -0
  166. package/treb-grid/src/render/svg_header_overlay.ts +93 -0
  167. package/treb-grid/src/render/svg_selection_block.ts +187 -0
  168. package/treb-grid/src/render/tile_renderer.ts +2122 -0
  169. package/treb-grid/src/types/annotation.ts +216 -0
  170. package/treb-grid/src/types/border_constants.ts +34 -0
  171. package/treb-grid/src/types/clipboard_data.ts +31 -0
  172. package/treb-grid/src/types/data_model.ts +334 -0
  173. package/treb-grid/src/types/drag_mask.ts +81 -0
  174. package/treb-grid/src/types/grid.ts +7743 -0
  175. package/treb-grid/src/types/grid_base.ts +3644 -0
  176. package/treb-grid/src/types/grid_command.ts +470 -0
  177. package/treb-grid/src/types/grid_events.ts +124 -0
  178. package/treb-grid/src/types/grid_options.ts +97 -0
  179. package/treb-grid/src/types/grid_selection.ts +60 -0
  180. package/treb-grid/src/types/named_range.ts +369 -0
  181. package/treb-grid/src/types/scale-control.ts +202 -0
  182. package/treb-grid/src/types/serialize_options.ts +72 -0
  183. package/treb-grid/src/types/set_range_options.ts +52 -0
  184. package/treb-grid/src/types/sheet.ts +3099 -0
  185. package/treb-grid/src/types/sheet_types.ts +95 -0
  186. package/treb-grid/src/types/tab_bar.ts +464 -0
  187. package/treb-grid/src/types/tile.ts +59 -0
  188. package/treb-grid/src/types/update_flags.ts +75 -0
  189. package/treb-grid/src/util/dom_utilities.ts +44 -0
  190. package/treb-grid/src/util/fontmetrics2.ts +179 -0
  191. package/treb-grid/src/util/ua.ts +104 -0
  192. package/treb-logo.svg +18 -0
  193. package/treb-parser/package.json +5 -0
  194. package/treb-parser/src/csv-parser.ts +122 -0
  195. package/treb-parser/src/index.ts +25 -0
  196. package/treb-parser/src/md-parser.ts +526 -0
  197. package/treb-parser/src/parser-types.ts +397 -0
  198. package/treb-parser/src/parser.test.ts +298 -0
  199. package/treb-parser/src/parser.ts +2673 -0
  200. package/treb-utils/package.json +5 -0
  201. package/treb-utils/src/dispatch.ts +57 -0
  202. package/treb-utils/src/event_source.ts +147 -0
  203. package/treb-utils/src/ievent_source.ts +33 -0
  204. package/treb-utils/src/index.ts +31 -0
  205. package/treb-utils/src/measurement.ts +174 -0
  206. package/treb-utils/src/resizable.ts +160 -0
  207. package/treb-utils/src/scale.ts +137 -0
  208. package/treb-utils/src/serialize_html.ts +124 -0
  209. package/treb-utils/src/template.ts +70 -0
  210. package/treb-utils/src/validate_uri.ts +61 -0
  211. package/tsconfig.json +10 -0
  212. package/tsproject.json +30 -0
  213. package/util/license-plugin-esbuild.js +86 -0
  214. package/util/list-css-vars.sh +46 -0
  215. package/README-esm.md +0 -37
  216. package/treb-bundle.css +0 -2
  217. package/treb-bundle.mjs +0 -15
@@ -0,0 +1,1066 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2023 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ /**
23
+ * switched to row-major, seems to have no ill effects
24
+ * (not sure if there are benefits yet either)
25
+ */
26
+
27
+ import { Area, IArea, ICellAddress, IsCellAddress } from './area';
28
+ import { Cell, DataValidation } from './cell';
29
+ import type { Table } from './table';
30
+ import { ValueType, GetValueType } from './value-type';
31
+ import type { CellValue, UnionValue } from './union';
32
+ import type { Style } from './style';
33
+
34
+ export interface CellSerializationOptions {
35
+ preserve_type?: boolean;
36
+ convert_address?: boolean;
37
+ calculated_value?: boolean;
38
+ expand_arrays?: boolean;
39
+ subset?: Area;
40
+ preserve_empty_strings?: boolean;
41
+ decorated_cells?: boolean;
42
+ tables?: boolean;
43
+
44
+ /**
45
+ * nest rows in columns, or vice-versa, depending on which is smaller.
46
+ */
47
+ nested?: boolean;
48
+
49
+ /**
50
+ * cell style refs to pack into cells
51
+ */
52
+ cell_style_refs?: number[][];
53
+
54
+ /** optionally attach an ID to the cells */
55
+ sheet_id?: number;
56
+
57
+ }
58
+
59
+ // our cell data is now somewhat complicated, from a type perspective. we
60
+ // support the original type, which was just an array of cells with each
61
+ // cell having {row, column}.
62
+ //
63
+ // more recent code compresses this by nesting blocks of rows or columns,
64
+ // using the structure (e.g.) { row, cells } where each cell in cells has
65
+ // a column.
66
+ //
67
+ // so type needs to support both flat and nested, where nested can be row-
68
+ // dominant or column-dominant.
69
+ //
70
+ // by the way, did we ever validate that this structure is significantly
71
+ // smaller, when compressed? (...)
72
+
73
+ export interface BaseCellData {
74
+ value: CellValue;
75
+ style_ref?: number;
76
+ calculated?: CellValue;
77
+ table?: Table;
78
+ area?: IArea;
79
+ merge_area?: IArea;
80
+ validation?: DataValidation;
81
+ calculated_type?: ValueType;
82
+ note?: string;
83
+ hyperlink?: string;
84
+ type?: ValueType;
85
+ sheet_id?: number;
86
+ // locked?: boolean;
87
+ }
88
+
89
+ export interface FlatCellData extends BaseCellData {
90
+ row: number;
91
+ column: number;
92
+ }
93
+
94
+ export interface NestedCellData {
95
+ cells: BaseCellData[];
96
+ }
97
+
98
+ export interface NestedRowData extends NestedCellData {
99
+ row: number;
100
+ cells: Array<{
101
+ column: number;
102
+ } & BaseCellData>;
103
+ }
104
+
105
+ export interface NestedColumnData extends NestedCellData {
106
+ column: number;
107
+ cells: Array<{
108
+ row: number;
109
+ } & BaseCellData>;
110
+ }
111
+
112
+ export type SerializedCellData = FlatCellData[]|NestedRowData[]|NestedColumnData[];
113
+
114
+ // some type guards for the various data types
115
+
116
+ /** @internal */
117
+ export const IsFlatData = (test: FlatCellData|NestedCellData): test is FlatCellData => {
118
+ return !(test as NestedCellData).cells;
119
+ }
120
+
121
+ /** @internal */
122
+ export const IsFlatDataArray = (test: FlatCellData[]|NestedCellData[]): test is FlatCellData[] => {
123
+ return (!!test[0]) && IsFlatData(test[0]);
124
+ };
125
+
126
+ /** @internal */
127
+ export const IsNestedRowArray = (test: NestedRowData[]|NestedColumnData[]): test is NestedRowData[] => {
128
+ return (!!test[0]) && ((test[0] as NestedRowData).row !== undefined);
129
+ };
130
+
131
+ // ...
132
+
133
+ /**
134
+ * collection of cells, basically a wrapper around an
135
+ * array, with some accessor and control methods.
136
+ */
137
+ export class Cells {
138
+
139
+ /** switching to row-major */
140
+ public data: Cell[][] = [];
141
+
142
+ // tslint:disable-next-line:variable-name
143
+ private rows_ = 0;
144
+
145
+ // tslint:disable-next-line:variable-name
146
+ private columns_ = 0;
147
+
148
+ get rows(): number { return this.rows_; }
149
+ get columns(): number { return this.columns_; }
150
+
151
+ /**
152
+ * the sheet wants to make sure this row exists, probably because it has
153
+ * a header. so we will update our dimensions to match. we don't actually
154
+ * add data.
155
+ *
156
+ * this is not serialized. specific headers aren't serialized either, at
157
+ * the moment, so it's sort of irrelevant. if we start serializing headers,
158
+ * the deserialization routine can call this function to pad out, so we
159
+ * don't need to store it here.
160
+ */
161
+ public EnsureRow(row: number): void {
162
+ this.rows_ = Math.max(row + 1, this.rows_);
163
+ }
164
+
165
+ /** @see EnsureRow */
166
+ public EnsureColumn(column: number): void {
167
+ this.columns_ = Math.max(column + 1, this.columns_);
168
+ }
169
+
170
+ /**
171
+ * this class does none of the validation/correction
172
+ * required when inserting rows/columns. that should
173
+ * be done by external logic. this method only does
174
+ * the mechanical work of inserting rows/columns.
175
+ */
176
+ public InsertColumns(before = 0, count = 1): void {
177
+
178
+ const pre = JSON.parse(JSON.stringify(this.data[13]));
179
+
180
+ // NOTE: iterating a sparse array, in chrome at least, only
181
+ // hits populated keys. the returned array has the same
182
+ // indexes. that is very nice.
183
+
184
+ this.data = this.data.map(row => {
185
+ if (row.length >= before){
186
+ const tmp = row.slice(0, before);
187
+ let index = before + count;
188
+
189
+ // this forEach is broken when there are empty values in the row,
190
+ // which doesn't happen so much anymore but can (and does) happen
191
+ // in some older sheets.
192
+
193
+ // row.slice(before).forEach((column) => tmp[index++] = column);
194
+
195
+ // do it with an explicit index loop, should resolve
196
+
197
+ const after = row.slice(before);
198
+ for (let i = 0; i < after.length; i++) {
199
+ tmp[index++] = after[i];
200
+ }
201
+
202
+ return tmp;
203
+ }
204
+ return row;
205
+ });
206
+
207
+ this.columns_ += count;
208
+
209
+ // wtf is this? some old debug stuff?
210
+ // const clone = JSON.parse(JSON.stringify(this.data[13]));
211
+ // console.info({pre, clone});
212
+
213
+ }
214
+
215
+ public DeleteColumns(index: number, count= 1): void {
216
+
217
+ // trap! splice returns _removed_ elements so don't use map()
218
+
219
+ this.data.forEach((row) => row.splice(index, count));
220
+ this.columns_ -= count;
221
+ }
222
+
223
+ public DeleteRows(index: number, count = 1): void {
224
+ this.data.splice(index, count);
225
+ this.rows_ -= count;
226
+ }
227
+
228
+ /**
229
+ * this class does none of the validation/correction
230
+ * required when inserting rows/columns. that should
231
+ * be done by external logic. this method only does
232
+ * the mechanical work of inserting rows/columns.
233
+ */
234
+ public InsertRows(before = 0, count = 1): void {
235
+ const args: [number, number, Cell[]] = [before, 0, []];
236
+ for ( let i = 1; i < count; i++) args.push([]);
237
+ Array.prototype.splice.apply(this.data, args);
238
+ this.rows_ += count;
239
+ }
240
+
241
+ /**
242
+ * return or create cell at the given address
243
+ */
244
+ public GetCell(address: ICellAddress, create_new: true): Cell;
245
+
246
+ /**
247
+ * return the cell at the given address or undefined if it doesn't exist
248
+ */
249
+ public GetCell(address: ICellAddress, create_new?: false): Cell | undefined;
250
+
251
+ /**
252
+ * return the given cell or `undefined`, optionally creating
253
+ * new cells as necessary
254
+ *
255
+ * @param create_new always return a cell
256
+ */
257
+ public GetCell(address: ICellAddress, create_new?: boolean): Cell|undefined {
258
+
259
+ const { row, column } = address;
260
+
261
+ if (!this.data[row]) {
262
+ if (create_new) {
263
+ this.data[row] = [];
264
+ this.rows_ = Math.max(this.rows_, row + 1);
265
+ }
266
+ else return undefined;
267
+ }
268
+
269
+ if (!this.data[row][column]) {
270
+ if (create_new) {
271
+ this.data[row][column] = new Cell();
272
+ this.columns_ = Math.max(this.columns_, column + 1);
273
+ }
274
+ }
275
+
276
+ return this.data[row][column];
277
+
278
+ }
279
+
280
+ /**
281
+ * apply function to range or address. skips empty cells (for now...)
282
+ * (already have this function, it's called "IterateArea". "Apply" is better.)
283
+ * /
284
+ public Apply(target: ICellAddress|IArea, func: (cell: Cell) => void): void {
285
+
286
+ if (IsCellAddress(target)) {
287
+ target = new Area(target);
288
+ }
289
+
290
+ const start = target.start;
291
+ const end = target.end;
292
+
293
+ for (let r = start.row; r <= end.row; r++) {
294
+ if (this.data[r]) {
295
+ const row = this.data[r];
296
+ for (let c = start.column; c < end.column; c++) {
297
+ if (this.data[r][c]) {
298
+ func.call(undefined, row[c]);
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ }
305
+ */
306
+
307
+ /** returns an existing cell or creates a new cell. */
308
+ public EnsureCell(address: ICellAddress): Cell {
309
+ const { row, column } = address;
310
+ let ref = this.data[row];
311
+ if (!ref) {
312
+ this.data[row] = ref = [];
313
+ this.rows_ = Math.max(this.rows_, row + 1);
314
+ }
315
+ let cell = ref[column];
316
+ if (!cell) {
317
+ cell = ref[column] = new Cell();
318
+ this.columns_ = Math.max(this.columns_, column + 1);
319
+ }
320
+ return cell;
321
+ }
322
+
323
+ /**
324
+ * with the update, we assume the passed-in data is row-major.
325
+ * when reading an older file, transpose.
326
+ */
327
+ public FromArray(data: CellValue[][] = [], transpose = false): void {
328
+ this.data = [];
329
+
330
+ let rows = 0;
331
+ let columns = 0;
332
+
333
+ if (transpose){
334
+ columns = data.length;
335
+ for ( let c = 0; c < columns; c++ ){
336
+ const ref = data[c];
337
+ rows = Math.max(rows, ref.length);
338
+ for ( let r = 0; r < ref.length; r++ ){
339
+ if (!this.data[r]) this.data[r] = [];
340
+ this.data[r][c] = new Cell(ref[r]);
341
+ }
342
+ }
343
+ }
344
+ else {
345
+ rows = data.length;
346
+ for ( let r = 0; r < rows; r++ ){
347
+ const column: Cell[] = [];
348
+ const ref = data[r];
349
+ columns = Math.max(columns, ref.length);
350
+ for ( let c = 0; c < ref.length; c++ ) column[c] = new Cell(ref[c]);
351
+ this.data[r] = column;
352
+ }
353
+ }
354
+ this.rows_ = rows;
355
+ this.columns_ = columns;
356
+ }
357
+
358
+ /**
359
+ * UPDATE: adding optional style refs, for export
360
+ */
361
+ public FromJSON(data: SerializedCellData = [], style_refs?: Style.Properties[]): void {
362
+
363
+ this.data = [];
364
+
365
+ // handle nested data; fix. we can make the simplifying assumption
366
+ // that data is either nested, or not, but never both. therefore, we
367
+ // just need to check the first element.
368
+
369
+ if (!IsFlatDataArray(data)) {
370
+
371
+ const new_data: FlatCellData[] = [];
372
+
373
+ if (IsNestedRowArray(data)) {
374
+ for (const block of data) {
375
+ for (const cell of block.cells) {
376
+ new_data.push({...cell, row: block.row});
377
+ }
378
+ }
379
+ }
380
+ else {
381
+ for (const block of data) {
382
+ for (const cell of block.cells) {
383
+ new_data.push({...cell, column: block.column});
384
+ }
385
+ }
386
+ }
387
+
388
+ data = new_data;
389
+
390
+ }
391
+
392
+ /*
393
+ if (data[0] && data[0].cells) {
394
+
395
+ // console.info('reading nested data');
396
+
397
+ const new_data: any[] = [];
398
+ for (const element of data) {
399
+ if (typeof element.row !== 'undefined') {
400
+ for (const cell of element.cells) {
401
+ new_data.push({row: element.row, ...cell});
402
+ }
403
+ }
404
+ else if (typeof element.column !== 'undefined') {
405
+ for (const cell of element.cells) {
406
+ new_data.push({column: element.column, ...cell});
407
+ }
408
+ }
409
+ }
410
+ data = new_data;
411
+
412
+ }
413
+ */
414
+
415
+ const tables: Table[] = [];
416
+
417
+ for (const obj of data) {
418
+
419
+ if (!this.data[obj.row]) this.data[obj.row] = [];
420
+ const cell = new Cell(obj.value);
421
+ if (typeof obj.calculated !== 'undefined') {
422
+ // cell.calculated = obj.calculated;
423
+ // cell.calculated_type = obj.calculated_type;
424
+ cell.SetCalculatedValue(obj.calculated, obj.calculated_type);
425
+ }
426
+
427
+ if (style_refs) {
428
+ if (typeof obj.style_ref !== 'undefined') {
429
+ cell.style = style_refs[obj.style_ref];
430
+ }
431
+ }
432
+
433
+ if (typeof obj.note !== 'undefined') {
434
+ cell.note = obj.note;
435
+ }
436
+ if (typeof obj.hyperlink !== 'undefined') {
437
+ cell.hyperlink = obj.hyperlink;
438
+ }
439
+
440
+ // stop wrecking arrays
441
+
442
+ if (this.data[obj.row][obj.column] && this.data[obj.row][obj.column].area) {
443
+ cell.area = this.data[obj.row][obj.column].area;
444
+ }
445
+
446
+ this.data[obj.row][obj.column] = cell;
447
+
448
+ // since we are serializing the array data (when storing calculated
449
+ // values), is this getting called every time? I think it might be...
450
+ // we're fixing the former, anyway.
451
+
452
+ if (obj.area){
453
+ const area = new Area(obj.area.start, obj.area.end); // isn't there a clone method?
454
+ for ( let row = area.start.row; row <= area.end.row; row++){
455
+ for ( let column = area.start.column; column <= area.end.column; column++){
456
+ if (!this.data[row]) this.data[row] = [];
457
+ if (!this.data[row][column]) this.data[row][column] = new Cell();
458
+ this.data[row][column].area = area;
459
+ }
460
+ }
461
+ }
462
+
463
+ // collect tables, then apply them after reading all the cells.
464
+ // FIXME: why are we not doing this for merges? would be more
465
+ // efficient, no?
466
+
467
+ if (obj.table) {
468
+ tables.push({
469
+ ...obj.table,
470
+ });
471
+
472
+ /*
473
+ for ( let row = table.area.start.row; row <= table.area.end.row; row++){
474
+ for ( let column = table.area.start.column; column <= table.area.end.column; column++){
475
+ if (!this.data[row]) this.data[row] = [];
476
+ if (!this.data[row][column]) this.data[row][column] = new Cell();
477
+ this.data[row][column].table = table;
478
+ }
479
+ }
480
+ */
481
+ }
482
+
483
+ if (obj.merge_area){
484
+ const merge_area = new Area(obj.merge_area.start, obj.merge_area.end);
485
+ for ( let row = merge_area.start.row; row <= merge_area.end.row; row++){
486
+ for ( let column = merge_area.start.column; column <= merge_area.end.column; column++){
487
+ if (!this.data[row]) this.data[row] = [];
488
+ if (!this.data[row][column]) this.data[row][column] = new Cell();
489
+ this.data[row][column].merge_area = merge_area;
490
+ }
491
+ }
492
+ }
493
+
494
+ if (obj.validation) {
495
+ cell.validation = obj.validation;
496
+ }
497
+
498
+ }
499
+
500
+ for (const table of tables) {
501
+ for ( let row = table.area.start.row; row <= table.area.end.row; row++){
502
+ for ( let column = table.area.start.column; column <= table.area.end.column; column++){
503
+ if (!this.data[row]) this.data[row] = [];
504
+ if (!this.data[row][column]) this.data[row][column] = new Cell();
505
+ this.data[row][column].table = table;
506
+ }
507
+ }
508
+ }
509
+
510
+ this.rows_ = this.data.length;
511
+ this.columns_ = this.data.reduce((max, row) => Math.max(max, row.length), 0);
512
+
513
+ }
514
+
515
+ public toJSON(options: CellSerializationOptions = {}) : {
516
+ data: SerializedCellData;
517
+ rows: number;
518
+ columns: number;
519
+ } {
520
+
521
+ let start_column = 0;
522
+ let start_row = 0;
523
+ let end_row = this.data.length - 1;
524
+ let end_column;
525
+
526
+ if (options.subset){
527
+ start_column = options.subset.start.column;
528
+ start_row = options.subset.start.row;
529
+ end_row = options.subset.end.row;
530
+ }
531
+
532
+ const data: FlatCellData[] = [];
533
+
534
+ let last_row = -1;
535
+ let last_col = -1;
536
+
537
+ // unifying [FIXME: move into class]
538
+
539
+ // FIXME: why not use the original, instead of requiring a method
540
+ // call, and then re-order? that also makes it easier to pivot
541
+ // (order by rows or columns)
542
+
543
+ // ... (we did that)
544
+
545
+ const row_keys: {[index: number]: number} = {};
546
+ const column_keys: {[index: number]: number} = {};
547
+
548
+ for ( let row = start_row; row <= end_row; row++ ){
549
+ if ( this.data[row]){
550
+ const ref = this.data[row];
551
+
552
+ end_column = ref.length - 1;
553
+ if (options.subset) end_column = options.subset.end.column;
554
+
555
+ for ( let column = start_column; column <= end_column; column++ ){
556
+ const cell = ref[column];
557
+
558
+ // because only the array head will have a value, this test
559
+ // will filter out empty cells and non-head array cells
560
+
561
+ // update: also add merge heads
562
+ const merge_head = cell && cell.merge_area
563
+ && cell.merge_area.start.row === row
564
+ && cell.merge_area.start.column === column;
565
+
566
+ const array_head = cell && cell.area
567
+ && cell.area.start.row === row
568
+ && cell.area.start.column === column;
569
+
570
+ const table_head = cell && cell.table
571
+ && cell.table.area.start.row === row
572
+ && cell.table.area.start.column === column;
573
+
574
+ const is_empty = cell ? (cell.type === ValueType.string && !cell.value) : true;
575
+
576
+ // NOTE: we added the check on calculated && calculated_value,
577
+ // so we preserve rendered data for arrays. but that actually writes
578
+ // the array data as well, which is unnecessary (?) -- FIXME
579
+ //
580
+ // actually, check how that's interpreted on load, because it might
581
+ // break if we have a value but not the array area (...)
582
+
583
+ // FIXME: what's up with this? we check style? (...) can't recall
584
+ // why we do that, because we should ensure empty cells if there's
585
+ // a style (separately).
586
+
587
+ // NOTE: switching test from "calculated" to "calculated type": this
588
+ // should preserve zeros.
589
+
590
+ if (cell && (!is_empty || options.preserve_empty_strings) &&
591
+ (merge_head || cell.type || (cell.calculated_type && options.expand_arrays) ||
592
+ (cell.calculated_type && options.calculated_value) ||
593
+ (cell.note) ||
594
+ (cell.validation) ||
595
+ (options.decorated_cells && cell.style &&
596
+ ( cell.style.fill || cell.style.border_bottom ||
597
+ cell.style.border_top || cell.style.border_left || cell.style.border_right)))){
598
+
599
+ const obj: FlatCellData = { row, column, value: cell.value };
600
+ if (cell.note) {
601
+ obj.note = cell.note;
602
+ }
603
+ if (cell.hyperlink) {
604
+ obj.hyperlink = cell.hyperlink;
605
+ }
606
+
607
+ if (options.preserve_type) obj.type = cell.type;
608
+ if (options.sheet_id) obj.sheet_id = options.sheet_id;
609
+ if (options.calculated_value &&
610
+ typeof cell.calculated !== 'undefined') { // && cell.calculated_type !== ValueType.error) {
611
+ obj.calculated = cell.calculated;
612
+
613
+ // always preserve error type, because we can't infer
614
+ if (options.preserve_type || cell.calculated_type === ValueType.error) {
615
+ obj.calculated_type = cell.calculated_type;
616
+ }
617
+ }
618
+ if (cell.table && table_head) {
619
+ if (options.tables) {
620
+ obj.table = JSON.parse(JSON.stringify(cell.table));
621
+ }
622
+ }
623
+ if (cell.area && array_head) {
624
+ obj.area = cell.area.toJSON();
625
+ }
626
+ if (cell.merge_area) {
627
+ obj.merge_area = cell.merge_area.toJSON();
628
+ }
629
+ if (cell.validation) {
630
+ obj.validation = cell.validation; // safe?
631
+ }
632
+
633
+ if (options.cell_style_refs &&
634
+ options.cell_style_refs[column] &&
635
+ options.cell_style_refs[column][row]) {
636
+
637
+ obj.style_ref = options.cell_style_refs[column][row];
638
+ options.cell_style_refs[column][row] = 0; // consume
639
+
640
+ // console.info(`consume @ ${column}, ${row}: ${obj.style_ref } => ${options.cell_style_refs[column][row]}`);
641
+
642
+ }
643
+
644
+ row_keys[row] = row;
645
+ column_keys[column] = column;
646
+
647
+ last_row = Math.max(row, last_row);
648
+ last_col = Math.max(column, last_col);
649
+
650
+ data.push(obj);
651
+ }
652
+
653
+ }
654
+ }
655
+ }
656
+
657
+ if (options.nested) {
658
+
659
+ const row_key_map = Object.keys(row_keys);
660
+ const col_key_map = Object.keys(column_keys);
661
+
662
+ // extra test to make sure it's not empty
663
+
664
+ if ((row_key_map.length <= col_key_map.length) && row_key_map.length) {
665
+
666
+ const cells: {[index: number]: Array<BaseCellData & {column: number}>} = {};
667
+
668
+ // use rows
669
+ const new_data: NestedRowData[] = [];
670
+
671
+ for (const element of data) {
672
+ const {row, ...remainder} = element;
673
+ if (!cells[element.row]) cells[element.row] = [];
674
+ cells[element.row].push(remainder);
675
+ }
676
+ for (const key of row_key_map) {
677
+ const row = Number(key);
678
+ new_data.push({ row, cells: cells[row] });
679
+ }
680
+ return { data: new_data, rows: last_row, columns: last_col + 1 };
681
+
682
+ }
683
+ else if (col_key_map.length) {
684
+
685
+ const cells: {[index: number]: Array<BaseCellData & {row: number}>} = {};
686
+
687
+ // use columns
688
+ const new_data: NestedColumnData[] = [];
689
+
690
+ for (const element of data) {
691
+ const {column, ...remainder} = element;
692
+ if (!cells[element.column]) cells[element.column] = [];
693
+ cells[element.column].push(remainder);
694
+ }
695
+ for (const key of col_key_map) {
696
+ const column = Number(key);
697
+ new_data.push({ column, cells: cells[column] });
698
+ }
699
+ return { data: new_data, rows: last_row, columns: last_col + 1 };
700
+
701
+ }
702
+
703
+ }
704
+
705
+ return { data, rows: last_row + 1, columns: last_col + 1 };
706
+
707
+ }
708
+
709
+ public GetAll(transpose = false){
710
+ return this.GetRange({row: 0, column: 0}, {row: this.rows_ - 1, column: this.columns_ - 1}, transpose);
711
+ }
712
+
713
+ /** simply cannot make this work with overloads (prove me wrong) */
714
+ public Normalize2(from: ICellAddress, to: ICellAddress): {from: ICellAddress, to: ICellAddress} {
715
+
716
+ if (from.column === Infinity) {
717
+ from = { ...from, column: 0, };
718
+ }
719
+
720
+ if (from.row === Infinity) {
721
+ from = { ...from, row: 0, };
722
+ }
723
+
724
+ if (to.column === Infinity) {
725
+ to = { ...to, column: this.columns_ - 1};
726
+ }
727
+
728
+ if (to.row === Infinity) {
729
+ to = { ...to, row: this.rows_ - 1};
730
+ }
731
+
732
+ return {from, to};
733
+ }
734
+
735
+ /** simply cannot make this work with overloads (prove me wrong) */
736
+ public Normalize1(from: ICellAddress): ICellAddress {
737
+
738
+ if (from.column === Infinity) {
739
+ from = { ...from, column: 0, };
740
+ }
741
+
742
+ if (from.row === Infinity) {
743
+ from = { ...from, row: 0, };
744
+ }
745
+
746
+ return from;
747
+
748
+ }
749
+
750
+ /**
751
+ * get raw values (i.e. not calculated). anything outside of actual
752
+ * range will be undefined OR not populated.
753
+ *
754
+ * to match GetRange, we return a single value in the case of a single cell,
755
+ * or a matrix.
756
+ *
757
+ * NOTE that I'm not sure this is good behavior. if you're going to
758
+ * return a single value for one cell, you should return a vector for
759
+ * a single row OR a single column. alternatively, you should always
760
+ * return a matrix.
761
+ *
762
+ * @param from
763
+ * @param to
764
+ * @param transpose
765
+ */
766
+ public RawValue(from: ICellAddress, to: ICellAddress = from): CellValue | CellValue[][] | undefined {
767
+
768
+ ({from, to} = this.Normalize2(from, to));
769
+
770
+ if (from.row === to.row && from.column === to.column) {
771
+ if (this.data[from.row] && this.data[from.row][from.column]) {
772
+ return this.data[from.row][from.column].value;
773
+ }
774
+ return undefined;
775
+ }
776
+
777
+ const result: CellValue[][] = [];
778
+
779
+ // grab rows
780
+ const rows = this.data.slice(from.row, to.row + 1);
781
+
782
+ // now columns
783
+ const start = from.column;
784
+ const end = to.column + 1;
785
+
786
+ for (const source of rows) {
787
+ const target: CellValue[] = [];
788
+ for (let column = start, index = 0; column < end; column++, index++ ) {
789
+ const cell = source[column];
790
+ target.push(cell ? cell.value : undefined);
791
+ }
792
+ result.push(target);
793
+ }
794
+
795
+ return result;
796
+
797
+ }
798
+
799
+ /** gets range as values */
800
+ public GetRange(from: ICellAddress, to?: ICellAddress, transpose = false){
801
+
802
+ if (to) {
803
+ ({from, to} = this.Normalize2(from, to));
804
+ }
805
+ else {
806
+ from = this.Normalize1(from);
807
+ }
808
+
809
+ // console.info("getrange", from, to, transpose);
810
+
811
+ if (!to || from === to || (from.column === to.column && from.row === to.row )){
812
+ if (this.data[from.row] && this.data[from.row][from.column]){
813
+ return this.data[from.row][from.column].GetValue();
814
+ }
815
+ return undefined;
816
+ }
817
+
818
+ const value: CellValue[][] = [];
819
+
820
+ if (transpose){
821
+ for ( let c = from.column; c <= to.column; c++ ){
822
+ const column: CellValue[] = [];
823
+ for ( let r = from.row; r <= to.row; r++ ){
824
+ if (this.data[r] && this.data[r][c]) column.push(this.data[r][c].GetValue());
825
+ else column.push(undefined);
826
+ }
827
+ value.push(column);
828
+ }
829
+ }
830
+ else {
831
+ for ( let r = from.row; r <= to.row; r++ ){
832
+ const row: CellValue[] = [];
833
+ for ( let c = from.column; c <= to.column; c++ ){
834
+ if (this.data[r] && this.data[r][c]) row.push(this.data[r][c].GetValue());
835
+ else row.push(undefined);
836
+ }
837
+ value.push(row);
838
+ }
839
+ }
840
+
841
+ // console.info(value)
842
+ return value;
843
+
844
+ }
845
+
846
+ /* *
847
+ * updated version of GetRange that preserves errors, by calling
848
+ * the GetValue2 cell function.
849
+ * /
850
+ public GetRange2(from: ICellAddress, to?: ICellAddress, transpose = false) {
851
+
852
+ if (!to || from === to || (from.column === to.column && from.row === to.row )){
853
+ if (this.data[from.row] && this.data[from.row][from.column]){
854
+ return this.data[from.row][from.column].GetValue2();
855
+ }
856
+ return undefined;
857
+ }
858
+
859
+ const value = [];
860
+
861
+ if (transpose){
862
+ for ( let c = from.column; c <= to.column; c++ ){
863
+ const column = [];
864
+ for ( let r = from.row; r <= to.row; r++ ){
865
+ if (this.data[r] && this.data[r][c]) column.push(this.data[r][c].GetValue2());
866
+ else column.push(undefined);
867
+ }
868
+ value.push(column);
869
+ }
870
+ }
871
+ else {
872
+ for ( let r = from.row; r <= to.row; r++ ){
873
+ const row = [];
874
+ for ( let c = from.column; c <= to.column; c++ ){
875
+ if (this.data[r] && this.data[r][c]) row.push(this.data[r][c].GetValue2());
876
+ else row.push(undefined);
877
+ }
878
+ value.push(row);
879
+ }
880
+ }
881
+
882
+ return value;
883
+
884
+ }
885
+ */
886
+
887
+ public GetRange4(from: ICellAddress, to: ICellAddress = from, transpose = false): UnionValue {
888
+
889
+ ({from, to} = this.Normalize2(from, to));
890
+
891
+ if (from.row === to.row && from.column === to.column) {
892
+ if (this.data[from.row] && this.data[from.row][from.column]){
893
+ return this.data[from.row][from.column].GetValue4();
894
+ }
895
+ return { value: undefined, type: ValueType.undefined };
896
+ }
897
+
898
+ const value: UnionValue[][] = [];
899
+
900
+ if (transpose){
901
+ for ( let c = from.column; c <= to.column; c++ ){
902
+ const column: UnionValue[] = [];
903
+ for ( let r = from.row; r <= to.row; r++ ){
904
+ if (this.data[r] && this.data[r][c]) column.push(this.data[r][c].GetValue4());
905
+ else column.push({type: ValueType.undefined});
906
+ }
907
+ value.push(column);
908
+ }
909
+ }
910
+ else {
911
+ for ( let r = from.row; r <= to.row; r++ ){
912
+ const row: UnionValue[] = [];
913
+ for ( let c = from.column; c <= to.column; c++ ){
914
+ if (this.data[r] && this.data[r][c]) row.push(this.data[r][c].GetValue4());
915
+ else row.push({type: ValueType.undefined});
916
+ }
917
+ value.push(row);
918
+ }
919
+ }
920
+
921
+ return {type: ValueType.array, value};
922
+
923
+ }
924
+
925
+ /**
926
+ * apply function to address/area
927
+ */
928
+ public Apply(area: Area|ICellAddress, f: (cell: Cell, c?: number, r?: number) => void, create_missing_cells = false): void {
929
+
930
+ // allow single address
931
+ if (IsCellAddress(area)) {
932
+ area = new Area(area);
933
+ }
934
+
935
+ // why not just cap? (...)
936
+ if (area.entire_column || area.entire_row) {
937
+ throw new Error(`don't iterate infinite cells`);
938
+ }
939
+
940
+ // these are accessors so we don't want them in the loop
941
+ const start = area.start;
942
+ const end = area.end;
943
+
944
+ if (create_missing_cells){
945
+ for ( let r = start.row; r <= end.row; r++ ){
946
+ if (!this.data[r]) this.data[r] = [];
947
+ const row = this.data[r];
948
+ for ( let c = start.column; c <= end.column; c++ ){
949
+ if (!row[c]) row[c] = new Cell();
950
+ f(row[c], c, r);
951
+ }
952
+ }
953
+ }
954
+ else {
955
+ // we can loop over indexes that don't exist, just check for existence
956
+ for ( let r = start.row; r <= end.row; r++ ){
957
+ if (this.data[r]){
958
+ const row = this.data[r];
959
+ for ( let c = start.column; c <= end.column; c++ ){
960
+ if (row[c]) f(row[c], c, r);
961
+ }
962
+ }
963
+ }
964
+ }
965
+ }
966
+
967
+ /**
968
+ * set area. shortcut to reduce overhead. consolidates single value
969
+ * and array value methods, although the implementation is separate.
970
+ *
971
+ * watch out for typed arrays, which do not satisfy Array.isArray
972
+ *
973
+ * when would this function get a 1D typed array? can't figure that out.
974
+ * just drop for the time being.
975
+ *
976
+ */
977
+ public SetArea(area: Area, values: CellValue|CellValue[][]): void {
978
+
979
+ if (ArrayBuffer.isView(values)) {
980
+ throw new Error('ABIV');
981
+ }
982
+
983
+ if (Array.isArray(values)) { // || ArrayBuffer.isView(values)) {
984
+ for (let r = area.start.row, i = 0; r <= area.end.row; r++, i++) {
985
+ if (!this.data[r]) this.data[r] = [];
986
+ const row = this.data[r];
987
+ if (values[i]) {
988
+ for (let c = area.start.column, j = 0; c <= area.end.column; c++, j++) {
989
+ if (!row[c]) row[c] = new Cell();
990
+ row[c].Set(values[i][j]); // undefined should be implicit
991
+ }
992
+ }
993
+ }
994
+ }
995
+ else {
996
+ const value_type = GetValueType(values); // otherwise we'd just call it every time
997
+
998
+ for (let r = area.start.row; r <= area.end.row; r++) {
999
+ if (!this.data[r]) this.data[r] = [];
1000
+ const row = this.data[r];
1001
+ for (let c = area.start.column; c <= area.end.column; c++) {
1002
+ if (!row[c]) row[c] = new Cell();
1003
+ row[c].Set(values, value_type);
1004
+ }
1005
+ }
1006
+ }
1007
+
1008
+ this.rows_ = Math.max(this.rows_, area.end.row + 1);
1009
+ this.columns_ = Math.max(this.columns_, area.end.column + 1);
1010
+
1011
+ }
1012
+
1013
+ /**
1014
+ * iterates over all cells (using loops) and runs function per-cell.
1015
+ * FIXME: switch to indexing on empty indexes? (...)
1016
+ */
1017
+ public IterateAll(func: (cell: Cell) => void){
1018
+ /*
1019
+ const row_keys = Object.keys(this.data);
1020
+ for (const row of row_keys){
1021
+ const n_row = Number(row) || 0;
1022
+ const column_keys = Object.keys(this.data[n_row]);
1023
+ for (const column_key of column_keys){
1024
+ f(this.data[n_row][Number(column_key)]);
1025
+ }
1026
+ }
1027
+ */
1028
+ for (const row of this.data) {
1029
+ if (row) {
1030
+ for (const cell of row) {
1031
+ if (cell) {
1032
+ func(cell);
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ }
1039
+
1040
+ /** moved from sheet, so we can do it non-functional style (for perf) */
1041
+ public FlushCellStyles() {
1042
+ for (const row of this.data) {
1043
+ if (row) {
1044
+ for (const cell of row) {
1045
+ if (cell) {
1046
+ cell.FlushStyle();
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ /** moved from sheet, so we can do it non-functional style (for perf) */
1054
+ public FlushCachedValues() {
1055
+ for (const row of this.data) {
1056
+ if (row) {
1057
+ for (const cell of row) {
1058
+ if (cell) {
1059
+ cell.FlushCache();
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ }