@trebco/treb 23.6.2 → 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} +293 -299
- 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,2204 @@
|
|
|
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
|
+
* rewrite of export. we'll still use a template, but do more direct
|
|
24
|
+
* writing and less DOM manipulation. this should be cleaner in the long
|
|
25
|
+
* run, but it will take a bit more work.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import JSZip from 'jszip';
|
|
29
|
+
|
|
30
|
+
import { PixelsToColumnWidth } from './column-width';
|
|
31
|
+
|
|
32
|
+
const XMLDeclaration = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n`;
|
|
33
|
+
|
|
34
|
+
import { template } from './template-2';
|
|
35
|
+
import type { SerializedSheet } from 'treb-grid';
|
|
36
|
+
|
|
37
|
+
import { IArea, Area, ICellAddress, Cells, ValueType, CellValue, Style, DataValidation, ValidationType,
|
|
38
|
+
AnnotationLayout, Corner as LayoutCorner, ICellAddress2, Table, Cell } from 'treb-base-types';
|
|
39
|
+
|
|
40
|
+
// import * as xmlparser from 'fast-xml-parser';
|
|
41
|
+
import { XMLParser, XmlBuilderOptions, XMLBuilder } from 'fast-xml-parser';
|
|
42
|
+
|
|
43
|
+
import { SharedStrings } from './shared-strings2';
|
|
44
|
+
import { StyleCache, XlColor, BorderEdge } from './workbook-style2';
|
|
45
|
+
import { Theme } from './workbook-theme2';
|
|
46
|
+
|
|
47
|
+
import { RelationshipMap, AddRel } from './relationship';
|
|
48
|
+
import { XMLOptions2 } from './xml-utils';
|
|
49
|
+
|
|
50
|
+
import { Parser, UnitAddress, UnitRange, ExpressionUnit, IllegalSheetNameRegex, QuotedSheetNameRegex } from 'treb-parser';
|
|
51
|
+
|
|
52
|
+
// FIXME: move
|
|
53
|
+
import { Chart, ChartOptions } from './drawing2/chart2';
|
|
54
|
+
import type { ImageOptions } from './drawing2/embedded-image';
|
|
55
|
+
import { Drawing, TwoCellAnchor } from './drawing2/drawing2';
|
|
56
|
+
import type { TableDescription, TableFooterType } from './workbook2';
|
|
57
|
+
|
|
58
|
+
export class Exporter {
|
|
59
|
+
|
|
60
|
+
public zip?: JSZip;
|
|
61
|
+
|
|
62
|
+
public xmloptions: Partial<XmlBuilderOptions> = {
|
|
63
|
+
format: true,
|
|
64
|
+
attributesGroupName: 'a$',
|
|
65
|
+
textNodeName: 't$',
|
|
66
|
+
ignoreAttributes: false,
|
|
67
|
+
suppressEmptyNode: true,
|
|
68
|
+
|
|
69
|
+
// OK so now I am turning this off altogether. not sure why we
|
|
70
|
+
// were using it in the first place -- which is a problem, since
|
|
71
|
+
// there's probably something I don't know.
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
tagValueProcessor: (name: string, a: string) => {
|
|
75
|
+
|
|
76
|
+
// we were including unsafe symbols here, but that was
|
|
77
|
+
// resulting in double-encoding. not sure why this is
|
|
78
|
+
// here at all, unless we need it for unicode? in any
|
|
79
|
+
// event (atm) allowing unsafe symbols is sufficient
|
|
80
|
+
|
|
81
|
+
return a; // ?
|
|
82
|
+
|
|
83
|
+
return (typeof a === 'string') ? he.encode(a, { useNamedReferences: true, allowUnsafeSymbols: true }) : a;
|
|
84
|
+
},
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
// there's a "isAttributeValue" for decode, but no option for encode?
|
|
88
|
+
// we only want to encode ' and "
|
|
89
|
+
|
|
90
|
+
// attrValueProcessor: a => (typeof a === 'string') ? he.encode(a, { useNamedReferences: true }) : a,
|
|
91
|
+
|
|
92
|
+
// why is this double-encoding? is there arlready implicit encoding? (...)
|
|
93
|
+
// there must have been a reason we used it in the first place... but I don't know what that was.
|
|
94
|
+
// do we need to encode apostrophes?
|
|
95
|
+
|
|
96
|
+
// attributeValueProcessor: (name: string, a: string) => (typeof a === 'string') ?
|
|
97
|
+
// a.replace(/"/g, '"').replace(/'/g, ''') : a,
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// public xmlparser = new xmlparser.j2xParser(this.xmloptions);
|
|
104
|
+
public xmlbuilder1 = new XMLBuilder(this.xmloptions);
|
|
105
|
+
public xmlparser2 = new XMLParser(XMLOptions2);
|
|
106
|
+
|
|
107
|
+
// FIXME: need a way to share/pass parser flags
|
|
108
|
+
public parser = new Parser();
|
|
109
|
+
|
|
110
|
+
public decorated_functions: Record<string, string> = {};
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
constructor() {
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* init used to load the template file. we added a parameter to
|
|
120
|
+
* pass in the list of functions that need decoration (_xlfn).
|
|
121
|
+
*
|
|
122
|
+
* @param decorated_functions
|
|
123
|
+
*/
|
|
124
|
+
public async Init(decorated_functions: Record<string, string> = {}): Promise<void> {
|
|
125
|
+
|
|
126
|
+
// this.decorated_functions = decorated_functions.map(name => name.toLowerCase()); // normalized
|
|
127
|
+
|
|
128
|
+
for (const key of Object.keys(decorated_functions)) {
|
|
129
|
+
this.decorated_functions[key.toLowerCase()] = decorated_functions[key]; // normalized
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.zip = await new JSZip().loadAsync(template, {base64: true});
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async WriteRels(rels: RelationshipMap, path: string, dump = false): Promise<void> {
|
|
137
|
+
|
|
138
|
+
if (!this.zip) {
|
|
139
|
+
throw new Error('missing zip');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const keys = Object.keys(rels);
|
|
143
|
+
|
|
144
|
+
const dom = {
|
|
145
|
+
Relationships: {
|
|
146
|
+
a$: {
|
|
147
|
+
xmlns: 'http://schemas.openxmlformats.org/package/2006/relationships',
|
|
148
|
+
},
|
|
149
|
+
Relationship: keys.map(key => {
|
|
150
|
+
const rel = rels[key];
|
|
151
|
+
const a$: any = {
|
|
152
|
+
Id: rel.id,
|
|
153
|
+
Target: rel.target,
|
|
154
|
+
Type: rel.type,
|
|
155
|
+
};
|
|
156
|
+
if (rel.mode) {
|
|
157
|
+
a$.TargetMode = rel.mode;
|
|
158
|
+
}
|
|
159
|
+
return { a$ };
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
|
|
165
|
+
|
|
166
|
+
// console.info({dom, xml});
|
|
167
|
+
|
|
168
|
+
if (dump) {
|
|
169
|
+
console.info(xml);
|
|
170
|
+
}
|
|
171
|
+
await this.zip?.file(path, xml);
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* format and write styles
|
|
177
|
+
*/
|
|
178
|
+
public async WriteStyleCache(style_cache: StyleCache): Promise<void> {
|
|
179
|
+
|
|
180
|
+
if (!this.zip) {
|
|
181
|
+
throw new Error('missing zip');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const ColorAttributes = (color: XlColor) => {
|
|
185
|
+
|
|
186
|
+
// we could just pass through except that we have argb and excel has rgb
|
|
187
|
+
|
|
188
|
+
const attrs: any = {};
|
|
189
|
+
if (color.indexed !== undefined) {
|
|
190
|
+
attrs.indexed = color.indexed;
|
|
191
|
+
}
|
|
192
|
+
if (color.theme !== undefined) {
|
|
193
|
+
attrs.theme = color.theme;
|
|
194
|
+
}
|
|
195
|
+
if (color.tint !== undefined) {
|
|
196
|
+
attrs.tint = color.tint;
|
|
197
|
+
}
|
|
198
|
+
if (color.argb !== undefined) {
|
|
199
|
+
attrs.rgb = color.argb;
|
|
200
|
+
}
|
|
201
|
+
return attrs;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const xfs = style_cache.cell_xfs.map(xf => {
|
|
205
|
+
const block: any = {
|
|
206
|
+
a$: {
|
|
207
|
+
numFmtId: xf.number_format,
|
|
208
|
+
fontId: xf.font,
|
|
209
|
+
fillId: xf.fill,
|
|
210
|
+
borderId: xf.border,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (xf.horizontal_alignment || xf.vertical_alignment || xf.wrap_text) {
|
|
215
|
+
// block.a$.applyAlignment = 1;
|
|
216
|
+
block.alignment = { a$: {}};
|
|
217
|
+
if (xf.horizontal_alignment) {
|
|
218
|
+
block.alignment.a$.horizontal = xf.horizontal_alignment;
|
|
219
|
+
}
|
|
220
|
+
if (xf.vertical_alignment) {
|
|
221
|
+
block.alignment.a$.vertical = xf.vertical_alignment;
|
|
222
|
+
}
|
|
223
|
+
if (xf.wrap_text) {
|
|
224
|
+
block.alignment.a$.wrapText = 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return block;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const BorderColorAttributes = (edge: BorderEdge) => {
|
|
232
|
+
if (edge.color) {
|
|
233
|
+
return { indexed: edge.color };
|
|
234
|
+
}
|
|
235
|
+
if (edge.rgba) {
|
|
236
|
+
return { rgb: edge.rgba };
|
|
237
|
+
}
|
|
238
|
+
if (edge.theme) {
|
|
239
|
+
const attrs: any = {
|
|
240
|
+
theme: edge.theme,
|
|
241
|
+
}
|
|
242
|
+
if (edge.tint) {
|
|
243
|
+
attrs.tint = edge.tint;
|
|
244
|
+
}
|
|
245
|
+
return attrs;
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const borders = style_cache.borders.map(border => {
|
|
251
|
+
const block: any = {
|
|
252
|
+
left: {},
|
|
253
|
+
right: {},
|
|
254
|
+
top: {},
|
|
255
|
+
bottom: {},
|
|
256
|
+
diagonal: {},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if (border.top.style) {
|
|
260
|
+
block.top.a$ = {
|
|
261
|
+
style: border.top.style,
|
|
262
|
+
};
|
|
263
|
+
const attrs = BorderColorAttributes(border.top);
|
|
264
|
+
if (attrs) { block.top.color = {a$: attrs}; }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (border.left.style) {
|
|
268
|
+
block.left.a$ = {
|
|
269
|
+
style: border.left.style,
|
|
270
|
+
};
|
|
271
|
+
const attrs = BorderColorAttributes(border.left);
|
|
272
|
+
if (attrs) { block.left.color = {a$: attrs}; }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (border.bottom.style) {
|
|
276
|
+
block.bottom.a$ = {
|
|
277
|
+
style: border.bottom.style,
|
|
278
|
+
};
|
|
279
|
+
const attrs = BorderColorAttributes(border.bottom);
|
|
280
|
+
if (attrs) { block.bottom.color = {a$: attrs}; }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (border.right.style) {
|
|
284
|
+
block.right.a$ = {
|
|
285
|
+
style: border.right.style,
|
|
286
|
+
};
|
|
287
|
+
const attrs = BorderColorAttributes(border.right);
|
|
288
|
+
if (attrs) { block.right.color = {a$: attrs}; }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (border.diagonal.style) {
|
|
292
|
+
block.diagonal.a$ = {
|
|
293
|
+
style: border.diagonal.style,
|
|
294
|
+
};
|
|
295
|
+
const attrs = BorderColorAttributes(border.diagonal);
|
|
296
|
+
if (attrs) { block.diagonal.color = {a$: attrs}; }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return block;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// console.info("SC", style_cache);
|
|
303
|
+
|
|
304
|
+
const fills = style_cache.fills.map(fill => {
|
|
305
|
+
const block: any = {
|
|
306
|
+
a$: { patternType: fill.pattern_type },
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (fill.pattern_gray !== undefined) {
|
|
310
|
+
block.a$.patternType = `gray${fill.pattern_gray}`;
|
|
311
|
+
}
|
|
312
|
+
if (fill.bg_color) {
|
|
313
|
+
block.bgColor = { a$: ColorAttributes(fill.bg_color) };
|
|
314
|
+
}
|
|
315
|
+
if (fill.fg_color) {
|
|
316
|
+
block.fgColor = { a$: ColorAttributes(fill.fg_color) };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {patternFill: block};
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const fonts = style_cache.fonts.map(font => {
|
|
323
|
+
const block: any = {};
|
|
324
|
+
|
|
325
|
+
// flags
|
|
326
|
+
|
|
327
|
+
if (font.bold) { block.b = ''; }
|
|
328
|
+
if (font.italic) { block.i = ''; }
|
|
329
|
+
if (font.underline) { block.u = ''; }
|
|
330
|
+
if (font.strike) { block.strike = ''; }
|
|
331
|
+
|
|
332
|
+
// "val" props
|
|
333
|
+
|
|
334
|
+
if (font.size !== undefined) {
|
|
335
|
+
block.sz = { a$: { val: font.size }};
|
|
336
|
+
}
|
|
337
|
+
if (font.family !== undefined) {
|
|
338
|
+
block.family = { a$: { val: font.family }};
|
|
339
|
+
}
|
|
340
|
+
if (font.name !== undefined) {
|
|
341
|
+
block.name = { a$: { val: font.name }};
|
|
342
|
+
}
|
|
343
|
+
if (font.scheme !== undefined) {
|
|
344
|
+
block.scheme = { a$: { val: font.scheme }};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// color
|
|
348
|
+
|
|
349
|
+
if (font.color_argb !== undefined) {
|
|
350
|
+
block.color = { a$: { rgb: font.color_argb }};
|
|
351
|
+
}
|
|
352
|
+
else if (font.color_theme !== undefined) {
|
|
353
|
+
block.color = { a$: { theme: font.color_theme }};
|
|
354
|
+
if (font.color_tint) {
|
|
355
|
+
block.color.a$.tint = font.color_tint;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return block;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const dom: any = {
|
|
363
|
+
styleSheet: {
|
|
364
|
+
a$: {
|
|
365
|
+
'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
366
|
+
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
|
|
367
|
+
'mc:Ignorable': 'x14ac x16r2 xr',
|
|
368
|
+
'xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac',
|
|
369
|
+
'xmlns:x16r2': 'http://schemas.microsoft.com/office/spreadsheetml/2015/02/main',
|
|
370
|
+
'xmlns:xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision',
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// we're only adding elements here if they are not empty, but in
|
|
376
|
+
// practice only numFmts can be empty (because there are implicit
|
|
377
|
+
// formats); everything else has a default 0 entry
|
|
378
|
+
|
|
379
|
+
if (style_cache.number_formats.length) {
|
|
380
|
+
|
|
381
|
+
dom.styleSheet.numFmts = {
|
|
382
|
+
a$: { count: style_cache.number_formats.length },
|
|
383
|
+
numFmt: style_cache.number_formats.map(format => {
|
|
384
|
+
return {
|
|
385
|
+
a$: {
|
|
386
|
+
numFmtId: format.id,
|
|
387
|
+
formatCode: format.format,
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}),
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// console.info(style_cache.number_formats);
|
|
394
|
+
// console.info(dom.styleSheet.numFmts);
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (fonts.length) {
|
|
399
|
+
dom.styleSheet.fonts = {
|
|
400
|
+
a$: { count: fonts.length },
|
|
401
|
+
font: fonts,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (fills.length) {
|
|
406
|
+
dom.styleSheet.fills = {
|
|
407
|
+
a$: { count: fills.length },
|
|
408
|
+
fill: fills,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (borders.length) {
|
|
413
|
+
dom.styleSheet.borders = {
|
|
414
|
+
a$: { count: borders.length },
|
|
415
|
+
border: borders,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// console.info("B", borders, JSON.stringify(dom.styleSheet.borders, undefined, 2))
|
|
420
|
+
|
|
421
|
+
if (xfs.length) {
|
|
422
|
+
dom.styleSheet.cellXfs = {
|
|
423
|
+
a$: { count: xfs.length },
|
|
424
|
+
xf: xfs,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// not used:
|
|
429
|
+
//
|
|
430
|
+
// cellStyleXfs
|
|
431
|
+
// cellStyles
|
|
432
|
+
// dxfs
|
|
433
|
+
// tableStyles
|
|
434
|
+
|
|
435
|
+
// ------------
|
|
436
|
+
|
|
437
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
|
|
438
|
+
// console.info(xml);
|
|
439
|
+
|
|
440
|
+
await this.zip?.file('xl/styles.xml', xml);
|
|
441
|
+
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* format and write shared strings file to the zip archive. this will
|
|
446
|
+
* replace any existing shared strings file.
|
|
447
|
+
*/
|
|
448
|
+
public async WriteSharedStrings(shared_strings: SharedStrings): Promise<void> {
|
|
449
|
+
|
|
450
|
+
// console.info({shared_strings});
|
|
451
|
+
|
|
452
|
+
if (!this.zip) {
|
|
453
|
+
throw new Error('missing zip');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const dom: any = {
|
|
457
|
+
sst: {
|
|
458
|
+
a$: {
|
|
459
|
+
'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
460
|
+
count: shared_strings.strings.length,
|
|
461
|
+
uniqueCount: shared_strings.strings.length,
|
|
462
|
+
},
|
|
463
|
+
si: [
|
|
464
|
+
...shared_strings.strings.map(t => { return {t}}),
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
|
|
470
|
+
|
|
471
|
+
// console.info(xml);
|
|
472
|
+
|
|
473
|
+
await this.zip?.file('xl/sharedStrings.xml', xml);
|
|
474
|
+
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* FIXME: merge with workbook function (put somewhere else)
|
|
479
|
+
* /
|
|
480
|
+
public async ReadRels(zip?: JSZip, path = ''): Promise<RelationshipMap> {
|
|
481
|
+
|
|
482
|
+
const rels: RelationshipMap = {};
|
|
483
|
+
const data = await zip?.file(path)?.async('text') as string;
|
|
484
|
+
//
|
|
485
|
+
// force array on <Relationship/> elements, but be slack on the rest
|
|
486
|
+
// (we know they are single elements)
|
|
487
|
+
//
|
|
488
|
+
const xml = this.xmlparser2.parse(data || '');
|
|
489
|
+
console.info(path, xml);
|
|
490
|
+
|
|
491
|
+
for (const relationship of xml.Relationships?.Relationship || []) {
|
|
492
|
+
const id = relationship.a$.Id;
|
|
493
|
+
rels[id] = {
|
|
494
|
+
id,
|
|
495
|
+
type: relationship.a$.Type,
|
|
496
|
+
target: relationship.a$.Target,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return rels;
|
|
501
|
+
|
|
502
|
+
}
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
public StyleFromCell(sheet: SerializedSheet, style_cache: StyleCache, row: number, column: number, style: Style.Properties = {}) {
|
|
506
|
+
|
|
507
|
+
//if (row === 2 && column === 5)
|
|
508
|
+
// console.info("SFC", JSON.stringify(style, undefined, 2));
|
|
509
|
+
|
|
510
|
+
const cell_style_refs = sheet.styles || sheet.cell_style_refs || [];
|
|
511
|
+
|
|
512
|
+
const list: Style.Properties[] = [sheet.sheet_style];
|
|
513
|
+
|
|
514
|
+
if (sheet.row_pattern && sheet.row_pattern.length) {
|
|
515
|
+
list.push(sheet.row_pattern[row % sheet.row_pattern.length]);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// is this backwards, vis a vis our rendering? I think it might be...
|
|
519
|
+
// YES: should be row pattern -> row -> column -> cell [corrected]
|
|
520
|
+
|
|
521
|
+
// if (sheet.row_style && sheet.row_style[row]) {
|
|
522
|
+
// list.push(sheet.row_style[row]);
|
|
523
|
+
// }
|
|
524
|
+
|
|
525
|
+
if (sheet.row_style) {
|
|
526
|
+
let style = sheet.row_style[row];
|
|
527
|
+
if (typeof style === 'number') {
|
|
528
|
+
style = cell_style_refs[style];
|
|
529
|
+
if (style) {
|
|
530
|
+
list.push(style);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else if (style) {
|
|
534
|
+
list.push(style);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// this can now be a number, and possibly 0 (?)
|
|
539
|
+
|
|
540
|
+
// actually 0 is by default a null style, although that's more of
|
|
541
|
+
// a convention than a hard rule, not sure we should rely on it
|
|
542
|
+
|
|
543
|
+
if (sheet.column_style) {
|
|
544
|
+
let style = sheet.column_style[column];
|
|
545
|
+
if (typeof style === 'number') {
|
|
546
|
+
style = cell_style_refs[style];
|
|
547
|
+
if (style) {
|
|
548
|
+
list.push(style);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else if (style) {
|
|
552
|
+
list.push(style);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
//if (sheet.column_style && sheet.column_style[column]) {
|
|
557
|
+
// list.push(sheet.column_style[column]);
|
|
558
|
+
//}
|
|
559
|
+
|
|
560
|
+
/*
|
|
561
|
+
if (cell.ref) {
|
|
562
|
+
list.push(sheet_source.cell_style_refs[cell.ref]);
|
|
563
|
+
}
|
|
564
|
+
else if (cell.style_ref) {
|
|
565
|
+
list.push(sheet_source.cell_style_refs[cell.style_ref]);
|
|
566
|
+
}
|
|
567
|
+
else if (style_map[cell.column] && style_map[cell.column][cell.row]) {
|
|
568
|
+
list.push(style_map[cell.column][cell.row]);
|
|
569
|
+
}
|
|
570
|
+
*/
|
|
571
|
+
list.push(style);
|
|
572
|
+
|
|
573
|
+
const options = style_cache.StyleOptionsFromProperties(Style.Composite(list));
|
|
574
|
+
|
|
575
|
+
return style_cache.EnsureStyle(options);
|
|
576
|
+
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** overload for return type */
|
|
580
|
+
public NormalizeAddress(unit: UnitAddress, sheet: SerializedSheet): UnitAddress;
|
|
581
|
+
|
|
582
|
+
/** overload for return type */
|
|
583
|
+
public NormalizeAddress(unit: UnitRange, sheet: SerializedSheet): UnitRange;
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* for charts we need addresses to be absolute ($) and ensure there's
|
|
587
|
+
* a sheet name -- use the active sheet if it's not explicitly referenced
|
|
588
|
+
*/
|
|
589
|
+
public NormalizeAddress(unit: UnitAddress|UnitRange, sheet: SerializedSheet): UnitAddress|UnitRange {
|
|
590
|
+
|
|
591
|
+
const addresses = (unit.type === 'address') ? [unit] : [unit.start, unit.end];
|
|
592
|
+
|
|
593
|
+
for (const address of addresses) {
|
|
594
|
+
address.absolute_row = true;
|
|
595
|
+
address.absolute_column = true;
|
|
596
|
+
if (!address.sheet) {
|
|
597
|
+
address.sheet = sheet.name;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (unit.type === 'range') {
|
|
601
|
+
unit.end.sheet = undefined;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
unit.label = this.parser.Render(unit);
|
|
605
|
+
return unit; // fluent
|
|
606
|
+
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* new-style annotation layout (kind of a two-cell anchor) to two-cell anchor
|
|
612
|
+
*/
|
|
613
|
+
public AnnotationLayoutToAnchor(layout: AnnotationLayout, sheet: SerializedSheet): TwoCellAnchor {
|
|
614
|
+
|
|
615
|
+
// our offsets are % of cell. their offsets are in excel units,
|
|
616
|
+
// but when the chart is added our method will convert from pixels.
|
|
617
|
+
|
|
618
|
+
const address_to_anchor = (corner: LayoutCorner) => {
|
|
619
|
+
|
|
620
|
+
const width = (sheet.column_width && sheet.column_width[corner.address.column]) ?
|
|
621
|
+
sheet.column_width[corner.address.column] : (sheet.default_column_width || 100);
|
|
622
|
+
|
|
623
|
+
const height = (sheet.row_height && sheet.row_height[corner.address.row]) ?
|
|
624
|
+
sheet.row_height[corner.address.row] : (sheet.default_row_height || 20);
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
...corner.address,
|
|
628
|
+
row_offset: Math.round(corner.offset.y * height),
|
|
629
|
+
column_offset: Math.round(corner.offset.x * width),
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
return {
|
|
635
|
+
from: address_to_anchor(layout.tl),
|
|
636
|
+
to: address_to_anchor(layout.br),
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* convert a rectangle (pixels) to a two-cell anchor. note that
|
|
643
|
+
* our offsets are in pixels, they'll need to be changed to whatever
|
|
644
|
+
* the target units are.
|
|
645
|
+
*/
|
|
646
|
+
public AnnotationRectToAnchor(
|
|
647
|
+
annotation_rect: {
|
|
648
|
+
left: number;
|
|
649
|
+
top: number;
|
|
650
|
+
width: number;
|
|
651
|
+
height: number;
|
|
652
|
+
},
|
|
653
|
+
sheet: SerializedSheet): TwoCellAnchor {
|
|
654
|
+
|
|
655
|
+
const anchor: TwoCellAnchor = {
|
|
656
|
+
from: {row: -1, column: -1},
|
|
657
|
+
to: {row: -1, column: -1},
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const rect = {
|
|
661
|
+
...annotation_rect, // {top, left, width, height}
|
|
662
|
+
right: annotation_rect.left + annotation_rect.width,
|
|
663
|
+
bottom: annotation_rect.top + annotation_rect.height,
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
for (let x = 0, column = 0; column < 1000; column++) {
|
|
667
|
+
const width = (sheet.column_width && sheet.column_width[column]) ? sheet.column_width[column] : (sheet.default_column_width || 100);
|
|
668
|
+
if (anchor.from.column < 0 && rect.left <= x + width) {
|
|
669
|
+
anchor.from.column = column;
|
|
670
|
+
anchor.from.column_offset = (rect.left - x);
|
|
671
|
+
}
|
|
672
|
+
if (anchor.to.column < 0 && rect.right <= x + width) {
|
|
673
|
+
anchor.to.column = column;
|
|
674
|
+
anchor.to.column_offset = (rect.right - x);
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
x += width;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
for (let y = 0, row = 0; row < 1000; row++) {
|
|
681
|
+
const height = (sheet.row_height && sheet.row_height[row]) ? sheet.row_height[row] : (sheet.default_row_height || 20);
|
|
682
|
+
if (anchor.from.row < 0 && rect.top <= y + height) {
|
|
683
|
+
anchor.from.row = row;
|
|
684
|
+
anchor.from.row_offset = (rect.top - y);
|
|
685
|
+
}
|
|
686
|
+
if (anchor.to.row < 0 && rect.bottom <= y + height) {
|
|
687
|
+
anchor.to.row = row;
|
|
688
|
+
anchor.to.row_offset = (rect.bottom - y);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
y += height;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return anchor;
|
|
695
|
+
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
public ParseImages(sheet_source: SerializedSheet): Array<{ anchor: TwoCellAnchor, options: ImageOptions }> {
|
|
699
|
+
|
|
700
|
+
const images: Array<{ anchor: TwoCellAnchor, options: ImageOptions }> = [];
|
|
701
|
+
|
|
702
|
+
for (const annotation of sheet_source.annotations || []) {
|
|
703
|
+
if (annotation.type === 'image' && annotation.data?.src) {
|
|
704
|
+
|
|
705
|
+
// this is (should be) a data URI in base64. at least (atm)
|
|
706
|
+
// that's all we support for exporting.
|
|
707
|
+
|
|
708
|
+
const src = annotation.data.src;
|
|
709
|
+
const match = src.match(/^data:image\/([^;]*?);base64,/);
|
|
710
|
+
|
|
711
|
+
if (match) {
|
|
712
|
+
|
|
713
|
+
const data = src.substr(match[0].length);
|
|
714
|
+
const mimetype = match[1];
|
|
715
|
+
|
|
716
|
+
const options: ImageOptions = {
|
|
717
|
+
data,
|
|
718
|
+
mimetype,
|
|
719
|
+
encoding: 'base64',
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
switch (mimetype) {
|
|
723
|
+
case 'svg+xml':
|
|
724
|
+
case 'webp':
|
|
725
|
+
case 'jpeg':
|
|
726
|
+
case 'jpg':
|
|
727
|
+
case 'image/png':
|
|
728
|
+
case 'png':
|
|
729
|
+
case 'gif':
|
|
730
|
+
|
|
731
|
+
if (annotation.rect) {
|
|
732
|
+
images.push({
|
|
733
|
+
anchor: this.AnnotationRectToAnchor(annotation.rect, sheet_source), options});
|
|
734
|
+
}
|
|
735
|
+
else if (annotation.layout) {
|
|
736
|
+
images.push({
|
|
737
|
+
anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
console.warn('annotation missing layout');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
break;
|
|
744
|
+
|
|
745
|
+
default:
|
|
746
|
+
console.info('unhandled image type', mimetype);
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return images;
|
|
756
|
+
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
public ParseCharts(sheet_source: SerializedSheet): Array<{ anchor: TwoCellAnchor, options: ChartOptions }> {
|
|
760
|
+
|
|
761
|
+
const charts: Array<{
|
|
762
|
+
anchor: TwoCellAnchor,
|
|
763
|
+
options: ChartOptions,
|
|
764
|
+
}> = [];
|
|
765
|
+
|
|
766
|
+
const parse_series = (arg: ExpressionUnit, options: ChartOptions, ref?: string) => {
|
|
767
|
+
|
|
768
|
+
if (arg.type === 'range') {
|
|
769
|
+
options.data.push(this.NormalizeAddress(arg, sheet_source));
|
|
770
|
+
}
|
|
771
|
+
else if (arg.type === 'call') {
|
|
772
|
+
if (/group/i.test(arg.name)) {
|
|
773
|
+
// recurse
|
|
774
|
+
for (const value of (arg.args || [])) {
|
|
775
|
+
parse_series(value, options, ref ? ref + ` (recurse)` : undefined);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
else if (/series/i.test(arg.name)) {
|
|
779
|
+
|
|
780
|
+
const [label, x, y] = arg.args; // y is required
|
|
781
|
+
|
|
782
|
+
if (y && y.type === 'range') {
|
|
783
|
+
options.data.push(this.NormalizeAddress(y, sheet_source));
|
|
784
|
+
|
|
785
|
+
if (label) {
|
|
786
|
+
|
|
787
|
+
if (!options.names) { options.names = []; }
|
|
788
|
+
|
|
789
|
+
if (label.type === 'address') {
|
|
790
|
+
this.NormalizeAddress(label, sheet_source);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (label.type === 'range') {
|
|
794
|
+
this.NormalizeAddress(label.start, sheet_source);
|
|
795
|
+
options.names[options.data.length - 1] = label.start;
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
options.names[options.data.length - 1] = label;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!options.labels2) { options.labels2 = []; }
|
|
804
|
+
if (x && x.type === 'range') {
|
|
805
|
+
options.labels2[options.data.length - 1] = this.NormalizeAddress(x, sheet_source);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
console.info('invalid series missing Y', {y, arg, ref});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
for (const annotation of sheet_source.annotations || []) {
|
|
818
|
+
|
|
819
|
+
const parse_result = this.parser.Parse(annotation.formula || '');
|
|
820
|
+
if (parse_result.expression && parse_result.expression.type === 'call') {
|
|
821
|
+
|
|
822
|
+
let type = '';
|
|
823
|
+
switch (parse_result.expression.name.toLowerCase()) {
|
|
824
|
+
case 'line.chart':
|
|
825
|
+
type = 'scatter';
|
|
826
|
+
break;
|
|
827
|
+
case 'scatter.line':
|
|
828
|
+
type = 'scatter2';
|
|
829
|
+
break;
|
|
830
|
+
case 'donut.chart':
|
|
831
|
+
type = 'donut';
|
|
832
|
+
break;
|
|
833
|
+
case 'bar.chart':
|
|
834
|
+
type = 'bar';
|
|
835
|
+
break;
|
|
836
|
+
case 'column.chart':
|
|
837
|
+
type = 'column';
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (type === 'column' || type === 'donut' || type === 'bar' || type === 'scatter' || type === 'scatter2') {
|
|
842
|
+
|
|
843
|
+
const options: ChartOptions = { type, data: [] };
|
|
844
|
+
|
|
845
|
+
const title_index = (type === 'scatter2') ? 1 : 2;
|
|
846
|
+
const title_arg = parse_result.expression.args[title_index];
|
|
847
|
+
|
|
848
|
+
if (title_arg && title_arg.type === 'literal') {
|
|
849
|
+
options.title = title_arg;
|
|
850
|
+
}
|
|
851
|
+
else if (title_arg && title_arg.type === 'address') {
|
|
852
|
+
options.title = this.NormalizeAddress(title_arg, sheet_source);
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
|
|
856
|
+
// FIXME: formula here will not work. we need to bring
|
|
857
|
+
// a calculator into this class? (!) or somehow cache the value...
|
|
858
|
+
|
|
859
|
+
// console.info('chart title arg', title_arg)
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// we changed our Series() to Group(), and then added a new Series()
|
|
863
|
+
// function which adds data labels and per-series X values... will
|
|
864
|
+
// need to incorporate somehow. for now, just s/series/group to get
|
|
865
|
+
// the data in the chart
|
|
866
|
+
|
|
867
|
+
// oh we already did that... duh
|
|
868
|
+
|
|
869
|
+
if (parse_result.expression.args[0]) {
|
|
870
|
+
const arg0 = parse_result.expression.args[0];
|
|
871
|
+
if (type === 'scatter2' || type === 'bar' || type === 'column' || type === 'scatter') {
|
|
872
|
+
parse_series(arg0, options, sheet_source.name);
|
|
873
|
+
}
|
|
874
|
+
else if (arg0.type === 'range') {
|
|
875
|
+
options.data.push(this.NormalizeAddress(arg0, sheet_source));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// so the next cases cannot happen? (...) donut? (...)
|
|
879
|
+
|
|
880
|
+
else if (arg0.type === 'call' && /group/i.test(arg0.name)) {
|
|
881
|
+
for (const series of arg0.args) {
|
|
882
|
+
|
|
883
|
+
// in group, could be a range or a Series()
|
|
884
|
+
if (series.type === 'range') {
|
|
885
|
+
options.data.push(this.NormalizeAddress(series, sheet_source));
|
|
886
|
+
}
|
|
887
|
+
else if (series.type === 'call' && /series/i.test(series.name)) {
|
|
888
|
+
|
|
889
|
+
// in Series(), args are (name, X, range of data)
|
|
890
|
+
|
|
891
|
+
if (series.args[2] && series.args[2].type === 'range') {
|
|
892
|
+
options.data.push(this.NormalizeAddress(series.args[2], sheet_source));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
else if (arg0.type === 'call' && /series/i.test(arg0.name)) {
|
|
898
|
+
|
|
899
|
+
// another case, single Series()
|
|
900
|
+
if (arg0.args[2] && arg0.args[2].type === 'range') {
|
|
901
|
+
options.data.push(this.NormalizeAddress(arg0.args[2], sheet_source));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/*
|
|
906
|
+
else if (arg0.type === 'call' && /series/i.test(arg0.name)) {
|
|
907
|
+
for (const series of arg0.args) {
|
|
908
|
+
if (series.type === 'range') {
|
|
909
|
+
options.data.push(this.NormalizeAddress(series, sheet_source));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
*/
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (type !== 'scatter2') {
|
|
917
|
+
if (parse_result.expression.args[1] && parse_result.expression.args[1].type === 'range') {
|
|
918
|
+
options.labels = this.NormalizeAddress(parse_result.expression.args[1], sheet_source);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (type === 'scatter'
|
|
923
|
+
&& parse_result.expression.args[4]
|
|
924
|
+
&& parse_result.expression.args[4].type === 'literal'
|
|
925
|
+
&& parse_result.expression.args[4].value.toString().toLowerCase() === 'smooth') {
|
|
926
|
+
|
|
927
|
+
options.smooth = true;
|
|
928
|
+
}
|
|
929
|
+
else if (type === 'scatter2' && parse_result.expression.args[2]) {
|
|
930
|
+
if (parse_result.expression.args[2].type === 'literal'
|
|
931
|
+
&& /smooth/i.test(parse_result.expression.args[2].value.toString())) {
|
|
932
|
+
options.smooth = true;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (annotation.rect) {
|
|
937
|
+
charts.push({
|
|
938
|
+
anchor: this.AnnotationRectToAnchor(annotation.rect, sheet_source), options});
|
|
939
|
+
// sheet.AddChart(this.AnnotationRectToAnchor(annotation.rect, sheet_source), options);
|
|
940
|
+
}
|
|
941
|
+
else if (annotation.layout) {
|
|
942
|
+
charts.push({
|
|
943
|
+
anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
|
|
944
|
+
// sheet.AddChart(this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options);
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
console.warn('annotation missing layout');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return charts;
|
|
958
|
+
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
public FormulaText(text: string, context: Cell): string {
|
|
962
|
+
|
|
963
|
+
// let mared = false;
|
|
964
|
+
|
|
965
|
+
if (text[0] !== '=') {
|
|
966
|
+
return text;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const parse_result = this.parser.Parse(text);
|
|
970
|
+
|
|
971
|
+
if (!parse_result.expression) {
|
|
972
|
+
console.warn('parsing function failed');
|
|
973
|
+
console.warn(text);
|
|
974
|
+
return text.substring(1);
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
|
|
978
|
+
// if (this.decorated_functions.length) {
|
|
979
|
+
{
|
|
980
|
+
this.parser.Walk(parse_result.expression, (unit) => {
|
|
981
|
+
if (unit.type === 'call') {
|
|
982
|
+
// unit.name = unit.name.toUpperCase();
|
|
983
|
+
|
|
984
|
+
const lc = unit.name.toLowerCase();
|
|
985
|
+
|
|
986
|
+
/*
|
|
987
|
+
for (const test of this.decorated_functions) {
|
|
988
|
+
if (test === lc) {
|
|
989
|
+
unit.name = '_xlfn.' + unit.name;
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
*/
|
|
994
|
+
|
|
995
|
+
if (this.decorated_functions[lc]) {
|
|
996
|
+
// mared = true;
|
|
997
|
+
unit.name = this.decorated_functions[lc] + '.' + unit.name;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
}
|
|
1001
|
+
return true;
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
//if (mared) {
|
|
1006
|
+
// console.info("MARED", this.parser.Render(parse_result.expression, undefined, ''));
|
|
1007
|
+
//}
|
|
1008
|
+
|
|
1009
|
+
// const x = this.parser.Render(parse_result.expression, undefined, '');
|
|
1010
|
+
// console.info("T", text, x);
|
|
1011
|
+
|
|
1012
|
+
const table_name = context.table?.name || '';
|
|
1013
|
+
|
|
1014
|
+
/*
|
|
1015
|
+
console.info('tn', table_name);
|
|
1016
|
+
const temp = this.parser.Render(parse_result.expression, undefined, '', undefined, undefined, undefined, true, table_name);
|
|
1017
|
+
console.info({temp});
|
|
1018
|
+
*/
|
|
1019
|
+
|
|
1020
|
+
return this.parser.Render(parse_result.expression, {
|
|
1021
|
+
missing: '',
|
|
1022
|
+
long_structured_references: true,
|
|
1023
|
+
table_name });
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
public async Export(source: {
|
|
1029
|
+
sheet_data: SerializedSheet[];
|
|
1030
|
+
active_sheet?: number;
|
|
1031
|
+
named_ranges?: {[index: string]: IArea};
|
|
1032
|
+
named_expressions?: Array<{ name: string, expression: string }>;
|
|
1033
|
+
decimal_mark: ','|'.';
|
|
1034
|
+
}): Promise<void> {
|
|
1035
|
+
|
|
1036
|
+
// --- create a map --------------------------------------------------------
|
|
1037
|
+
|
|
1038
|
+
// active_sheet, in source, is a sheet ID. we need to map
|
|
1039
|
+
// that to an index. luckily we preserve index order. we can
|
|
1040
|
+
// do that as a side effect of creating the map, although we
|
|
1041
|
+
// will need a loop index.
|
|
1042
|
+
|
|
1043
|
+
let active_sheet = 0;
|
|
1044
|
+
|
|
1045
|
+
const sheet_name_map: string[] = [];
|
|
1046
|
+
for (let index = 0; index < source.sheet_data.length; index++) {
|
|
1047
|
+
const sheet = source.sheet_data[index];
|
|
1048
|
+
|
|
1049
|
+
const id = sheet.id || 0;
|
|
1050
|
+
if (id) {
|
|
1051
|
+
sheet_name_map[id] = sheet.name || '';
|
|
1052
|
+
}
|
|
1053
|
+
if (id === source.active_sheet) {
|
|
1054
|
+
active_sheet = index;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// console.info("active sheet", source.active_sheet, active_sheet);
|
|
1059
|
+
|
|
1060
|
+
// --- init workbook globals -----------------------------------------------
|
|
1061
|
+
|
|
1062
|
+
// shared strings, start empty
|
|
1063
|
+
const shared_strings = new SharedStrings();
|
|
1064
|
+
|
|
1065
|
+
// style and theme: use the template so we have the base values
|
|
1066
|
+
const style_cache = new StyleCache();
|
|
1067
|
+
const theme = new Theme();
|
|
1068
|
+
|
|
1069
|
+
let data = await this.zip?.file('xl/theme/theme1.xml')?.async('text') as string;
|
|
1070
|
+
theme.FromXML(this.xmlparser2.parse(data || ''));
|
|
1071
|
+
// console.info({data, xml: this.xmlparser2.parse(data)})
|
|
1072
|
+
|
|
1073
|
+
data = await this.zip?.file('xl/styles.xml')?.async('text') as string;
|
|
1074
|
+
style_cache.FromXML(this.xmlparser2.parse(data || ''), theme);
|
|
1075
|
+
|
|
1076
|
+
// reset counters
|
|
1077
|
+
|
|
1078
|
+
Drawing.next_drawing_index = 1;
|
|
1079
|
+
Chart.next_chart_index = 1;
|
|
1080
|
+
|
|
1081
|
+
const drawings: Drawing[] = [];
|
|
1082
|
+
|
|
1083
|
+
// we need to keep track of tables in all sheets
|
|
1084
|
+
|
|
1085
|
+
const global_tables: TableDescription[] = [];
|
|
1086
|
+
|
|
1087
|
+
// --- now sheets ----------------------------------------------------------
|
|
1088
|
+
|
|
1089
|
+
for (let sheet_index = 0; sheet_index < source.sheet_data.length; sheet_index++) {
|
|
1090
|
+
|
|
1091
|
+
const sheet = source.sheet_data[sheet_index];
|
|
1092
|
+
const sheet_rels: RelationshipMap = {};
|
|
1093
|
+
|
|
1094
|
+
// FIXME: we could, in theory, type this thing...
|
|
1095
|
+
|
|
1096
|
+
const sheet_attributes = {
|
|
1097
|
+
'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
1098
|
+
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
1099
|
+
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
|
|
1100
|
+
'mc:Ignorable': 'x14ac xr xr2 xr3',
|
|
1101
|
+
'xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac',
|
|
1102
|
+
'xmlns:xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision',
|
|
1103
|
+
'xmlns:xr2': 'http://schemas.microsoft.com/office/spreadsheetml/2015/revision2',
|
|
1104
|
+
'xmlns:xr3': 'http://schemas.microsoft.com/office/spreadsheetml/2016/revision3',
|
|
1105
|
+
'xr:uid': '{D37933E2-499F-4789-8D13-194E11B743FC}',
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const default_row_height = sheet.default_row_height ? (sheet.default_row_height / 20 * 15) : 15;
|
|
1109
|
+
|
|
1110
|
+
const dom: any = {
|
|
1111
|
+
worksheet: {
|
|
1112
|
+
a$: {
|
|
1113
|
+
...sheet_attributes,
|
|
1114
|
+
},
|
|
1115
|
+
dimension: {
|
|
1116
|
+
a$: {
|
|
1117
|
+
ref: 'A1',
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
sheetViews: {
|
|
1121
|
+
sheetView: {
|
|
1122
|
+
a$: {
|
|
1123
|
+
// tabSelected: (sheet_index === active_sheet ? 1 : 0),
|
|
1124
|
+
workbookViewId: 0,
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
},
|
|
1128
|
+
sheetFormatPr: {
|
|
1129
|
+
a$: default_row_height === 15 ? {
|
|
1130
|
+
'x14ac:dyDescent': 0.25,
|
|
1131
|
+
} : {
|
|
1132
|
+
defaultRowHeight: default_row_height,
|
|
1133
|
+
customHeight: 1,
|
|
1134
|
+
'x14ac:dyDescent': 0.25,
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
1137
|
+
cols: {},
|
|
1138
|
+
sheetData: {},
|
|
1139
|
+
mergeCells: {
|
|
1140
|
+
a$: { count: 0 },
|
|
1141
|
+
},
|
|
1142
|
+
dataValidations: {},
|
|
1143
|
+
hyperlinks: {},
|
|
1144
|
+
pageMargins: {
|
|
1145
|
+
a$: {
|
|
1146
|
+
left: 0.7,
|
|
1147
|
+
right: 0.7,
|
|
1148
|
+
top: 0.75,
|
|
1149
|
+
bottom: 0.75,
|
|
1150
|
+
header: 0.3,
|
|
1151
|
+
footer: 0.3,
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
drawing: {},
|
|
1155
|
+
tableParts: {
|
|
1156
|
+
a$: {
|
|
1157
|
+
count: 0,
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
extLst: {
|
|
1161
|
+
ext: {
|
|
1162
|
+
a$: {
|
|
1163
|
+
uri: '{05C60535-1F16-4fd2-B633-F4F36F0B64E0}',
|
|
1164
|
+
'xmlns:x14': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main',
|
|
1165
|
+
},
|
|
1166
|
+
},
|
|
1167
|
+
},
|
|
1168
|
+
|
|
1169
|
+
},
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
/*
|
|
1173
|
+
const column_styles = (sheet.column_style as any) || {};
|
|
1174
|
+
|
|
1175
|
+
for (const key of Object.keys(column_styles)) {
|
|
1176
|
+
console.info(key, '=>', column_styles[key]);
|
|
1177
|
+
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return;
|
|
1181
|
+
*/
|
|
1182
|
+
|
|
1183
|
+
// data has different representations. it is either blocked into rows or
|
|
1184
|
+
// columns, or a set of individual cells. we could theoretically guarantee
|
|
1185
|
+
// a particular encoding if we wanted to (by row would be optimal for excel).
|
|
1186
|
+
|
|
1187
|
+
// but we don't do that at the moment, so let's just unwind it using the
|
|
1188
|
+
// standard class (adding support for cell styles)
|
|
1189
|
+
|
|
1190
|
+
const cell_style_refs = sheet.styles || sheet.cell_style_refs || [];
|
|
1191
|
+
|
|
1192
|
+
const cells = new Cells();
|
|
1193
|
+
cells.FromJSON(sheet.data, cell_style_refs);
|
|
1194
|
+
|
|
1195
|
+
// these are cells with style but no contents
|
|
1196
|
+
|
|
1197
|
+
for (const entry of sheet.cell_styles) {
|
|
1198
|
+
const cell = cells.EnsureCell(entry); // cheating
|
|
1199
|
+
if (!cell.style) {
|
|
1200
|
+
cell.style = cell_style_refs[entry.ref];
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// start with an extent from (0, 0). we can shift this as necessary.
|
|
1205
|
+
|
|
1206
|
+
const extent: IArea = {
|
|
1207
|
+
start: { row: cells.rows + 1, column: cells.columns + 1, },
|
|
1208
|
+
end: { row: cells.rows + 1, column: cells.columns + 1, }};
|
|
1209
|
+
|
|
1210
|
+
// const FormulaText = (text: string) => (text[0] === '=') ? TranslateFormula(text.substr(1)) : text;
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
// cells data is row-major, and sparse.
|
|
1214
|
+
|
|
1215
|
+
const sheet_data: any = { row: [] };
|
|
1216
|
+
|
|
1217
|
+
const hyperlinks: Array<{
|
|
1218
|
+
rel: string,
|
|
1219
|
+
target: string,
|
|
1220
|
+
address: ICellAddress,
|
|
1221
|
+
}> = [];
|
|
1222
|
+
|
|
1223
|
+
const sparklines: Array<{
|
|
1224
|
+
address: ICellAddress,
|
|
1225
|
+
formula: string,
|
|
1226
|
+
style?: Style.Properties,
|
|
1227
|
+
}> = [];
|
|
1228
|
+
|
|
1229
|
+
const merges: Area[] = [];
|
|
1230
|
+
const tables: TableDescription[] = [];
|
|
1231
|
+
|
|
1232
|
+
const validations: Array<{
|
|
1233
|
+
address: ICellAddress,
|
|
1234
|
+
validation: DataValidation,
|
|
1235
|
+
}> = [];
|
|
1236
|
+
|
|
1237
|
+
// --
|
|
1238
|
+
|
|
1239
|
+
for (let r = 0; r < cells.data.length; r++ ) {
|
|
1240
|
+
if (cells.data[r] && cells.data[r].length) {
|
|
1241
|
+
|
|
1242
|
+
// push out the extent (reversed)
|
|
1243
|
+
if (r < extent.start.row) {
|
|
1244
|
+
extent.start.row = r;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// row span
|
|
1248
|
+
const span = {start: -1, end: -1};
|
|
1249
|
+
const row: any = [];
|
|
1250
|
+
|
|
1251
|
+
for (let c = 0; c < cells.data[r].length; c++) {
|
|
1252
|
+
const cell = cells.data[r][c];
|
|
1253
|
+
if (cell) {
|
|
1254
|
+
|
|
1255
|
+
// create a table reference at the table head, we can ignore the rest
|
|
1256
|
+
|
|
1257
|
+
if (cell.table &&
|
|
1258
|
+
cell.table.area.start.row === r &&
|
|
1259
|
+
cell.table.area.start.column === c) {
|
|
1260
|
+
|
|
1261
|
+
const area = new Area(cell.table.area.start, cell.table.area.end);
|
|
1262
|
+
const global_count = global_tables.length + 1;
|
|
1263
|
+
const path = `../tables/table${global_count}.xml`;
|
|
1264
|
+
|
|
1265
|
+
// column names must match the text in the column. AND, they
|
|
1266
|
+
// have to be unique. case-insensitive unique! we are not (atm)
|
|
1267
|
+
// enforcing those rules, so we need to enforce them on export.
|
|
1268
|
+
|
|
1269
|
+
// also, values (and column headers) MUST BE STRINGS.
|
|
1270
|
+
|
|
1271
|
+
const columns: string[] = [];
|
|
1272
|
+
for (let i = 0; i < area.columns; i++) {
|
|
1273
|
+
const header = cells.data[r][c + i];
|
|
1274
|
+
let value = '';
|
|
1275
|
+
|
|
1276
|
+
if (header.type !== ValueType.string) {
|
|
1277
|
+
if (typeof header.calculated !== 'undefined') {
|
|
1278
|
+
value = (header.calculated).toString();
|
|
1279
|
+
}
|
|
1280
|
+
else if (typeof header.value !== 'undefined') {
|
|
1281
|
+
value = (header.value).toString();
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
header.type = ValueType.string;
|
|
1285
|
+
header.value = value;
|
|
1286
|
+
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
value = (header.value as string) || '';
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (!value) {
|
|
1293
|
+
value = `Column${i + 1}`;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
let proposed = value;
|
|
1297
|
+
let success = false;
|
|
1298
|
+
let index = 1;
|
|
1299
|
+
|
|
1300
|
+
while (!success) {
|
|
1301
|
+
success = true;
|
|
1302
|
+
inner_loop:
|
|
1303
|
+
for (const check of columns) {
|
|
1304
|
+
if (check.toLowerCase() === proposed.toLowerCase()) {
|
|
1305
|
+
success = false;
|
|
1306
|
+
proposed = `${value}${++index}`;
|
|
1307
|
+
break inner_loop;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
header.value = proposed;
|
|
1313
|
+
columns.push(proposed);
|
|
1314
|
+
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
let footers: TableFooterType[]|undefined = undefined;
|
|
1318
|
+
|
|
1319
|
+
if (cell.table.totals_row) {
|
|
1320
|
+
|
|
1321
|
+
footers = [];
|
|
1322
|
+
|
|
1323
|
+
for (let i = 0; i < area.columns; i++) {
|
|
1324
|
+
const footer = cells.data[area.end.row][area.start.column + i];
|
|
1325
|
+
if (footer.type) {
|
|
1326
|
+
if (footer.type === ValueType.formula) {
|
|
1327
|
+
footers[i] = {
|
|
1328
|
+
type: 'formula',
|
|
1329
|
+
value: (footer.value || '').toString().substring(1),
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
|
|
1334
|
+
if (footer.type !== ValueType.string) {
|
|
1335
|
+
footer.type = ValueType.string;
|
|
1336
|
+
footer.value = footer.value?.toString() || '';
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
footers[i] = {
|
|
1340
|
+
type: 'label',
|
|
1341
|
+
value: footer.value as string,
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// console.info({footer});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// console.info({columns});
|
|
1350
|
+
|
|
1351
|
+
const description: TableDescription = {
|
|
1352
|
+
rel: AddRel(
|
|
1353
|
+
sheet_rels,
|
|
1354
|
+
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table',
|
|
1355
|
+
path,
|
|
1356
|
+
),
|
|
1357
|
+
index: global_count,
|
|
1358
|
+
ref: area.spreadsheet_label,
|
|
1359
|
+
name: `Table${global_count}`,
|
|
1360
|
+
display_name: `Table${global_count}`,
|
|
1361
|
+
totals_row_shown: 0,
|
|
1362
|
+
totals_row_count: cell.table?.totals_row? 1 : 0,
|
|
1363
|
+
columns,
|
|
1364
|
+
footers,
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
if (cell.table.totals_row) {
|
|
1368
|
+
const filter_area = new Area(area.start, {
|
|
1369
|
+
row: area.end.row - 1,
|
|
1370
|
+
column: area.end.column,
|
|
1371
|
+
});
|
|
1372
|
+
description.filterRef = filter_area.spreadsheet_label;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// console.info({description});
|
|
1376
|
+
|
|
1377
|
+
// this list is used to add tables on this sheet
|
|
1378
|
+
tables.push(description);
|
|
1379
|
+
|
|
1380
|
+
// but we also need global references to create the files
|
|
1381
|
+
global_tables.push(description);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// merges
|
|
1385
|
+
|
|
1386
|
+
if (cell.merge_area &&
|
|
1387
|
+
cell.merge_area.start.row === r &&
|
|
1388
|
+
cell.merge_area.start.column === c) {
|
|
1389
|
+
merges.push(new Area(cell.merge_area.start, cell.merge_area.end));
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// links
|
|
1393
|
+
|
|
1394
|
+
if (cell.hyperlink) {
|
|
1395
|
+
const rel = AddRel(sheet_rels,
|
|
1396
|
+
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
|
|
1397
|
+
cell.hyperlink, 'External');
|
|
1398
|
+
hyperlinks.push({
|
|
1399
|
+
rel, target: cell.hyperlink, address: {row: r, column: c},
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (cell.validation && (cell.validation.type === ValidationType.List || cell.validation.type === ValidationType.Range)) {
|
|
1404
|
+
validations.push({
|
|
1405
|
+
address: {row: r, column: c},
|
|
1406
|
+
validation: cell.validation,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// short-circuit here
|
|
1411
|
+
if (cell.type === ValueType.formula && /^=?sparkline\./i.test(cell.value as string)) {
|
|
1412
|
+
sparklines.push({
|
|
1413
|
+
address: {row: r, column: c},
|
|
1414
|
+
formula: cell.value as string,
|
|
1415
|
+
style: cell.style,
|
|
1416
|
+
})
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// push out the extent (reversed)
|
|
1421
|
+
if (c < extent.start.column) {
|
|
1422
|
+
extent.start.column = c;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// update span: end is implicit
|
|
1426
|
+
if (span.start < 0) {
|
|
1427
|
+
span.start = c;
|
|
1428
|
+
}
|
|
1429
|
+
span.end = c;
|
|
1430
|
+
|
|
1431
|
+
// we have to stack the styles? what if there's no cell style?
|
|
1432
|
+
// there are definitely column styles...
|
|
1433
|
+
|
|
1434
|
+
// s is style, index into the style table
|
|
1435
|
+
const s: number|undefined = this.StyleFromCell(sheet, style_cache, r, c, cell.style);
|
|
1436
|
+
|
|
1437
|
+
// v (child element) is the value
|
|
1438
|
+
let v: CellValue = undefined;
|
|
1439
|
+
let t: string|undefined;
|
|
1440
|
+
let f: any; // string|undefined;
|
|
1441
|
+
|
|
1442
|
+
switch (cell.type) {
|
|
1443
|
+
case ValueType.formula:
|
|
1444
|
+
f = this.FormulaText(cell.value as string, cell);
|
|
1445
|
+
switch (cell.calculated_type) {
|
|
1446
|
+
case ValueType.string:
|
|
1447
|
+
v = cell.calculated;
|
|
1448
|
+
t = 'str';
|
|
1449
|
+
break;
|
|
1450
|
+
|
|
1451
|
+
case ValueType.number:
|
|
1452
|
+
v = cell.calculated;
|
|
1453
|
+
break;
|
|
1454
|
+
|
|
1455
|
+
case ValueType.boolean:
|
|
1456
|
+
v = (cell.calculated ? 1 : 0);
|
|
1457
|
+
t = 'b';
|
|
1458
|
+
break;
|
|
1459
|
+
}
|
|
1460
|
+
break;
|
|
1461
|
+
|
|
1462
|
+
case ValueType.string:
|
|
1463
|
+
v = shared_strings.Ensure(cell.value as string);
|
|
1464
|
+
t = 's'; // shared string
|
|
1465
|
+
break;
|
|
1466
|
+
|
|
1467
|
+
case ValueType.number:
|
|
1468
|
+
v = cell.value;
|
|
1469
|
+
break;
|
|
1470
|
+
|
|
1471
|
+
case ValueType.boolean:
|
|
1472
|
+
v = (cell.value ? 1 : 0);
|
|
1473
|
+
t = 'b';
|
|
1474
|
+
break;
|
|
1475
|
+
|
|
1476
|
+
//default:
|
|
1477
|
+
// v = 0;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
if (cell.area && cell.area.start.row === r && cell.area.start.column === c) {
|
|
1481
|
+
if (typeof f === 'string') {
|
|
1482
|
+
f = {
|
|
1483
|
+
t$: f,
|
|
1484
|
+
a$: {
|
|
1485
|
+
t: 'array',
|
|
1486
|
+
ref: cell.area.spreadsheet_label,
|
|
1487
|
+
},
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// zerp
|
|
1493
|
+
const element: any = {
|
|
1494
|
+
a$: {
|
|
1495
|
+
r: Area.CellAddressToLabel({row: r, column: c}),
|
|
1496
|
+
// t,
|
|
1497
|
+
// s,
|
|
1498
|
+
},
|
|
1499
|
+
// v,
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
if (t !== undefined) {
|
|
1503
|
+
element.a$.t = t;
|
|
1504
|
+
}
|
|
1505
|
+
if (s !== undefined) {
|
|
1506
|
+
element.a$.s = s;
|
|
1507
|
+
}
|
|
1508
|
+
if (f !== undefined) {
|
|
1509
|
+
element.f = f;
|
|
1510
|
+
}
|
|
1511
|
+
if (v !== undefined) {
|
|
1512
|
+
element.v = v;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
row.push(element);
|
|
1516
|
+
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
if (row.length) {
|
|
1521
|
+
const row_data: any = {
|
|
1522
|
+
a$: {
|
|
1523
|
+
r: r + 1,
|
|
1524
|
+
spans: `${span.start + 1}:${span.end + 1}`,
|
|
1525
|
+
},
|
|
1526
|
+
c: row,
|
|
1527
|
+
};
|
|
1528
|
+
if (sheet.row_height
|
|
1529
|
+
&& (typeof sheet.row_height[r] === 'number')
|
|
1530
|
+
&& sheet.row_height[r] !== sheet.default_row_height) {
|
|
1531
|
+
|
|
1532
|
+
row_data.a$.customHeight = 1;
|
|
1533
|
+
row_data.a$.ht = sheet.row_height[r] * 3 / 4;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
sheet_data.row.push(row_data);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// --- cols ----------------------------------------------------------------
|
|
1543
|
+
|
|
1544
|
+
// the "cols" element represents column styles and nonstandard column
|
|
1545
|
+
// widths. FIXME: should we put sheet style in here as well? I think so...
|
|
1546
|
+
|
|
1547
|
+
const column_entries: Array<{
|
|
1548
|
+
style?: number;
|
|
1549
|
+
width?: number;
|
|
1550
|
+
index: number;
|
|
1551
|
+
}> = [];
|
|
1552
|
+
|
|
1553
|
+
if (sheet.default_column_width) {
|
|
1554
|
+
dom.worksheet.sheetFormatPr.a$.defaultColWidth = // sheet.default_column_width * one_hundred_pixels / 100;
|
|
1555
|
+
PixelsToColumnWidth(sheet.default_column_width);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
for (let c = 0; c < sheet.columns; c++) {
|
|
1559
|
+
const entry: { style?: number, width?: number, index: number } = { index: c };
|
|
1560
|
+
if (sheet.column_width
|
|
1561
|
+
&& sheet.default_column_width
|
|
1562
|
+
&& (typeof sheet.column_width[c] === 'number')
|
|
1563
|
+
&& sheet.column_width[c] !== sheet.default_column_width) {
|
|
1564
|
+
|
|
1565
|
+
entry.width = // sheet.column_width[c] * one_hundred_pixels / 100;
|
|
1566
|
+
PixelsToColumnWidth(sheet.column_width[c]);
|
|
1567
|
+
|
|
1568
|
+
// console.info("COLUMN", c, 'width', sheet.column_width[c], 'calc?', entry.width, '100p', one_hundred_pixels);
|
|
1569
|
+
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
let style = sheet.column_style[c];
|
|
1573
|
+
if (typeof style === 'number') {
|
|
1574
|
+
style = cell_style_refs[style];
|
|
1575
|
+
if (style) {
|
|
1576
|
+
entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(style));
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
else if (style) {
|
|
1580
|
+
entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(style));
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
//if (sheet.column_style[c]) {
|
|
1584
|
+
// entry.style = style_cache.EnsureStyle(style_cache.StyleOptionsFromProperties(sheet.column_style[c]));
|
|
1585
|
+
//}
|
|
1586
|
+
|
|
1587
|
+
if (entry.style !== undefined || entry.width !== undefined) {
|
|
1588
|
+
column_entries[c] = entry;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// we're short-cutting here, these should be arranged in blocks if
|
|
1593
|
+
// there's overlap. not sure how much of an issue that is though.
|
|
1594
|
+
|
|
1595
|
+
if (column_entries.length) {
|
|
1596
|
+
for (const entry of column_entries) {
|
|
1597
|
+
dom.worksheet.cols.col = column_entries.map(entry => {
|
|
1598
|
+
const a$: any = {
|
|
1599
|
+
min: entry.index + 1,
|
|
1600
|
+
max: entry.index + 1,
|
|
1601
|
+
};
|
|
1602
|
+
if (entry.style !== undefined) {
|
|
1603
|
+
a$.style = entry.style;
|
|
1604
|
+
}
|
|
1605
|
+
if (entry.width !== undefined) {
|
|
1606
|
+
a$.width = entry.width;
|
|
1607
|
+
a$.customWidth = 1;
|
|
1608
|
+
}
|
|
1609
|
+
else {
|
|
1610
|
+
a$.width = // (sheet.default_column_width || 100) / 100 * one_hundred_pixels;
|
|
1611
|
+
PixelsToColumnWidth(sheet.default_column_width || 90);
|
|
1612
|
+
}
|
|
1613
|
+
return {a$};
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
else {
|
|
1618
|
+
delete dom.worksheet.cols;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// --- validation ----------------------------------------------------------
|
|
1622
|
+
|
|
1623
|
+
if (validations.length) {
|
|
1624
|
+
|
|
1625
|
+
dom.worksheet.dataValidations = {
|
|
1626
|
+
a$: { count: validations.length },
|
|
1627
|
+
dataValidation: validations.map(validation => {
|
|
1628
|
+
const entry: any = {
|
|
1629
|
+
a$: {
|
|
1630
|
+
type: 'list',
|
|
1631
|
+
allowBlank: 1,
|
|
1632
|
+
showInputMessage: 1,
|
|
1633
|
+
showErrorMessage: 1,
|
|
1634
|
+
sqref: new Area(validation.address).spreadsheet_label,
|
|
1635
|
+
},
|
|
1636
|
+
};
|
|
1637
|
+
if (validation.validation.type === ValidationType.Range) {
|
|
1638
|
+
|
|
1639
|
+
const range: UnitRange = {
|
|
1640
|
+
id: 0,
|
|
1641
|
+
type: 'range',
|
|
1642
|
+
label: '', position: 0,
|
|
1643
|
+
start:
|
|
1644
|
+
{...validation.validation.area.start, absolute_column: true, absolute_row: true, id: 0, label: '', position: 0, type: 'address', },
|
|
1645
|
+
end:
|
|
1646
|
+
{...validation.validation.area.end, absolute_column: true, absolute_row: true, id: 0, label: '', position: 0, type: 'address', }
|
|
1647
|
+
,
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
if (typeof validation.validation.area.start.sheet_id !== 'undefined') {
|
|
1651
|
+
range.start.sheet = sheet_name_map[validation.validation.area.start.sheet_id];
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/*
|
|
1655
|
+
const area = new Area(
|
|
1656
|
+
{...validation.validation.area.start, absolute_column: true, absolute_row: true},
|
|
1657
|
+
{...validation.validation.area.end, absolute_column: true, absolute_row: true},
|
|
1658
|
+
);
|
|
1659
|
+
|
|
1660
|
+
entry.formula1 = `${area.spreadsheet_label}`;
|
|
1661
|
+
*/
|
|
1662
|
+
entry.formula1 = this.parser.Render(range);
|
|
1663
|
+
|
|
1664
|
+
}
|
|
1665
|
+
else if (validation.validation.type === ValidationType.List) {
|
|
1666
|
+
entry.formula1 = `"${validation.validation.list.join(',')}"`;
|
|
1667
|
+
}
|
|
1668
|
+
return entry;
|
|
1669
|
+
}),
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
}
|
|
1673
|
+
else {
|
|
1674
|
+
delete dom.worksheet.dataValidations;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// --- tables ------------------------------------------------------------
|
|
1678
|
+
|
|
1679
|
+
if (tables.length) {
|
|
1680
|
+
dom.worksheet.tableParts.a$.count = tables.length;
|
|
1681
|
+
dom.worksheet.tableParts.tablePart = tables.map(table => {
|
|
1682
|
+
return {
|
|
1683
|
+
a$: {
|
|
1684
|
+
'r:id': table.rel || '',
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
delete dom.worksheet.tableParts;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// const GUID = () => '{' + uuidv4().toUpperCase() + '}';
|
|
1694
|
+
|
|
1695
|
+
for (const table of tables) {
|
|
1696
|
+
|
|
1697
|
+
const totals_attributes: { totalsRowCount?: number } = {};
|
|
1698
|
+
if (table.totals_row_count) {
|
|
1699
|
+
totals_attributes.totalsRowCount = 1;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const tableColumns: any = {
|
|
1703
|
+
a$: {
|
|
1704
|
+
count: (table.columns || []).length,
|
|
1705
|
+
},
|
|
1706
|
+
tableColumn: [],
|
|
1707
|
+
/*
|
|
1708
|
+
tableColumn: (table.columns||[]).map((column, index) => ({
|
|
1709
|
+
a$: {
|
|
1710
|
+
id: index + 1,
|
|
1711
|
+
// 'xr3:uid': GUID(),
|
|
1712
|
+
name: column || ('Column' + (index + 1)),
|
|
1713
|
+
},
|
|
1714
|
+
})),
|
|
1715
|
+
*/
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
if (table.columns) {
|
|
1719
|
+
for (let i = 0; i < table.columns.length; i++) {
|
|
1720
|
+
const column = table.columns[i];
|
|
1721
|
+
const footer = (table.footers || [])[i];
|
|
1722
|
+
const obj: any = {
|
|
1723
|
+
a$: {
|
|
1724
|
+
id: i + 1,
|
|
1725
|
+
name: column || `Column${i + 1}`,
|
|
1726
|
+
},
|
|
1727
|
+
};
|
|
1728
|
+
|
|
1729
|
+
if (footer) {
|
|
1730
|
+
if (footer.type === 'label') {
|
|
1731
|
+
obj.a$.totalsRowLabel = footer.value;
|
|
1732
|
+
}
|
|
1733
|
+
else if (footer.type === 'formula') {
|
|
1734
|
+
obj.a$.totalsRowFunction = 'custom';
|
|
1735
|
+
obj.totalsRowFormula = footer.value;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
tableColumns.tableColumn.push(obj);
|
|
1740
|
+
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
const table_dom = {
|
|
1745
|
+
table: {
|
|
1746
|
+
a$: {
|
|
1747
|
+
xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
1748
|
+
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
|
|
1749
|
+
'mc:Ignorable': 'xr xr3',
|
|
1750
|
+
'xmlns:xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision',
|
|
1751
|
+
'xmlns:xr3': 'http://schemas.microsoft.com/office/spreadsheetml/2016/revision3',
|
|
1752
|
+
id: table.index || 0,
|
|
1753
|
+
// 'xr:uid': '{676B775D-AA84-41B6-8450-8515A94D2D7B}',
|
|
1754
|
+
name: table.name,
|
|
1755
|
+
displayName: table.display_name,
|
|
1756
|
+
...totals_attributes,
|
|
1757
|
+
ref: table.ref,
|
|
1758
|
+
// 'xr:uid': GUID(),
|
|
1759
|
+
},
|
|
1760
|
+
|
|
1761
|
+
autoFilter: {
|
|
1762
|
+
a$: {
|
|
1763
|
+
ref: table.filterRef || table.ref,
|
|
1764
|
+
// 'xr:uid': GUID(),
|
|
1765
|
+
},
|
|
1766
|
+
},
|
|
1767
|
+
|
|
1768
|
+
tableColumns,
|
|
1769
|
+
|
|
1770
|
+
tableStyleInfo: {
|
|
1771
|
+
a$: {
|
|
1772
|
+
name: 'TableStyleMedium2',
|
|
1773
|
+
showFirstColumn: 0,
|
|
1774
|
+
showLastColumn: 0,
|
|
1775
|
+
showRowStripes: 1,
|
|
1776
|
+
showColumnStripes: 0,
|
|
1777
|
+
},
|
|
1778
|
+
},
|
|
1779
|
+
|
|
1780
|
+
},
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(table_dom);
|
|
1784
|
+
await this.zip?.file(`xl/tables/table${table.index}.xml`, xml);
|
|
1785
|
+
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// --- merges ------------------------------------------------------------
|
|
1789
|
+
|
|
1790
|
+
if (merges.length) {
|
|
1791
|
+
dom.worksheet.mergeCells.a$.count = merges.length;
|
|
1792
|
+
dom.worksheet.mergeCells.mergeCell = merges.map(merge => {
|
|
1793
|
+
return {
|
|
1794
|
+
a$: { ref: merge.spreadsheet_label }
|
|
1795
|
+
};
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
else {
|
|
1799
|
+
delete dom.worksheet.mergeCells;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// --- hyperlinks --------------------------------------------------------
|
|
1803
|
+
|
|
1804
|
+
if (hyperlinks.length) {
|
|
1805
|
+
dom.worksheet.hyperlinks.hyperlink = hyperlinks.map(link => {
|
|
1806
|
+
return {
|
|
1807
|
+
a$: {
|
|
1808
|
+
'r:id': link.rel,
|
|
1809
|
+
ref: new Area(link.address).spreadsheet_label,
|
|
1810
|
+
'xr:uid': '{0C6B7792-7EA0-4932-BF15-D49C453C565D}',
|
|
1811
|
+
},
|
|
1812
|
+
};
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
else {
|
|
1816
|
+
delete dom.worksheet.hyperlinks;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// --- sparklines --------------------------------------------------------
|
|
1820
|
+
|
|
1821
|
+
if (sparklines.length) {
|
|
1822
|
+
dom.worksheet.extLst.ext['x14:sparklineGroups'] = {
|
|
1823
|
+
a$: {
|
|
1824
|
+
'xmlns:xm': 'http://schemas.microsoft.com/office/excel/2006/main',
|
|
1825
|
+
},
|
|
1826
|
+
'x14:sparklineGroup': sparklines.map(sparkline => {
|
|
1827
|
+
|
|
1828
|
+
const result = this.parser.Parse(sparkline.formula);
|
|
1829
|
+
let source = '';
|
|
1830
|
+
if (result.expression
|
|
1831
|
+
&& result.expression.type === 'call'
|
|
1832
|
+
&& result.expression.args.length > 0) {
|
|
1833
|
+
const arg = result.expression.args[0];
|
|
1834
|
+
if (arg.type === 'range' || arg.type === 'address') {
|
|
1835
|
+
|
|
1836
|
+
const start = (arg.type === 'range') ? arg.start : arg;
|
|
1837
|
+
if (!start.sheet) {
|
|
1838
|
+
if (typeof start.sheet_id !== 'undefined') {
|
|
1839
|
+
start.sheet = sheet_name_map[start.sheet_id];
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
start.sheet = sheet.name;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
source = this.parser.Render(arg);
|
|
1846
|
+
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
const a$: any = {
|
|
1851
|
+
displayEmptyCellsAs: 'gap',
|
|
1852
|
+
};
|
|
1853
|
+
|
|
1854
|
+
if (/column/i.test(sparkline.formula)) {
|
|
1855
|
+
a$.type = 'column';
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
return {
|
|
1859
|
+
a$,
|
|
1860
|
+
'x14:colorSeries': { a$: { rgb: 'FF376092' }},
|
|
1861
|
+
'x14:sparklines': {
|
|
1862
|
+
'x14:sparkline': {
|
|
1863
|
+
'xm:f': source,
|
|
1864
|
+
'xm:sqref': new Area(sparkline.address).spreadsheet_label,
|
|
1865
|
+
},
|
|
1866
|
+
},
|
|
1867
|
+
}
|
|
1868
|
+
}),
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
else {
|
|
1872
|
+
delete dom.worksheet.extLst;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
dom.worksheet.sheetData = sheet_data;
|
|
1876
|
+
|
|
1877
|
+
// --- charts ------------------------------------------------------------
|
|
1878
|
+
|
|
1879
|
+
const charts = this.ParseCharts(sheet);
|
|
1880
|
+
const images = this.ParseImages(sheet);
|
|
1881
|
+
|
|
1882
|
+
// if a sheet has one or more charts, it has a single drawing. for a
|
|
1883
|
+
// drawing, we need
|
|
1884
|
+
//
|
|
1885
|
+
// (1) entry in sheet xml
|
|
1886
|
+
// (2) drawing xml file
|
|
1887
|
+
// (3) relationship sheet -> drawing
|
|
1888
|
+
// (4) drawing rels file (for charts, later)
|
|
1889
|
+
// (5) entry in [ContentTypes]
|
|
1890
|
+
//
|
|
1891
|
+
// each chart in the drawing then needs
|
|
1892
|
+
//
|
|
1893
|
+
// (1) entry in drawing file
|
|
1894
|
+
// (2) chart xml file
|
|
1895
|
+
// (3) relationship drawing -> chart
|
|
1896
|
+
// (4) chart/colors xml file
|
|
1897
|
+
// (5) chart/style xml file
|
|
1898
|
+
// (6) chart rels file
|
|
1899
|
+
// (7) relationship chart -> colors
|
|
1900
|
+
// (8) relationship chart -> style
|
|
1901
|
+
// (9) entry in [ContentTypes]
|
|
1902
|
+
//
|
|
1903
|
+
// check: we can get away with not including colors or style, which
|
|
1904
|
+
// will revert to defaults -- let's do that for the time being if we can
|
|
1905
|
+
//
|
|
1906
|
+
// merging in images, which use the same drawing (and in a single
|
|
1907
|
+
// sheet, a single drawing holds both charts and images).
|
|
1908
|
+
|
|
1909
|
+
if (charts.length || images.length) {
|
|
1910
|
+
|
|
1911
|
+
const drawing = new Drawing();
|
|
1912
|
+
|
|
1913
|
+
for (const chart of charts) {
|
|
1914
|
+
drawing.AddChart(chart.options, chart.anchor);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
for (const image of images) {
|
|
1918
|
+
drawing.AddImage(image.options, image.anchor);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
for (const {image} of drawing.images) {
|
|
1922
|
+
// console.info({image}, `xl/media/image${image.index}.${image.extension}`);
|
|
1923
|
+
await this.zip?.file(`xl/media/image${image.index}.${image.extension}`, image.options.data||'', {
|
|
1924
|
+
base64: image.options.encoding === 'base64'
|
|
1925
|
+
});
|
|
1926
|
+
// no media rels!
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
for (const {chart} of drawing.charts) {
|
|
1930
|
+
const dom = chart.toJSON();
|
|
1931
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
|
|
1932
|
+
await this.zip?.file(`xl/charts/chart${chart.index}.xml`, xml);
|
|
1933
|
+
await this.WriteRels(chart.relationships, `xl/charts/_rels/chart${chart.index}.xml.rels`);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
await this.WriteRels(drawing.relationships, `xl/drawings/_rels/drawing${drawing.index}.xml.rels`);
|
|
1937
|
+
|
|
1938
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(drawing.toJSON());
|
|
1939
|
+
|
|
1940
|
+
await this.zip?.file(`xl/drawings/drawing${drawing.index}.xml`, xml);
|
|
1941
|
+
|
|
1942
|
+
drawings.push(drawing); // for [ContentTypes]
|
|
1943
|
+
|
|
1944
|
+
const drawing_rel = AddRel(sheet_rels,
|
|
1945
|
+
`http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing`,
|
|
1946
|
+
`../drawings/drawing${drawing.index}.xml`);
|
|
1947
|
+
|
|
1948
|
+
dom.worksheet.drawing = {
|
|
1949
|
+
a$: {
|
|
1950
|
+
'r:id': drawing_rel,
|
|
1951
|
+
},
|
|
1952
|
+
};
|
|
1953
|
+
|
|
1954
|
+
}
|
|
1955
|
+
else {
|
|
1956
|
+
delete dom.worksheet.drawing;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// --- end? --------------------------------------------------------------
|
|
1960
|
+
|
|
1961
|
+
// it seems like chrome, at least, will maintain order. but this is
|
|
1962
|
+
// not gauranteed and we can't rely on it. the best thing to do might
|
|
1963
|
+
// be to use the renderer on blocks and then assemble the blocks ourselves.
|
|
1964
|
+
|
|
1965
|
+
dom.worksheet.dimension.a$.ref = new Area(extent.start, extent.end).spreadsheet_label;
|
|
1966
|
+
const xml = XMLDeclaration + this.xmlbuilder1.build(dom);
|
|
1967
|
+
|
|
1968
|
+
// write this into the file
|
|
1969
|
+
|
|
1970
|
+
await this.zip?.file(`xl/worksheets/sheet${sheet_index + 1}.xml`, xml);
|
|
1971
|
+
if (Object.keys(sheet_rels).length) {
|
|
1972
|
+
this.WriteRels(sheet_rels, `xl/worksheets/_rels/sheet${sheet_index + 1}.xml.rels`);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
|
|
1978
|
+
|
|
1979
|
+
// these are workbook global so after all sheets are done
|
|
1980
|
+
|
|
1981
|
+
await this.WriteSharedStrings(shared_strings);
|
|
1982
|
+
await this.WriteStyleCache(style_cache);
|
|
1983
|
+
|
|
1984
|
+
// now have to write/update
|
|
1985
|
+
//
|
|
1986
|
+
// (1) contentTypes
|
|
1987
|
+
// (2) workbook.xml
|
|
1988
|
+
// (3) workbook.xml.rels
|
|
1989
|
+
//
|
|
1990
|
+
|
|
1991
|
+
const workbook_rels: RelationshipMap = {};
|
|
1992
|
+
AddRel(workbook_rels, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', 'styles.xml');
|
|
1993
|
+
AddRel(workbook_rels, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', 'theme/theme1.xml');
|
|
1994
|
+
AddRel(workbook_rels, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', 'sharedStrings.xml');
|
|
1995
|
+
|
|
1996
|
+
const worksheet_rels_map = source.sheet_data.map((sheet, index) => AddRel(
|
|
1997
|
+
workbook_rels, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',
|
|
1998
|
+
`worksheets/sheet${index + 1}.xml`,
|
|
1999
|
+
));
|
|
2000
|
+
|
|
2001
|
+
await this.WriteRels(workbook_rels, `xl/_rels/workbook.xml.rels`);
|
|
2002
|
+
|
|
2003
|
+
let definedNames: any = {definedName: []};
|
|
2004
|
+
if (source.named_ranges) {
|
|
2005
|
+
const keys = Object.keys(source.named_ranges);
|
|
2006
|
+
for (const key of keys) {
|
|
2007
|
+
let sheet_name = '';
|
|
2008
|
+
const area = new Area(source.named_ranges[key].start, source.named_ranges[key].end);
|
|
2009
|
+
area.start.absolute_column = area.start.absolute_row = true;
|
|
2010
|
+
area.end.absolute_column = area.end.absolute_row = true;
|
|
2011
|
+
|
|
2012
|
+
if (area.start.sheet_id) {
|
|
2013
|
+
for (const sheet of source.sheet_data) {
|
|
2014
|
+
if (sheet.id === area.start.sheet_id) {
|
|
2015
|
+
sheet_name = sheet.name || '';
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
if (sheet_name) {
|
|
2022
|
+
if (QuotedSheetNameRegex.test(sheet_name)) {
|
|
2023
|
+
sheet_name = `'${sheet_name}'`;
|
|
2024
|
+
}
|
|
2025
|
+
sheet_name += '!';
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
// console.info({key, area, lx: area.spreadsheet_label, sheet_name });
|
|
2029
|
+
definedNames.definedName.push({
|
|
2030
|
+
a$: { name: key },
|
|
2031
|
+
t$: sheet_name + area.spreadsheet_label,
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (source.named_expressions) {
|
|
2037
|
+
for (const entry of source.named_expressions) {
|
|
2038
|
+
definedNames.definedName.push({
|
|
2039
|
+
a$: { name: entry.name },
|
|
2040
|
+
t$: entry.expression,
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
if (!definedNames.definedName.length) {
|
|
2045
|
+
definedNames = undefined;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
const workbook_dom: any = {
|
|
2049
|
+
workbook: {
|
|
2050
|
+
a$: {
|
|
2051
|
+
'xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
|
2052
|
+
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
2053
|
+
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
|
|
2054
|
+
'mc:Ignorable': 'x15 xr xr6 xr10 xr2',
|
|
2055
|
+
'xmlns:x15': 'http://schemas.microsoft.com/office/spreadsheetml/2010/11/main',
|
|
2056
|
+
'xmlns:xr': 'http://schemas.microsoft.com/office/spreadsheetml/2014/revision',
|
|
2057
|
+
'xmlns:xr6': 'http://schemas.microsoft.com/office/spreadsheetml/2016/revision6',
|
|
2058
|
+
'xmlns:xr10': 'http://schemas.microsoft.com/office/spreadsheetml/2016/revision10',
|
|
2059
|
+
'xmlns:xr2': 'http://schemas.microsoft.com/office/spreadsheetml/2015/revision2',
|
|
2060
|
+
},
|
|
2061
|
+
workbookPr: {
|
|
2062
|
+
a$: {
|
|
2063
|
+
defaultThemeVersion: '166925',
|
|
2064
|
+
},
|
|
2065
|
+
},
|
|
2066
|
+
bookViews: {
|
|
2067
|
+
workbookView: {
|
|
2068
|
+
a$: {
|
|
2069
|
+
activeTab: (active_sheet || 0),
|
|
2070
|
+
},
|
|
2071
|
+
},
|
|
2072
|
+
},
|
|
2073
|
+
sheets: {
|
|
2074
|
+
sheet: source.sheet_data.map((sheet, index) => {
|
|
2075
|
+
const a$: any = {
|
|
2076
|
+
name: sheet.name || `Sheet${index + 1}`,
|
|
2077
|
+
sheetId: index + 1,
|
|
2078
|
+
'r:id': worksheet_rels_map[index],
|
|
2079
|
+
};
|
|
2080
|
+
if (sheet.visible === false) {
|
|
2081
|
+
a$.state = 'hidden';
|
|
2082
|
+
}
|
|
2083
|
+
return { a$ };
|
|
2084
|
+
}),
|
|
2085
|
+
},
|
|
2086
|
+
definedNames,
|
|
2087
|
+
},
|
|
2088
|
+
};
|
|
2089
|
+
|
|
2090
|
+
const workbook_xml = XMLDeclaration + this.xmlbuilder1.build(workbook_dom);
|
|
2091
|
+
// console.info(workbook_xml);
|
|
2092
|
+
await this.zip?.file(`xl/workbook.xml`, workbook_xml);
|
|
2093
|
+
|
|
2094
|
+
// const extensions: Array<{ Extension: string, ContentType: string }> = [];
|
|
2095
|
+
const extensions: Record<string, string> = {};
|
|
2096
|
+
for (const drawing of drawings) {
|
|
2097
|
+
for (const image of drawing.images) {
|
|
2098
|
+
switch (image.image.extension) {
|
|
2099
|
+
case 'gif':
|
|
2100
|
+
case 'png':
|
|
2101
|
+
case 'jpeg':
|
|
2102
|
+
extensions[image.image.extension] = 'image/' + image.image.extension;
|
|
2103
|
+
break;
|
|
2104
|
+
|
|
2105
|
+
case 'svg':
|
|
2106
|
+
extensions['svg'] = 'image/svg+xml';
|
|
2107
|
+
break;
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
const content_types_dom: any = {
|
|
2113
|
+
Types: {
|
|
2114
|
+
a$: {
|
|
2115
|
+
'xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types',
|
|
2116
|
+
},
|
|
2117
|
+
Default: [
|
|
2118
|
+
{a$: { Extension: 'rels', ContentType: 'application/vnd.openxmlformats-package.relationships+xml' }},
|
|
2119
|
+
{a$: { Extension: 'xml', ContentType: 'application/xml' }},
|
|
2120
|
+
...Object.keys(extensions).map(key => ({
|
|
2121
|
+
a$: { Extension: key, ContentType: extensions[key] },
|
|
2122
|
+
})),
|
|
2123
|
+
],
|
|
2124
|
+
Override: [
|
|
2125
|
+
{ a$: { PartName: '/xl/workbook.xml', ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml' }},
|
|
2126
|
+
|
|
2127
|
+
// sheets
|
|
2128
|
+
...source.sheet_data.map((sheet, index) => {
|
|
2129
|
+
return { a$: {
|
|
2130
|
+
ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml',
|
|
2131
|
+
PartName: `/xl/worksheets/sheet${index + 1}.xml`,
|
|
2132
|
+
}};
|
|
2133
|
+
}),
|
|
2134
|
+
|
|
2135
|
+
// charts and drawings
|
|
2136
|
+
...drawings.reduce((a: any, drawing) => {
|
|
2137
|
+
return a.concat([
|
|
2138
|
+
...drawing.charts.map(chart => {
|
|
2139
|
+
return { a$: {
|
|
2140
|
+
ContentType: 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml',
|
|
2141
|
+
PartName: `/xl/charts/chart${chart.chart.index}.xml`,
|
|
2142
|
+
}};
|
|
2143
|
+
}),
|
|
2144
|
+
{ a$: {
|
|
2145
|
+
ContentType: 'application/vnd.openxmlformats-officedocument.drawing+xml',
|
|
2146
|
+
PartName: `/xl/drawings/drawing${drawing.index}.xml`,
|
|
2147
|
+
}},
|
|
2148
|
+
]);
|
|
2149
|
+
}, []),
|
|
2150
|
+
|
|
2151
|
+
{ a$: { PartName: '/xl/theme/theme1.xml', ContentType: 'application/vnd.openxmlformats-officedocument.theme+xml' }},
|
|
2152
|
+
{ a$: { PartName: '/xl/styles.xml', ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml' }},
|
|
2153
|
+
{ a$: { PartName: '/xl/sharedStrings.xml', ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml' }},
|
|
2154
|
+
|
|
2155
|
+
// tables
|
|
2156
|
+
...global_tables.map(table => {
|
|
2157
|
+
return { a$: {
|
|
2158
|
+
ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml',
|
|
2159
|
+
PartName: `/xl/tables/table${table.index || 0}.xml`,
|
|
2160
|
+
}};
|
|
2161
|
+
}),
|
|
2162
|
+
|
|
2163
|
+
{ a$: { PartName: '/docProps/core.xml', ContentType: 'application/vnd.openxmlformats-package.core-properties+xml' }},
|
|
2164
|
+
{ a$: { PartName: '/docProps/app.xml', ContentType: 'application/vnd.openxmlformats-officedocument.extended-properties+xml' }},
|
|
2165
|
+
|
|
2166
|
+
],
|
|
2167
|
+
},
|
|
2168
|
+
};
|
|
2169
|
+
|
|
2170
|
+
const content_types_xml = XMLDeclaration + this.xmlbuilder1.build(content_types_dom);
|
|
2171
|
+
// console.info(content_types_xml);
|
|
2172
|
+
await this.zip?.file(`[Content_Types].xml`, content_types_xml);
|
|
2173
|
+
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
/** zip -> binary string */
|
|
2177
|
+
public async AsBinaryString(compression_level?: number) {
|
|
2178
|
+
if (!this.zip) {
|
|
2179
|
+
throw new Error('missing zip');
|
|
2180
|
+
}
|
|
2181
|
+
const opts: JSZip.JSZipGeneratorOptions = { type: 'binarystring' };
|
|
2182
|
+
if (typeof compression_level !== 'undefined') {
|
|
2183
|
+
opts.compression = 'DEFLATE';
|
|
2184
|
+
opts.compressionOptions = {level: compression_level };
|
|
2185
|
+
}
|
|
2186
|
+
const output = await this.zip.generateAsync(opts);
|
|
2187
|
+
return output;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
/** zip -> blob */
|
|
2191
|
+
public async AsBlob(compression_level?: number) {
|
|
2192
|
+
if (!this.zip) {
|
|
2193
|
+
throw new Error('missing zip');
|
|
2194
|
+
}
|
|
2195
|
+
const opts: JSZip.JSZipGeneratorOptions = { type: 'blob' };
|
|
2196
|
+
if (typeof compression_level !== 'undefined') {
|
|
2197
|
+
opts.compression = 'DEFLATE';
|
|
2198
|
+
opts.compressionOptions = {level: compression_level };
|
|
2199
|
+
}
|
|
2200
|
+
const output = await this.zip.generateAsync(opts);
|
|
2201
|
+
return output;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
}
|