@cj-tech-master/excelts 4.0.4 → 4.1.0
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/dist/browser/modules/excel/cell.js +39 -1
- package/dist/browser/modules/excel/enums.d.ts +2 -1
- package/dist/browser/modules/excel/enums.js +2 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +1 -0
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +19 -1
- package/dist/browser/modules/excel/table.d.ts +6 -2
- package/dist/browser/modules/excel/table.js +33 -5
- package/dist/browser/modules/excel/types.d.ts +5 -1
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +2 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.js +4 -2
- package/dist/browser/modules/excel/xlsx/rel-type.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/rel-type.js +2 -1
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +7 -0
- package/dist/browser/modules/excel/xlsx/xform/core/feature-property-bag-xform.d.ts +8 -0
- package/dist/browser/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +36 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
- package/dist/browser/modules/excel/xlsx/xform/style/style-xform.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +11 -0
- package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +2 -0
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +19 -0
- package/dist/cjs/modules/excel/cell.js +39 -1
- package/dist/cjs/modules/excel/enums.js +2 -1
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +19 -1
- package/dist/cjs/modules/excel/table.js +33 -5
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +4 -2
- package/dist/cjs/modules/excel/xlsx/rel-type.js +2 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +7 -0
- package/dist/cjs/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +39 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
- package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +11 -0
- package/dist/cjs/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +19 -0
- package/dist/esm/modules/excel/cell.js +39 -1
- package/dist/esm/modules/excel/enums.js +2 -1
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +19 -1
- package/dist/esm/modules/excel/table.js +33 -5
- package/dist/esm/modules/excel/utils/ooxml-paths.js +4 -2
- package/dist/esm/modules/excel/xlsx/rel-type.js +2 -1
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +7 -0
- package/dist/esm/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +36 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
- package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +11 -0
- package/dist/esm/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +19 -0
- package/dist/iife/excelts.iife.js +165 -12
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +28 -28
- package/dist/types/modules/excel/enums.d.ts +2 -1
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -0
- package/dist/types/modules/excel/table.d.ts +6 -2
- package/dist/types/modules/excel/types.d.ts +5 -1
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/rel-type.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/core/feature-property-bag-xform.d.ts +8 -0
- package/dist/types/modules/excel/xlsx/xform/style/style-xform.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/xform/style/styles-xform.d.ts +2 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +2 -0
- package/package.json +8 -8
|
@@ -23,6 +23,7 @@ const content_types_xform_1 = require("../xlsx/xform/core/content-types-xform.js
|
|
|
23
23
|
const app_xform_1 = require("../xlsx/xform/core/app-xform.js");
|
|
24
24
|
const workbook_xform_1 = require("../xlsx/xform/book/workbook-xform.js");
|
|
25
25
|
const shared_strings_xform_1 = require("../xlsx/xform/strings/shared-strings-xform.js");
|
|
26
|
+
const feature_property_bag_xform_1 = require("../xlsx/xform/core/feature-property-bag-xform.js");
|
|
26
27
|
const theme1_1 = require("../xlsx/xml/theme1.js");
|
|
27
28
|
const _stream_1 = require("../../stream/index.js");
|
|
28
29
|
const ooxml_paths_1 = require("../utils/ooxml-paths.js");
|
|
@@ -137,6 +138,7 @@ class WorkbookWriterBase {
|
|
|
137
138
|
this.addCore(),
|
|
138
139
|
this.addSharedStrings(),
|
|
139
140
|
this.addStyles(),
|
|
141
|
+
this.addFeaturePropertyBag(),
|
|
140
142
|
this.addWorkbookRels()
|
|
141
143
|
]);
|
|
142
144
|
await this.addWorkbook();
|
|
@@ -229,7 +231,8 @@ class WorkbookWriterBase {
|
|
|
229
231
|
worksheets: this._worksheets.filter(Boolean),
|
|
230
232
|
sharedStrings: this.sharedStrings,
|
|
231
233
|
commentRefs: this.commentRefs,
|
|
232
|
-
media: this.media
|
|
234
|
+
media: this.media,
|
|
235
|
+
hasCheckboxes: this.styles.hasCheckboxes
|
|
233
236
|
};
|
|
234
237
|
const xform = new content_types_xform_1.ContentTypesXform();
|
|
235
238
|
this._addFile(xform.toXml(model), ooxml_paths_1.OOXML_PATHS.contentTypes);
|
|
@@ -283,6 +286,13 @@ class WorkbookWriterBase {
|
|
|
283
286
|
}
|
|
284
287
|
return Promise.resolve();
|
|
285
288
|
}
|
|
289
|
+
addFeaturePropertyBag() {
|
|
290
|
+
if (this.styles.hasCheckboxes) {
|
|
291
|
+
const xform = new feature_property_bag_xform_1.FeaturePropertyBagXform();
|
|
292
|
+
this._addFile(xform.toXml({}), ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag);
|
|
293
|
+
}
|
|
294
|
+
return Promise.resolve();
|
|
295
|
+
}
|
|
286
296
|
addWorkbookRels() {
|
|
287
297
|
let count = 1;
|
|
288
298
|
const relationships = [
|
|
@@ -296,6 +306,14 @@ class WorkbookWriterBase {
|
|
|
296
306
|
Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookSharedStrings
|
|
297
307
|
});
|
|
298
308
|
}
|
|
309
|
+
// Add FeaturePropertyBag relationship if checkboxes are used
|
|
310
|
+
if (this.styles.hasCheckboxes) {
|
|
311
|
+
relationships.push({
|
|
312
|
+
Id: `rId${count++}`,
|
|
313
|
+
Type: rel_type_1.RelType.FeaturePropertyBag,
|
|
314
|
+
Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookFeaturePropertyBag
|
|
315
|
+
});
|
|
316
|
+
}
|
|
299
317
|
this._worksheets.forEach(ws => {
|
|
300
318
|
if (ws) {
|
|
301
319
|
ws.rId = `rId${count++}`;
|
|
@@ -60,6 +60,26 @@ class Table {
|
|
|
60
60
|
this.worksheet = worksheet;
|
|
61
61
|
if (table) {
|
|
62
62
|
this.table = table;
|
|
63
|
+
// When loading tables from xlsx, Excel stores table ranges and cell values in the worksheet,
|
|
64
|
+
// but may not embed row data into the table definition. Hydrate rows from the worksheet so
|
|
65
|
+
// table mutations (e.g. addRow) can correctly expand table ranges and serialize.
|
|
66
|
+
if (Array.isArray(table.rows) && table.rows.length === 0 && table.tableRef) {
|
|
67
|
+
const decoded = col_cache_1.colCache.decode(table.tableRef);
|
|
68
|
+
if ("dimensions" in decoded) {
|
|
69
|
+
const startRow = decoded.top + (table.headerRow === false ? 0 : 1);
|
|
70
|
+
const endRow = decoded.bottom - (table.totalsRow === true ? 1 : 0);
|
|
71
|
+
if (endRow >= startRow) {
|
|
72
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
73
|
+
const row = worksheet.getRow(r);
|
|
74
|
+
const values = [];
|
|
75
|
+
for (let c = decoded.left; c <= decoded.right; c++) {
|
|
76
|
+
values.push(row.getCell(c).value);
|
|
77
|
+
}
|
|
78
|
+
table.rows.push(values);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
63
83
|
// check things are ok first
|
|
64
84
|
this.validate();
|
|
65
85
|
this.store();
|
|
@@ -136,9 +156,10 @@ class Table {
|
|
|
136
156
|
const { row, col } = table.tl;
|
|
137
157
|
assert(row > 0, "Table must be on valid row");
|
|
138
158
|
assert(col > 0, "Table must be on valid col");
|
|
139
|
-
const { width,
|
|
140
|
-
// autoFilterRef is a range that
|
|
141
|
-
|
|
159
|
+
const { width, tableHeight } = this;
|
|
160
|
+
// autoFilterRef is a single-row range that targets the header row only.
|
|
161
|
+
// Excel uses this for filter buttons; including data rows can break filter rendering.
|
|
162
|
+
table.autoFilterRef = col_cache_1.colCache.encode(row, col, row, col + width - 1);
|
|
142
163
|
// tableRef is a range that includes optional headers and totals
|
|
143
164
|
table.tableRef = col_cache_1.colCache.encode(row, col, row + tableHeight - 1, col + width - 1);
|
|
144
165
|
table.columns.forEach((column, i) => {
|
|
@@ -307,8 +328,9 @@ class Table {
|
|
|
307
328
|
}
|
|
308
329
|
}
|
|
309
330
|
this.store();
|
|
331
|
+
this._cache = undefined;
|
|
310
332
|
}
|
|
311
|
-
addRow(values, rowNumber) {
|
|
333
|
+
addRow(values, rowNumber, options) {
|
|
312
334
|
// Add a row of data, either insert at rowNumber or append
|
|
313
335
|
this.cacheState();
|
|
314
336
|
if (rowNumber === undefined) {
|
|
@@ -317,11 +339,17 @@ class Table {
|
|
|
317
339
|
else {
|
|
318
340
|
this.table.rows.splice(rowNumber, 0, values);
|
|
319
341
|
}
|
|
342
|
+
if (options?.commit !== false) {
|
|
343
|
+
this.commit();
|
|
344
|
+
}
|
|
320
345
|
}
|
|
321
|
-
removeRows(rowIndex, count = 1) {
|
|
346
|
+
removeRows(rowIndex, count = 1, options) {
|
|
322
347
|
// Remove a rows of data
|
|
323
348
|
this.cacheState();
|
|
324
349
|
this.table.rows.splice(rowIndex, count);
|
|
350
|
+
if (options?.commit !== false) {
|
|
351
|
+
this.commit();
|
|
352
|
+
}
|
|
325
353
|
}
|
|
326
354
|
getColumn(colIndex) {
|
|
327
355
|
const column = this.table.columns[colIndex];
|
|
@@ -58,7 +58,8 @@ exports.OOXML_PATHS = {
|
|
|
58
58
|
xlWorkbookRels: "xl/_rels/workbook.xml.rels",
|
|
59
59
|
xlSharedStrings: "xl/sharedStrings.xml",
|
|
60
60
|
xlStyles: "xl/styles.xml",
|
|
61
|
-
xlTheme1: "xl/theme/theme1.xml"
|
|
61
|
+
xlTheme1: "xl/theme/theme1.xml",
|
|
62
|
+
xlFeaturePropertyBag: "xl/featurePropertyBag/featurePropertyBag.xml"
|
|
62
63
|
};
|
|
63
64
|
const worksheetXmlRegex = /^xl\/worksheets\/sheet(\d+)[.]xml$/;
|
|
64
65
|
const worksheetRelsXmlRegex = /^xl\/worksheets\/_rels\/sheet(\d+)[.]xml[.]rels$/;
|
|
@@ -213,7 +214,8 @@ exports.OOXML_REL_TARGETS = {
|
|
|
213
214
|
// Targets inside xl/_rels/workbook.xml.rels (base: xl/)
|
|
214
215
|
workbookStyles: "styles.xml",
|
|
215
216
|
workbookSharedStrings: "sharedStrings.xml",
|
|
216
|
-
workbookTheme1: "theme/theme1.xml"
|
|
217
|
+
workbookTheme1: "theme/theme1.xml",
|
|
218
|
+
workbookFeaturePropertyBag: "featurePropertyBag/featurePropertyBag.xml"
|
|
217
219
|
};
|
|
218
220
|
function pivotCacheDefinitionRelTargetFromWorkbook(n) {
|
|
219
221
|
// Target inside xl/_rels/workbook.xml.rels (base: xl/)
|
|
@@ -17,6 +17,7 @@ const RelType = {
|
|
|
17
17
|
Table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
|
|
18
18
|
PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
|
|
19
19
|
PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
|
|
20
|
-
PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
|
|
20
|
+
PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
|
|
21
|
+
FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
|
|
21
22
|
};
|
|
22
23
|
exports.RelType = RelType;
|
|
@@ -66,6 +66,13 @@ class ContentTypesXform extends base_xform_1.BaseXform {
|
|
|
66
66
|
PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.xlStyles),
|
|
67
67
|
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
|
|
68
68
|
});
|
|
69
|
+
// Add FeaturePropertyBag if checkboxes are used
|
|
70
|
+
if (model.hasCheckboxes) {
|
|
71
|
+
xmlStream.leafNode("Override", {
|
|
72
|
+
PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag),
|
|
73
|
+
ContentType: "application/vnd.ms-excel.featurepropertybag+xml"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
69
76
|
const hasSharedStrings = model.sharedStrings && model.sharedStrings.count;
|
|
70
77
|
if (hasSharedStrings) {
|
|
71
78
|
xmlStream.leafNode("Override", {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FeaturePropertyBagXform = void 0;
|
|
4
|
+
const base_xform_1 = require("../base-xform.js");
|
|
5
|
+
// FeaturePropertyBag is used to enable checkbox functionality
|
|
6
|
+
// This is a static XML file that MS Excel requires for checkboxes to work
|
|
7
|
+
class FeaturePropertyBagXform extends base_xform_1.BaseXform {
|
|
8
|
+
render(xmlStream) {
|
|
9
|
+
xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
|
|
10
|
+
xmlStream.openNode("FeaturePropertyBags", {
|
|
11
|
+
xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
|
|
12
|
+
});
|
|
13
|
+
// Checkbox feature
|
|
14
|
+
xmlStream.leafNode("bag", { type: "Checkbox" });
|
|
15
|
+
// XFControls bag
|
|
16
|
+
xmlStream.openNode("bag", { type: "XFControls" });
|
|
17
|
+
xmlStream.leafNode("bagId", { k: "CellControl" }, "0");
|
|
18
|
+
xmlStream.closeNode();
|
|
19
|
+
// XFComplement bag
|
|
20
|
+
xmlStream.openNode("bag", { type: "XFComplement" });
|
|
21
|
+
xmlStream.leafNode("bagId", { k: "XFControls" }, "1");
|
|
22
|
+
xmlStream.closeNode();
|
|
23
|
+
// XFComplements bag
|
|
24
|
+
xmlStream.openNode("bag", { type: "XFComplements", extRef: "XFComplementsMapperExtRef" });
|
|
25
|
+
xmlStream.openNode("a", { k: "MappedFeaturePropertyBags" });
|
|
26
|
+
xmlStream.leafNode("bagId", {}, "2");
|
|
27
|
+
xmlStream.closeNode();
|
|
28
|
+
xmlStream.closeNode();
|
|
29
|
+
xmlStream.closeNode();
|
|
30
|
+
}
|
|
31
|
+
parseOpen() {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
parseText() { }
|
|
35
|
+
parseClose() {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.FeaturePropertyBagXform = FeaturePropertyBagXform;
|
|
@@ -192,6 +192,11 @@ class CellXform extends base_xform_1.BaseXform {
|
|
|
192
192
|
xmlStream.addAttribute("t", "b");
|
|
193
193
|
xmlStream.leafNode("v", null, model.value ? "1" : "0");
|
|
194
194
|
break;
|
|
195
|
+
case enums_1.Enums.ValueType.Checkbox:
|
|
196
|
+
// Checkboxes are stored as boolean values
|
|
197
|
+
xmlStream.addAttribute("t", "b");
|
|
198
|
+
xmlStream.leafNode("v", null, model.value ? "1" : "0");
|
|
199
|
+
break;
|
|
195
200
|
case enums_1.Enums.ValueType.Error:
|
|
196
201
|
xmlStream.addAttribute("t", "e");
|
|
197
202
|
xmlStream.leafNode("v", null, model.value.error);
|
|
@@ -55,6 +55,17 @@ class StyleXform extends base_xform_1.BaseXform {
|
|
|
55
55
|
if (model.protection) {
|
|
56
56
|
this.map.protection.render(xmlStream, model.protection);
|
|
57
57
|
}
|
|
58
|
+
// Add checkbox extLst if needed
|
|
59
|
+
if (model.checkbox && model.xfComplementIndex !== undefined) {
|
|
60
|
+
xmlStream.openNode("extLst");
|
|
61
|
+
xmlStream.openNode("ext", {
|
|
62
|
+
"xmlns:xfpb": "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
|
|
63
|
+
uri: "{C7286773-470A-42A8-94C5-96B5CB345126}"
|
|
64
|
+
});
|
|
65
|
+
xmlStream.leafNode("xfpb:xfComplement", { i: model.xfComplementIndex });
|
|
66
|
+
xmlStream.closeNode();
|
|
67
|
+
xmlStream.closeNode();
|
|
68
|
+
}
|
|
58
69
|
xmlStream.closeNode();
|
|
59
70
|
}
|
|
60
71
|
parseOpen(node) {
|
|
@@ -85,6 +85,7 @@ class StylesXform extends base_xform_1.BaseXform {
|
|
|
85
85
|
this._addFill({ type: "pattern", pattern: "none" });
|
|
86
86
|
this._addFill({ type: "pattern", pattern: "gray125" });
|
|
87
87
|
this.weakMap = new WeakMap();
|
|
88
|
+
this._hasCheckboxes = false;
|
|
88
89
|
}
|
|
89
90
|
render(xmlStream, model) {
|
|
90
91
|
const renderModel = model || this.model;
|
|
@@ -225,12 +226,15 @@ class StylesXform extends base_xform_1.BaseXform {
|
|
|
225
226
|
// default (zero) font
|
|
226
227
|
this._addFont({ size: 11, color: { theme: 1 }, name: "Calibri", family: 2, scheme: "minor" });
|
|
227
228
|
}
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
const type = cellType || enums_1.Enums.ValueType.Number;
|
|
230
|
+
// If we have seen this style object before, assume it has the same styleId.
|
|
231
|
+
// Do not cache by object identity for checkbox cells because the styleId must
|
|
232
|
+
// include checkbox-specific extLst, and the same style object may be reused
|
|
233
|
+
// for non-checkbox cells.
|
|
234
|
+
if (type !== enums_1.Enums.ValueType.Checkbox && this.weakMap && this.weakMap.has(model)) {
|
|
230
235
|
return this.weakMap.get(model);
|
|
231
236
|
}
|
|
232
237
|
const style = {};
|
|
233
|
-
const type = cellType || enums_1.Enums.ValueType.Number;
|
|
234
238
|
if (model.numFmt) {
|
|
235
239
|
style.numFmtId = this._addNumFmtStr(model.numFmt);
|
|
236
240
|
}
|
|
@@ -261,8 +265,17 @@ class StylesXform extends base_xform_1.BaseXform {
|
|
|
261
265
|
if (model.protection) {
|
|
262
266
|
style.protection = model.protection;
|
|
263
267
|
}
|
|
268
|
+
if (type === enums_1.Enums.ValueType.Checkbox) {
|
|
269
|
+
// Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
|
|
270
|
+
// Force applyAlignment="1" (without emitting an <alignment/> node) by providing
|
|
271
|
+
// an empty alignment object when none is specified.
|
|
272
|
+
this._hasCheckboxes = true;
|
|
273
|
+
style.alignment = style.alignment || {};
|
|
274
|
+
style.checkbox = true;
|
|
275
|
+
style.xfComplementIndex = 0;
|
|
276
|
+
}
|
|
264
277
|
const styleId = this._addStyle(style);
|
|
265
|
-
if (this.weakMap) {
|
|
278
|
+
if (type !== enums_1.Enums.ValueType.Checkbox && this.weakMap) {
|
|
266
279
|
this.weakMap.set(model, styleId);
|
|
267
280
|
}
|
|
268
281
|
return styleId;
|
|
@@ -325,6 +338,10 @@ class StylesXform extends base_xform_1.BaseXform {
|
|
|
325
338
|
getDxfStyle(id) {
|
|
326
339
|
return this.model.dxfs[id];
|
|
327
340
|
}
|
|
341
|
+
// Check if workbook uses checkbox feature
|
|
342
|
+
get hasCheckboxes() {
|
|
343
|
+
return !!this._hasCheckboxes;
|
|
344
|
+
}
|
|
328
345
|
// =========================================================================
|
|
329
346
|
// Private Interface
|
|
330
347
|
_addStyle(style) {
|
|
@@ -461,12 +478,19 @@ class StylesXformMock extends StylesXform {
|
|
|
461
478
|
// the styleId is returned. Note: cellType is used when numFmt not defined
|
|
462
479
|
addStyleModel(model, cellType) {
|
|
463
480
|
switch (cellType) {
|
|
481
|
+
case enums_1.Enums.ValueType.Checkbox:
|
|
482
|
+
// Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
|
|
483
|
+
// The mock style manager intentionally does not build those structures.
|
|
484
|
+
throw new Error("Checkbox cells require styles to be enabled (useStyles: true)");
|
|
464
485
|
case enums_1.Enums.ValueType.Date:
|
|
465
486
|
return this.dateStyleId;
|
|
466
487
|
default:
|
|
467
488
|
return 0;
|
|
468
489
|
}
|
|
469
490
|
}
|
|
491
|
+
get hasCheckboxes() {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
470
494
|
get dateStyleId() {
|
|
471
495
|
if (!this._dateStyleId) {
|
|
472
496
|
const dateStyle = {
|
|
@@ -20,6 +20,7 @@ const content_types_xform_1 = require("./xform/core/content-types-xform.js");
|
|
|
20
20
|
const app_xform_1 = require("./xform/core/app-xform.js");
|
|
21
21
|
const workbook_xform_1 = require("./xform/book/workbook-xform.js");
|
|
22
22
|
const worksheet_xform_1 = require("./xform/sheet/worksheet-xform.js");
|
|
23
|
+
const feature_property_bag_xform_1 = require("./xform/core/feature-property-bag-xform.js");
|
|
23
24
|
const drawing_xform_1 = require("./xform/drawing/drawing-xform.js");
|
|
24
25
|
const table_xform_1 = require("./xform/table/table-xform.js");
|
|
25
26
|
const pivot_cache_records_xform_1 = require("./xform/pivot-table/pivot-cache-records-xform.js");
|
|
@@ -206,6 +207,7 @@ class XLSX {
|
|
|
206
207
|
this.addTables(zip, model);
|
|
207
208
|
this.addPivotTables(zip, model);
|
|
208
209
|
await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
|
|
210
|
+
await this.addFeaturePropertyBag(zip, model);
|
|
209
211
|
await this.addMedia(zip, model);
|
|
210
212
|
await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]);
|
|
211
213
|
await this.addWorkbook(zip, model);
|
|
@@ -974,6 +976,14 @@ class XLSX {
|
|
|
974
976
|
Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookSharedStrings
|
|
975
977
|
});
|
|
976
978
|
}
|
|
979
|
+
// Add FeaturePropertyBag relationship if checkboxes are used
|
|
980
|
+
if (model.hasCheckboxes) {
|
|
981
|
+
relationships.push({
|
|
982
|
+
Id: `rId${count++}`,
|
|
983
|
+
Type: XLSX.RelType.FeaturePropertyBag,
|
|
984
|
+
Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookFeaturePropertyBag
|
|
985
|
+
});
|
|
986
|
+
}
|
|
977
987
|
(model.pivotTables || []).forEach((pivotTable) => {
|
|
978
988
|
pivotTable.rId = `rId${count++}`;
|
|
979
989
|
relationships.push({
|
|
@@ -995,6 +1005,13 @@ class XLSX {
|
|
|
995
1005
|
const xml = xform.toXml(relationships);
|
|
996
1006
|
zip.append(xml, { name: ooxml_paths_1.OOXML_PATHS.xlWorkbookRels });
|
|
997
1007
|
}
|
|
1008
|
+
async addFeaturePropertyBag(zip, model) {
|
|
1009
|
+
if (!model.hasCheckboxes) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const xform = new feature_property_bag_xform_1.FeaturePropertyBagXform();
|
|
1013
|
+
zip.append(xform.toXml({}), { name: ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag });
|
|
1014
|
+
}
|
|
998
1015
|
async addSharedStrings(zip, model) {
|
|
999
1016
|
if (model.sharedStrings && model.sharedStrings.count) {
|
|
1000
1017
|
zip.append(model.sharedStrings.xml, { name: ooxml_paths_1.OOXML_PATHS.xlSharedStrings });
|
|
@@ -1149,6 +1166,8 @@ class XLSX {
|
|
|
1149
1166
|
});
|
|
1150
1167
|
worksheetXform.prepare(worksheet, worksheetOptions);
|
|
1151
1168
|
});
|
|
1169
|
+
// ContentTypesXform expects this flag
|
|
1170
|
+
model.hasCheckboxes = model.styles.hasCheckboxes;
|
|
1152
1171
|
}
|
|
1153
1172
|
}
|
|
1154
1173
|
exports.XLSX = XLSX;
|
|
@@ -809,6 +809,40 @@ class BooleanValue {
|
|
|
809
809
|
return this.model.value.toString();
|
|
810
810
|
}
|
|
811
811
|
}
|
|
812
|
+
class CheckboxValue {
|
|
813
|
+
constructor(cell, value) {
|
|
814
|
+
this.model = {
|
|
815
|
+
address: cell.address,
|
|
816
|
+
type: Cell.Types.Checkbox,
|
|
817
|
+
value: value.checkbox
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
get value() {
|
|
821
|
+
return { checkbox: this.model.value };
|
|
822
|
+
}
|
|
823
|
+
set value(value) {
|
|
824
|
+
this.model.value = value.checkbox;
|
|
825
|
+
}
|
|
826
|
+
get type() {
|
|
827
|
+
return Cell.Types.Checkbox;
|
|
828
|
+
}
|
|
829
|
+
get effectiveType() {
|
|
830
|
+
return Cell.Types.Boolean;
|
|
831
|
+
}
|
|
832
|
+
get address() {
|
|
833
|
+
return this.model.address;
|
|
834
|
+
}
|
|
835
|
+
set address(value) {
|
|
836
|
+
this.model.address = value;
|
|
837
|
+
}
|
|
838
|
+
toCsvString() {
|
|
839
|
+
return this.model.value ? 1 : 0;
|
|
840
|
+
}
|
|
841
|
+
release() { }
|
|
842
|
+
toString() {
|
|
843
|
+
return this.model.value.toString();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
812
846
|
class ErrorValue {
|
|
813
847
|
constructor(cell, value) {
|
|
814
848
|
this.model = {
|
|
@@ -898,6 +932,9 @@ const Value = {
|
|
|
898
932
|
return Cell.Types.Date;
|
|
899
933
|
}
|
|
900
934
|
if (typeof value === "object") {
|
|
935
|
+
if ("checkbox" in value && typeof value.checkbox === "boolean") {
|
|
936
|
+
return Cell.Types.Checkbox;
|
|
937
|
+
}
|
|
901
938
|
if ("text" in value && value.text && "hyperlink" in value && value.hyperlink) {
|
|
902
939
|
return Cell.Types.Hyperlink;
|
|
903
940
|
}
|
|
@@ -930,7 +967,8 @@ const Value = {
|
|
|
930
967
|
{ t: Cell.Types.SharedString, f: SharedStringValue },
|
|
931
968
|
{ t: Cell.Types.RichText, f: RichTextValue },
|
|
932
969
|
{ t: Cell.Types.Boolean, f: BooleanValue },
|
|
933
|
-
{ t: Cell.Types.Error, f: ErrorValue }
|
|
970
|
+
{ t: Cell.Types.Error, f: ErrorValue },
|
|
971
|
+
{ t: Cell.Types.Checkbox, f: CheckboxValue }
|
|
934
972
|
].reduce((p, t) => {
|
|
935
973
|
p[t.t] = t.f;
|
|
936
974
|
return p;
|
|
@@ -11,7 +11,8 @@ export var ValueType;
|
|
|
11
11
|
ValueType[ValueType["RichText"] = 8] = "RichText";
|
|
12
12
|
ValueType[ValueType["Boolean"] = 9] = "Boolean";
|
|
13
13
|
ValueType[ValueType["Error"] = 10] = "Error";
|
|
14
|
-
ValueType[ValueType["JSON"] = 11] = "JSON";
|
|
14
|
+
ValueType[ValueType["JSON"] = 11] = "JSON";
|
|
15
|
+
ValueType[ValueType["Checkbox"] = 12] = "Checkbox";
|
|
15
16
|
})(ValueType || (ValueType = {}));
|
|
16
17
|
export var FormulaType;
|
|
17
18
|
(function (FormulaType) {
|
|
@@ -20,6 +20,7 @@ import { ContentTypesXform } from "../xlsx/xform/core/content-types-xform.js";
|
|
|
20
20
|
import { AppXform } from "../xlsx/xform/core/app-xform.js";
|
|
21
21
|
import { WorkbookXform } from "../xlsx/xform/book/workbook-xform.js";
|
|
22
22
|
import { SharedStringsXform } from "../xlsx/xform/strings/shared-strings-xform.js";
|
|
23
|
+
import { FeaturePropertyBagXform } from "../xlsx/xform/core/feature-property-bag-xform.js";
|
|
23
24
|
import { theme1Xml } from "../xlsx/xml/theme1.js";
|
|
24
25
|
import { Writeable, stringToUint8Array } from "../../stream/index.js";
|
|
25
26
|
import { mediaPath, OOXML_PATHS, OOXML_REL_TARGETS, worksheetRelTarget } from "../utils/ooxml-paths.js";
|
|
@@ -134,6 +135,7 @@ export class WorkbookWriterBase {
|
|
|
134
135
|
this.addCore(),
|
|
135
136
|
this.addSharedStrings(),
|
|
136
137
|
this.addStyles(),
|
|
138
|
+
this.addFeaturePropertyBag(),
|
|
137
139
|
this.addWorkbookRels()
|
|
138
140
|
]);
|
|
139
141
|
await this.addWorkbook();
|
|
@@ -226,7 +228,8 @@ export class WorkbookWriterBase {
|
|
|
226
228
|
worksheets: this._worksheets.filter(Boolean),
|
|
227
229
|
sharedStrings: this.sharedStrings,
|
|
228
230
|
commentRefs: this.commentRefs,
|
|
229
|
-
media: this.media
|
|
231
|
+
media: this.media,
|
|
232
|
+
hasCheckboxes: this.styles.hasCheckboxes
|
|
230
233
|
};
|
|
231
234
|
const xform = new ContentTypesXform();
|
|
232
235
|
this._addFile(xform.toXml(model), OOXML_PATHS.contentTypes);
|
|
@@ -280,6 +283,13 @@ export class WorkbookWriterBase {
|
|
|
280
283
|
}
|
|
281
284
|
return Promise.resolve();
|
|
282
285
|
}
|
|
286
|
+
addFeaturePropertyBag() {
|
|
287
|
+
if (this.styles.hasCheckboxes) {
|
|
288
|
+
const xform = new FeaturePropertyBagXform();
|
|
289
|
+
this._addFile(xform.toXml({}), OOXML_PATHS.xlFeaturePropertyBag);
|
|
290
|
+
}
|
|
291
|
+
return Promise.resolve();
|
|
292
|
+
}
|
|
283
293
|
addWorkbookRels() {
|
|
284
294
|
let count = 1;
|
|
285
295
|
const relationships = [
|
|
@@ -293,6 +303,14 @@ export class WorkbookWriterBase {
|
|
|
293
303
|
Target: OOXML_REL_TARGETS.workbookSharedStrings
|
|
294
304
|
});
|
|
295
305
|
}
|
|
306
|
+
// Add FeaturePropertyBag relationship if checkboxes are used
|
|
307
|
+
if (this.styles.hasCheckboxes) {
|
|
308
|
+
relationships.push({
|
|
309
|
+
Id: `rId${count++}`,
|
|
310
|
+
Type: RelType.FeaturePropertyBag,
|
|
311
|
+
Target: OOXML_REL_TARGETS.workbookFeaturePropertyBag
|
|
312
|
+
});
|
|
313
|
+
}
|
|
296
314
|
this._worksheets.forEach(ws => {
|
|
297
315
|
if (ws) {
|
|
298
316
|
ws.rId = `rId${count++}`;
|
|
@@ -57,6 +57,26 @@ class Table {
|
|
|
57
57
|
this.worksheet = worksheet;
|
|
58
58
|
if (table) {
|
|
59
59
|
this.table = table;
|
|
60
|
+
// When loading tables from xlsx, Excel stores table ranges and cell values in the worksheet,
|
|
61
|
+
// but may not embed row data into the table definition. Hydrate rows from the worksheet so
|
|
62
|
+
// table mutations (e.g. addRow) can correctly expand table ranges and serialize.
|
|
63
|
+
if (Array.isArray(table.rows) && table.rows.length === 0 && table.tableRef) {
|
|
64
|
+
const decoded = colCache.decode(table.tableRef);
|
|
65
|
+
if ("dimensions" in decoded) {
|
|
66
|
+
const startRow = decoded.top + (table.headerRow === false ? 0 : 1);
|
|
67
|
+
const endRow = decoded.bottom - (table.totalsRow === true ? 1 : 0);
|
|
68
|
+
if (endRow >= startRow) {
|
|
69
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
70
|
+
const row = worksheet.getRow(r);
|
|
71
|
+
const values = [];
|
|
72
|
+
for (let c = decoded.left; c <= decoded.right; c++) {
|
|
73
|
+
values.push(row.getCell(c).value);
|
|
74
|
+
}
|
|
75
|
+
table.rows.push(values);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
60
80
|
// check things are ok first
|
|
61
81
|
this.validate();
|
|
62
82
|
this.store();
|
|
@@ -133,9 +153,10 @@ class Table {
|
|
|
133
153
|
const { row, col } = table.tl;
|
|
134
154
|
assert(row > 0, "Table must be on valid row");
|
|
135
155
|
assert(col > 0, "Table must be on valid col");
|
|
136
|
-
const { width,
|
|
137
|
-
// autoFilterRef is a range that
|
|
138
|
-
|
|
156
|
+
const { width, tableHeight } = this;
|
|
157
|
+
// autoFilterRef is a single-row range that targets the header row only.
|
|
158
|
+
// Excel uses this for filter buttons; including data rows can break filter rendering.
|
|
159
|
+
table.autoFilterRef = colCache.encode(row, col, row, col + width - 1);
|
|
139
160
|
// tableRef is a range that includes optional headers and totals
|
|
140
161
|
table.tableRef = colCache.encode(row, col, row + tableHeight - 1, col + width - 1);
|
|
141
162
|
table.columns.forEach((column, i) => {
|
|
@@ -304,8 +325,9 @@ class Table {
|
|
|
304
325
|
}
|
|
305
326
|
}
|
|
306
327
|
this.store();
|
|
328
|
+
this._cache = undefined;
|
|
307
329
|
}
|
|
308
|
-
addRow(values, rowNumber) {
|
|
330
|
+
addRow(values, rowNumber, options) {
|
|
309
331
|
// Add a row of data, either insert at rowNumber or append
|
|
310
332
|
this.cacheState();
|
|
311
333
|
if (rowNumber === undefined) {
|
|
@@ -314,11 +336,17 @@ class Table {
|
|
|
314
336
|
else {
|
|
315
337
|
this.table.rows.splice(rowNumber, 0, values);
|
|
316
338
|
}
|
|
339
|
+
if (options?.commit !== false) {
|
|
340
|
+
this.commit();
|
|
341
|
+
}
|
|
317
342
|
}
|
|
318
|
-
removeRows(rowIndex, count = 1) {
|
|
343
|
+
removeRows(rowIndex, count = 1, options) {
|
|
319
344
|
// Remove a rows of data
|
|
320
345
|
this.cacheState();
|
|
321
346
|
this.table.rows.splice(rowIndex, count);
|
|
347
|
+
if (options?.commit !== false) {
|
|
348
|
+
this.commit();
|
|
349
|
+
}
|
|
322
350
|
}
|
|
323
351
|
getColumn(colIndex) {
|
|
324
352
|
const column = this.table.columns[colIndex];
|
|
@@ -7,7 +7,8 @@ export const OOXML_PATHS = {
|
|
|
7
7
|
xlWorkbookRels: "xl/_rels/workbook.xml.rels",
|
|
8
8
|
xlSharedStrings: "xl/sharedStrings.xml",
|
|
9
9
|
xlStyles: "xl/styles.xml",
|
|
10
|
-
xlTheme1: "xl/theme/theme1.xml"
|
|
10
|
+
xlTheme1: "xl/theme/theme1.xml",
|
|
11
|
+
xlFeaturePropertyBag: "xl/featurePropertyBag/featurePropertyBag.xml"
|
|
11
12
|
};
|
|
12
13
|
const worksheetXmlRegex = /^xl\/worksheets\/sheet(\d+)[.]xml$/;
|
|
13
14
|
const worksheetRelsXmlRegex = /^xl\/worksheets\/_rels\/sheet(\d+)[.]xml[.]rels$/;
|
|
@@ -162,7 +163,8 @@ export const OOXML_REL_TARGETS = {
|
|
|
162
163
|
// Targets inside xl/_rels/workbook.xml.rels (base: xl/)
|
|
163
164
|
workbookStyles: "styles.xml",
|
|
164
165
|
workbookSharedStrings: "sharedStrings.xml",
|
|
165
|
-
workbookTheme1: "theme/theme1.xml"
|
|
166
|
+
workbookTheme1: "theme/theme1.xml",
|
|
167
|
+
workbookFeaturePropertyBag: "featurePropertyBag/featurePropertyBag.xml"
|
|
166
168
|
};
|
|
167
169
|
export function pivotCacheDefinitionRelTargetFromWorkbook(n) {
|
|
168
170
|
// Target inside xl/_rels/workbook.xml.rels (base: xl/)
|
|
@@ -14,6 +14,7 @@ const RelType = {
|
|
|
14
14
|
Table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
|
|
15
15
|
PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
|
|
16
16
|
PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
|
|
17
|
-
PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
|
|
17
|
+
PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
|
|
18
|
+
FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
|
|
18
19
|
};
|
|
19
20
|
export { RelType };
|
|
@@ -63,6 +63,13 @@ class ContentTypesXform extends BaseXform {
|
|
|
63
63
|
PartName: toContentTypesPartName(OOXML_PATHS.xlStyles),
|
|
64
64
|
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
|
|
65
65
|
});
|
|
66
|
+
// Add FeaturePropertyBag if checkboxes are used
|
|
67
|
+
if (model.hasCheckboxes) {
|
|
68
|
+
xmlStream.leafNode("Override", {
|
|
69
|
+
PartName: toContentTypesPartName(OOXML_PATHS.xlFeaturePropertyBag),
|
|
70
|
+
ContentType: "application/vnd.ms-excel.featurepropertybag+xml"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
66
73
|
const hasSharedStrings = model.sharedStrings && model.sharedStrings.count;
|
|
67
74
|
if (hasSharedStrings) {
|
|
68
75
|
xmlStream.leafNode("Override", {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BaseXform } from "../base-xform.js";
|
|
2
|
+
// FeaturePropertyBag is used to enable checkbox functionality
|
|
3
|
+
// This is a static XML file that MS Excel requires for checkboxes to work
|
|
4
|
+
class FeaturePropertyBagXform extends BaseXform {
|
|
5
|
+
render(xmlStream) {
|
|
6
|
+
xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
|
|
7
|
+
xmlStream.openNode("FeaturePropertyBags", {
|
|
8
|
+
xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
|
|
9
|
+
});
|
|
10
|
+
// Checkbox feature
|
|
11
|
+
xmlStream.leafNode("bag", { type: "Checkbox" });
|
|
12
|
+
// XFControls bag
|
|
13
|
+
xmlStream.openNode("bag", { type: "XFControls" });
|
|
14
|
+
xmlStream.leafNode("bagId", { k: "CellControl" }, "0");
|
|
15
|
+
xmlStream.closeNode();
|
|
16
|
+
// XFComplement bag
|
|
17
|
+
xmlStream.openNode("bag", { type: "XFComplement" });
|
|
18
|
+
xmlStream.leafNode("bagId", { k: "XFControls" }, "1");
|
|
19
|
+
xmlStream.closeNode();
|
|
20
|
+
// XFComplements bag
|
|
21
|
+
xmlStream.openNode("bag", { type: "XFComplements", extRef: "XFComplementsMapperExtRef" });
|
|
22
|
+
xmlStream.openNode("a", { k: "MappedFeaturePropertyBags" });
|
|
23
|
+
xmlStream.leafNode("bagId", {}, "2");
|
|
24
|
+
xmlStream.closeNode();
|
|
25
|
+
xmlStream.closeNode();
|
|
26
|
+
xmlStream.closeNode();
|
|
27
|
+
}
|
|
28
|
+
parseOpen() {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
parseText() { }
|
|
32
|
+
parseClose() {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export { FeaturePropertyBagXform };
|