@node-projects/excelforge 2.4.0 → 3.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.
Files changed (74) hide show
  1. package/.github/FUNDING.yml +4 -0
  2. package/FEATURES.md +294 -0
  3. package/README.md +628 -12
  4. package/dist/core/SharedStrings.js +6 -2
  5. package/dist/core/SharedStrings.js.map +1 -1
  6. package/dist/core/Workbook.d.ts +43 -1
  7. package/dist/core/Workbook.js +881 -58
  8. package/dist/core/Workbook.js.map +1 -1
  9. package/dist/core/WorkbookReader.d.ts +18 -4
  10. package/dist/core/WorkbookReader.js +1386 -20
  11. package/dist/core/WorkbookReader.js.map +1 -1
  12. package/dist/core/Worksheet.d.ts +136 -2
  13. package/dist/core/Worksheet.js +828 -63
  14. package/dist/core/Worksheet.js.map +1 -1
  15. package/dist/core/types.d.ts +311 -5
  16. package/dist/core/types.js +12 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/features/ChartBuilder.d.ts +9 -1
  19. package/dist/features/ChartBuilder.js +140 -14
  20. package/dist/features/ChartBuilder.js.map +1 -1
  21. package/dist/features/CsvModule.d.ts +11 -0
  22. package/dist/features/CsvModule.js +137 -0
  23. package/dist/features/CsvModule.js.map +1 -0
  24. package/dist/features/Encryption.d.ts +6 -0
  25. package/dist/features/Encryption.js +806 -0
  26. package/dist/features/Encryption.js.map +1 -0
  27. package/dist/features/FormControlBuilder.d.ts +6 -0
  28. package/dist/features/FormControlBuilder.js +135 -0
  29. package/dist/features/FormControlBuilder.js.map +1 -0
  30. package/dist/features/FormulaEngine.d.ts +22 -0
  31. package/dist/features/FormulaEngine.js +498 -0
  32. package/dist/features/FormulaEngine.js.map +1 -0
  33. package/dist/features/HtmlModule.d.ts +22 -0
  34. package/dist/features/HtmlModule.js +1441 -0
  35. package/dist/features/HtmlModule.js.map +1 -0
  36. package/dist/features/JsonModule.d.ts +10 -0
  37. package/dist/features/JsonModule.js +76 -0
  38. package/dist/features/JsonModule.js.map +1 -0
  39. package/dist/features/PdfModule.d.ts +30 -0
  40. package/dist/features/PdfModule.js +1567 -0
  41. package/dist/features/PdfModule.js.map +1 -0
  42. package/dist/features/PivotTableBuilder.d.ts +7 -0
  43. package/dist/features/PivotTableBuilder.js +170 -0
  44. package/dist/features/PivotTableBuilder.js.map +1 -0
  45. package/dist/features/Signing.d.ts +12 -0
  46. package/dist/features/Signing.js +326 -0
  47. package/dist/features/Signing.js.map +1 -0
  48. package/dist/features/TableBuilder.js +2 -2
  49. package/dist/features/TableBuilder.js.map +1 -1
  50. package/dist/index-min.js +609 -147
  51. package/dist/index.d.ts +19 -1
  52. package/dist/index.js +11 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/styles/StyleRegistry.d.ts +14 -0
  55. package/dist/styles/StyleRegistry.js +95 -30
  56. package/dist/styles/StyleRegistry.js.map +1 -1
  57. package/dist/utils/helpers.d.ts +4 -0
  58. package/dist/utils/helpers.js +64 -14
  59. package/dist/utils/helpers.js.map +1 -1
  60. package/dist/utils/zip.js +145 -73
  61. package/dist/utils/zip.js.map +1 -1
  62. package/dist/vba/VbaProject.d.ts +31 -0
  63. package/dist/vba/VbaProject.js +576 -0
  64. package/dist/vba/VbaProject.js.map +1 -0
  65. package/dist/vba/cfb.d.ts +7 -0
  66. package/dist/vba/cfb.js +352 -0
  67. package/dist/vba/cfb.js.map +1 -0
  68. package/dist/vba/ovba.d.ts +2 -0
  69. package/dist/vba/ovba.js +137 -0
  70. package/dist/vba/ovba.js.map +1 -0
  71. package/package.json +4 -3
  72. package/validator.cs +0 -155
  73. package/validatorEpplus.cs +0 -27
  74. package/validatorReadData.cs +0 -111
@@ -3,20 +3,30 @@ import { StyleRegistry } from '../styles/StyleRegistry.js';
3
3
  import { SharedStrings } from './SharedStrings.js';
4
4
  import { buildChartXml } from '../features/ChartBuilder.js';
5
5
  import { buildTableXml } from '../features/TableBuilder.js';
6
+ import { buildPivotTableFiles } from '../features/PivotTableBuilder.js';
7
+ import { buildCtrlPropXml, buildFormControlVmlShape, buildVmlWithControls } from '../features/FormControlBuilder.js';
8
+ import { VbaProject } from '../vba/VbaProject.js';
6
9
  import { buildZip } from '../utils/zip.js';
7
10
  import { strToBytes, base64ToBytes, escapeXml, colIndexToLetter } from '../utils/helpers.js';
8
- import { readWorkbook } from './WorkbookReader.js';
11
+ import { readWorkbook, connTypeToNum, cmdTypeToNum } from './WorkbookReader.js';
9
12
  import { buildCoreXml, buildAppXml, buildCustomXml, } from './properties.js';
10
13
  export class Workbook {
11
14
  constructor() {
12
15
  this.sheets = [];
13
16
  this.namedRanges = [];
17
+ this.connections = [];
18
+ this.powerQueries = [];
19
+ this.externalLinks = [];
20
+ this.customPivotStyles = [];
21
+ this.pivotSlicers = [];
14
22
  this.properties = {};
15
23
  this.compressionLevel = 6;
16
24
  this.coreProperties = {};
17
25
  this.extendedProperties = {};
18
26
  this.customProperties = [];
27
+ this.isTemplate = false;
19
28
  this._dirtySheets = new Set();
29
+ this._customTableStyles = new Map();
20
30
  }
21
31
  markDirty(sheetIndexOrName) {
22
32
  if (typeof sheetIndexOrName === 'string') {
@@ -49,6 +59,50 @@ export class Workbook {
49
59
  status: result.core.contentStatus,
50
60
  };
51
61
  wb.sheets = result.sheets.map(s => s.ws);
62
+ wb.namedRanges = result.namedRanges;
63
+ wb.connections = result.connections;
64
+ wb.powerQueries = result.powerQueries;
65
+ const vbaData = result.unknownParts.get('xl/vbaProject.bin');
66
+ if (vbaData) {
67
+ try {
68
+ wb.vbaProject = VbaProject.fromBytes(vbaData);
69
+ }
70
+ catch { }
71
+ }
72
+ const calcPrMatch = result.workbookXml.match(/<calcPr([^>]*)\/?>/);
73
+ if (calcPrMatch) {
74
+ const a = calcPrMatch[1];
75
+ const cs = {};
76
+ const modeMatch = a.match(/calcMode="([^"]+)"/);
77
+ if (modeMatch)
78
+ cs.calcMode = modeMatch[1];
79
+ if (/fullCalcOnLoad="1"/.test(a))
80
+ cs.fullCalcOnLoad = true;
81
+ else if (/fullCalcOnLoad="0"/.test(a))
82
+ cs.fullCalcOnLoad = false;
83
+ if (/iterate="1"/.test(a))
84
+ cs.iterate = true;
85
+ const icMatch = a.match(/iterateCount="(\d+)"/);
86
+ if (icMatch)
87
+ cs.iterateCount = parseInt(icMatch[1], 10);
88
+ const idMatch = a.match(/iterateDelta="([^"]+)"/);
89
+ if (idMatch)
90
+ cs.iterateDelta = parseFloat(idMatch[1]);
91
+ if (/fullPrecision="1"/.test(a))
92
+ cs.fullPrecision = true;
93
+ else if (/fullPrecision="0"/.test(a))
94
+ cs.fullPrecision = false;
95
+ if (/calcOnSave="1"/.test(a))
96
+ cs.calcOnSave = true;
97
+ else if (/calcOnSave="0"/.test(a))
98
+ cs.calcOnSave = false;
99
+ if (/concurrentCalc="1"/.test(a))
100
+ cs.concurrentCalc = true;
101
+ else if (/concurrentCalc="0"/.test(a))
102
+ cs.concurrentCalc = false;
103
+ if (Object.keys(cs).length > 0)
104
+ wb.calcSettings = cs;
105
+ }
52
106
  return wb;
53
107
  }
54
108
  static async fromBase64(b64) {
@@ -78,14 +132,102 @@ export class Workbook {
78
132
  getSheetNames() {
79
133
  return this.sheets.map(s => s.name);
80
134
  }
135
+ getSheets() {
136
+ return this.sheets;
137
+ }
81
138
  removeSheet(name) {
82
139
  this.sheets = this.sheets.filter(s => s.name !== name);
83
140
  return this;
84
141
  }
142
+ addChartSheet(name, chart) {
143
+ const ws = this.addSheet(name);
144
+ ws._isChartSheet = true;
145
+ ws.addChart(chart);
146
+ return ws;
147
+ }
148
+ addDialogSheet(name) {
149
+ const ws = this.addSheet(name);
150
+ ws._isDialogSheet = true;
151
+ return ws;
152
+ }
153
+ copySheet(sourceName, newName) {
154
+ const src = this.getSheet(sourceName);
155
+ if (!src)
156
+ throw new Error(`Sheet "${sourceName}" not found`);
157
+ const ws = this.addSheet(newName);
158
+ const cells = src.readAllCells();
159
+ for (const { row, col, cell } of cells) {
160
+ const target = ws.getCell(row, col);
161
+ if (cell.value != null)
162
+ target.value = cell.value;
163
+ if (cell.formula)
164
+ target.formula = cell.formula;
165
+ if (cell.arrayFormula)
166
+ target.arrayFormula = cell.arrayFormula;
167
+ if (cell.richText)
168
+ target.richText = cell.richText.map(r => ({ ...r, font: r.font ? { ...r.font } : undefined }));
169
+ if (cell.style)
170
+ target.style = { ...cell.style };
171
+ if (cell.comment)
172
+ target.comment = { ...cell.comment };
173
+ if (cell.hyperlink)
174
+ target.hyperlink = { ...cell.hyperlink };
175
+ }
176
+ for (const m of src.getMerges()) {
177
+ ws.merge(m.startRow, m.startCol, m.endRow, m.endCol);
178
+ }
179
+ const range = src.getUsedRange();
180
+ if (range) {
181
+ for (let c = range.startCol; c <= range.endCol; c++) {
182
+ const cd = src.getColumn(c);
183
+ if (cd)
184
+ ws.setColumn(c, { ...cd });
185
+ }
186
+ }
187
+ if (src.pageSetup)
188
+ ws.pageSetup = { ...src.pageSetup };
189
+ if (src.printArea)
190
+ ws.printArea = src.printArea;
191
+ return ws;
192
+ }
193
+ registerTableStyle(name, def) {
194
+ this._customTableStyles.set(name, def);
195
+ return this;
196
+ }
85
197
  addNamedRange(nr) {
86
198
  this.namedRanges.push(nr);
87
199
  return this;
88
200
  }
201
+ getNamedRanges() {
202
+ return this.namedRanges;
203
+ }
204
+ getNamedRange(name) {
205
+ return this.namedRanges.find(nr => nr.name === name);
206
+ }
207
+ removeNamedRange(name) {
208
+ this.namedRanges = this.namedRanges.filter(nr => nr.name !== name);
209
+ return this;
210
+ }
211
+ addConnection(conn) {
212
+ this.connections.push(conn);
213
+ return this;
214
+ }
215
+ getConnections() {
216
+ return this.connections;
217
+ }
218
+ getConnection(name) {
219
+ return this.connections.find(c => c.name === name);
220
+ }
221
+ removeConnection(name) {
222
+ this.connections = this.connections.filter(c => c.name !== name);
223
+ return this;
224
+ }
225
+ getPowerQueries() {
226
+ return this.powerQueries;
227
+ }
228
+ getPowerQuery(name) {
229
+ return this.powerQueries.find(q => q.name === name);
230
+ }
89
231
  getCustomProperty(name) {
90
232
  return this.customProperties.find(p => p.name === name);
91
233
  }
@@ -101,6 +243,24 @@ export class Workbook {
101
243
  this.customProperties = this.customProperties.filter(p => p.name !== name);
102
244
  return this;
103
245
  }
246
+ addExternalLink(link) {
247
+ this.externalLinks.push(link);
248
+ return this;
249
+ }
250
+ getExternalLinks() {
251
+ return this.externalLinks;
252
+ }
253
+ registerPivotStyle(style) {
254
+ this.customPivotStyles.push(style);
255
+ return this;
256
+ }
257
+ addPivotSlicer(slicer) {
258
+ this.pivotSlicers.push(slicer);
259
+ return this;
260
+ }
261
+ getPivotSlicers() {
262
+ return this.pivotSlicers;
263
+ }
104
264
  async build() {
105
265
  this._syncLegacyProperties();
106
266
  return this._readResult ? this._buildPatched() : this._buildFresh();
@@ -113,6 +273,13 @@ export class Workbook {
113
273
  const shared = new SharedStrings();
114
274
  const sheetXmls = new Map();
115
275
  if (hasDirty) {
276
+ const dxfRe = /<dxf>([\s\S]*?)<\/dxf>|<dxf\/>/g;
277
+ const rawDxfs = [];
278
+ let m;
279
+ while ((m = dxfRe.exec(rr.stylesXml)) !== null)
280
+ rawDxfs.push(m[1] ?? '');
281
+ if (rawDxfs.length)
282
+ styles.prependRawDxfs(rawDxfs);
116
283
  for (let i = 0; i < this.sheets.length; i++) {
117
284
  sheetXmls.set(i, this.sheets[i].toXml(styles, shared));
118
285
  }
@@ -127,7 +294,7 @@ export class Workbook {
127
294
  ...rr.extended,
128
295
  ...this.extendedProperties,
129
296
  titlesOfParts: this.sheets.map(s => s.name),
130
- headingPairs: [{ name: 'Worksheets', count: this.sheets.length }],
297
+ headingPairs: this._headingPairs(),
131
298
  }, rr.extendedUnknownRaw)),
132
299
  });
133
300
  const customProps = this.customProperties.length > 0
@@ -137,6 +304,10 @@ export class Workbook {
137
304
  entries.push({ name: 'docProps/custom.xml', data: strToBytes(buildCustomXml(customProps)) });
138
305
  }
139
306
  entries.push({ name: 'xl/workbook.xml', data: strToBytes(this._patchWorkbookXml(rr.workbookXml)) });
307
+ const connectionsXml = this._connectionsXml(rr.connectionsXml);
308
+ if (connectionsXml) {
309
+ entries.push({ name: 'xl/connections.xml', data: strToBytes(connectionsXml) });
310
+ }
140
311
  if (hasDirty) {
141
312
  entries.push({ name: 'xl/styles.xml', data: strToBytes(styles.toXml()) });
142
313
  entries.push({ name: 'xl/sharedStrings.xml', data: strToBytes(shared.toXml()) });
@@ -146,38 +317,43 @@ export class Workbook {
146
317
  entries.push({ name: 'xl/sharedStrings.xml', data: strToBytes(rr.sharedXml) });
147
318
  }
148
319
  for (let i = 0; i < this.sheets.length; i++) {
149
- const path = `xl/worksheets/sheet${i + 1}.xml`;
320
+ const ws = this.sheets[i];
321
+ const folder = ws._isChartSheet ? 'chartsheets' : ws._isDialogSheet ? 'dialogsheets' : 'worksheets';
322
+ const path = `xl/${folder}/sheet${i + 1}.xml`;
150
323
  if (hasDirty) {
151
- entries.push({ name: path, data: strToBytes(sheetXmls.get(i) ?? '') });
152
- }
153
- else {
154
- const orig = rr.sheets[i]?.originalXml ?? '';
155
- const unknown = rr.sheets[i]?.unknownParts.join('\n') ?? '';
156
- entries.push({ name: path, data: strToBytes(unknown ? orig.replace('</worksheet>', `${unknown}</worksheet>`) : orig) });
157
- }
158
- }
159
- const dirtyTablePaths = new Set();
160
- if (hasDirty) {
161
- for (let i = 0; i < this.sheets.length; i++) {
162
- if (rr.sheets[i]?.tablePaths) {
163
- for (const tp of rr.sheets[i].tablePaths)
164
- dirtyTablePaths.add(tp);
324
+ if (ws._isChartSheet) {
325
+ entries.push({ name: path, data: strToBytes(ws.toChartSheetXml()) });
326
+ }
327
+ else if (ws._isDialogSheet) {
328
+ entries.push({ name: path, data: strToBytes(ws.toDialogSheetXml(styles, shared)) });
329
+ }
330
+ else {
331
+ entries.push({ name: path, data: strToBytes(sheetXmls.get(i) ?? '') });
165
332
  }
166
333
  }
167
- }
168
- for (const [path, data] of rr.unknownParts) {
169
- if (!dirtyTablePaths.has(path)) {
170
- entries.push({ name: path, data });
334
+ else {
335
+ entries.push({ name: path, data: strToBytes(rr.sheets[i]?.originalXml ?? '') });
171
336
  }
172
337
  }
173
- if (hasDirty) {
174
- for (let i = 0; i < this.sheets.length; i++) {
175
- const ws = this.sheets[i];
176
- const tables = ws.getTables();
177
- const paths = rr.sheets[i]?.tablePaths ?? [];
178
- for (let j = 0; j < tables.length; j++) {
179
- const tblPath = paths[j];
180
- if (tblPath) {
338
+ const allTablePaths = new Set();
339
+ for (let i = 0; i < this.sheets.length; i++) {
340
+ const ws = this.sheets[i];
341
+ const tables = ws.getTables();
342
+ const paths = rr.sheets[i]?.tablePaths ?? [];
343
+ const xmls = rr.sheets[i]?.tableXmls ?? [];
344
+ for (let j = 0; j < tables.length; j++) {
345
+ const tblPath = paths[j];
346
+ if (tblPath) {
347
+ allTablePaths.add(tblPath);
348
+ if (j < xmls.length) {
349
+ let xml = xmls[j];
350
+ const origRefMatch = xml.match(/\bref="([^"]+)"/);
351
+ if (origRefMatch && origRefMatch[1] !== tables[j].ref) {
352
+ xml = xml.replace(`ref="${origRefMatch[1]}"`, `ref="${tables[j].ref}"`);
353
+ }
354
+ entries.push({ name: tblPath, data: strToBytes(xml) });
355
+ }
356
+ else {
181
357
  const idMatch = tblPath.match(/table(\d+)\.xml$/);
182
358
  const tableId = idMatch ? parseInt(idMatch[1], 10) : j + 1;
183
359
  entries.push({ name: tblPath, data: strToBytes(buildTableXml(tables[j], tableId)) });
@@ -185,8 +361,23 @@ export class Workbook {
185
361
  }
186
362
  }
187
363
  }
364
+ for (const [path, data] of rr.unknownParts) {
365
+ if (allTablePaths.has(path))
366
+ continue;
367
+ if (path === 'xl/vbaProject.bin' && this.vbaProject)
368
+ continue;
369
+ if (rr.allRels.has(path))
370
+ continue;
371
+ if (hasDirty && path === 'xl/calcChain.xml')
372
+ continue;
373
+ entries.push({ name: path, data });
374
+ }
375
+ if (this.vbaProject) {
376
+ this._ensureVbaSheetModules();
377
+ entries.push({ name: 'xl/vbaProject.bin', data: this.vbaProject.build() });
378
+ }
188
379
  entries.push({ name: '_rels/.rels', data: strToBytes(this._buildRootRels(customProps != null && customProps.length > 0)) });
189
- entries.push({ name: 'xl/_rels/workbook.xml.rels', data: strToBytes(this._buildWorkbookRels(rr)) });
380
+ entries.push({ name: 'xl/_rels/workbook.xml.rels', data: strToBytes(this._buildWorkbookRels(rr, hasDirty)) });
190
381
  for (const [relPath, relMap] of rr.allRels) {
191
382
  if (relPath === 'xl/_rels/workbook.xml.rels' || relPath === '_rels/.rels')
192
383
  continue;
@@ -194,7 +385,7 @@ export class Workbook {
194
385
  }
195
386
  entries.push({
196
387
  name: '[Content_Types].xml',
197
- data: strToBytes(this._patchContentTypes(rr.contentTypesXml, customProps != null && customProps.length > 0)),
388
+ data: strToBytes(this._patchContentTypes(rr.contentTypesXml, customProps != null && customProps.length > 0, hasDirty)),
198
389
  });
199
390
  return buildZip(entries, { level: this.compressionLevel });
200
391
  }
@@ -202,29 +393,35 @@ export class Workbook {
202
393
  const styles = new StyleRegistry();
203
394
  const shared = new SharedStrings();
204
395
  const entries = [];
396
+ for (const [name, def] of this._customTableStyles) {
397
+ styles.registerTableStyle(name, def);
398
+ }
205
399
  let globalRId = 1;
206
400
  for (const ws of this.sheets)
207
401
  ws.rId = `rId${globalRId++}`;
208
402
  const allImages = [];
209
403
  const allCharts = [];
210
404
  const allTables = [];
405
+ const allPivotTables = [];
211
406
  const sheetImageRIds = new Map();
212
407
  const sheetChartRIds = new Map();
213
408
  const sheetTableRIds = new Map();
214
- let imgCtr = 1, chartCtr = 1, tableCtr = 1, vmlCtr = 1;
409
+ const sheetPivotRIds = new Map();
410
+ let imgCtr = 1, chartCtr = 1, tableCtr = 1, vmlCtr = 1, pivotCtr = 1, pivotCacheIdCtr = 0, ctrlPropGlobal = 0, oleObjGlobal = 0;
215
411
  for (const ws of this.sheets) {
216
412
  const imgs = ws.getImages();
217
413
  const charts = ws.getCharts();
218
414
  const tables = ws.getTables();
219
415
  const imgRIds = [], chartRIds = [], tblRIds = [];
220
- if (imgs.length || charts.length)
416
+ if (imgs.length || charts.length || ws.getShapes().length || ws.getWordArt().length || ws.getMathEquations().length || ws.getTableSlicers().length || ws.getOleObjects().length)
221
417
  ws.drawingRId = `rId${globalRId++}`;
222
- if (ws.getComments().length)
418
+ const controls = ws.getFormControls();
419
+ if (ws.getComments().length || controls.length)
223
420
  ws.legacyDrawingRId = `rId${globalRId++}`;
224
421
  for (const img of imgs) {
225
422
  const r = `rId${globalRId++}`;
226
423
  imgRIds.push(r);
227
- allImages.push({ ws, img, ext: img.format === 'jpeg' ? 'jpg' : img.format, idx: imgCtr++ });
424
+ allImages.push({ ws, img, ext: imageExt(img.format), idx: imgCtr++ });
228
425
  }
229
426
  for (let i = 0; i < charts.length; i++) {
230
427
  const r = `rId${globalRId++}`;
@@ -236,64 +433,254 @@ export class Workbook {
236
433
  tblRIds.push(r);
237
434
  allTables.push({ ws, tableIdx: i, globalTableId: tableCtr++ });
238
435
  }
436
+ const oleRIds = [];
437
+ const oleIconRIds = [];
438
+ for (const ole of ws.getOleObjects()) {
439
+ oleRIds.push(`rId${globalRId++}`);
440
+ if (ole.iconData)
441
+ oleIconRIds.push(`rId${globalRId++}`);
442
+ else
443
+ oleIconRIds.push('');
444
+ }
445
+ ws.oleRIds = oleRIds;
446
+ ws.oleIconRIds = oleIconRIds;
447
+ const ctrlPropRIds = [];
448
+ for (let ci = 0; ci < controls.length; ci++)
449
+ ctrlPropRIds.push(`rId${globalRId++}`);
450
+ ws.ctrlPropRIds = ctrlPropRIds;
239
451
  sheetImageRIds.set(ws, imgRIds);
240
452
  sheetChartRIds.set(ws, chartRIds);
241
453
  sheetTableRIds.set(ws, tblRIds);
242
454
  ws.tableRIds = tblRIds;
455
+ const ptRIds = [];
456
+ for (const pt of ws.getPivotTables()) {
457
+ const pivotRId = `rId${globalRId++}`;
458
+ const cacheRId = `rId${globalRId++}`;
459
+ ptRIds.push(pivotRId);
460
+ allPivotTables.push({ ws, pt, pivotIdx: pivotCtr++, cacheId: pivotCacheIdCtr++, pivotRId, cacheRId });
461
+ }
462
+ sheetPivotRIds.set(ws, ptRIds);
463
+ }
464
+ const allCellImages = [];
465
+ let cellImgCtr = imgCtr;
466
+ let vmCounter = 1;
467
+ for (const ws of this.sheets) {
468
+ ws._cellImageVm = new Map();
469
+ for (const ci of ws.getCellImages()) {
470
+ const ext = imageExt(ci.format);
471
+ allCellImages.push({ img: ci, ext, idx: cellImgCtr++ });
472
+ ws._cellImageVm.set(ci.cell, vmCounter++);
473
+ ws.getCellByRef(ci.cell);
474
+ }
475
+ }
476
+ const hasCellImages = allCellImages.length > 0;
477
+ const sheetSlicerMap = new Map();
478
+ const allSlicerCaches = [];
479
+ let slicerDefCtr = 0, slicerCacheCtr = 0;
480
+ for (const ws of this.sheets) {
481
+ const tSlicers = ws.getTableSlicers();
482
+ if (tSlicers.length) {
483
+ sheetSlicerMap.set(ws, { tableSlicers: [], pivotSlicers: [], slicerDefRId: '', slicerDefIdx: 0 });
484
+ }
485
+ }
486
+ for (const ps of this.pivotSlicers) {
487
+ for (const ws of this.sheets) {
488
+ if (ws.getPivotTables().some(p => p.name === ps.pivotTableName)) {
489
+ if (!sheetSlicerMap.has(ws)) {
490
+ sheetSlicerMap.set(ws, { tableSlicers: [], pivotSlicers: [], slicerDefRId: '', slicerDefIdx: 0 });
491
+ }
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ for (const [ws, info] of sheetSlicerMap) {
497
+ if (!ws.drawingRId)
498
+ ws.drawingRId = `rId${globalRId++}`;
499
+ info.slicerDefRId = `rId${globalRId++}`;
500
+ info.slicerDefIdx = ++slicerDefCtr;
501
+ ws.slicerRId = info.slicerDefRId;
502
+ const drawingInfo = [];
503
+ for (const s of ws.getTableSlicers()) {
504
+ const table = ws.getTables().find(t => t.name === s.tableName);
505
+ const tableEntry = allTables.find(t => t.ws === ws && ws.getTables()[t.tableIdx] === table);
506
+ const tableId = tableEntry?.globalTableId ?? 1;
507
+ const columnIndex = table ? (table.columns?.findIndex(c => c.name === s.columnName) ?? 0) + 1 : 1;
508
+ info.tableSlicers.push({ slicer: s, tableId, columnIndex });
509
+ drawingInfo.push({ name: s.name, cell: s.cell });
510
+ allSlicerCaches.push({
511
+ name: s.name + '_cache', sourceName: s.columnName, type: 'table',
512
+ rId: `rId${globalRId++}`, idx: ++slicerCacheCtr,
513
+ tableId, columnIndex, sortOrder: s.sortOrder ?? 'ascending',
514
+ });
515
+ }
516
+ for (const ps of this.pivotSlicers) {
517
+ const pt = ws.getPivotTables().find(p => p.name === ps.pivotTableName);
518
+ if (!pt)
519
+ continue;
520
+ const ptEntry = allPivotTables.find(p => p.ws === ws && p.pt === pt);
521
+ const sheetIdx = this.sheets.indexOf(ws) + 1;
522
+ let items = [];
523
+ const sourceWs = this.sheets.find(s => s.name === pt.sourceSheet);
524
+ if (sourceWs) {
525
+ const sourceData = sourceWs.readRange(pt.sourceRef);
526
+ const headers = (sourceData[0] ?? []).map(v => String(v ?? ''));
527
+ const fieldIdx = headers.indexOf(ps.fieldName);
528
+ if (fieldIdx >= 0) {
529
+ const uniqueSet = new Set();
530
+ for (let r = 1; r < sourceData.length; r++)
531
+ uniqueSet.add(String(sourceData[r][fieldIdx] ?? ''));
532
+ items = [...uniqueSet];
533
+ }
534
+ }
535
+ info.pivotSlicers.push({ slicer: ps, pivotCacheId: ptEntry?.cacheId ?? 0 });
536
+ drawingInfo.push({ name: ps.name, cell: ps.cell });
537
+ allSlicerCaches.push({
538
+ name: ps.name + '_cache', sourceName: ps.fieldName, type: 'pivot',
539
+ rId: `rId${globalRId++}`, idx: ++slicerCacheCtr,
540
+ pivotTableName: ps.pivotTableName, pivotCacheId: ptEntry?.cacheId ?? 0,
541
+ tabId: sheetIdx, items,
542
+ });
543
+ }
544
+ ws._slicerDrawingInfo = drawingInfo;
243
545
  }
546
+ const hasSlicers = sheetSlicerMap.size > 0;
244
547
  const hasCustom = this.customProperties.length > 0;
548
+ const hasVba = !!this.vbaProject;
245
549
  const imgCTs = new Set();
246
- for (const { ext } of allImages) {
247
- const ct = ext === 'jpg' ? 'image/jpeg' : ext === 'png' ? 'image/png' : `image/${ext}`;
550
+ for (const { ext } of [...allImages, ...allCellImages]) {
551
+ const ct = imageContentType(ext);
248
552
  imgCTs.add(`<Default Extension="${ext}" ContentType="${ct}"/>`);
249
553
  }
250
554
  const sheetsWithComments = this.sheets.filter(ws => ws.getComments().length);
251
- const vmlCT = sheetsWithComments.length ? '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>' : '';
555
+ const sheetsWithVml = this.sheets.filter(ws => ws.getComments().length || ws.getFormControls().length);
556
+ const vmlCT = sheetsWithVml.length ? '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>' : '';
252
557
  let vmlIdx = 0;
253
558
  const commentsCTs = sheetsWithComments.map(() => `<Override PartName="/xl/comments${++vmlIdx}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>`).join('');
559
+ let ctrlPropCtr = 0;
560
+ const ctrlPropCTs = [];
561
+ for (const ws of this.sheets) {
562
+ if (ws._isDialogSheet)
563
+ continue;
564
+ for (let ci = 0; ci < ws.getFormControls().length; ci++) {
565
+ ctrlPropCTs.push(`<Override PartName="/xl/ctrlProps/ctrlProp${++ctrlPropCtr}.xml" ContentType="application/vnd.ms-excel.controlproperties+xml"/>`);
566
+ }
567
+ }
568
+ const hasOleObjects = this.sheets.some(ws => ws.getOleObjects().length > 0);
569
+ const oleCT = hasOleObjects ? '<Default Extension="bin" ContentType="application/vnd.openxmlformats-officedocument.oleObject"/>' : '';
254
570
  entries.push({ name: '[Content_Types].xml', data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
255
571
  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
256
572
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
257
573
  <Default Extension="xml" ContentType="application/xml"/>
258
574
  ${vmlCT}
575
+ ${oleCT}
259
576
  ${[...imgCTs].join('')}
260
- <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
577
+ <Override PartName="/xl/workbook.xml" ContentType="${hasVba ? 'application/vnd.ms-excel.sheet.macroEnabled.main+xml' : this.isTemplate ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'}"/>
578
+ ${hasVba ? '<Override PartName="/xl/vbaProject.bin" ContentType="application/vnd.ms-office.vbaProject"/>' : ''}
261
579
  <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
262
580
  <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
263
- ${this.sheets.map((_, i) => `<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`).join('')}
581
+ <Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
582
+ ${this.sheets.filter(ws => !ws._isChartSheet && !ws._isDialogSheet).map(ws => { const idx = this.sheets.indexOf(ws); return `<Override PartName="/xl/worksheets/sheet${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`; }).join('')}
583
+ ${this.sheets.filter(ws => ws._isChartSheet).map(ws => { const idx = this.sheets.indexOf(ws); return `<Override PartName="/xl/chartsheets/sheet${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"/>`; }).join('')}
584
+ ${this.sheets.filter(ws => ws._isDialogSheet).map(ws => { const idx = this.sheets.indexOf(ws); return `<Override PartName="/xl/dialogsheets/sheet${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"/>`; }).join('')}
264
585
  ${this.sheets.filter(ws => ws.drawingRId).map((_, i) => `<Override PartName="/xl/drawings/drawing${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>`).join('')}
265
586
  ${allCharts.map(({ globalIdx }) => `<Override PartName="/xl/charts/chart${globalIdx}.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`).join('')}
266
587
  ${allTables.map(({ globalTableId }) => `<Override PartName="/xl/tables/table${globalTableId}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"/>`).join('')}
588
+ ${allPivotTables.map(p => `<Override PartName="/xl/pivotTables/pivotTable${p.pivotIdx}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"/>`).join('\n')}
589
+ ${allPivotTables.map(p => `<Override PartName="/xl/pivotCache/pivotCacheDefinition${p.pivotIdx}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"/>`).join('\n')}
590
+ ${allPivotTables.map(p => `<Override PartName="/xl/pivotCache/pivotCacheRecords${p.pivotIdx}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"/>`).join('\n')}
267
591
  ${commentsCTs}
592
+ ${ctrlPropCTs.join('')}
268
593
  <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
269
594
  <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
270
595
  ${hasCustom ? '<Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/>' : ''}
596
+ ${this.connections.length ? '<Override PartName="/xl/connections.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"/>' : ''}
597
+ ${this.externalLinks.map((_, i) => `<Override PartName="/xl/externalLinks/externalLink${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"/>`).join('\n')}
598
+ ${[...sheetSlicerMap.values()].map(info => `<Override PartName="/xl/slicers/slicer${info.slicerDefIdx}.xml" ContentType="application/vnd.ms-excel.slicer+xml"/>`).join('\n')}
599
+ ${allSlicerCaches.map(sc => `<Override PartName="/xl/slicerCaches/slicerCache${sc.idx}.xml" ContentType="application/vnd.ms-excel.slicerCache+xml"/>`).join('\n')}
600
+ ${this.sheets.flatMap(ws => ws.getQueryTables()).map((_, i) => `<Override PartName="/xl/queryTables/queryTable${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"/>`).join('\n')}
601
+ ${hasCellImages ? `<Override PartName="/xl/metadata.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"/>
602
+ <Override PartName="/xl/richData/rdrichvalue.xml" ContentType="application/vnd.ms-excel.rdrichvalue+xml"/>
603
+ <Override PartName="/xl/richData/rdRichValueStructure.xml" ContentType="application/vnd.ms-excel.rdrichvaluestructure+xml"/>
604
+ <Override PartName="/xl/richData/richValueRel.xml" ContentType="application/vnd.ms-excel.richvaluerel+xml"/>
605
+ <Override PartName="/xl/richData/rdRichValueTypes.xml" ContentType="application/vnd.ms-excel.rdrichvaluetypes+xml"/>
606
+ <Override PartName="/xl/richData/rdarray.xml" ContentType="application/vnd.ms-excel.rdarray+xml"/>` : ''}
271
607
  </Types>`) });
272
608
  entries.push({ name: '_rels/.rels', data: strToBytes(this._buildRootRels(hasCustom)) });
273
609
  entries.push({ name: 'xl/_rels/workbook.xml.rels', data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
274
610
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
275
- ${this.sheets.map((ws, i) => `<Relationship Id="${ws.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`).join('')}
611
+ ${this.sheets.map((ws, i) => {
612
+ const type = ws._isChartSheet ? 'chartsheet' : ws._isDialogSheet ? 'dialogsheet' : 'worksheet';
613
+ const folder = ws._isChartSheet ? 'chartsheets' : ws._isDialogSheet ? 'dialogsheets' : 'worksheets';
614
+ return `<Relationship Id="${ws.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/${type}" Target="${folder}/sheet${i + 1}.xml"/>`;
615
+ }).join('')}
276
616
  <Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
277
617
  <Relationship Id="rIdShared" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
618
+ <Relationship Id="rIdTheme" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
619
+ ${allPivotTables.map(p => `<Relationship Id="${p.cacheRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" Target="pivotCache/pivotCacheDefinition${p.pivotIdx}.xml"/>`).join('\n')}
620
+ ${allSlicerCaches.map(sc => `<Relationship Id="${sc.rId}" Type="http://schemas.microsoft.com/office/2007/relationships/slicerCache" Target="slicerCaches/slicerCache${sc.idx}.xml"/>`).join('\n')}
621
+ ${hasVba ? '<Relationship Id="rIdVBA" Type="http://schemas.microsoft.com/office/2006/relationships/vbaProject" Target="vbaProject.bin"/>' : ''}
622
+ ${this.connections.length ? '<Relationship Id="rIdConns" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections" Target="connections.xml"/>' : ''}
623
+ ${this.externalLinks.map((_, i) => `<Relationship Id="rIdExtLink${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink" Target="externalLinks/externalLink${i + 1}.xml"/>`).join('\n')}
624
+ ${hasCellImages ? '<Relationship Id="rIdMeta" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata" Target="metadata.xml"/>' : ''}
625
+ ${hasCellImages ? '<Relationship Id="rIdRichValueRel" Type="http://schemas.microsoft.com/office/2022/10/relationships/richValueRel" Target="richData/richValueRel.xml"/>' : ''}
626
+ ${hasCellImages ? '<Relationship Id="rIdRichValue" Type="http://schemas.microsoft.com/office/2017/06/relationships/rdRichValue" Target="richData/rdrichvalue.xml"/>' : ''}
627
+ ${hasCellImages ? '<Relationship Id="rIdRichValueStruct" Type="http://schemas.microsoft.com/office/2017/06/relationships/rdRichValueStructure" Target="richData/rdRichValueStructure.xml"/>' : ''}
628
+ ${hasCellImages ? '<Relationship Id="rIdRichValueTypes" Type="http://schemas.microsoft.com/office/2017/06/relationships/rdRichValueTypes" Target="richData/rdRichValueTypes.xml"/>' : ''}
629
+ ${hasCellImages ? '<Relationship Id="rIdRdArray" Type="http://schemas.microsoft.com/office/2017/06/relationships/rdArray" Target="richData/rdarray.xml"/>' : ''}
278
630
  </Relationships>`) });
279
- const date1904 = this.properties.date1904 ? `<workbookPr date1904="1"/>` : '<workbookPr/>';
280
- const namedRangesXml = this.namedRanges.length
281
- ? `<definedNames>${this.namedRanges.map(nr => `<definedName name="${escapeXml(nr.name)}"${nr.scope ? ` localSheetId="${this.sheets.findIndex(s => s.name === nr.scope)}"` : ''}>${escapeXml(nr.ref)}</definedName>`).join('')}</definedNames>` : '';
631
+ if (hasVba) {
632
+ this._ensureVbaSheetModules();
633
+ entries.push({ name: 'xl/vbaProject.bin', data: this.vbaProject.build() });
634
+ }
635
+ const wbPrAttrs = [
636
+ this.properties.date1904 ? 'date1904="1"' : '',
637
+ hasVba ? 'codeName="ThisWorkbook"' : '',
638
+ ].filter(Boolean).join(' ');
639
+ const date1904 = `<workbookPr${wbPrAttrs ? ' ' + wbPrAttrs : ''}/>`;
640
+ const namedRangesXml = this._definedNamesXml(allSlicerCaches);
641
+ const pivotCachesXml = allPivotTables.length
642
+ ? `<pivotCaches>${allPivotTables.map(p => `<pivotCache cacheId="${p.cacheId}" r:id="${p.cacheRId}"/>`).join('')}</pivotCaches>`
643
+ : '';
282
644
  entries.push({ name: 'xl/workbook.xml', data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
283
645
  <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
284
646
  ${date1904}
285
647
  <bookViews><workbookView xWindow="0" yWindow="0" windowWidth="14400" windowHeight="8260"/></bookViews>
286
648
  <sheets>${this.sheets.map((ws, i) => `<sheet name="${escapeXml(ws.name)}" sheetId="${i + 1}" r:id="${ws.rId}"${ws.options?.state && ws.options.state !== 'visible' ? ` state="${ws.options.state}"` : ''}/>`).join('')}</sheets>
287
649
  ${namedRangesXml}
288
- <calcPr calcId="191028"/>
650
+ ${this._calcPrXml()}
651
+ ${pivotCachesXml}
652
+ ${hasSlicers ? (() => {
653
+ const tableSlicers = allSlicerCaches.filter(sc => sc.type === 'table');
654
+ const pivotSlicers = allSlicerCaches.filter(sc => sc.type === 'pivot');
655
+ let xml = '<extLst>';
656
+ if (pivotSlicers.length)
657
+ xml += `<ext uri="{BBE1A952-AA13-448e-AADC-164F8A28A991}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><x14:slicerCaches>${pivotSlicers.map(sc => `<x14:slicerCache r:id="${sc.rId}"/>`).join('')}</x14:slicerCaches></ext>`;
658
+ if (tableSlicers.length)
659
+ xml += `<ext uri="{46BE6895-7355-4a93-B00E-2C351335B9C9}" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"><x15:slicerCaches>${tableSlicers.map(sc => `<x14:slicerCache r:id="${sc.rId}"/>`).join('')}</x15:slicerCaches></ext>`;
660
+ xml += '</extLst>';
661
+ return xml;
662
+ })() : ''}
289
663
  </workbook>`) });
664
+ if (this.connections.length) {
665
+ entries.push({ name: 'xl/connections.xml', data: strToBytes(this._connectionsXml()) });
666
+ }
290
667
  for (let i = 0; i < this.sheets.length; i++) {
291
668
  const ws = this.sheets[i];
292
669
  const imgRIds = sheetImageRIds.get(ws) ?? [];
293
670
  const cRIds = sheetChartRIds.get(ws) ?? [];
294
671
  const tblEntries = allTables.filter(t => t.ws === ws);
295
672
  const tblRIds_ = sheetTableRIds.get(ws) ?? [];
296
- entries.push({ name: `xl/worksheets/sheet${i + 1}.xml`, data: strToBytes(ws.toXml(styles, shared)) });
673
+ const sheetFolder = ws._isChartSheet ? 'chartsheets' : ws._isDialogSheet ? 'dialogsheets' : 'worksheets';
674
+ const sheetPath = `xl/${sheetFolder}/sheet${i + 1}.xml`;
675
+ if (ws._isChartSheet) {
676
+ entries.push({ name: sheetPath, data: strToBytes(ws.toChartSheetXml()) });
677
+ }
678
+ else if (ws._isDialogSheet) {
679
+ entries.push({ name: sheetPath, data: strToBytes(ws.toDialogSheetXml(styles, shared)) });
680
+ }
681
+ else {
682
+ entries.push({ name: sheetPath, data: strToBytes(ws.toXml(styles, shared)) });
683
+ }
297
684
  const wsRels = [];
298
685
  if (ws.drawingRId) {
299
686
  const dIdx = this.sheets.filter((s, j) => j <= i && s.drawingRId).length;
@@ -307,17 +694,75 @@ ${namedRangesXml}
307
694
  for (let j = 0; j < tblEntries.length; j++) {
308
695
  wsRels.push(`<Relationship Id="${tblRIds_[j]}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" Target="../tables/table${tblEntries[j].globalTableId}.xml"/>`);
309
696
  }
697
+ const ptRIds_ = sheetPivotRIds.get(ws) ?? [];
698
+ const ptEntries = allPivotTables.filter(p => p.ws === ws);
699
+ for (let j = 0; j < ptEntries.length; j++) {
700
+ wsRels.push(`<Relationship Id="${ptRIds_[j]}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" Target="../pivotTables/pivotTable${ptEntries[j].pivotIdx}.xml"/>`);
701
+ }
702
+ const slicerInfo = sheetSlicerMap.get(ws);
703
+ if (slicerInfo) {
704
+ wsRels.push(`<Relationship Id="${slicerInfo.slicerDefRId}" Type="http://schemas.microsoft.com/office/2007/relationships/slicer" Target="../slicers/slicer${slicerInfo.slicerDefIdx}.xml"/>`);
705
+ }
310
706
  const sheetComments = ws.getComments();
311
- if (sheetComments.length && ws.legacyDrawingRId) {
707
+ const sheetControls = ws.getFormControls();
708
+ if ((sheetComments.length || sheetControls.length) && ws.legacyDrawingRId) {
312
709
  const vIdx = vmlCtr++;
313
- const commRId = `rId${globalRId++}`;
314
710
  wsRels.push(`<Relationship Id="${ws.legacyDrawingRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" Target="../drawings/vmlDrawing${vIdx}.vml"/>`);
315
- wsRels.push(`<Relationship Id="${commRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments${vIdx}.xml"/>`);
316
- entries.push({ name: `xl/comments${vIdx}.xml`, data: strToBytes(this._buildCommentsXml(sheetComments)) });
317
- entries.push({ name: `xl/drawings/vmlDrawing${vIdx}.vml`, data: strToBytes(this._buildVmlXml(sheetComments, i)) });
711
+ if (sheetComments.length) {
712
+ const commRId = `rId${globalRId++}`;
713
+ wsRels.push(`<Relationship Id="${commRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="../comments${vIdx}.xml"/>`);
714
+ entries.push({ name: `xl/comments${vIdx}.xml`, data: strToBytes(this._buildCommentsXml(sheetComments)) });
715
+ }
716
+ const commentShapes = sheetComments.map(({ row, col }, ci) => {
717
+ const left = (col + 1) * 64;
718
+ const top = (row - 1) * 20;
719
+ const sid = 1025 + i * 1000 + ci;
720
+ return `<v:shape id="_x0000_s${sid}" type="#_x0000_t202" style="position:absolute;margin-left:${left}pt;margin-top:${top}pt;width:108pt;height:59.25pt;z-index:${ci + 1};visibility:hidden" fillcolor="#ffffe1" o:insetmode="auto">
721
+ <v:fill color2="#ffffe1"/>
722
+ <v:shadow color="black" obscured="t"/>
723
+ <v:path o:connecttype="none"/>
724
+ <v:textbox style="mso-direction-alt:auto"><div style="text-align:left"/></v:textbox>
725
+ <x:ClientData ObjectType="Note"><x:MoveWithCells/><x:SizeWithCells/><x:Anchor>${col + 1},15,${row - 1},10,${col + 3},15,${row + 4},4</x:Anchor><x:AutoFill>False</x:AutoFill><x:Row>${row - 1}</x:Row><x:Column>${col - 1}</x:Column></x:ClientData>
726
+ </v:shape>`;
727
+ });
728
+ const ctrlBaseId = 1025 + ws.sheetIndex * 1000 + sheetComments.length;
729
+ const controlShapes = sheetControls.map((ctrl, ci) => buildFormControlVmlShape(ctrl, ctrlBaseId + ci));
730
+ entries.push({ name: `xl/drawings/vmlDrawing${vIdx}.vml`, data: strToBytes(buildVmlWithControls(commentShapes, controlShapes)) });
731
+ if (!ws._isDialogSheet) {
732
+ const ctrlRIds = ws.ctrlPropRIds;
733
+ for (let ci = 0; ci < sheetControls.length; ci++) {
734
+ const ctrlPropIdx = ++ctrlPropGlobal;
735
+ wsRels.push(`<Relationship Id="${ctrlRIds[ci]}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp" Target="../ctrlProps/ctrlProp${ctrlPropIdx}.xml"/>`);
736
+ entries.push({ name: `xl/ctrlProps/ctrlProp${ctrlPropIdx}.xml`, data: strToBytes(buildCtrlPropXml(sheetControls[ci])) });
737
+ }
738
+ }
739
+ }
740
+ const oleObjects = ws.getOleObjects();
741
+ for (let oi = 0; oi < oleObjects.length; oi++) {
742
+ const ole = oleObjects[oi];
743
+ const oleIdx = ++oleObjGlobal;
744
+ const oleRId = ws.oleRIds[oi];
745
+ if (ole.linkToFile) {
746
+ wsRels.push(`<Relationship Id="${oleRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject" Target="${escapeXml(ole.linkPath ?? '')}" TargetMode="External"/>`);
747
+ }
748
+ else {
749
+ const embName = `xl/embeddings/oleObject${oleIdx}.bin`;
750
+ wsRels.push(`<Relationship Id="${oleRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject" Target="../embeddings/oleObject${oleIdx}.bin"/>`);
751
+ const oleData = !ole.data ? new Uint8Array(0)
752
+ : typeof ole.data === 'string' ? base64ToBytes(ole.data) : ole.data;
753
+ entries.push({ name: embName, data: oleData });
754
+ }
755
+ if (ole.iconData) {
756
+ const iconExt = ole.iconFormat ?? 'emf';
757
+ const iconIdx = imgCtr++;
758
+ const iconRId = ws.oleIconRIds[oi];
759
+ wsRels.push(`<Relationship Id="${iconRId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image${iconIdx}.${iconExt}"/>`);
760
+ const iconData = typeof ole.iconData === 'string' ? base64ToBytes(ole.iconData) : ole.iconData;
761
+ entries.push({ name: `xl/media/image${iconIdx}.${iconExt}`, data: iconData });
762
+ }
318
763
  }
319
764
  if (wsRels.length) {
320
- entries.push({ name: `xl/worksheets/_rels/sheet${i + 1}.xml.rels`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
765
+ entries.push({ name: `xl/${sheetFolder}/_rels/sheet${i + 1}.xml.rels`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
321
766
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
322
767
  ${wsRels.join('\n')}
323
768
  </Relationships>`) });
@@ -347,14 +792,105 @@ ${dRels.join('\n')}
347
792
  for (const { img, ext, idx } of allImages) {
348
793
  entries.push({ name: `xl/media/image${idx}.${ext}`, data: typeof img.data === 'string' ? base64ToBytes(img.data) : img.data });
349
794
  }
795
+ if (hasCellImages) {
796
+ const cellImgRIds = [];
797
+ const cellImgRels = [];
798
+ for (let ci = 0; ci < allCellImages.length; ci++) {
799
+ const { img, ext, idx } = allCellImages[ci];
800
+ const rId = `rId${ci + 1}`;
801
+ cellImgRIds.push(rId);
802
+ cellImgRels.push(`<Relationship Id="${rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="/xl/media/image${idx}.${ext}"/>`);
803
+ entries.push({ name: `xl/media/image${idx}.${ext}`, data: typeof img.data === 'string' ? base64ToBytes(img.data) : img.data });
804
+ }
805
+ entries.push({ name: 'xl/metadata.xml', data: strToBytes(buildMetadataXml(allCellImages.length)) });
806
+ entries.push({ name: 'xl/richData/rdrichvalue.xml', data: strToBytes(buildRichValueXml(allCellImages.length)) });
807
+ entries.push({ name: 'xl/richData/richValueRel.xml', data: strToBytes(buildRichValueRelXml(cellImgRIds)) });
808
+ entries.push({ name: 'xl/richData/rdRichValueStructure.xml', data: strToBytes(buildRichValueStructureXml()) });
809
+ entries.push({ name: 'xl/richData/rdRichValueTypes.xml', data: strToBytes(buildRichValueTypesXml()) });
810
+ entries.push({ name: 'xl/richData/rdarray.xml', data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><arrayData xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2" count="0"></arrayData>`) });
811
+ entries.push({ name: 'xl/richData/_rels/richValueRel.xml.rels', data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
812
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
813
+ ${cellImgRels.join('\n')}
814
+ </Relationships>`) });
815
+ }
350
816
  for (const { ws, chartIdx, globalIdx } of allCharts) {
351
817
  entries.push({ name: `xl/charts/chart${globalIdx}.xml`, data: strToBytes(buildChartXml(ws.getCharts()[chartIdx])) });
352
818
  }
353
819
  for (const { ws, tableIdx, globalTableId } of allTables) {
354
820
  entries.push({ name: `xl/tables/table${globalTableId}.xml`, data: strToBytes(buildTableXml(ws.getTables()[tableIdx], globalTableId)) });
355
821
  }
822
+ for (const { ws, pt, pivotIdx, cacheId: cId } of allPivotTables) {
823
+ const sourceWs = this.sheets.find(s => s.name === pt.sourceSheet);
824
+ const sourceData = sourceWs ? sourceWs.readRange(pt.sourceRef) : [[]];
825
+ const { pivotTableXml, cacheDefXml, cacheRecordsXml } = buildPivotTableFiles(pt, sourceData, pivotIdx, cId);
826
+ const wbRel = (type, target) => `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\n<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/${type}" Target="${target}"/>\n</Relationships>`;
827
+ entries.push({ name: `xl/pivotTables/pivotTable${pivotIdx}.xml`, data: strToBytes(pivotTableXml) });
828
+ entries.push({ name: `xl/pivotTables/_rels/pivotTable${pivotIdx}.xml.rels`, data: strToBytes(wbRel('pivotCacheDefinition', `../pivotCache/pivotCacheDefinition${pivotIdx}.xml`)) });
829
+ entries.push({ name: `xl/pivotCache/pivotCacheDefinition${pivotIdx}.xml`, data: strToBytes(cacheDefXml) });
830
+ entries.push({ name: `xl/pivotCache/_rels/pivotCacheDefinition${pivotIdx}.xml.rels`, data: strToBytes(wbRel('pivotCacheRecords', `pivotCacheRecords${pivotIdx}.xml`)) });
831
+ entries.push({ name: `xl/pivotCache/pivotCacheRecords${pivotIdx}.xml`, data: strToBytes(cacheRecordsXml) });
832
+ }
356
833
  entries.push({ name: 'xl/styles.xml', data: strToBytes(styles.toXml()) });
357
834
  entries.push({ name: 'xl/sharedStrings.xml', data: strToBytes(shared.toXml()) });
835
+ entries.push({ name: 'xl/theme/theme1.xml', data: strToBytes(this._buildThemeXml()) });
836
+ for (let i = 0; i < this.externalLinks.length; i++) {
837
+ const link = this.externalLinks[i];
838
+ const idx = i + 1;
839
+ const sheetsXml = link.sheets.map(s => {
840
+ const dNames = s.definedNames?.map(d => `<definedName name="${escapeXml(d.name)}" refersTo="${escapeXml(d.ref)}"/>`).join('') ?? '';
841
+ return `<sheetName val="${escapeXml(s.name)}"/>${dNames ? `<sheetDataSet>${dNames}</sheetDataSet>` : ''}`;
842
+ }).join('');
843
+ entries.push({ name: `xl/externalLinks/externalLink${idx}.xml`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
844
+ <externalLink xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
845
+ <externalBook r:id="rId1"><sheetNames>${sheetsXml}</sheetNames></externalBook>
846
+ </externalLink>`) });
847
+ entries.push({ name: `xl/externalLinks/_rels/externalLink${idx}.xml.rels`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
848
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
849
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath" Target="${escapeXml(link.target)}" TargetMode="External"/>
850
+ </Relationships>`) });
851
+ }
852
+ for (const [ws, info] of sheetSlicerMap) {
853
+ const allSheetSlicerItems = [];
854
+ for (const ts of info.tableSlicers) {
855
+ const s = ts.slicer;
856
+ allSheetSlicerItems.push(`<slicer name="${escapeXml(s.name)}" cache="${escapeXml(s.name + '_cache')}" caption="${escapeXml(s.caption ?? s.columnName)}" rowHeight="241300" columnCount="${s.columnCount ?? 1}" style="${s.style ?? 'SlicerStyleLight1'}"/>`);
857
+ }
858
+ for (const ps of info.pivotSlicers) {
859
+ const s = ps.slicer;
860
+ allSheetSlicerItems.push(`<slicer name="${escapeXml(s.name)}" cache="${escapeXml(s.name + '_cache')}" caption="${escapeXml(s.caption ?? s.fieldName)}" rowHeight="241300" columnCount="${s.columnCount ?? 1}" style="${s.style ?? 'SlicerStyleLight1'}"/>`);
861
+ }
862
+ entries.push({ name: `xl/slicers/slicer${info.slicerDefIdx}.xml`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
863
+ <slicers xmlns="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" mc:Ignorable="x">
864
+ ${allSheetSlicerItems.join('\n')}
865
+ </slicers>`) });
866
+ }
867
+ for (const sc of allSlicerCaches) {
868
+ let cacheBody;
869
+ if (sc.type === 'table') {
870
+ cacheBody = `<extLst><x:ext uri="{2F2917AC-EB37-4324-AD4E-5DD8C200BD13}" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"><x15:tableSlicerCache tableId="${sc.tableId}" column="${sc.columnIndex}" sortOrder="${sc.sortOrder ?? 'ascending'}"/></x:ext></extLst>`;
871
+ }
872
+ else {
873
+ const itemsXml = (sc.items ?? []).map((_, xi) => `<i x="${xi}" s="1"/>`).join('');
874
+ cacheBody = `<pivotTables><pivotTable tabId="${sc.tabId}" name="${escapeXml(sc.pivotTableName ?? '')}"/></pivotTables>` +
875
+ (sc.items?.length ? `<data><tabular pivotCacheId="${sc.pivotCacheId}" showMissing="0"><items count="${sc.items.length}">${itemsXml}</items></tabular></data>` : '');
876
+ }
877
+ entries.push({ name: `xl/slicerCaches/slicerCache${sc.idx}.xml`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
878
+ <slicerCacheDefinition xmlns="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x" name="${escapeXml(sc.name)}" sourceName="${escapeXml(sc.sourceName)}">
879
+ ${cacheBody}
880
+ </slicerCacheDefinition>`) });
881
+ }
882
+ const allQueryTables = this.sheets.flatMap(ws => ws.getQueryTables());
883
+ for (let i = 0; i < allQueryTables.length; i++) {
884
+ const qt = allQueryTables[i];
885
+ entries.push({ name: `xl/queryTables/queryTable${i + 1}.xml`, data: strToBytes(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
886
+ <queryTable xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" name="${escapeXml(qt.name)}" connectionId="${qt.connectionId}" autoFormatId="16" applyNumberFormats="0" applyBorderFormats="0" applyFontFormats="0" applyPatternFormats="0" applyAlignmentFormats="0" applyWidthHeightFormats="0">
887
+ <queryTableRefresh nextId="${(qt.columns?.length ?? 0) + 1}">
888
+ <queryTableFields count="${qt.columns?.length ?? 0}">
889
+ ${(qt.columns ?? []).map((c, ci) => `<queryTableField id="${ci + 1}" name="${escapeXml(c)}" tableColumnId="${ci + 1}"/>`).join('\n')}
890
+ </queryTableFields>
891
+ </queryTableRefresh>
892
+ </queryTable>`) });
893
+ }
358
894
  const cp = { ...this.coreProperties, created: this.coreProperties.created ?? new Date(), modified: new Date() };
359
895
  if (!cp.creator && this.properties.author)
360
896
  cp.creator = this.properties.author;
@@ -364,12 +900,27 @@ ${dRels.join('\n')}
364
900
  application: this.extendedProperties.application ?? 'ExcelForge',
365
901
  company: this.extendedProperties.company ?? this.properties.company,
366
902
  titlesOfParts: this.sheets.map(s => s.name),
367
- headingPairs: [{ name: 'Worksheets', count: this.sheets.length }],
903
+ headingPairs: this._headingPairs(),
368
904
  })) });
369
905
  if (hasCustom)
370
906
  entries.push({ name: 'docProps/custom.xml', data: strToBytes(buildCustomXml(this.customProperties)) });
371
907
  return buildZip(entries, { level: this.compressionLevel });
372
908
  }
909
+ _headingPairs() {
910
+ const normalCount = this.sheets.filter(ws => !ws._isChartSheet && !ws._isDialogSheet).length;
911
+ const chartCount = this.sheets.filter(ws => ws._isChartSheet).length;
912
+ const dialogCount = this.sheets.filter(ws => ws._isDialogSheet).length;
913
+ const pairs = [];
914
+ if (normalCount)
915
+ pairs.push({ name: 'Worksheets', count: normalCount });
916
+ if (chartCount)
917
+ pairs.push({ name: 'Charts', count: chartCount });
918
+ if (dialogCount)
919
+ pairs.push({ name: 'Dialogs', count: dialogCount });
920
+ if (!pairs.length)
921
+ pairs.push({ name: 'Worksheets', count: 0 });
922
+ return pairs;
923
+ }
373
924
  _syncLegacyProperties() {
374
925
  const p = this.properties;
375
926
  if (p.title)
@@ -393,19 +944,136 @@ ${dRels.join('\n')}
393
944
  if (p.status)
394
945
  this.coreProperties.contentStatus ??= p.status;
395
946
  }
947
+ _ensureVbaSheetModules() {
948
+ if (!this.vbaProject)
949
+ return;
950
+ const existingDocModules = this.vbaProject.modules.filter(m => m.type === 'document' && m.name !== 'ThisWorkbook');
951
+ if (existingDocModules.length >= this.sheets.length)
952
+ return;
953
+ for (const ws of this.sheets) {
954
+ const sheetCodeName = ws.name.replace(/[^A-Za-z0-9_]/g, '_');
955
+ if (!this.vbaProject.getModule(sheetCodeName)) {
956
+ this.vbaProject.addModule({ name: sheetCodeName, type: 'document', code: '' });
957
+ }
958
+ }
959
+ }
396
960
  _patchWorkbookXml(originalXml) {
397
961
  let xml = originalXml;
398
962
  for (let i = 0; i < this.sheets.length; i++) {
399
963
  xml = xml.replace(new RegExp(`(<sheet[^>]+sheetId="${i + 1}"[^>]+)name="[^"]*"`), `$1name="${escapeXml(this.sheets[i].name)}"`);
400
964
  }
965
+ if (this.vbaProject && !xml.includes('codeName=')) {
966
+ xml = xml.replace('<workbookPr', '<workbookPr codeName="ThisWorkbook"');
967
+ if (!xml.includes('<workbookPr')) {
968
+ xml = xml.replace('<bookViews', '<workbookPr codeName="ThisWorkbook"/><bookViews');
969
+ }
970
+ }
971
+ const dnXml = this._definedNamesXml();
972
+ if (xml.includes('<definedNames')) {
973
+ xml = xml.replace(/<definedNames[\s\S]*?<\/definedNames>/, dnXml);
974
+ }
975
+ else if (dnXml) {
976
+ xml = xml.replace('</sheets>', `</sheets>${dnXml}`);
977
+ }
978
+ if (this.calcSettings) {
979
+ const newCalcPr = this._calcPrXml();
980
+ if (xml.includes('<calcPr')) {
981
+ xml = xml.replace(/<calcPr[^>]*\/>|<calcPr[^>]*>[\s\S]*?<\/calcPr>/, newCalcPr);
982
+ }
983
+ else {
984
+ xml = xml.replace('</workbook>', `${newCalcPr}</workbook>`);
985
+ }
986
+ }
401
987
  return xml;
402
988
  }
403
- _buildWorkbookRels(rr) {
404
- const rels = [...rr.workbookRels.entries()].map(([id, rel]) => `<Relationship Id="${id}" Type="${rel.type}" Target="${rel.target}"/>`);
989
+ _definedNamesXml(slicerCaches) {
990
+ const printAreaNames = [];
991
+ for (const ws of this.sheets) {
992
+ if (ws.printArea) {
993
+ const ref = ws.printArea.includes('!') ? ws.printArea : `'${ws.name}'!${ws.printArea}`;
994
+ printAreaNames.push({ name: '_xlnm.Print_Area', ref, scope: ws.name });
995
+ }
996
+ }
997
+ const slicerNames = (slicerCaches ?? []).map(sc => ({ name: sc.name, ref: '#N/A' }));
998
+ const all = [...this.namedRanges, ...printAreaNames, ...slicerNames];
999
+ if (!all.length)
1000
+ return '';
1001
+ return `<definedNames>${all.map(nr => {
1002
+ let attrs = `name="${escapeXml(nr.name)}"`;
1003
+ if (nr.scope) {
1004
+ const idx = this.sheets.findIndex(s => s.name === nr.scope);
1005
+ if (idx >= 0)
1006
+ attrs += ` localSheetId="${idx}"`;
1007
+ }
1008
+ if (nr.comment)
1009
+ attrs += ` comment="${escapeXml(nr.comment)}"`;
1010
+ return `<definedName ${attrs}>${escapeXml(nr.ref)}</definedName>`;
1011
+ }).join('')}</definedNames>`;
1012
+ }
1013
+ _calcPrXml() {
1014
+ const cs = this.calcSettings;
1015
+ let attrs = 'calcId="191028"';
1016
+ if (cs) {
1017
+ if (cs.calcMode === 'manual')
1018
+ attrs += ' calcMode="manual"';
1019
+ else if (cs.calcMode === 'autoNoTable')
1020
+ attrs += ' calcMode="autoNoTable"';
1021
+ if (cs.fullCalcOnLoad)
1022
+ attrs += ' fullCalcOnLoad="1"';
1023
+ if (cs.iterate) {
1024
+ attrs += ' iterate="1"';
1025
+ if (cs.iterateCount != null)
1026
+ attrs += ` iterateCount="${cs.iterateCount}"`;
1027
+ if (cs.iterateDelta != null)
1028
+ attrs += ` iterateDelta="${cs.iterateDelta}"`;
1029
+ }
1030
+ if (cs.fullPrecision === false)
1031
+ attrs += ' fullPrecision="0"';
1032
+ if (cs.calcOnSave === false)
1033
+ attrs += ' calcOnSave="0"';
1034
+ if (cs.concurrentCalc === false)
1035
+ attrs += ' concurrentCalc="0"';
1036
+ }
1037
+ return `<calcPr ${attrs}/>`;
1038
+ }
1039
+ _connectionsXml(originalXml) {
1040
+ if (!this.connections.length)
1041
+ return '';
1042
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1043
+ <connections xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">${this.connections.map(c => {
1044
+ if (c._rawXml)
1045
+ return c._rawXml;
1046
+ let attrs = ` id="${c.id}" name="${escapeXml(c.name)}" type="${connTypeToNum(c.type)}" refreshedVersion="6"`;
1047
+ if (c.description)
1048
+ attrs += ` description="${escapeXml(c.description)}"`;
1049
+ if (c.refreshOnLoad)
1050
+ attrs += ' refreshOnLoad="1"';
1051
+ if (c.background)
1052
+ attrs += ' background="1"';
1053
+ if (c.saveData)
1054
+ attrs += ' saveData="1"';
1055
+ if (c.keepAlive)
1056
+ attrs += ' keepAlive="1"';
1057
+ if (c.interval)
1058
+ attrs += ` interval="${c.interval}"`;
1059
+ const dbPr = c.connectionString || c.command
1060
+ ? `<dbPr${c.connectionString ? ` connection="${escapeXml(c.connectionString)}"` : ''}${c.command ? ` command="${escapeXml(c.command)}"` : ''}${c.commandType ? ` commandType="${cmdTypeToNum(c.commandType)}"` : ''}/>`
1061
+ : '';
1062
+ return `<connection${attrs}>${dbPr}</connection>`;
1063
+ }).join('')}</connections>`;
1064
+ }
1065
+ _buildWorkbookRels(rr, dropCalcChain = false) {
1066
+ const rels = [...rr.workbookRels.entries()]
1067
+ .filter(([_, rel]) => !(dropCalcChain && rel.type.includes('/calcChain')))
1068
+ .map(([id, rel]) => `<Relationship Id="${id}" Type="${rel.type}" Target="${rel.target}"/>`);
405
1069
  if (![...rr.workbookRels.values()].some(r => r.type.includes('/styles')))
406
1070
  rels.push(`<Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`);
407
1071
  if (![...rr.workbookRels.values()].some(r => r.type.includes('/sharedStrings')))
408
1072
  rels.push(`<Relationship Id="rIdShared" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`);
1073
+ if (this.vbaProject && ![...rr.workbookRels.values()].some(r => r.type.includes('vbaProject')))
1074
+ rels.push(`<Relationship Id="rIdVBA" Type="http://schemas.microsoft.com/office/2006/relationships/vbaProject" Target="vbaProject.bin"/>`);
1075
+ if (this.connections.length && ![...rr.workbookRels.values()].some(r => r.type.includes('/connections')))
1076
+ rels.push(`<Relationship Id="rIdConns" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections" Target="connections.xml"/>`);
409
1077
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
410
1078
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
411
1079
  ${rels.join('\n')}
@@ -414,8 +1082,51 @@ ${rels.join('\n')}
414
1082
  _relsToXml(relMap) {
415
1083
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
416
1084
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
417
- ${[...relMap.entries()].map(([id, r]) => `<Relationship Id="${id}" Type="${r.type}" Target="${r.target}"/>`).join('\n')}
1085
+ ${[...relMap.entries()].map(([id, r]) => `<Relationship Id="${escapeXml(id)}" Type="${escapeXml(r.type)}" Target="${escapeXml(r.target)}"${r.targetMode ? ` TargetMode="${escapeXml(r.targetMode)}"` : ''}/>`).join('\n')}
418
1086
  </Relationships>`;
1087
+ }
1088
+ _buildThemeXml() {
1089
+ const t = this.theme;
1090
+ const majorFont = t?.majorFont ?? 'Calibri Light';
1091
+ const minorFont = t?.minorFont ?? 'Calibri';
1092
+ const defaultColors = [
1093
+ { name: 'dk1', color: '000000' }, { name: 'lt1', color: 'FFFFFF' },
1094
+ { name: 'dk2', color: '44546A' }, { name: 'lt2', color: 'E7E6E6' },
1095
+ { name: 'accent1', color: '4472C4' }, { name: 'accent2', color: 'ED7D31' },
1096
+ { name: 'accent3', color: 'A5A5A5' }, { name: 'accent4', color: 'FFC000' },
1097
+ { name: 'accent5', color: '5B9BD5' }, { name: 'accent6', color: '70AD47' },
1098
+ { name: 'hlink', color: '0563C1' }, { name: 'folHlink', color: '954F72' },
1099
+ ];
1100
+ const colors = t?.colors?.map(c => {
1101
+ let hex = c.color.replace(/^#/, '');
1102
+ if (hex.length === 8)
1103
+ hex = hex.substring(2);
1104
+ return { name: c.name, color: hex };
1105
+ }) ?? defaultColors;
1106
+ const colorElements = colors.map(c => {
1107
+ if (c.name === 'dk1' || c.name === 'lt1') {
1108
+ return `<a:${c.name}><a:sysClr val="${c.name === 'dk1' ? 'windowText' : 'window'}" lastClr="${c.color}"/></a:${c.name}>`;
1109
+ }
1110
+ return `<a:${c.name}><a:srgbClr val="${c.color}"/></a:${c.name}>`;
1111
+ }).join('');
1112
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1113
+ <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="${escapeXml(t?.name ?? 'Office Theme')}">
1114
+ <a:themeElements>
1115
+ <a:clrScheme name="Office">${colorElements}</a:clrScheme>
1116
+ <a:fontScheme name="Office">
1117
+ <a:majorFont><a:latin typeface="${escapeXml(majorFont)}"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>
1118
+ <a:minorFont><a:latin typeface="${escapeXml(minorFont)}"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>
1119
+ </a:fontScheme>
1120
+ <a:fmtScheme name="Office">
1121
+ <a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst>
1122
+ <a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst>
1123
+ <a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst>
1124
+ <a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst>
1125
+ </a:fmtScheme>
1126
+ </a:themeElements>
1127
+ <a:objectDefaults/>
1128
+ <a:extraClrSchemeLst/>
1129
+ </a:theme>`;
419
1130
  }
420
1131
  _buildRootRels(hasCustom) {
421
1132
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -426,12 +1137,24 @@ ${[...relMap.entries()].map(([id, r]) => `<Relationship Id="${id}" Type="${r.typ
426
1137
  ${hasCustom ? `<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" Target="docProps/custom.xml"/>` : ''}
427
1138
  </Relationships>`;
428
1139
  }
429
- _patchContentTypes(originalXml, addCustom) {
1140
+ _patchContentTypes(originalXml, addCustom, dropCalcChain = false) {
430
1141
  let xml = originalXml;
1142
+ if (dropCalcChain)
1143
+ xml = xml.replace(/<Override[^>]*calcChain[^>]*\/>/g, '');
431
1144
  if (!xml.includes('sharedStrings'))
432
1145
  xml = xml.replace('</Types>', `<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>\n</Types>`);
433
1146
  if (addCustom && !xml.includes('custom-properties'))
434
1147
  xml = xml.replace('</Types>', `<Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/>\n</Types>`);
1148
+ if (this.vbaProject) {
1149
+ if (!xml.includes('vbaProject.bin'))
1150
+ xml = xml.replace('</Types>', `<Override PartName="/xl/vbaProject.bin" ContentType="application/vnd.ms-office.vbaProject"/>\n</Types>`);
1151
+ xml = xml.replace('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
1152
+ }
1153
+ if (this.isTemplate && !this.vbaProject) {
1154
+ xml = xml.replace('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml');
1155
+ }
1156
+ if (this.connections.length && !xml.includes('connections.xml'))
1157
+ xml = xml.replace('</Types>', `<Override PartName="/xl/connections.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"/>\n</Types>`);
435
1158
  return xml;
436
1159
  }
437
1160
  async buildBase64() {
@@ -452,7 +1175,37 @@ ${hasCustom ? `<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/o
452
1175
  const commentsXml = comments.map(({ row, col, comment }) => {
453
1176
  const ref = `${colIndexToLetter(col)}${row}`;
454
1177
  const authorIdx = authors.indexOf(comment.author ?? '');
455
- return `<comment ref="${ref}" authorId="${authorIdx}"><text><r><t>${escapeXml(comment.text)}</t></r></text></comment>`;
1178
+ let textXml;
1179
+ if (comment.richText && comment.richText.length > 0) {
1180
+ textXml = comment.richText.map(run => {
1181
+ let rPr = '';
1182
+ if (run.font) {
1183
+ const f = run.font;
1184
+ if (f.bold)
1185
+ rPr += '<b/>';
1186
+ if (f.italic)
1187
+ rPr += '<i/>';
1188
+ if (f.underline && f.underline !== 'none')
1189
+ rPr += `<u val="${f.underline === 'single' ? 'single' : f.underline}"/>`;
1190
+ if (f.strike)
1191
+ rPr += '<strike/>';
1192
+ if (f.size)
1193
+ rPr += `<sz val="${f.size}"/>`;
1194
+ if (f.color)
1195
+ rPr += `<color rgb="${f.color}"/>`;
1196
+ if (f.name)
1197
+ rPr += `<rFont val="${escapeXml(f.name)}"/>`;
1198
+ if (f.family != null)
1199
+ rPr += `<family val="${f.family}"/>`;
1200
+ }
1201
+ const rPrTag = rPr ? `<rPr>${rPr}</rPr>` : '';
1202
+ return `<r>${rPrTag}<t xml:space="preserve">${escapeXml(run.text)}</t></r>`;
1203
+ }).join('');
1204
+ }
1205
+ else {
1206
+ textXml = `<r><t>${escapeXml(comment.text)}</t></r>`;
1207
+ }
1208
+ return `<comment ref="${ref}" authorId="${authorIdx}"><text>${textXml}</text></comment>`;
456
1209
  }).join('');
457
1210
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
458
1211
  <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
@@ -489,4 +1242,74 @@ ${shapes}
489
1242
  URL.revokeObjectURL(url);
490
1243
  }
491
1244
  }
1245
+ function imageContentType(ext) {
1246
+ switch (ext) {
1247
+ case 'jpg': return 'image/jpeg';
1248
+ case 'png': return 'image/png';
1249
+ case 'gif': return 'image/gif';
1250
+ case 'bmp': return 'image/bmp';
1251
+ case 'tiff': return 'image/tiff';
1252
+ case 'emf': return 'image/x-emf';
1253
+ case 'wmf': return 'image/x-wmf';
1254
+ case 'svg': return 'image/svg+xml';
1255
+ case 'ico': return 'image/x-icon';
1256
+ case 'webp': return 'image/webp';
1257
+ default: return `image/${ext}`;
1258
+ }
1259
+ }
1260
+ function imageExt(format) {
1261
+ return format === 'jpeg' ? 'jpg' : format;
1262
+ }
1263
+ function buildMetadataXml(count) {
1264
+ const bks = Array.from({ length: count }, (_, i) => `<bk><extLst><ext uri="{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}"><xlrd:rvb i="${i}"/></ext></extLst></bk>`).join('');
1265
+ const cellBks = Array.from({ length: count }, (_, i) => `<bk><rc t="1" v="${i}"/></bk>`).join('');
1266
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
1267
+ `<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"` +
1268
+ ` xmlns:xlrd="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata">` +
1269
+ `<metadataTypes count="1">` +
1270
+ `<metadataType name="XLRICHVALUE" minSupportedVersion="120000" copy="1" pasteAll="1" pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1" clearComments="1" assign="1" coerce="1"/>` +
1271
+ `</metadataTypes>` +
1272
+ `<futureMetadata name="XLRICHVALUE" count="${count}">${bks}</futureMetadata>` +
1273
+ `<valueMetadata count="${count}">${cellBks}</valueMetadata>` +
1274
+ `</metadata>`;
1275
+ }
1276
+ function buildRichValueXml(count) {
1277
+ const rvs = Array.from({ length: count }, (_, i) => `<rv s="0"><v>${i}</v><v>5</v><v></v></rv>`).join('');
1278
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
1279
+ `<rvData xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" count="${count}">${rvs}</rvData>`;
1280
+ }
1281
+ function buildRichValueRelXml(rIds) {
1282
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
1283
+ `<richValueRels xmlns="http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel"` +
1284
+ ` xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">` +
1285
+ rIds.map(id => `<rel r:id="${id}"/>`).join('') +
1286
+ `</richValueRels>`;
1287
+ }
1288
+ function buildRichValueStructureXml() {
1289
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
1290
+ `<rvStructures xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata" count="1">` +
1291
+ `<s t="_localImage"><k n="_rvRel:LocalImageIdentifier" t="i"/><k n="CalcOrigin" t="i"/><k n="Text" t="s"/></s>` +
1292
+ `</rvStructures>`;
1293
+ }
1294
+ function buildRichValueTypesXml() {
1295
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` +
1296
+ `<rvTypesInfo xmlns="http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2"` +
1297
+ ` xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"` +
1298
+ ` mc:Ignorable="x"` +
1299
+ ` xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">` +
1300
+ `<global><keyFlags>` +
1301
+ `<key name="_Self"><flag name="ExcludeFromFile" value="1"/><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1302
+ `<key name="_DisplayString"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1303
+ `<key name="_Flags"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1304
+ `<key name="_Format"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1305
+ `<key name="_SubLabel"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1306
+ `<key name="_Attribution"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1307
+ `<key name="_Icon"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1308
+ `<key name="_Display"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1309
+ `<key name="_CanonicalPropertyNames"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1310
+ `<key name="_ClassificationId"><flag name="ExcludeFromCalcComparison" value="1"/></key>` +
1311
+ `</keyFlags></global>` +
1312
+ `<types></types>` +
1313
+ `</rvTypesInfo>`;
1314
+ }
492
1315
  //# sourceMappingURL=Workbook.js.map