@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.
- package/.eslintignore +8 -0
- package/.eslintrc.js +164 -0
- package/README-shadow-DOM.md +88 -0
- package/README.md +37 -130
- package/api-config.json +29 -0
- package/api-generator/api-generator-types.ts +82 -0
- package/api-generator/api-generator.ts +1172 -0
- package/api-generator/package.json +3 -0
- package/build/treb-spreadsheet.mjs +14 -0
- package/{treb.d.ts → build/treb.d.ts} +285 -269
- package/esbuild-custom-element.mjs +336 -0
- package/esbuild.js +305 -0
- package/package.json +43 -14
- package/treb-base-types/package.json +5 -0
- package/treb-base-types/src/api_types.ts +36 -0
- package/treb-base-types/src/area.ts +583 -0
- package/treb-base-types/src/basic_types.ts +45 -0
- package/treb-base-types/src/cell.ts +612 -0
- package/treb-base-types/src/cells.ts +1066 -0
- package/treb-base-types/src/color.ts +124 -0
- package/treb-base-types/src/import.ts +71 -0
- package/treb-base-types/src/index-standalone.ts +29 -0
- package/treb-base-types/src/index.ts +42 -0
- package/treb-base-types/src/layout.ts +47 -0
- package/treb-base-types/src/localization.ts +187 -0
- package/treb-base-types/src/rectangle.ts +145 -0
- package/treb-base-types/src/render_text.ts +72 -0
- package/treb-base-types/src/style.ts +545 -0
- package/treb-base-types/src/table.ts +109 -0
- package/treb-base-types/src/text_part.ts +54 -0
- package/treb-base-types/src/theme.ts +608 -0
- package/treb-base-types/src/union.ts +152 -0
- package/treb-base-types/src/value-type.ts +164 -0
- package/treb-base-types/style/resizable.css +59 -0
- package/treb-calculator/modern.tsconfig.json +11 -0
- package/treb-calculator/package.json +5 -0
- package/treb-calculator/src/calculator.ts +2546 -0
- package/treb-calculator/src/complex-math.ts +558 -0
- package/treb-calculator/src/dag/array-vertex.ts +198 -0
- package/treb-calculator/src/dag/graph.ts +951 -0
- package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
- package/treb-calculator/src/dag/vertex.ts +352 -0
- package/treb-calculator/src/descriptors.ts +162 -0
- package/treb-calculator/src/expression-calculator.ts +1069 -0
- package/treb-calculator/src/function-error.ts +103 -0
- package/treb-calculator/src/function-library.ts +103 -0
- package/treb-calculator/src/functions/base-functions.ts +1214 -0
- package/treb-calculator/src/functions/checkbox.ts +164 -0
- package/treb-calculator/src/functions/complex-functions.ts +253 -0
- package/treb-calculator/src/functions/finance-functions.ts +399 -0
- package/treb-calculator/src/functions/information-functions.ts +102 -0
- package/treb-calculator/src/functions/matrix-functions.ts +182 -0
- package/treb-calculator/src/functions/sparkline.ts +335 -0
- package/treb-calculator/src/functions/statistics-functions.ts +350 -0
- package/treb-calculator/src/functions/text-functions.ts +298 -0
- package/treb-calculator/src/index.ts +27 -0
- package/treb-calculator/src/notifier-types.ts +59 -0
- package/treb-calculator/src/primitives.ts +428 -0
- package/treb-calculator/src/utilities.ts +305 -0
- package/treb-charts/package.json +5 -0
- package/treb-charts/src/chart-functions.ts +156 -0
- package/treb-charts/src/chart-types.ts +230 -0
- package/treb-charts/src/chart.ts +1288 -0
- package/treb-charts/src/index.ts +24 -0
- package/treb-charts/src/main.ts +37 -0
- package/treb-charts/src/rectangle.ts +52 -0
- package/treb-charts/src/renderer.ts +1841 -0
- package/treb-charts/src/util.ts +122 -0
- package/treb-charts/style/charts.scss +221 -0
- package/treb-charts/style/old-charts.scss +250 -0
- package/treb-embed/markup/layout.html +137 -0
- package/treb-embed/markup/toolbar.html +175 -0
- package/treb-embed/modern.tsconfig.json +25 -0
- package/treb-embed/src/custom-element/content-types.d.ts +18 -0
- package/treb-embed/src/custom-element/global.d.ts +11 -0
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1227 -0
- package/treb-embed/src/custom-element/treb-global.ts +44 -0
- package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
- package/treb-embed/src/embedded-spreadsheet.ts +5362 -0
- package/treb-embed/src/index.ts +16 -0
- package/treb-embed/src/language-model.ts +41 -0
- package/treb-embed/src/options.ts +320 -0
- package/treb-embed/src/progress-dialog.ts +228 -0
- package/treb-embed/src/selection-state.ts +16 -0
- package/treb-embed/src/spinner.ts +42 -0
- package/treb-embed/src/toolbar-message.ts +96 -0
- package/treb-embed/src/types.ts +167 -0
- package/treb-embed/style/autocomplete.scss +103 -0
- package/treb-embed/style/dark-theme.scss +114 -0
- package/treb-embed/style/defaults.scss +36 -0
- package/treb-embed/style/dialog.scss +181 -0
- package/treb-embed/style/dropdown-select.scss +101 -0
- package/treb-embed/style/formula-bar.scss +193 -0
- package/treb-embed/style/grid.scss +374 -0
- package/treb-embed/style/layout.scss +424 -0
- package/treb-embed/style/mouse-mask.scss +67 -0
- package/treb-embed/style/note.scss +92 -0
- package/treb-embed/style/overlay-editor.scss +102 -0
- package/treb-embed/style/spinner.scss +92 -0
- package/treb-embed/style/tab-bar.scss +228 -0
- package/treb-embed/style/table.scss +80 -0
- package/treb-embed/style/theme-defaults.scss +444 -0
- package/treb-embed/style/toolbar.scss +416 -0
- package/treb-embed/style/tooltip.scss +68 -0
- package/treb-embed/style/treb-icons.scss +130 -0
- package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
- package/treb-embed/style/z-index.scss +43 -0
- package/treb-export/docs/charts.md +68 -0
- package/treb-export/modern.tsconfig.json +19 -0
- package/treb-export/package.json +4 -0
- package/treb-export/src/address-type.ts +77 -0
- package/treb-export/src/base-template.ts +22 -0
- package/treb-export/src/column-width.ts +85 -0
- package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
- package/treb-export/src/drawing2/chart2.ts +282 -0
- package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
- package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
- package/treb-export/src/drawing2/drawing2.ts +355 -0
- package/treb-export/src/drawing2/embedded-image.ts +71 -0
- package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
- package/treb-export/src/export-worker/export-worker.ts +99 -0
- package/treb-export/src/export-worker/index-modern.ts +22 -0
- package/treb-export/src/export2.ts +2204 -0
- package/treb-export/src/import2.ts +882 -0
- package/treb-export/src/relationship.ts +36 -0
- package/treb-export/src/shared-strings2.ts +128 -0
- package/treb-export/src/template-2.ts +22 -0
- package/treb-export/src/unescape_xml.ts +47 -0
- package/treb-export/src/workbook-sheet2.ts +182 -0
- package/treb-export/src/workbook-style2.ts +1285 -0
- package/treb-export/src/workbook-theme2.ts +88 -0
- package/treb-export/src/workbook2.ts +491 -0
- package/treb-export/src/xml-utils.ts +201 -0
- package/treb-export/template/base/[Content_Types].xml +2 -0
- package/treb-export/template/base/_rels/.rels +2 -0
- package/treb-export/template/base/docProps/app.xml +2 -0
- package/treb-export/template/base/docProps/core.xml +12 -0
- package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
- package/treb-export/template/base/xl/sharedStrings.xml +2 -0
- package/treb-export/template/base/xl/styles.xml +2 -0
- package/treb-export/template/base/xl/theme/theme1.xml +2 -0
- package/treb-export/template/base/xl/workbook.xml +2 -0
- package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
- package/treb-export/template/base.xlsx +0 -0
- package/treb-format/package.json +8 -0
- package/treb-format/src/format.test.ts +213 -0
- package/treb-format/src/format.ts +942 -0
- package/treb-format/src/format_cache.ts +199 -0
- package/treb-format/src/format_parser.ts +723 -0
- package/treb-format/src/index.ts +25 -0
- package/treb-format/src/number_format_section.ts +100 -0
- package/treb-format/src/value_parser.ts +337 -0
- package/treb-grid/package.json +5 -0
- package/treb-grid/src/editors/autocomplete.ts +394 -0
- package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
- package/treb-grid/src/editors/formula_bar.ts +473 -0
- package/treb-grid/src/editors/formula_editor_base.ts +910 -0
- package/treb-grid/src/editors/overlay_editor.ts +511 -0
- package/treb-grid/src/index.ts +37 -0
- package/treb-grid/src/layout/base_layout.ts +2618 -0
- package/treb-grid/src/layout/grid_layout.ts +299 -0
- package/treb-grid/src/layout/rectangle_cache.ts +86 -0
- package/treb-grid/src/render/selection-renderer.ts +414 -0
- package/treb-grid/src/render/svg_header_overlay.ts +93 -0
- package/treb-grid/src/render/svg_selection_block.ts +187 -0
- package/treb-grid/src/render/tile_renderer.ts +2122 -0
- package/treb-grid/src/types/annotation.ts +216 -0
- package/treb-grid/src/types/border_constants.ts +34 -0
- package/treb-grid/src/types/clipboard_data.ts +31 -0
- package/treb-grid/src/types/data_model.ts +334 -0
- package/treb-grid/src/types/drag_mask.ts +81 -0
- package/treb-grid/src/types/grid.ts +7743 -0
- package/treb-grid/src/types/grid_base.ts +3644 -0
- package/treb-grid/src/types/grid_command.ts +470 -0
- package/treb-grid/src/types/grid_events.ts +124 -0
- package/treb-grid/src/types/grid_options.ts +97 -0
- package/treb-grid/src/types/grid_selection.ts +60 -0
- package/treb-grid/src/types/named_range.ts +369 -0
- package/treb-grid/src/types/scale-control.ts +202 -0
- package/treb-grid/src/types/serialize_options.ts +72 -0
- package/treb-grid/src/types/set_range_options.ts +52 -0
- package/treb-grid/src/types/sheet.ts +3099 -0
- package/treb-grid/src/types/sheet_types.ts +95 -0
- package/treb-grid/src/types/tab_bar.ts +464 -0
- package/treb-grid/src/types/tile.ts +59 -0
- package/treb-grid/src/types/update_flags.ts +75 -0
- package/treb-grid/src/util/dom_utilities.ts +44 -0
- package/treb-grid/src/util/fontmetrics2.ts +179 -0
- package/treb-grid/src/util/ua.ts +104 -0
- package/treb-logo.svg +18 -0
- package/treb-parser/package.json +5 -0
- package/treb-parser/src/csv-parser.ts +122 -0
- package/treb-parser/src/index.ts +25 -0
- package/treb-parser/src/md-parser.ts +526 -0
- package/treb-parser/src/parser-types.ts +397 -0
- package/treb-parser/src/parser.test.ts +298 -0
- package/treb-parser/src/parser.ts +2673 -0
- package/treb-utils/package.json +5 -0
- package/treb-utils/src/dispatch.ts +57 -0
- package/treb-utils/src/event_source.ts +147 -0
- package/treb-utils/src/ievent_source.ts +33 -0
- package/treb-utils/src/index.ts +31 -0
- package/treb-utils/src/measurement.ts +174 -0
- package/treb-utils/src/resizable.ts +160 -0
- package/treb-utils/src/scale.ts +137 -0
- package/treb-utils/src/serialize_html.ts +124 -0
- package/treb-utils/src/template.ts +70 -0
- package/treb-utils/src/validate_uri.ts +61 -0
- package/tsconfig.json +10 -0
- package/tsproject.json +30 -0
- package/util/license-plugin-esbuild.js +86 -0
- package/util/list-css-vars.sh +46 -0
- package/README-esm.md +0 -37
- package/treb-bundle.css +0 -2
- 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
|
+
}
|