node-pptx-templater 1.0.17 → 1.0.19
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/README.md +178 -4
- package/package.json +1 -1
- package/src/core/OutputWriter.js +12 -14
- package/src/core/PPTXTemplater.js +205 -5
- package/src/managers/ChartManager.js +0 -3
- package/src/managers/ImageManager.js +14 -18
- package/src/managers/MediaManager.js +72 -28
- package/src/managers/ShapeManager.js +19 -28
- package/src/managers/SlideManager.js +247 -4
- package/src/managers/TableManager.js +56 -87
- package/src/managers/ZOrderManager.js +0 -5
- package/src/managers/ZipManager.js +120 -14
- package/src/managers/charts/ChartWorkbookUpdater.js +1 -1
- package/src/utils/contentTypesHelper.js +6 -9
- package/src/utils/imageMetadata.js +227 -0
|
@@ -67,12 +67,7 @@ class TableManager {
|
|
|
67
67
|
* @throws {TableNotFoundError} If the table is not found.
|
|
68
68
|
*/
|
|
69
69
|
updateTable(slideIndex, tableId, data, slideManager) {
|
|
70
|
-
const
|
|
71
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
72
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
73
|
-
if (!tblObj) {
|
|
74
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
75
|
-
}
|
|
70
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
76
71
|
|
|
77
72
|
const trs = tblObj['a:tr'] || []
|
|
78
73
|
if (trs.length === 0) {
|
|
@@ -174,8 +169,7 @@ class TableManager {
|
|
|
174
169
|
|
|
175
170
|
tblObj['a:tr'] = newRows
|
|
176
171
|
|
|
177
|
-
|
|
178
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
172
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
179
173
|
|
|
180
174
|
const finalMerges = [...templateMerges, ...generatedMerges]
|
|
181
175
|
for (const merge of finalMerges) {
|
|
@@ -204,12 +198,7 @@ class TableManager {
|
|
|
204
198
|
* @param {SlideManager} slideManager
|
|
205
199
|
*/
|
|
206
200
|
addTableRow(slideIndex, tableId, rowData, slideManager) {
|
|
207
|
-
const
|
|
208
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
209
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
210
|
-
if (!tblObj) {
|
|
211
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
212
|
-
}
|
|
201
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
213
202
|
|
|
214
203
|
const trs = tblObj['a:tr'] || []
|
|
215
204
|
if (trs.length === 0) {
|
|
@@ -230,8 +219,7 @@ class TableManager {
|
|
|
230
219
|
|
|
231
220
|
trs.push(newRow)
|
|
232
221
|
|
|
233
|
-
|
|
234
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
222
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
235
223
|
}
|
|
236
224
|
|
|
237
225
|
/**
|
|
@@ -243,12 +231,7 @@ class TableManager {
|
|
|
243
231
|
* @param {SlideManager} slideManager
|
|
244
232
|
*/
|
|
245
233
|
removeTableRow(slideIndex, tableId, rowIndex, slideManager) {
|
|
246
|
-
const
|
|
247
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
248
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
249
|
-
if (!tblObj) {
|
|
250
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
251
|
-
}
|
|
234
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
252
235
|
|
|
253
236
|
const trs = tblObj['a:tr'] || []
|
|
254
237
|
if (rowIndex < 0 || rowIndex >= trs.length) {
|
|
@@ -257,8 +240,7 @@ class TableManager {
|
|
|
257
240
|
|
|
258
241
|
trs.splice(rowIndex, 1)
|
|
259
242
|
|
|
260
|
-
|
|
261
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
243
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
262
244
|
}
|
|
263
245
|
|
|
264
246
|
/**
|
|
@@ -271,12 +253,7 @@ class TableManager {
|
|
|
271
253
|
* @param {SlideManager} slideManager
|
|
272
254
|
*/
|
|
273
255
|
insertTableRow(slideIndex, tableId, rowIndex, rowData, slideManager) {
|
|
274
|
-
const
|
|
275
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
276
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
277
|
-
if (!tblObj) {
|
|
278
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
279
|
-
}
|
|
256
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
280
257
|
|
|
281
258
|
const trs = tblObj['a:tr'] || []
|
|
282
259
|
if (rowIndex < 0 || rowIndex > trs.length) {
|
|
@@ -302,8 +279,7 @@ class TableManager {
|
|
|
302
279
|
|
|
303
280
|
trs.splice(rowIndex, 0, newRow)
|
|
304
281
|
|
|
305
|
-
|
|
306
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
282
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
307
283
|
}
|
|
308
284
|
|
|
309
285
|
/**
|
|
@@ -316,12 +292,7 @@ class TableManager {
|
|
|
316
292
|
* @param {SlideManager} slideManager
|
|
317
293
|
*/
|
|
318
294
|
cloneTableRow(slideIndex, tableId, sourceRowIndex, targetRowIndex, slideManager) {
|
|
319
|
-
const
|
|
320
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
321
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
322
|
-
if (!tblObj) {
|
|
323
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
324
|
-
}
|
|
295
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
325
296
|
|
|
326
297
|
const trs = tblObj['a:tr'] || []
|
|
327
298
|
if (sourceRowIndex < 0 || sourceRowIndex >= trs.length) {
|
|
@@ -337,8 +308,7 @@ class TableManager {
|
|
|
337
308
|
|
|
338
309
|
trs.splice(targetRowIndex, 0, newRow)
|
|
339
310
|
|
|
340
|
-
|
|
341
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
311
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
342
312
|
}
|
|
343
313
|
|
|
344
314
|
/**
|
|
@@ -353,12 +323,7 @@ class TableManager {
|
|
|
353
323
|
* @param {SlideManager} slideManager
|
|
354
324
|
*/
|
|
355
325
|
updateCell(slideIndex, tableId, rowIndex, colIndex, value, options = {}, slideManager) {
|
|
356
|
-
const
|
|
357
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
358
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
359
|
-
if (!tblObj) {
|
|
360
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
361
|
-
}
|
|
326
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
362
327
|
|
|
363
328
|
const row = tblObj['a:tr']?.[rowIndex]
|
|
364
329
|
if (!row) {
|
|
@@ -407,8 +372,7 @@ class TableManager {
|
|
|
407
372
|
}
|
|
408
373
|
}
|
|
409
374
|
|
|
410
|
-
|
|
411
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
375
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
412
376
|
}
|
|
413
377
|
|
|
414
378
|
/**
|
|
@@ -425,9 +389,8 @@ class TableManager {
|
|
|
425
389
|
*/
|
|
426
390
|
validateMergeRegion(slideIndex, tableId, startRow, startCol, endRow, endCol, slideManager) {
|
|
427
391
|
const errors = []
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
392
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
393
|
+
const tblObj = this.#findTableObj(slideObj, tableId, slideManager, slideIndex)
|
|
431
394
|
if (!tblObj) {
|
|
432
395
|
errors.push(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
433
396
|
return { valid: false, errors }
|
|
@@ -507,9 +470,7 @@ class TableManager {
|
|
|
507
470
|
throw new PPTXError(`Invalid merge region: ${validation.errors.join('; ')}`)
|
|
508
471
|
}
|
|
509
472
|
|
|
510
|
-
const
|
|
511
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
512
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
473
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
513
474
|
const trs = tblObj['a:tr'] || []
|
|
514
475
|
|
|
515
476
|
const allTexts = []
|
|
@@ -561,8 +522,7 @@ class TableManager {
|
|
|
561
522
|
const combinedText = allTexts.filter(t => t.trim() !== '').join('\n')
|
|
562
523
|
this.#setCellTextObj(originCell, combinedText)
|
|
563
524
|
|
|
564
|
-
|
|
565
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
525
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
566
526
|
}
|
|
567
527
|
|
|
568
528
|
/**
|
|
@@ -587,9 +547,8 @@ class TableManager {
|
|
|
587
547
|
actualEndCol = undefined
|
|
588
548
|
}
|
|
589
549
|
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
550
|
+
const slideObj = actualSlideManager.getSlideObj(slideIndex)
|
|
551
|
+
const tblObj = this.#findTableObj(slideObj, tableId, actualSlideManager, slideIndex)
|
|
593
552
|
if (!tblObj) {
|
|
594
553
|
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
595
554
|
}
|
|
@@ -629,8 +588,7 @@ class TableManager {
|
|
|
629
588
|
}
|
|
630
589
|
}
|
|
631
590
|
|
|
632
|
-
|
|
633
|
-
actualSlideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
591
|
+
actualSlideManager.markSlideObjDirty(slideIndex)
|
|
634
592
|
}
|
|
635
593
|
|
|
636
594
|
/**
|
|
@@ -642,9 +600,8 @@ class TableManager {
|
|
|
642
600
|
* @returns {Array<Object>} List of merged region coordinates
|
|
643
601
|
*/
|
|
644
602
|
getMergedCells(slideIndex, tableId, slideManager) {
|
|
645
|
-
const
|
|
646
|
-
const
|
|
647
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
603
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
604
|
+
const tblObj = this.#findTableObj(slideObj, tableId, slideManager, slideIndex)
|
|
648
605
|
if (!tblObj) {
|
|
649
606
|
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
650
607
|
}
|
|
@@ -759,9 +716,8 @@ class TableManager {
|
|
|
759
716
|
slideManager
|
|
760
717
|
)
|
|
761
718
|
|
|
762
|
-
const
|
|
763
|
-
const
|
|
764
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
719
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
720
|
+
const tblObj = this.#findTableObj(slideObj, tableId, slideManager, slideIndex)
|
|
765
721
|
if (!tblObj) return
|
|
766
722
|
|
|
767
723
|
const trs = tblObj['a:tr'] || []
|
|
@@ -777,8 +733,7 @@ class TableManager {
|
|
|
777
733
|
}
|
|
778
734
|
}
|
|
779
735
|
|
|
780
|
-
|
|
781
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
736
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
782
737
|
}
|
|
783
738
|
|
|
784
739
|
/**
|
|
@@ -789,12 +744,7 @@ class TableManager {
|
|
|
789
744
|
* @param {SlideManager} slideManager
|
|
790
745
|
*/
|
|
791
746
|
autoFitTable(slideIndex, tableId, slideManager) {
|
|
792
|
-
const
|
|
793
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
794
|
-
const tblObj = this.#findTableObj(slideObj, tableId)
|
|
795
|
-
if (!tblObj) {
|
|
796
|
-
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
797
|
-
}
|
|
747
|
+
const { tblObj } = this.#getTableContext(slideIndex, tableId, slideManager)
|
|
798
748
|
|
|
799
749
|
const trs = tblObj['a:tr'] || []
|
|
800
750
|
const gridCols = tblObj['a:tblGrid']?.['a:gridCol']
|
|
@@ -835,8 +785,7 @@ class TableManager {
|
|
|
835
785
|
gridCols[c]['@_w'] = String(width)
|
|
836
786
|
}
|
|
837
787
|
|
|
838
|
-
|
|
839
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
788
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
840
789
|
}
|
|
841
790
|
|
|
842
791
|
/**
|
|
@@ -849,10 +798,12 @@ class TableManager {
|
|
|
849
798
|
* @param {SlideManager} slideManager
|
|
850
799
|
*/
|
|
851
800
|
resizeTable(slideIndex, tableId, width, height, slideManager) {
|
|
852
|
-
const
|
|
853
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
801
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
854
802
|
|
|
855
|
-
const spTree =
|
|
803
|
+
const spTree =
|
|
804
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
805
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
806
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
856
807
|
if (!spTree) return
|
|
857
808
|
|
|
858
809
|
let frames = spTree['p:graphicFrame'] || []
|
|
@@ -931,8 +882,7 @@ class TableManager {
|
|
|
931
882
|
}
|
|
932
883
|
}
|
|
933
884
|
|
|
934
|
-
|
|
935
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
885
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
936
886
|
}
|
|
937
887
|
|
|
938
888
|
/**
|
|
@@ -943,11 +893,13 @@ class TableManager {
|
|
|
943
893
|
* @returns {Array<{name: string, id: string, rows: number, cols: number}>}
|
|
944
894
|
*/
|
|
945
895
|
inspectTables(slideIndex, slideManager) {
|
|
946
|
-
const
|
|
947
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
896
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
948
897
|
const tables = []
|
|
949
898
|
|
|
950
|
-
const spTree =
|
|
899
|
+
const spTree =
|
|
900
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
901
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
902
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
951
903
|
if (!spTree) return []
|
|
952
904
|
|
|
953
905
|
let frames = spTree['p:graphicFrame'] || []
|
|
@@ -1064,8 +1016,16 @@ class TableManager {
|
|
|
1064
1016
|
/**
|
|
1065
1017
|
* Helper to find a table element inside a slide parsed object.
|
|
1066
1018
|
*/
|
|
1067
|
-
#findTableObj(slideObj, tableId) {
|
|
1068
|
-
|
|
1019
|
+
#findTableObj(slideObj, tableId, slideManager, slideIndex) {
|
|
1020
|
+
if (slideManager && slideIndex !== undefined) {
|
|
1021
|
+
const res = slideManager.getSlideTable(slideIndex, tableId)
|
|
1022
|
+
return res ? res.table : null
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const spTree =
|
|
1026
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
1027
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
1028
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
1069
1029
|
if (!spTree) return null
|
|
1070
1030
|
|
|
1071
1031
|
let frames = spTree['p:graphicFrame'] || []
|
|
@@ -1089,6 +1049,15 @@ class TableManager {
|
|
|
1089
1049
|
return null
|
|
1090
1050
|
}
|
|
1091
1051
|
|
|
1052
|
+
#getTableContext(slideIndex, tableId, slideManager) {
|
|
1053
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
1054
|
+
const tblObj = this.#findTableObj(slideObj, tableId, slideManager, slideIndex)
|
|
1055
|
+
if (!tblObj) {
|
|
1056
|
+
throw new TableNotFoundError(`Table "${tableId}" not found in slide ${slideIndex}`)
|
|
1057
|
+
}
|
|
1058
|
+
return { slideObj, tblObj }
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1092
1061
|
/**
|
|
1093
1062
|
* Generates a new rowId for the given row object.
|
|
1094
1063
|
*/
|
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
* @fileoverview ZOrderManager - Handles slide element Z-order (layer stacking) operations.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const { createLogger } = require('../utils/logger.js')
|
|
6
5
|
const { PPTXError } = require('../utils/errors.js')
|
|
7
6
|
const { Z_ORDER_SYMBOL } = require('../parsers/XMLParser.js')
|
|
8
7
|
|
|
9
|
-
const logger = createLogger('ZOrderManager')
|
|
10
|
-
|
|
11
|
-
const drawingTags = new Set(['p:sp', 'p:pic', 'p:graphicFrame', 'p:grpSp', 'p:cxnSp'])
|
|
12
|
-
|
|
13
8
|
function detectElementType(tag, item) {
|
|
14
9
|
if (tag === 'p:sp') {
|
|
15
10
|
const isTxBox =
|
|
@@ -82,6 +82,18 @@ class ZipManager {
|
|
|
82
82
|
*/
|
|
83
83
|
#removedFiles = new Set()
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @private
|
|
87
|
+
* @type {Map<string, { type: string, content: string|Buffer|Uint8Array }>|null}
|
|
88
|
+
*/
|
|
89
|
+
#cachedFiles = null
|
|
90
|
+
|
|
91
|
+
async loadFromCache(cachedFilesMap) {
|
|
92
|
+
this.#cachedFiles = cachedFilesMap
|
|
93
|
+
await this.#loadCoreProperties()
|
|
94
|
+
logger.debug(`Loaded from cache. Files: ${cachedFilesMap.size}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
async load(source) {
|
|
86
98
|
try {
|
|
87
99
|
const path = require('path')
|
|
@@ -221,6 +233,19 @@ class ZipManager {
|
|
|
221
233
|
return this.#dirtyFiles.get(normalPath)
|
|
222
234
|
}
|
|
223
235
|
|
|
236
|
+
if (this.#cachedFiles && this.#cachedFiles.has(normalPath)) {
|
|
237
|
+
const entry = this.#cachedFiles.get(normalPath)
|
|
238
|
+
let content
|
|
239
|
+
if (entry.type === 'text') {
|
|
240
|
+
content = entry.content
|
|
241
|
+
} else {
|
|
242
|
+
const { TextDecoder } = require('util')
|
|
243
|
+
content = new TextDecoder('utf-8').decode(entry.content)
|
|
244
|
+
}
|
|
245
|
+
this.#xmlCache.set(normalPath, content)
|
|
246
|
+
return content
|
|
247
|
+
}
|
|
248
|
+
|
|
224
249
|
if (this.#isFolderMode) {
|
|
225
250
|
const path = require('path')
|
|
226
251
|
const fs = require('fs-extra')
|
|
@@ -234,6 +259,7 @@ class ZipManager {
|
|
|
234
259
|
return content
|
|
235
260
|
}
|
|
236
261
|
|
|
262
|
+
if (!this.#zip) return null
|
|
237
263
|
const file = this.#zip.file(normalPath)
|
|
238
264
|
if (!file) {
|
|
239
265
|
logger.debug(`File not found in ZIP: ${normalPath}`)
|
|
@@ -256,7 +282,22 @@ class ZipManager {
|
|
|
256
282
|
if (this.#dirtyFiles.has(normalPath)) {
|
|
257
283
|
return this.#dirtyFiles.get(normalPath)
|
|
258
284
|
}
|
|
259
|
-
|
|
285
|
+
if (this.#xmlCache.has(normalPath)) {
|
|
286
|
+
return this.#xmlCache.get(normalPath)
|
|
287
|
+
}
|
|
288
|
+
if (this.#cachedFiles && this.#cachedFiles.has(normalPath)) {
|
|
289
|
+
const entry = this.#cachedFiles.get(normalPath)
|
|
290
|
+
let content
|
|
291
|
+
if (entry.type === 'text') {
|
|
292
|
+
content = entry.content
|
|
293
|
+
} else {
|
|
294
|
+
const { TextDecoder } = require('util')
|
|
295
|
+
content = new TextDecoder('utf-8').decode(entry.content)
|
|
296
|
+
}
|
|
297
|
+
this.#xmlCache.set(normalPath, content)
|
|
298
|
+
return content
|
|
299
|
+
}
|
|
300
|
+
return null
|
|
260
301
|
}
|
|
261
302
|
|
|
262
303
|
/**
|
|
@@ -270,6 +311,10 @@ class ZipManager {
|
|
|
270
311
|
if (this.#dirtyBinaryFiles.has(normalPath)) {
|
|
271
312
|
return this.#dirtyBinaryFiles.get(normalPath)
|
|
272
313
|
}
|
|
314
|
+
if (this.#cachedFiles && this.#cachedFiles.has(normalPath)) {
|
|
315
|
+
const entry = this.#cachedFiles.get(normalPath)
|
|
316
|
+
return entry.content
|
|
317
|
+
}
|
|
273
318
|
if (this.#isFolderMode) {
|
|
274
319
|
const path = require('path')
|
|
275
320
|
const fs = require('fs-extra')
|
|
@@ -277,6 +322,7 @@ class ZipManager {
|
|
|
277
322
|
if (!(await fs.pathExists(diskPath))) return null
|
|
278
323
|
return fs.readFile(diskPath)
|
|
279
324
|
}
|
|
325
|
+
if (!this.#zip) return null
|
|
280
326
|
const file = this.#zip.file(normalPath)
|
|
281
327
|
if (!file) return null
|
|
282
328
|
return file.async('uint8array')
|
|
@@ -354,10 +400,11 @@ class ZipManager {
|
|
|
354
400
|
const normalPath = zipPath.replace(/\\/g, '/')
|
|
355
401
|
if (this.#removedFiles.has(normalPath)) return false
|
|
356
402
|
if (this.#dirtyFiles.has(normalPath) || this.#dirtyBinaryFiles.has(normalPath)) return true
|
|
403
|
+
if (this.#cachedFiles && this.#cachedFiles.has(normalPath)) return true
|
|
357
404
|
if (this.#isFolderMode) {
|
|
358
405
|
return this.#folderFiles.has(normalPath)
|
|
359
406
|
}
|
|
360
|
-
return this.#zip.file(normalPath) !== null
|
|
407
|
+
return this.#zip && this.#zip.file(normalPath) !== null
|
|
361
408
|
}
|
|
362
409
|
|
|
363
410
|
/**
|
|
@@ -367,6 +414,14 @@ class ZipManager {
|
|
|
367
414
|
* @returns {string[]} Array of matching file paths.
|
|
368
415
|
*/
|
|
369
416
|
listFiles(prefix = '') {
|
|
417
|
+
if (this.#cachedFiles) {
|
|
418
|
+
const allFiles = new Set([
|
|
419
|
+
...this.#cachedFiles.keys(),
|
|
420
|
+
...this.#dirtyFiles.keys(),
|
|
421
|
+
...this.#dirtyBinaryFiles.keys(),
|
|
422
|
+
])
|
|
423
|
+
return Array.from(allFiles).filter(f => !this.#removedFiles.has(f) && f.startsWith(prefix))
|
|
424
|
+
}
|
|
370
425
|
if (this.#isFolderMode) {
|
|
371
426
|
const allFiles = new Set([
|
|
372
427
|
...this.#folderFiles,
|
|
@@ -375,6 +430,7 @@ class ZipManager {
|
|
|
375
430
|
])
|
|
376
431
|
return Array.from(allFiles).filter(f => !this.#removedFiles.has(f) && f.startsWith(prefix))
|
|
377
432
|
}
|
|
433
|
+
if (!this.#zip) return []
|
|
378
434
|
return Object.keys(this.#zip.files).filter(f => !this.#zip.files[f].dir && f.startsWith(prefix))
|
|
379
435
|
}
|
|
380
436
|
|
|
@@ -384,23 +440,43 @@ class ZipManager {
|
|
|
384
440
|
*
|
|
385
441
|
* @returns {Promise<Buffer>} Compressed PPTX as a Buffer.
|
|
386
442
|
*/
|
|
387
|
-
async toBuffer() {
|
|
443
|
+
async toBuffer(options = {}) {
|
|
388
444
|
await this.#ensureZipForExport()
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
compression: 'DEFLATE',
|
|
392
|
-
compressionOptions: { level: 6 },
|
|
393
|
-
})
|
|
445
|
+
const zipOptions = this.#getZipOptions(options)
|
|
446
|
+
return this.#zip.generateAsync(zipOptions)
|
|
394
447
|
}
|
|
395
448
|
|
|
396
|
-
async toStream() {
|
|
449
|
+
async toStream(options = {}) {
|
|
397
450
|
await this.#ensureZipForExport()
|
|
398
|
-
|
|
451
|
+
const zipOptions = this.#getZipOptions(options)
|
|
452
|
+
zipOptions.streamFiles = true
|
|
453
|
+
return this.#zip.generateNodeStream(zipOptions)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#getZipOptions(options = {}) {
|
|
457
|
+
const compression = options.compression || 'balanced'
|
|
458
|
+
let method = 'DEFLATE'
|
|
459
|
+
let level = 6
|
|
460
|
+
|
|
461
|
+
if (compression === 'none' || compression === 'store') {
|
|
462
|
+
method = 'STORE'
|
|
463
|
+
level = 0
|
|
464
|
+
} else if (compression === 'fast') {
|
|
465
|
+
method = 'DEFLATE'
|
|
466
|
+
level = 1
|
|
467
|
+
} else if (compression === 'balanced') {
|
|
468
|
+
method = 'DEFLATE'
|
|
469
|
+
level = 6
|
|
470
|
+
} else if (compression === 'maximum') {
|
|
471
|
+
method = 'DEFLATE'
|
|
472
|
+
level = 9
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
399
476
|
type: 'nodebuffer',
|
|
400
|
-
compression:
|
|
401
|
-
compressionOptions: { level:
|
|
402
|
-
|
|
403
|
-
})
|
|
477
|
+
compression: method,
|
|
478
|
+
compressionOptions: method === 'DEFLATE' ? { level } : undefined,
|
|
479
|
+
}
|
|
404
480
|
}
|
|
405
481
|
|
|
406
482
|
/**
|
|
@@ -594,6 +670,36 @@ class ZipManager {
|
|
|
594
670
|
|
|
595
671
|
const zip = new JSZip()
|
|
596
672
|
|
|
673
|
+
if (this.#cachedFiles) {
|
|
674
|
+
// 1. Read all files from cache (that are not removed)
|
|
675
|
+
for (const [relPath, entry] of this.#cachedFiles.entries()) {
|
|
676
|
+
if (this.#removedFiles.has(relPath)) continue
|
|
677
|
+
|
|
678
|
+
if (this.#dirtyFiles.has(relPath)) {
|
|
679
|
+
zip.file(relPath, this.#dirtyFiles.get(relPath))
|
|
680
|
+
} else if (this.#dirtyBinaryFiles.has(relPath)) {
|
|
681
|
+
zip.file(relPath, this.#dirtyBinaryFiles.get(relPath))
|
|
682
|
+
} else {
|
|
683
|
+
zip.file(relPath, entry.content)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// 2. Write any new files that were added (and not already in cache)
|
|
688
|
+
for (const [relPath, content] of this.#dirtyFiles.entries()) {
|
|
689
|
+
if (!this.#cachedFiles.has(relPath) && !this.#removedFiles.has(relPath)) {
|
|
690
|
+
zip.file(relPath, content)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
for (const [relPath, data] of this.#dirtyBinaryFiles.entries()) {
|
|
694
|
+
if (!this.#cachedFiles.has(relPath) && !this.#removedFiles.has(relPath)) {
|
|
695
|
+
zip.file(relPath, data)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
this.#zip = zip
|
|
700
|
+
return zip
|
|
701
|
+
}
|
|
702
|
+
|
|
597
703
|
// 1. Read all files from the original folder structure (that are not removed)
|
|
598
704
|
for (const relPath of this.#folderFiles) {
|
|
599
705
|
if (this.#removedFiles.has(relPath)) continue
|
|
@@ -184,7 +184,7 @@ class ChartWorkbookUpdater {
|
|
|
184
184
|
static #serializeSheetXml(sheetXml, cells) {
|
|
185
185
|
// Group cells by row
|
|
186
186
|
const rows = {}
|
|
187
|
-
for (const
|
|
187
|
+
for (const ref of Object.keys(cells)) {
|
|
188
188
|
const rowMatch = /\d+$/.exec(ref)
|
|
189
189
|
if (!rowMatch) continue
|
|
190
190
|
const r = parseInt(rowMatch[0], 10)
|
|
@@ -70,10 +70,9 @@ class ContentTypesHelper {
|
|
|
70
70
|
*/
|
|
71
71
|
addMediaDefault(zipManager, extension, mimeType) {
|
|
72
72
|
this.#updateQueue = this.#updateQueue.then(async () => {
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
73
|
+
const content = await zipManager.readFile('[Content_Types].xml')
|
|
74
|
+
if (!content) return
|
|
75
75
|
|
|
76
|
-
const content = await xmlFile.async('text')
|
|
77
76
|
const entry = `Extension="${extension}" ContentType="${mimeType}"`
|
|
78
77
|
if (!content.includes(entry)) {
|
|
79
78
|
const updated = content.replace('</Types>', ` <Default ${entry}/>\n</Types>`)
|
|
@@ -92,12 +91,11 @@ class ContentTypesHelper {
|
|
|
92
91
|
*/
|
|
93
92
|
#addOverride(zipManager, partName, contentType) {
|
|
94
93
|
this.#updateQueue = this.#updateQueue.then(async () => {
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
94
|
+
const content = await zipManager.readFile('[Content_Types].xml')
|
|
95
|
+
if (!content) {
|
|
97
96
|
logger.warn('[Content_Types].xml not found')
|
|
98
97
|
return
|
|
99
98
|
}
|
|
100
|
-
const content = await xmlFile.async('text')
|
|
101
99
|
const entry = `PartName="${partName}"`
|
|
102
100
|
if (!content.includes(entry)) {
|
|
103
101
|
const override = `<Override PartName="${partName}" ContentType="${contentType}"/>`
|
|
@@ -115,12 +113,11 @@ class ContentTypesHelper {
|
|
|
115
113
|
*/
|
|
116
114
|
#removeOverride(zipManager, partName) {
|
|
117
115
|
this.#updateQueue = this.#updateQueue.then(async () => {
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
116
|
+
const content = await zipManager.readFile('[Content_Types].xml')
|
|
117
|
+
if (!content) {
|
|
120
118
|
logger.warn('[Content_Types].xml not found')
|
|
121
119
|
return
|
|
122
120
|
}
|
|
123
|
-
const content = await xmlFile.async('text')
|
|
124
121
|
const regex = new RegExp(`<Override[^>]*PartName="${partName}"[^>]*/>\\s*`, 'g')
|
|
125
122
|
if (regex.test(content)) {
|
|
126
123
|
const updated = content.replace(regex, '')
|