@trebco/treb 23.6.5 → 25.0.0-rc1

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} +285 -269
  11. package/esbuild-custom-element.mjs +336 -0
  12. package/esbuild.js +305 -0
  13. package/package.json +43 -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 +1227 -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 +5362 -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 +320 -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,882 @@
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
+ //import * as JSZip from 'jszip';
23
+ import JSZip from 'jszip';
24
+
25
+ import { AnchoredChartDescription, ChartType, TableDescription, Workbook } from './workbook2';
26
+ import { Parser, ParseResult } from 'treb-parser';
27
+ import { is_range, RangeType, ShiftRange, InRange, AddressType, is_address, HyperlinkType } from './address-type';
28
+ import { ImportedSheetData, AnchoredAnnotation, CellParseResult, ValueType, AnnotationLayout, Corner as LayoutCorner, ICellAddress, DataValidation, ValidationType, IArea } from 'treb-base-types/src';
29
+ import { Sheet, VisibleState } from './workbook-sheet2';
30
+ import type { CellAnchor } from './drawing2/drawing2';
31
+ import { XMLUtils } from './xml-utils';
32
+
33
+ // import { one_hundred_pixels } from './constants';
34
+ import { ColumnWidthToPixels } from './column-width';
35
+
36
+ interface SharedFormula {
37
+ row: number;
38
+ column: number;
39
+ formula: string;
40
+ parse_result: ParseResult;
41
+ }
42
+
43
+ interface SharedFormulaMap { [index: string]: SharedFormula }
44
+
45
+ export class Importer {
46
+
47
+ // FIXME: need a way to share/pass parser flags
48
+ public parser = new Parser();
49
+
50
+ public workbook?: Workbook;
51
+
52
+ public archive?: JSZip;
53
+
54
+ public async Init(data: string | JSZip): Promise<void> {
55
+
56
+ if (typeof data === 'string') {
57
+ this.archive = await JSZip().loadAsync(data);
58
+ }
59
+ else {
60
+ this.archive = data;
61
+ }
62
+
63
+ if (this.archive) {
64
+ this.workbook = new Workbook(this.archive);
65
+ await this.workbook.Init();
66
+ }
67
+
68
+ }
69
+
70
+ /** FIXME: accessor */
71
+ public SheetCount(): number {
72
+ return this.workbook?.sheet_count || 0;
73
+ }
74
+
75
+ public ParseCell(
76
+ sheet: Sheet,
77
+ element: {
78
+ a$: {
79
+ r?: string;
80
+ t?: string;
81
+ s?: string;
82
+ };
83
+ v?: string|number; // is this never an object? (note: booleans are numbers in Excel)
84
+ f?: string|{
85
+ t$: string;
86
+ a$?: {
87
+ si?: string;
88
+ t?: string;
89
+ ref?: string;
90
+ },
91
+ };
92
+ }, // ElementTree.Element,
93
+ shared_formulae: SharedFormulaMap,
94
+ arrays: RangeType[],
95
+ merges: RangeType[],
96
+ links: HyperlinkType[],
97
+ validations: Array<{ address: ICellAddress, validation: DataValidation }>,
98
+ ): CellParseResult | undefined {
99
+
100
+ // must have, at minimum, an address (must be a single cell? FIXME)
101
+ const address_attr = element.a$?.r;
102
+ if (!address_attr) {
103
+ console.warn('cell missing address');
104
+ return undefined;
105
+ }
106
+
107
+ const address = sheet.TranslateAddress(address_attr);
108
+ if (is_range(address)) {
109
+ console.warn('cell has range address');
110
+ return undefined;
111
+ }
112
+
113
+ // console.info(element);
114
+
115
+ let value: undefined | number | boolean | string;
116
+ let type: ValueType = ValueType.undefined;
117
+
118
+ let calculated_value: undefined | number | boolean | string;
119
+ let calculated_type: ValueType = ValueType.undefined;
120
+
121
+ // QUESTIONS:
122
+ //
123
+ // 1. is v always a value, or can it be an object?
124
+ // if it is always a value, we can drop some of the
125
+ // casting stuff below
126
+ //
127
+ // 2. can we reframe f so it's always an object, moving the string
128
+ // inside -- to remove the simple case and remove all the testing?
129
+ //
130
+
131
+
132
+ // assuming we have single element per tag...
133
+
134
+ /*
135
+ const mapped: { [index: string]: ElementTree.Element } = {};
136
+ for (const child of element.getchildren()) {
137
+ if (child.tag) mapped[child.tag.toString()] = child;
138
+ }
139
+ */
140
+
141
+ // console.info(address, 'e', element, 'm', mapped);
142
+
143
+ if (element.a$?.t && element.a$.t === 's') {
144
+ type = ValueType.string;
145
+ if (typeof element.v !== undefined) {
146
+ const index = Number(element.v);
147
+ if (!isNaN(index) && sheet.shared_strings) {
148
+ value = sheet.shared_strings.Get(index) || '';
149
+ if (value[0] === '=') { value = '\'' + value; }
150
+ }
151
+ }
152
+ }
153
+ else {
154
+ if (typeof element.f !== 'undefined') {
155
+ type = ValueType.formula;
156
+
157
+ const formula = (typeof element.f === 'string' ? element.f : element.f.t$) || '';
158
+
159
+ if (formula) {
160
+
161
+ // doing it like this is sloppy (also does not work properly).
162
+ value = '=' + formula.replace(/^_xll\./g, '');
163
+
164
+ const parse_result = this.parser.Parse(formula); // l10n?
165
+ if (parse_result.expression) {
166
+ this.parser.Walk(parse_result.expression, (unit) => {
167
+ if (unit.type === 'call' && /^_xll\./.test(unit.name)) {
168
+ unit.name = unit.name.substr(5);
169
+ }
170
+ return true;
171
+ });
172
+ value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
173
+ }
174
+
175
+ if (typeof element.f !== 'string') {
176
+ if (element.f.a$?.t === 'shared' && element.f.a$.si) {
177
+ shared_formulae[element.f.a$.si] = {
178
+ row: address.row - 1,
179
+ column: address.col - 1,
180
+ formula: value,
181
+ parse_result: this.parser.Parse(value),
182
+ };
183
+ }
184
+ }
185
+
186
+ }
187
+ else if ((typeof element.f !== 'string') && element.f.a$?.t === 'shared' && element.f.a$.si) {
188
+ const f = shared_formulae[element.f.a$.si];
189
+ if (f) {
190
+ if (f.parse_result.expression) {
191
+ value = '=' + this.parser.Render(f.parse_result.expression, {
192
+ offset: {
193
+ rows: address.row - 1 - f.row,
194
+ columns: address.col - 1 - f.column,
195
+ },
196
+ missing: ''
197
+ });
198
+ }
199
+ else value = f.formula;
200
+ }
201
+ else {
202
+ // console.info("MISSING SHARED", mapped.f.attrib.si);
203
+ }
204
+ }
205
+
206
+ if (typeof element.f !== 'string' && element.f.a$?.t === 'array') {
207
+ const translated = sheet.TranslateAddress(element.f.a$.ref || '');
208
+ if (is_range(translated)) {
209
+ arrays.push(ShiftRange(translated, -1, -1));
210
+ }
211
+ }
212
+
213
+ if (typeof element.v !== 'undefined') {
214
+ const num = Number(element.v.toString());
215
+ if (!isNaN(num)) {
216
+ calculated_type = ValueType.number;
217
+ calculated_value = num;
218
+ }
219
+ else {
220
+ calculated_type = ValueType.string;
221
+ calculated_value = element.v.toString();
222
+ }
223
+ }
224
+
225
+ }
226
+ else if (typeof element.v !== 'undefined') {
227
+ const num = Number(element.v.toString());
228
+ if (!isNaN(num)) {
229
+ type = ValueType.number;
230
+ value = num;
231
+ }
232
+ else {
233
+ type = ValueType.string;
234
+ value = element.v.toString();
235
+ }
236
+ }
237
+ }
238
+
239
+ const shifted: AddressType = { row: address.row - 1, col: address.col - 1 };
240
+
241
+ // check if we are in an array. we're relying on the fact that
242
+ // the array head is the top-left, which I _think_ is universal,
243
+ // but perhaps we should check that... although at this point we have
244
+ // already added the array so we need to check for root
245
+
246
+ for (const array of arrays) {
247
+ if (InRange(array, shifted) && (shifted.row !== array.from.row || shifted.col !== array.from.col)) {
248
+ calculated_type = type;
249
+ calculated_value = value;
250
+ value = undefined;
251
+ type = ValueType.undefined;
252
+ }
253
+ }
254
+
255
+ const result: CellParseResult = {
256
+ row: shifted.row, column: shifted.col, value, type,
257
+ };
258
+
259
+ if (typeof calculated_value !== 'undefined') {
260
+ result.calculated_type = calculated_type;
261
+ result.calculated = calculated_value;
262
+ }
263
+
264
+ if (element.a$?.s) {
265
+ result.style_ref = Number(element.a$.s);
266
+ }
267
+
268
+ for (const link of links) {
269
+ if (link.address.row === address.row && link.address.col === address.col) {
270
+ result.hyperlink = link.reference;
271
+ // FIXME: pop?
272
+ }
273
+ }
274
+
275
+ for (const validation of validations) {
276
+ if (validation.address.row === shifted.row && validation.address.column === shifted.col) {
277
+ result.validation = validation.validation;
278
+ break;
279
+ }
280
+ }
281
+
282
+ for (const range of merges) {
283
+ if (InRange(range, shifted)) {
284
+ result.merge_area = {
285
+ start: {
286
+ row: range.from.row,
287
+ column: range.from.col,
288
+ }, end: {
289
+ row: range.to.row,
290
+ column: range.to.col,
291
+ },
292
+ };
293
+ }
294
+ }
295
+
296
+ for (const range of arrays) {
297
+ if (InRange(range, shifted)) {
298
+ result.area = {
299
+ start: {
300
+ row: range.from.row,
301
+ column: range.from.col,
302
+ }, end: {
303
+ row: range.to.row,
304
+ column: range.to.col,
305
+ },
306
+ };
307
+
308
+ }
309
+ }
310
+
311
+ return result;
312
+
313
+ }
314
+
315
+ public async GetSheet(index = 0): Promise<ImportedSheetData> {
316
+
317
+ if (!this.workbook) {
318
+ throw new Error('missing workbook');
319
+ }
320
+
321
+ const sheet = this.workbook.sheets[index];
322
+ // console.info(sheet.sheet_data);
323
+
324
+ // console.info(sheet.options.name);
325
+
326
+ // we want a sparse array
327
+
328
+ const data: CellParseResult[] = [];
329
+ const shared_formulae: {[index: string]: SharedFormula} = {};
330
+ const arrays: RangeType[] = [];
331
+ const merges: RangeType[] = [];
332
+ const links: HyperlinkType[] = [];
333
+ const validations: Array<{
334
+ address: ICellAddress,
335
+ validation: DataValidation,
336
+ }> = [];
337
+ const annotations: AnchoredAnnotation[] = [];
338
+
339
+ const FindAll = XMLUtils.FindAll.bind(XMLUtils, sheet.sheet_data);
340
+
341
+ // merges
342
+
343
+ const merge_cells = FindAll('worksheet/mergeCells/mergeCell');
344
+
345
+ for (const element of merge_cells) {
346
+ if (element.a$.ref) {
347
+ const merge = sheet.TranslateAddress(element.a$.ref);
348
+ if (is_range(merge)) {
349
+ merges.push(ShiftRange(merge, -1, -1));
350
+ }
351
+ }
352
+ }
353
+
354
+ // validation
355
+
356
+ const validation_entries = FindAll('worksheet/dataValidations/dataValidation');
357
+ for (const entry of validation_entries) {
358
+ const type = entry.a$?.type;
359
+ const ref = entry.a$?.sqref;
360
+ const formula = entry.formula1;
361
+
362
+ if (ref && formula && type === 'list') {
363
+ let address: ICellAddress|undefined;
364
+ let validation: DataValidation|undefined;
365
+ let parse_result = this.parser.Parse(ref);
366
+
367
+ // apparently these are encoded as ranges for merged cells...
368
+
369
+ if (parse_result.expression) {
370
+ if (parse_result.expression.type === 'address') {
371
+ address = parse_result.expression;
372
+ }
373
+ else if (parse_result.expression.type === 'range') {
374
+ address = parse_result.expression.start;
375
+ }
376
+ }
377
+
378
+ parse_result = this.parser.Parse(formula);
379
+ if (parse_result.expression) {
380
+ if (parse_result.expression.type === 'range') {
381
+ validation = {
382
+ type: ValidationType.Range,
383
+ area: parse_result.expression,
384
+ };
385
+ }
386
+ else if (parse_result.expression.type === 'literal') {
387
+ validation = {
388
+ type: ValidationType.List,
389
+ list: parse_result.expression.value.toString().split(/,/).map(value => {
390
+ const tmp = this.parser.Parse(value);
391
+
392
+ // if type is "group", that means we saw some spaces. this
393
+ // is (probably) an unquoted string literal. for the time
394
+ // being let's assume that. need a counterexample.
395
+
396
+ if (tmp.expression?.type === 'group' && /\s/.test(value)) {
397
+ return value;
398
+ }
399
+ if (tmp.expression?.type === 'literal') {
400
+ return tmp.expression.value;
401
+ }
402
+ if (tmp.expression?.type === 'identifier') {
403
+ return tmp.expression.name;
404
+ }
405
+ return undefined;
406
+ }),
407
+ };
408
+ }
409
+ }
410
+
411
+ if (address && validation) {
412
+ validations.push({address, validation});
413
+ }
414
+
415
+ }
416
+
417
+ }
418
+
419
+ // links
420
+
421
+ const hyperlinks = FindAll('worksheet/hyperlinks/hyperlink');
422
+
423
+ for (const child of hyperlinks) {
424
+
425
+ let address = sheet.TranslateAddress(child.a$?.ref || '');
426
+ if (is_range(address)) {
427
+ address = address.from;
428
+ }
429
+
430
+ let text = '';
431
+ let reference = '';
432
+
433
+ if (child.a$ && child.a$['r:id']) {
434
+
435
+ text = 'remote link';
436
+ const relationship = sheet.rels[child.a$['r:id']];
437
+ if (relationship) {
438
+ reference = relationship.target || '';
439
+ }
440
+
441
+ }
442
+ else {
443
+ reference = child.__location || '';
444
+ text = child.__display || '';
445
+ }
446
+
447
+ links.push({ address, reference, text });
448
+ }
449
+
450
+ // base
451
+
452
+ let default_row_height = 21;
453
+ let default_column_width = 100; // ?
454
+
455
+ const sheet_format = sheet.sheet_data.worksheet?.sheetFormatPr;
456
+ if (sheet_format) {
457
+ if (sheet_format.a$?.defaultColWidth) {
458
+ const width = Number(sheet_format.a$.defaultColWidth);
459
+ if (!isNaN(width)) {
460
+ // default_column_width = Math.round(width / one_hundred_pixels * 100);
461
+ default_column_width = ColumnWidthToPixels(width);
462
+ }
463
+ }
464
+ if (sheet_format.a$?.defaultRowHeight) {
465
+ const height = Number(sheet_format.a$.defaultRowHeight);
466
+ if (!isNaN(height)) {
467
+ default_row_height = Math.round(height * 4 / 3); // ??
468
+ }
469
+ }
470
+ }
471
+
472
+ // data (and row heights)
473
+
474
+ const row_heights: number[] = [];
475
+
476
+ const rows = FindAll('worksheet/sheetData/row');
477
+
478
+ for (const row of rows) {
479
+ const row_index = row.a$?.r ? Number(row.a$.r) : 1;
480
+
481
+ let height = default_row_height;
482
+ if (row.a$?.ht) {
483
+ const num = Number(row.a$.ht);
484
+ if (!isNaN(num)) {
485
+ height = Math.round(num * 4 / 3); // seems to be the excel unit -> pixel ratio
486
+ }
487
+ }
488
+
489
+ // if there's a height which is not === default height, but
490
+ // the customHeight attribute is not set, then it's been auto-sized.
491
+ // not sure that's something we need to care about necessarily...
492
+
493
+ if (height !== default_row_height) {
494
+ row_heights[row_index - 1] = height;
495
+ }
496
+
497
+ /*
498
+ if (row.a$?.ht && row.a$?.customHeight) {
499
+ const num = Number(row.a$.ht);
500
+ if (!isNaN(num)) {
501
+ row_heights[row_index - 1] = Math.round(num * 4 / 3); // seems to be the excel unit -> pixel ratio
502
+ }
503
+ }
504
+ */
505
+
506
+ let cells = row.c || [];
507
+ if (!Array.isArray(cells)) { cells = [cells]; }
508
+
509
+ for (const element of cells) {
510
+ const cell = this.ParseCell(sheet, element, shared_formulae, arrays, merges, links, validations);
511
+ if (cell) {
512
+ data.push(cell);
513
+ }
514
+ }
515
+ }
516
+
517
+ const column_styles: number[] = [];
518
+ let default_column_style = -1;
519
+ const column_widths: number[] = [];
520
+
521
+ const columns = FindAll('worksheet/cols/col');
522
+
523
+ for (const child of columns) {
524
+
525
+ const min = Number(child.a$?.min);
526
+ const max = Number(child.a$?.max);
527
+
528
+ if (child.a$?.style) {
529
+
530
+ const style = Number(child.a$.style);
531
+
532
+ if (!isNaN(min) && !isNaN(max) && !isNaN(style)) {
533
+
534
+ // this is not the way to do this? for the time being
535
+ // it's OK because style doesn't need to extend past
536
+ // extent (but width does)
537
+
538
+ if (sheet.extent && max >= sheet.extent.to.col || max - min > 100) { // just spitballing on that last one
539
+ default_column_style = style;
540
+ }
541
+ else {
542
+ for (let i = min; i <= max; i++) {
543
+ column_styles[i] = style;
544
+ }
545
+ }
546
+
547
+ }
548
+
549
+ }
550
+ if (child.a$?.customWidth) {
551
+
552
+ let width = Number(child.a$.width);
553
+
554
+ if (!isNaN(min) && !isNaN(max) && !isNaN(width)) {
555
+
556
+ if (max === 16384) {
557
+
558
+ // ...
559
+ }
560
+ else {
561
+
562
+ // otherwise it will set -> 16384
563
+ // if (sheet.extent) {
564
+ // max = Math.min(max, sheet.extent.to.col + 1);
565
+ // }
566
+
567
+ // width = Math.round(width / one_hundred_pixels * 100);
568
+ width = ColumnWidthToPixels(width);
569
+
570
+ for (let i = min; i <= max; i++) column_widths[i - 1] = width;
571
+ }
572
+ }
573
+
574
+ }
575
+ }
576
+
577
+ // --- import tables -------------------------------------------------------
578
+
579
+ const table_references = FindAll('worksheet/tableParts/tablePart')
580
+ for (const child of table_references) {
581
+ const rel = child.a$ ? child.a$['r:id'] : undefined;
582
+ if (rel) {
583
+ let reference = '';
584
+
585
+ const relationship = sheet.rels[rel];
586
+ if (relationship) {
587
+ reference = relationship.target || '';
588
+ const description = await this.workbook.ReadTable(reference);
589
+ if (description) {
590
+
591
+ // console.info({description});
592
+
593
+ const ref = sheet.TranslateAddress(description.ref);
594
+ const area: IArea = is_address(ref) ? {
595
+ start: { row: ref.row - 1, column: ref.col - 1},
596
+ end: { row: ref.row - 1, column: ref.col - 1},
597
+ } : {
598
+ start: { row: ref.from.row - 1, column: ref.from.col - 1},
599
+ end: { row: ref.to.row - 1, column: ref.to.col - 1},
600
+ };
601
+
602
+ for (const cell of data) {
603
+ if (cell.row === area.start.row && cell.column === area.start.column) {
604
+ cell.table = {
605
+ area,
606
+ name: description.name,
607
+ totals_row: (!!description.totals_row_count),
608
+
609
+ // NOTE: column headers are added on first load, we don't
610
+ // read them from here. not super efficient but we do it
611
+ // that way for regular loads as well
612
+
613
+ };
614
+ break;
615
+ }
616
+ }
617
+
618
+ }
619
+
620
+ }
621
+ }
622
+ }
623
+
624
+ // --- import drawings -----------------------------------------------------
625
+
626
+ // wip...
627
+
628
+ const drawings = FindAll('worksheet/drawing');
629
+ const chart_descriptors: AnchoredChartDescription[] = [];
630
+
631
+ for (const child of drawings) {
632
+ const rel = child.a$ ? child.a$['r:id'] : undefined;
633
+ if (rel) {
634
+
635
+ let reference = '';
636
+
637
+ const relationship = sheet.rels[rel];
638
+ if (relationship) {
639
+ reference = relationship.target || '';
640
+ }
641
+
642
+ if (reference) {
643
+ const drawing = await this.workbook.ReadDrawing(reference);
644
+ if (drawing && drawing.length) {
645
+ chart_descriptors.push(...drawing);
646
+ }
647
+ }
648
+
649
+ }
650
+ }
651
+
652
+ const AnchorToCorner = (anchor: CellAnchor): LayoutCorner => {
653
+
654
+ const result: LayoutCorner = {
655
+ address: {
656
+ row: anchor.row,
657
+ column: anchor.column,
658
+ },
659
+ offset: {
660
+ x: 0, // anchor.column_offset || 0, // FIXME: scale
661
+ y: 0, // anchor.row_offset || 0, // FIXME: scale
662
+ },
663
+ };
664
+
665
+ if (anchor.row_offset) {
666
+ let row_height = row_heights[anchor.row];
667
+ if (row_height === undefined) {
668
+ row_height = default_row_height; // FIXME
669
+ }
670
+ result.offset.y = (anchor.row_offset / 9525) / row_height;
671
+ }
672
+
673
+ if (anchor.column_offset) {
674
+ let column_width = column_widths[anchor.column];
675
+ if (column_width === undefined) {
676
+ column_width = default_column_width;
677
+ }
678
+ result.offset.x = (anchor.column_offset / 9525) / column_width;
679
+ }
680
+
681
+ return result;
682
+
683
+ };
684
+
685
+ for (const descriptor of chart_descriptors) {
686
+ if (descriptor && descriptor.chart) {
687
+
688
+ // convert the anchor to the annotation type
689
+
690
+ const layout: AnnotationLayout = {
691
+ tl: AnchorToCorner(descriptor.anchor.from),
692
+ br: AnchorToCorner(descriptor.anchor.to),
693
+ };
694
+
695
+ let type: string|undefined;
696
+ const args: Array<string|undefined> = [];
697
+ let func = '';
698
+ const series = descriptor.chart?.series;
699
+
700
+ switch(descriptor.chart.type) {
701
+ case ChartType.Scatter:
702
+ type = 'treb-chart';
703
+ func = 'Scatter.Line';
704
+ if (series && series.length) {
705
+ args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})` || '').join(', ')})`;
706
+ }
707
+ args[1] = descriptor.chart.title;
708
+ break;
709
+
710
+ case ChartType.Donut:
711
+ case ChartType.Pie:
712
+
713
+ func = descriptor.chart.type === ChartType.Donut ? 'Donut.Chart' : 'Pie.Chart';
714
+ type = 'treb-chart';
715
+ if (series && series[0]) {
716
+ args[0] = series[0].values;
717
+ args[1] = series[0]?.categories || '';
718
+ }
719
+ args[2] = descriptor.chart.title;
720
+ break;
721
+
722
+ case ChartType.Bar:
723
+ case ChartType.Column:
724
+ case ChartType.Line:
725
+
726
+ args[2] = descriptor.chart.title;
727
+ type = 'treb-chart';
728
+ switch (descriptor.chart.type) {
729
+ case ChartType.Bar:
730
+ func = 'Bar.Chart';
731
+ break;
732
+ case ChartType.Column:
733
+ func = 'Column.Chart';
734
+ break;
735
+ default:
736
+ func = 'Line.Chart';
737
+ }
738
+
739
+ if (series) {
740
+ if (series.length > 1) {
741
+ args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})` || '').join(', ')})`;
742
+ }
743
+ else if (series.length === 1) {
744
+ if (series[0].title) {
745
+ args[0] = `Series(${series[0].title || ''},,${series[0].values||''})`;
746
+ }
747
+ else {
748
+ args[0] = series[0].values;
749
+ }
750
+ }
751
+ args[1] = series[0]?.categories || '';
752
+ }
753
+
754
+ break;
755
+ }
756
+
757
+ const formula = `=${func}(${args.join(', ')})`;
758
+ // console.info('f', formula);
759
+
760
+ if (type && formula) {
761
+ annotations.push({
762
+ layout,
763
+ type,
764
+ formula,
765
+ });
766
+ }
767
+
768
+ }
769
+ }
770
+
771
+ // /wip
772
+
773
+ const ext = FindAll('worksheet/extLst/ext');
774
+ for (const entry of ext) {
775
+
776
+ // find the prefix
777
+ let prefix = '';
778
+ for (const key of Object.keys(entry?.a$ || {})) {
779
+ const match = key.match(/^xmlns:(.*)$/);
780
+ if (match) {
781
+ prefix = match[1];
782
+ break;
783
+ }
784
+ }
785
+
786
+ const groups = XMLUtils.FindAll(entry, `${prefix}:sparklineGroups/${prefix}:sparklineGroup`);
787
+ for (const group of groups) {
788
+ let func = 'Sparkline.line';
789
+ let reference = '';
790
+ let source = '';
791
+
792
+ if (group.a$?.type === 'column') {
793
+ func = 'Sparkline.column';
794
+ }
795
+
796
+ // TODO: gap optional
797
+ // TODO: colors
798
+
799
+ const sparklines = XMLUtils.FindAll(group, `${prefix}:sparklines/${prefix}:sparkline`);
800
+ for (const sparkline of sparklines) {
801
+ for (const key of Object.keys(sparkline)) {
802
+ if (/:f$/.test(key)) {
803
+ source = sparkline[key];
804
+ }
805
+ else if (/:sqref$/.test(key)) {
806
+ reference = sparkline[key];
807
+ }
808
+ }
809
+ }
810
+
811
+ //
812
+
813
+ if (source && reference) {
814
+ const constructed_function = `=${func}(${source})`;
815
+
816
+ // 1: merges
817
+ // 2: maybe already in the list? need to filter
818
+
819
+ const translated = sheet.TranslateAddress(reference);
820
+
821
+ if (is_address(translated)) {
822
+
823
+ const result = {
824
+ row: translated.row - 1,
825
+ column: translated.col - 1,
826
+ value: constructed_function,
827
+ type: ValueType.formula,
828
+ };
829
+
830
+ let matched = false;
831
+
832
+ for (const element of data) {
833
+ if (element.row === result.row && element.column === result.column) {
834
+ matched = true;
835
+ element.type = ValueType.formula;
836
+ element.value = constructed_function;
837
+ break;
838
+ }
839
+ }
840
+
841
+ if (!matched) {
842
+ data.push(result);
843
+ }
844
+
845
+ }
846
+ }
847
+
848
+ //
849
+
850
+ }
851
+
852
+ }
853
+
854
+ const result: ImportedSheetData = {
855
+ name: sheet.options.name,
856
+ cells: data,
857
+ default_column_width,
858
+ column_widths,
859
+ row_heights,
860
+ annotations,
861
+ styles: this.workbook?.style_cache?.CellXfToStyles() || [],
862
+ };
863
+
864
+ if (sheet.visible_state === VisibleState.hidden || sheet.visible_state === VisibleState.very_hidden) {
865
+ result.hidden = true;
866
+ }
867
+
868
+ if (default_column_style >= 0) {
869
+ result.sheet_style = default_column_style;
870
+ }
871
+
872
+ if (column_styles.length) {
873
+ result.column_styles = column_styles;
874
+ }
875
+
876
+ return result;
877
+
878
+ }
879
+
880
+
881
+
882
+ }