@cj-tech-master/excelts 4.0.4-canary.20260109050555.4f97ebb → 4.0.4-canary.20260110000241.8ac37ef
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/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/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/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 +145 -8
- 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/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 +1 -1
|
@@ -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++}`;
|
|
@@ -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 };
|
|
@@ -189,6 +189,11 @@ class CellXform extends BaseXform {
|
|
|
189
189
|
xmlStream.addAttribute("t", "b");
|
|
190
190
|
xmlStream.leafNode("v", null, model.value ? "1" : "0");
|
|
191
191
|
break;
|
|
192
|
+
case Enums.ValueType.Checkbox:
|
|
193
|
+
// Checkboxes are stored as boolean values
|
|
194
|
+
xmlStream.addAttribute("t", "b");
|
|
195
|
+
xmlStream.leafNode("v", null, model.value ? "1" : "0");
|
|
196
|
+
break;
|
|
192
197
|
case Enums.ValueType.Error:
|
|
193
198
|
xmlStream.addAttribute("t", "e");
|
|
194
199
|
xmlStream.leafNode("v", null, model.value.error);
|
|
@@ -52,6 +52,17 @@ class StyleXform extends BaseXform {
|
|
|
52
52
|
if (model.protection) {
|
|
53
53
|
this.map.protection.render(xmlStream, model.protection);
|
|
54
54
|
}
|
|
55
|
+
// Add checkbox extLst if needed
|
|
56
|
+
if (model.checkbox && model.xfComplementIndex !== undefined) {
|
|
57
|
+
xmlStream.openNode("extLst");
|
|
58
|
+
xmlStream.openNode("ext", {
|
|
59
|
+
"xmlns:xfpb": "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
|
|
60
|
+
uri: "{C7286773-470A-42A8-94C5-96B5CB345126}"
|
|
61
|
+
});
|
|
62
|
+
xmlStream.leafNode("xfpb:xfComplement", { i: model.xfComplementIndex });
|
|
63
|
+
xmlStream.closeNode();
|
|
64
|
+
xmlStream.closeNode();
|
|
65
|
+
}
|
|
55
66
|
xmlStream.closeNode();
|
|
56
67
|
}
|
|
57
68
|
parseOpen(node) {
|
|
@@ -82,6 +82,7 @@ class StylesXform extends BaseXform {
|
|
|
82
82
|
this._addFill({ type: "pattern", pattern: "none" });
|
|
83
83
|
this._addFill({ type: "pattern", pattern: "gray125" });
|
|
84
84
|
this.weakMap = new WeakMap();
|
|
85
|
+
this._hasCheckboxes = false;
|
|
85
86
|
}
|
|
86
87
|
render(xmlStream, model) {
|
|
87
88
|
const renderModel = model || this.model;
|
|
@@ -222,12 +223,15 @@ class StylesXform extends BaseXform {
|
|
|
222
223
|
// default (zero) font
|
|
223
224
|
this._addFont({ size: 11, color: { theme: 1 }, name: "Calibri", family: 2, scheme: "minor" });
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
const type = cellType || Enums.ValueType.Number;
|
|
227
|
+
// If we have seen this style object before, assume it has the same styleId.
|
|
228
|
+
// Do not cache by object identity for checkbox cells because the styleId must
|
|
229
|
+
// include checkbox-specific extLst, and the same style object may be reused
|
|
230
|
+
// for non-checkbox cells.
|
|
231
|
+
if (type !== Enums.ValueType.Checkbox && this.weakMap && this.weakMap.has(model)) {
|
|
227
232
|
return this.weakMap.get(model);
|
|
228
233
|
}
|
|
229
234
|
const style = {};
|
|
230
|
-
const type = cellType || Enums.ValueType.Number;
|
|
231
235
|
if (model.numFmt) {
|
|
232
236
|
style.numFmtId = this._addNumFmtStr(model.numFmt);
|
|
233
237
|
}
|
|
@@ -258,8 +262,17 @@ class StylesXform extends BaseXform {
|
|
|
258
262
|
if (model.protection) {
|
|
259
263
|
style.protection = model.protection;
|
|
260
264
|
}
|
|
265
|
+
if (type === Enums.ValueType.Checkbox) {
|
|
266
|
+
// Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
|
|
267
|
+
// Force applyAlignment="1" (without emitting an <alignment/> node) by providing
|
|
268
|
+
// an empty alignment object when none is specified.
|
|
269
|
+
this._hasCheckboxes = true;
|
|
270
|
+
style.alignment = style.alignment || {};
|
|
271
|
+
style.checkbox = true;
|
|
272
|
+
style.xfComplementIndex = 0;
|
|
273
|
+
}
|
|
261
274
|
const styleId = this._addStyle(style);
|
|
262
|
-
if (this.weakMap) {
|
|
275
|
+
if (type !== Enums.ValueType.Checkbox && this.weakMap) {
|
|
263
276
|
this.weakMap.set(model, styleId);
|
|
264
277
|
}
|
|
265
278
|
return styleId;
|
|
@@ -322,6 +335,10 @@ class StylesXform extends BaseXform {
|
|
|
322
335
|
getDxfStyle(id) {
|
|
323
336
|
return this.model.dxfs[id];
|
|
324
337
|
}
|
|
338
|
+
// Check if workbook uses checkbox feature
|
|
339
|
+
get hasCheckboxes() {
|
|
340
|
+
return !!this._hasCheckboxes;
|
|
341
|
+
}
|
|
325
342
|
// =========================================================================
|
|
326
343
|
// Private Interface
|
|
327
344
|
_addStyle(style) {
|
|
@@ -457,12 +474,19 @@ class StylesXformMock extends StylesXform {
|
|
|
457
474
|
// the styleId is returned. Note: cellType is used when numFmt not defined
|
|
458
475
|
addStyleModel(model, cellType) {
|
|
459
476
|
switch (cellType) {
|
|
477
|
+
case Enums.ValueType.Checkbox:
|
|
478
|
+
// Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
|
|
479
|
+
// The mock style manager intentionally does not build those structures.
|
|
480
|
+
throw new Error("Checkbox cells require styles to be enabled (useStyles: true)");
|
|
460
481
|
case Enums.ValueType.Date:
|
|
461
482
|
return this.dateStyleId;
|
|
462
483
|
default:
|
|
463
484
|
return 0;
|
|
464
485
|
}
|
|
465
486
|
}
|
|
487
|
+
get hasCheckboxes() {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
466
490
|
get dateStyleId() {
|
|
467
491
|
if (!this._dateStyleId) {
|
|
468
492
|
const dateStyle = {
|
|
@@ -17,6 +17,7 @@ import { ContentTypesXform } from "./xform/core/content-types-xform.js";
|
|
|
17
17
|
import { AppXform } from "./xform/core/app-xform.js";
|
|
18
18
|
import { WorkbookXform } from "./xform/book/workbook-xform.js";
|
|
19
19
|
import { WorkSheetXform } from "./xform/sheet/worksheet-xform.js";
|
|
20
|
+
import { FeaturePropertyBagXform } from "./xform/core/feature-property-bag-xform.js";
|
|
20
21
|
import { DrawingXform } from "./xform/drawing/drawing-xform.js";
|
|
21
22
|
import { TableXform } from "./xform/table/table-xform.js";
|
|
22
23
|
import { PivotCacheRecordsXform } from "./xform/pivot-table/pivot-cache-records-xform.js";
|
|
@@ -203,6 +204,7 @@ class XLSX {
|
|
|
203
204
|
this.addTables(zip, model);
|
|
204
205
|
this.addPivotTables(zip, model);
|
|
205
206
|
await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
|
|
207
|
+
await this.addFeaturePropertyBag(zip, model);
|
|
206
208
|
await this.addMedia(zip, model);
|
|
207
209
|
await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]);
|
|
208
210
|
await this.addWorkbook(zip, model);
|
|
@@ -971,6 +973,14 @@ class XLSX {
|
|
|
971
973
|
Target: OOXML_REL_TARGETS.workbookSharedStrings
|
|
972
974
|
});
|
|
973
975
|
}
|
|
976
|
+
// Add FeaturePropertyBag relationship if checkboxes are used
|
|
977
|
+
if (model.hasCheckboxes) {
|
|
978
|
+
relationships.push({
|
|
979
|
+
Id: `rId${count++}`,
|
|
980
|
+
Type: XLSX.RelType.FeaturePropertyBag,
|
|
981
|
+
Target: OOXML_REL_TARGETS.workbookFeaturePropertyBag
|
|
982
|
+
});
|
|
983
|
+
}
|
|
974
984
|
(model.pivotTables || []).forEach((pivotTable) => {
|
|
975
985
|
pivotTable.rId = `rId${count++}`;
|
|
976
986
|
relationships.push({
|
|
@@ -992,6 +1002,13 @@ class XLSX {
|
|
|
992
1002
|
const xml = xform.toXml(relationships);
|
|
993
1003
|
zip.append(xml, { name: OOXML_PATHS.xlWorkbookRels });
|
|
994
1004
|
}
|
|
1005
|
+
async addFeaturePropertyBag(zip, model) {
|
|
1006
|
+
if (!model.hasCheckboxes) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const xform = new FeaturePropertyBagXform();
|
|
1010
|
+
zip.append(xform.toXml({}), { name: OOXML_PATHS.xlFeaturePropertyBag });
|
|
1011
|
+
}
|
|
995
1012
|
async addSharedStrings(zip, model) {
|
|
996
1013
|
if (model.sharedStrings && model.sharedStrings.count) {
|
|
997
1014
|
zip.append(model.sharedStrings.xml, { name: OOXML_PATHS.xlSharedStrings });
|
|
@@ -1146,6 +1163,8 @@ class XLSX {
|
|
|
1146
1163
|
});
|
|
1147
1164
|
worksheetXform.prepare(worksheet, worksheetOptions);
|
|
1148
1165
|
});
|
|
1166
|
+
// ContentTypesXform expects this flag
|
|
1167
|
+
model.hasCheckboxes = model.styles.hasCheckboxes;
|
|
1149
1168
|
}
|
|
1150
1169
|
}
|
|
1151
1170
|
XLSX.RelType = RelType;
|