node-pptx-templater 1.0.2 → 1.0.4

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 (38) hide show
  1. package/CHANGELOG.md +28 -3
  2. package/README.md +175 -327
  3. package/package.json +12 -3
  4. package/src/cli/commands/build.js +30 -31
  5. package/src/cli/commands/debug.js +23 -23
  6. package/src/cli/commands/extract.js +21 -21
  7. package/src/cli/commands/inspect.js +23 -23
  8. package/src/cli/commands/validate.js +17 -17
  9. package/src/cli/index.js +39 -36
  10. package/src/core/OutputWriter.js +79 -78
  11. package/src/core/PPTXTemplater.js +856 -273
  12. package/src/core/TemplateEngine.js +67 -71
  13. package/src/core/ValidationEngine.js +246 -0
  14. package/src/index.js +30 -17
  15. package/src/managers/ChartManager.js +195 -70
  16. package/src/managers/ContentTypesManager.js +49 -45
  17. package/src/managers/HyperlinkManager.js +146 -142
  18. package/src/managers/ImageManager.js +336 -0
  19. package/src/managers/MediaManager.js +62 -81
  20. package/src/managers/RelationshipManager.js +99 -95
  21. package/src/managers/ShapeManager.js +340 -0
  22. package/src/managers/SlideManager.js +408 -311
  23. package/src/managers/TableManager.js +979 -262
  24. package/src/managers/TextManager.js +197 -0
  25. package/src/managers/ZipManager.js +69 -69
  26. package/src/managers/charts/ChartCacheGenerator.js +75 -58
  27. package/src/managers/charts/ChartParser.js +9 -13
  28. package/src/managers/charts/ChartRelationshipManager.js +12 -10
  29. package/src/managers/charts/ChartWorkbookUpdater.js +59 -56
  30. package/src/parsers/XMLParser.js +47 -50
  31. package/src/templates/blankPptx.js +3 -2
  32. package/src/templates/slideTemplate.js +28 -34
  33. package/src/utils/contentTypesHelper.js +40 -54
  34. package/src/utils/errors.js +18 -18
  35. package/src/utils/idUtils.js +16 -14
  36. package/src/utils/logger.js +18 -16
  37. package/src/utils/relationshipUtils.js +19 -20
  38. package/src/utils/xmlUtils.js +26 -26
@@ -24,21 +24,25 @@
24
24
  * └── app.xml — application metadata
25
25
  */
26
26
 
27
- const { ZipManager } = require('../managers/ZipManager.js');
28
- const { XMLParser } = require('../parsers/XMLParser.js');
29
- const { ContentTypesManager } = require('../managers/ContentTypesManager.js');
30
- const { SlideManager } = require('../managers/SlideManager.js');
31
- const { ChartManager } = require('../managers/ChartManager.js');
32
- const { TableManager } = require('../managers/TableManager.js');
33
- const { HyperlinkManager } = require('../managers/HyperlinkManager.js');
34
- const { MediaManager } = require('../managers/MediaManager.js');
35
- const { RelationshipManager } = require('../managers/RelationshipManager.js');
36
- const { OutputWriter } = require('./OutputWriter.js');
37
- const { TemplateEngine } = require('./TemplateEngine.js');
38
- const { createLogger } = require('../utils/logger.js');
39
- const { PPTXError } = require('../utils/errors.js');
40
-
41
- const logger = createLogger('PPTXTemplater');
27
+ const { ZipManager } = require('../managers/ZipManager.js')
28
+ const { XMLParser } = require('../parsers/XMLParser.js')
29
+ const { ContentTypesManager } = require('../managers/ContentTypesManager.js')
30
+ const { SlideManager } = require('../managers/SlideManager.js')
31
+ const { ChartManager } = require('../managers/ChartManager.js')
32
+ const { TableManager } = require('../managers/TableManager.js')
33
+ const { HyperlinkManager } = require('../managers/HyperlinkManager.js')
34
+ const { MediaManager } = require('../managers/MediaManager.js')
35
+ const { RelationshipManager } = require('../managers/RelationshipManager.js')
36
+ const { ShapeManager } = require('../managers/ShapeManager.js')
37
+ const { ImageManager } = require('../managers/ImageManager.js')
38
+ const { TextManager } = require('../managers/TextManager.js')
39
+ const { ValidationEngine } = require('./ValidationEngine.js')
40
+ const { OutputWriter } = require('./OutputWriter.js')
41
+ const { TemplateEngine } = require('./TemplateEngine.js')
42
+ const { createLogger } = require('../utils/logger.js')
43
+ const { PPTXError } = require('../utils/errors.js')
44
+
45
+ const logger = createLogger('PPTXTemplater')
42
46
 
43
47
  /**
44
48
  * @class PPTXTemplater
@@ -55,92 +59,117 @@ class PPTXTemplater {
55
59
  * @private
56
60
  * @type {ZipManager}
57
61
  */
58
- #zipManager;
62
+ #zipManager
59
63
 
60
64
  /**
61
65
  * @private
62
66
  * @type {XMLParser}
63
67
  */
64
- #xmlParser;
68
+ #xmlParser
65
69
 
66
70
  /**
67
71
  * @private
68
72
  * @type {ContentTypesManager}
69
73
  */
70
- #contentTypesManager;
74
+ #contentTypesManager
71
75
 
72
76
  /**
73
77
  * @private
74
78
  * @type {SlideManager}
75
79
  */
76
- #slideManager;
80
+ #slideManager
77
81
 
78
82
  /**
79
83
  * @private
80
84
  * @type {ChartManager}
81
85
  */
82
- #chartManager;
86
+ #chartManager
83
87
 
84
88
  /**
85
89
  * @private
86
90
  * @type {TableManager}
87
91
  */
88
- #tableManager;
92
+ #tableManager
89
93
 
90
94
  /**
91
95
  * @private
92
96
  * @type {HyperlinkManager}
93
97
  */
94
- #hyperlinkManager;
98
+ #hyperlinkManager
95
99
 
96
100
  /**
97
101
  * @private
98
102
  * @type {MediaManager}
99
103
  */
100
- #mediaManager;
104
+ #mediaManager
105
+
106
+ /**
107
+ * @private
108
+ * @type {ShapeManager}
109
+ */
110
+ #shapeManager
111
+
112
+ /**
113
+ * @private
114
+ * @type {ImageManager}
115
+ */
116
+ #imageManager
117
+
118
+ /**
119
+ * @private
120
+ * @type {TextManager}
121
+ */
122
+ #textManager
101
123
 
102
124
  /**
103
125
  * @private
104
126
  * @type {RelationshipManager}
105
127
  */
106
- #relationshipManager;
128
+ #relationshipManager
107
129
 
108
130
  /**
109
131
  * @private
110
132
  * @type {OutputWriter}
111
133
  */
112
- #outputWriter;
134
+ #outputWriter
113
135
 
114
136
  /**
115
137
  * @private
116
138
  * @type {TemplateEngine}
117
139
  */
118
- #templateEngine;
140
+ #templateEngine
119
141
 
120
142
  /**
121
143
  * @private
122
144
  * @type {number[]} - Currently selected slide indices (1-based)
123
145
  */
124
- #selectedSlides = [];
146
+ #selectedSlides = []
125
147
 
126
148
  /**
127
149
  * @private
128
150
  * @type {boolean}
129
151
  */
130
- #loaded = false;
152
+ #loaded = false
131
153
 
132
154
  constructor() {
133
- this.#xmlParser = new XMLParser();
134
- this.#zipManager = new ZipManager();
135
- this.#contentTypesManager = new ContentTypesManager(this.#xmlParser);
136
- this.#relationshipManager = new RelationshipManager(this.#xmlParser);
137
- this.#slideManager = new SlideManager(this.#xmlParser, this.#relationshipManager, this.#contentTypesManager);
138
- this.#chartManager = new ChartManager(this.#xmlParser, this.#contentTypesManager);
139
- this.#tableManager = new TableManager(this.#xmlParser);
140
- this.#hyperlinkManager = new HyperlinkManager(this.#xmlParser, this.#relationshipManager);
141
- this.#mediaManager = new MediaManager(this.#contentTypesManager);
142
- this.#templateEngine = new TemplateEngine(this.#xmlParser);
143
- this.#outputWriter = new OutputWriter(this.#zipManager, this.#contentTypesManager);
155
+ this.#xmlParser = new XMLParser()
156
+ this.#zipManager = new ZipManager()
157
+ this.#contentTypesManager = new ContentTypesManager(this.#xmlParser)
158
+ this.#relationshipManager = new RelationshipManager(this.#xmlParser)
159
+ this.#slideManager = new SlideManager(
160
+ this.#xmlParser,
161
+ this.#relationshipManager,
162
+ this.#contentTypesManager
163
+ )
164
+ this.#chartManager = new ChartManager(this.#xmlParser, this.#contentTypesManager)
165
+ this.#tableManager = new TableManager(this.#xmlParser)
166
+ this.#hyperlinkManager = new HyperlinkManager(this.#xmlParser, this.#relationshipManager)
167
+ this.#mediaManager = new MediaManager(this.#contentTypesManager)
168
+ this.#shapeManager = new ShapeManager(this.#xmlParser)
169
+ this.#imageManager = new ImageManager(this.#xmlParser)
170
+ this.#textManager = new TextManager(this.#xmlParser)
171
+ this.#templateEngine = new TemplateEngine(this.#xmlParser)
172
+ this.#outputWriter = new OutputWriter(this.#zipManager, this.#contentTypesManager)
144
173
  }
145
174
 
146
175
  /**
@@ -160,9 +189,9 @@ class PPTXTemplater {
160
189
  * const ppt = await PPTXTemplater.load(buffer);
161
190
  */
162
191
  static async load(source) {
163
- const engine = new PPTXTemplater();
164
- await engine.#initialize(source);
165
- return engine;
192
+ const engine = new PPTXTemplater()
193
+ await engine.#initialize(source)
194
+ return engine
166
195
  }
167
196
 
168
197
  /**
@@ -177,9 +206,9 @@ class PPTXTemplater {
177
206
  * await ppt.saveToFile('./new.pptx');
178
207
  */
179
208
  static async create() {
180
- const engine = new PPTXTemplater();
181
- await engine.#initializeBlank();
182
- return engine;
209
+ const engine = new PPTXTemplater()
210
+ await engine.#initializeBlank()
211
+ return engine
183
212
  }
184
213
 
185
214
  /**
@@ -188,31 +217,31 @@ class PPTXTemplater {
188
217
  * @param {string|Buffer} source
189
218
  */
190
219
  async #initialize(source) {
191
- logger.debug(`Loading PPTX from ${typeof source === 'string' ? source : 'buffer'}`);
220
+ logger.debug(`Loading PPTX from ${typeof source === 'string' ? source : 'buffer'}`)
192
221
 
193
222
  // Load and extract the ZIP archive (PPTX is just a ZIP)
194
- await this.#zipManager.load(source);
223
+ await this.#zipManager.load(source)
195
224
 
196
225
  // Initialize content types manager first!
197
- await this.#contentTypesManager.initialize(this.#zipManager);
226
+ await this.#contentTypesManager.initialize(this.#zipManager)
198
227
 
199
228
  // Parse the core presentation relationships and structure
200
- await this.#relationshipManager.initialize(this.#zipManager);
229
+ await this.#relationshipManager.initialize(this.#zipManager)
201
230
 
202
231
  // Load all slide references from presentation.xml
203
- await this.#slideManager.initialize(this.#zipManager);
232
+ await this.#slideManager.initialize(this.#zipManager)
204
233
 
205
234
  // Pre-load all slide XML into cache to allow synchronous operations like replaceText()
206
- await this.#slideManager.preloadAll();
235
+ await this.#slideManager.preloadAll()
207
236
 
208
237
  // Initialize chart manager with zip context
209
- await this.#chartManager.initialize(this.#zipManager);
238
+ await this.#chartManager.initialize(this.#zipManager)
210
239
 
211
240
  // Deduplicate and index media files
212
- await this.#mediaManager.initialize(this.#zipManager);
241
+ await this.#mediaManager.initialize(this.#zipManager)
213
242
 
214
- this.#loaded = true;
215
- logger.debug(`Loaded ${this.#slideManager.slideCount} slides successfully`);
243
+ this.#loaded = true
244
+ logger.debug(`Loaded ${this.#slideManager.slideCount} slides successfully`)
216
245
  }
217
246
 
218
247
  /**
@@ -220,13 +249,13 @@ class PPTXTemplater {
220
249
  * @private
221
250
  */
222
251
  async #initializeBlank() {
223
- await this.#zipManager.createBlank();
224
- await this.#contentTypesManager.initialize(this.#zipManager);
225
- await this.#relationshipManager.initialize(this.#zipManager);
226
- await this.#slideManager.initialize(this.#zipManager);
227
- await this.#chartManager.initialize(this.#zipManager);
228
- await this.#mediaManager.initialize(this.#zipManager);
229
- this.#loaded = true;
252
+ await this.#zipManager.createBlank()
253
+ await this.#contentTypesManager.initialize(this.#zipManager)
254
+ await this.#relationshipManager.initialize(this.#zipManager)
255
+ await this.#slideManager.initialize(this.#zipManager)
256
+ await this.#chartManager.initialize(this.#zipManager)
257
+ await this.#mediaManager.initialize(this.#zipManager)
258
+ this.#loaded = true
230
259
  }
231
260
 
232
261
  /**
@@ -235,7 +264,7 @@ class PPTXTemplater {
235
264
  */
236
265
  #assertLoaded() {
237
266
  if (!this.#loaded) {
238
- throw new PPTXError('Engine not initialized. Call PPTXTemplater.load() first.');
267
+ throw new PPTXError('Engine not initialized. Call PPTXTemplater.load() first.')
239
268
  }
240
269
  }
241
270
 
@@ -253,10 +282,10 @@ class PPTXTemplater {
253
282
  * ppt.useSlide('intro'); // Select by custom tag
254
283
  */
255
284
  useSlide(...slideRefs) {
256
- this.#assertLoaded();
257
- this.#selectedSlides = slideRefs;
258
- logger.debug(`Selected slides: ${slideRefs.join(', ')}`);
259
- return this;
285
+ this.#assertLoaded()
286
+ this.#selectedSlides = slideRefs
287
+ logger.debug(`Selected slides: ${slideRefs.join(', ')}`)
288
+ return this
260
289
  }
261
290
 
262
291
  /**
@@ -264,9 +293,9 @@ class PPTXTemplater {
264
293
  * @returns {PPTXTemplater} this (chainable)
265
294
  */
266
295
  useAllSlides() {
267
- this.#assertLoaded();
268
- this.#selectedSlides = [];
269
- return this;
296
+ this.#assertLoaded()
297
+ this.#selectedSlides = []
298
+ return this
270
299
  }
271
300
 
272
301
  /**
@@ -277,13 +306,13 @@ class PPTXTemplater {
277
306
  */
278
307
  #getTargetSlideIndices() {
279
308
  if (this.#selectedSlides.length === 0) {
280
- return this.#slideManager.getAllSlideIndices();
309
+ return this.#slideManager.getAllSlideIndices()
281
310
  }
282
311
  return this.#selectedSlides.flatMap(ref => {
283
- if (typeof ref === 'number') return [ref];
312
+ if (typeof ref === 'number') return [ref]
284
313
  // Resolve by tag or ID
285
- return this.#slideManager.resolveSlideRef(ref);
286
- });
314
+ return this.#slideManager.resolveSlideRef(ref)
315
+ })
287
316
  }
288
317
 
289
318
  /**
@@ -301,17 +330,19 @@ class PPTXTemplater {
301
330
  * });
302
331
  */
303
332
  replaceText(replacements) {
304
- this.#assertLoaded();
305
- const targetIndices = this.#getTargetSlideIndices();
333
+ this.#assertLoaded()
334
+ const targetIndices = this.#getTargetSlideIndices()
306
335
 
307
336
  for (const slideIndex of targetIndices) {
308
- const slideXml = this.#slideManager.getSlideXml(slideIndex);
309
- const updated = this.#templateEngine.replaceTextInXml(slideXml, replacements);
310
- this.#slideManager.setSlideXml(slideIndex, updated);
337
+ const slideXml = this.#slideManager.getSlideXml(slideIndex)
338
+ const updated = this.#templateEngine.replaceTextInXml(slideXml, replacements)
339
+ this.#slideManager.setSlideXml(slideIndex, updated)
311
340
  }
312
341
 
313
- logger.debug(`Replaced ${Object.keys(replacements).length} placeholder(s) in ${targetIndices.length} slide(s)`);
314
- return this;
342
+ logger.debug(
343
+ `Replaced ${Object.keys(replacements).length} placeholder(s) in ${targetIndices.length} slide(s)`
344
+ )
345
+ return this
315
346
  }
316
347
 
317
348
  /**
@@ -334,8 +365,8 @@ class PPTXTemplater {
334
365
  * });
335
366
  */
336
367
  updateChart(chartId, data) {
337
- this.#assertLoaded();
338
- const targetIndices = this.#getTargetSlideIndices();
368
+ this.#assertLoaded()
369
+ const targetIndices = this.#getTargetSlideIndices()
339
370
 
340
371
  for (const slideIndex of targetIndices) {
341
372
  this.#chartManager.updateChart(
@@ -344,11 +375,11 @@ class PPTXTemplater {
344
375
  data,
345
376
  this.#slideManager,
346
377
  this.#relationshipManager
347
- );
378
+ )
348
379
  }
349
380
 
350
- logger.debug(`Updated chart "${chartId}" in ${targetIndices.length} slide(s)`);
351
- return this;
381
+ logger.debug(`Updated chart "${chartId}" in ${targetIndices.length} slide(s)`)
382
+ return this
352
383
  }
353
384
 
354
385
  /**
@@ -367,15 +398,15 @@ class PPTXTemplater {
367
398
  * ]);
368
399
  */
369
400
  updateTable(tableId, rows) {
370
- this.#assertLoaded();
371
- const targetIndices = this.#getTargetSlideIndices();
401
+ this.#assertLoaded()
402
+ const targetIndices = this.#getTargetSlideIndices()
372
403
 
373
404
  for (const slideIndex of targetIndices) {
374
- this.#tableManager.updateTable(slideIndex, tableId, rows, this.#slideManager);
405
+ this.#tableManager.updateTable(slideIndex, tableId, rows, this.#slideManager)
375
406
  }
376
407
 
377
- logger.debug(`Updated table "${tableId}" in ${targetIndices.length} slide(s)`);
378
- return this;
408
+ logger.debug(`Updated table "${tableId}" in ${targetIndices.length} slide(s)`)
409
+ return this
379
410
  }
380
411
 
381
412
  /**
@@ -391,8 +422,8 @@ class PPTXTemplater {
391
422
  * ppt.addHyperlink({ text: 'Open Website', url: 'https://example.com' });
392
423
  */
393
424
  addHyperlink(options) {
394
- this.#assertLoaded();
395
- const targetIndices = this.#getTargetSlideIndices();
425
+ this.#assertLoaded()
426
+ const targetIndices = this.#getTargetSlideIndices()
396
427
 
397
428
  for (const slideIndex of targetIndices) {
398
429
  this.#hyperlinkManager.addExternalHyperlink(
@@ -400,10 +431,10 @@ class PPTXTemplater {
400
431
  options,
401
432
  this.#slideManager,
402
433
  this.#relationshipManager
403
- );
434
+ )
404
435
  }
405
436
 
406
- return this;
437
+ return this
407
438
  }
408
439
 
409
440
  /**
@@ -416,8 +447,8 @@ class PPTXTemplater {
416
447
  * @returns {PPTXTemplater} this (chainable)
417
448
  */
418
449
  addSlideLink(options) {
419
- this.#assertLoaded();
420
- const { sourceSlide, targetSlide, element } = options;
450
+ this.#assertLoaded()
451
+ const { sourceSlide, targetSlide, element } = options
421
452
 
422
453
  // Fallback: If no element text is provided, link the slide number (legacy behavior)
423
454
  if (!element) {
@@ -426,7 +457,7 @@ class PPTXTemplater {
426
457
  targetSlide,
427
458
  this.#slideManager,
428
459
  this.#relationshipManager
429
- );
460
+ )
430
461
  } else {
431
462
  // Add a slide hyperlink on specific text
432
463
  this.#hyperlinkManager.addTextSlideLink(
@@ -435,9 +466,9 @@ class PPTXTemplater {
435
466
  targetSlide,
436
467
  this.#slideManager,
437
468
  this.#relationshipManager
438
- );
469
+ )
439
470
  }
440
- return this;
471
+ return this
441
472
  }
442
473
 
443
474
  /**
@@ -450,15 +481,15 @@ class PPTXTemplater {
450
481
  * @returns {PPTXTemplater} this
451
482
  */
452
483
  addImageLink(options) {
453
- this.#assertLoaded();
484
+ this.#assertLoaded()
454
485
  this.#hyperlinkManager.addShapeSlideLink(
455
486
  options.slide,
456
487
  options.imageId,
457
488
  options.targetSlide,
458
489
  this.#slideManager,
459
490
  this.#relationshipManager
460
- );
461
- return this;
491
+ )
492
+ return this
462
493
  }
463
494
 
464
495
  /**
@@ -471,15 +502,15 @@ class PPTXTemplater {
471
502
  * @returns {PPTXTemplater} this
472
503
  */
473
504
  addShapeLink(options) {
474
- this.#assertLoaded();
505
+ this.#assertLoaded()
475
506
  this.#hyperlinkManager.addShapeSlideLink(
476
507
  options.slide,
477
508
  options.shapeId,
478
509
  options.targetSlide,
479
510
  this.#slideManager,
480
511
  this.#relationshipManager
481
- );
482
- return this;
512
+ )
513
+ return this
483
514
  }
484
515
 
485
516
  /**
@@ -492,10 +523,10 @@ class PPTXTemplater {
492
523
  * @returns {PPTXTemplater} this (chainable)
493
524
  */
494
525
  addTextNavigationLink(options) {
495
- this.#assertLoaded();
496
- const { slide, element, action } = options;
497
- this.#hyperlinkManager.addTextNavigationLink(slide, element, action, this.#slideManager);
498
- return this;
526
+ this.#assertLoaded()
527
+ const { slide, element, action } = options
528
+ this.#hyperlinkManager.addTextNavigationLink(slide, element, action, this.#slideManager)
529
+ return this
499
530
  }
500
531
 
501
532
  /**
@@ -508,10 +539,10 @@ class PPTXTemplater {
508
539
  * @returns {PPTXTemplater} this (chainable)
509
540
  */
510
541
  addShapeNavigationLink(options) {
511
- this.#assertLoaded();
512
- const { slide, shapeId, action } = options;
513
- this.#hyperlinkManager.addShapeNavigationLink(slide, shapeId, action, this.#slideManager);
514
- return this;
542
+ this.#assertLoaded()
543
+ const { slide, shapeId, action } = options
544
+ this.#hyperlinkManager.addShapeNavigationLink(slide, shapeId, action, this.#slideManager)
545
+ return this
515
546
  }
516
547
 
517
548
  /**
@@ -534,10 +565,10 @@ class PPTXTemplater {
534
565
  * });
535
566
  */
536
567
  addSlide(options = {}) {
537
- this.#assertLoaded();
538
- this.#slideManager.addNewSlide(options, this.#relationshipManager, this.#mediaManager);
539
- logger.debug(`Added new slide: "${options.title || 'Untitled'}"`);
540
- return this;
568
+ this.#assertLoaded()
569
+ this.#slideManager.addNewSlide(options, this.#relationshipManager, this.#mediaManager)
570
+ logger.debug(`Added new slide: "${options.title || 'Untitled'}"`)
571
+ return this
541
572
  }
542
573
 
543
574
  /**
@@ -548,9 +579,9 @@ class PPTXTemplater {
548
579
  * @returns {PPTXTemplater} this (chainable)
549
580
  */
550
581
  cloneSlide(sourceSlideNumber, atPosition) {
551
- this.#assertLoaded();
552
- this.#slideManager.cloneSlide(sourceSlideNumber, atPosition, this.#relationshipManager);
553
- return this;
582
+ this.#assertLoaded()
583
+ this.#slideManager.cloneSlide(sourceSlideNumber, atPosition, this.#relationshipManager)
584
+ return this
554
585
  }
555
586
 
556
587
  /**
@@ -560,9 +591,9 @@ class PPTXTemplater {
560
591
  * @returns {PPTXTemplater} this (chainable)
561
592
  */
562
593
  removeSlide(slideNumber) {
563
- this.#assertLoaded();
564
- this.#slideManager.removeSlide(slideNumber);
565
- return this;
594
+ this.#assertLoaded()
595
+ this.#slideManager.removeSlide(slideNumber)
596
+ return this
566
597
  }
567
598
 
568
599
  /**
@@ -575,9 +606,9 @@ class PPTXTemplater {
575
606
  * ppt.reorderSlides([3, 1, 2]); // Move slide 3 to position 1
576
607
  */
577
608
  reorderSlides(order) {
578
- this.#assertLoaded();
579
- this.#slideManager.reorderSlides(order);
580
- return this;
609
+ this.#assertLoaded()
610
+ this.#slideManager.reorderSlides(order)
611
+ return this
581
612
  }
582
613
 
583
614
  /**
@@ -592,9 +623,9 @@ class PPTXTemplater {
592
623
  * ppt.useSlide('intro').replaceText({ '{{title}}': 'Hello' });
593
624
  */
594
625
  tagSlide(slideNumber, tag) {
595
- this.#assertLoaded();
596
- this.#slideManager.tagSlide(slideNumber, tag);
597
- return this;
626
+ this.#assertLoaded()
627
+ this.#slideManager.tagSlide(slideNumber, tag)
628
+ return this
598
629
  }
599
630
 
600
631
  /**
@@ -609,8 +640,8 @@ class PPTXTemplater {
609
640
  * await subset.saveToFile('./subset.pptx');
610
641
  */
611
642
  async exportSlides(...slideNumbers) {
612
- this.#assertLoaded();
613
- return this.#slideManager.exportSlides(slideNumbers, this);
643
+ this.#assertLoaded()
644
+ return this.#slideManager.exportSlides(slideNumbers, this)
614
645
  }
615
646
 
616
647
  /**
@@ -622,9 +653,9 @@ class PPTXTemplater {
622
653
  * @returns {Promise<PPTXTemplater>} this (chainable)
623
654
  */
624
655
  async importSlideFrom(sourceEngine, slideRef) {
625
- this.#assertLoaded();
626
- await this.#slideManager.importSlide(sourceEngine, slideRef, this.#mediaManager);
627
- return this;
656
+ this.#assertLoaded()
657
+ await this.#slideManager.importSlide(sourceEngine, slideRef, this.#mediaManager)
658
+ return this
628
659
  }
629
660
 
630
661
  /**
@@ -639,32 +670,34 @@ class PPTXTemplater {
639
670
  * ppt.importSlides([1, 3, 5]);
640
671
  */
641
672
  importSlides(slideIndices) {
642
- this.#assertLoaded();
643
- const slidesToKeep = slideIndices.map(i => this.#slideManager.getSlideInfo(i).slideId);
673
+ this.#assertLoaded()
674
+ const slidesToKeep = slideIndices.map(i => this.#slideManager.getSlideInfo(i).slideId)
644
675
 
645
676
  // Remove unneeded slides from highest to lowest index to avoid shifting issues
646
- const allIndices = this.#slideManager.getAllSlideIndices();
677
+ const allIndices = this.#slideManager.getAllSlideIndices()
647
678
  for (let i = allIndices.length; i >= 1; i--) {
648
- const info = this.#slideManager.getSlideInfo(i);
679
+ const info = this.#slideManager.getSlideInfo(i)
649
680
  if (!slidesToKeep.includes(info.slideId)) {
650
- this.#slideManager.removeSlide(i);
681
+ this.#slideManager.removeSlide(i)
651
682
  }
652
683
  }
653
684
 
654
685
  // Calculate new target order based on the requested slideIndices
655
- const currentOrder = this.#slideManager.getAllSlideIndices().map(i => this.#slideManager.getSlideInfo(i).slideId);
686
+ const currentOrder = this.#slideManager
687
+ .getAllSlideIndices()
688
+ .map(i => this.#slideManager.getSlideInfo(i).slideId)
656
689
 
657
690
  const newOrder = slidesToKeep.map(id => {
658
- return currentOrder.indexOf(id) + 1;
659
- });
691
+ return currentOrder.indexOf(id) + 1
692
+ })
660
693
 
661
694
  // Only reorder if needed
662
695
  if (newOrder.join(',') !== currentOrder.map((_, i) => i + 1).join(',')) {
663
- this.#slideManager.reorderSlides(newOrder);
696
+ this.#slideManager.reorderSlides(newOrder)
664
697
  }
665
698
 
666
- logger.debug(`Imported ${slideIndices.length} slide(s).`);
667
- return this;
699
+ logger.debug(`Imported ${slideIndices.length} slide(s).`)
700
+ return this
668
701
  }
669
702
 
670
703
  /**
@@ -673,7 +706,7 @@ class PPTXTemplater {
673
706
  * @returns {PresentationInfo} Metadata object.
674
707
  */
675
708
  getInfo() {
676
- this.#assertLoaded();
709
+ this.#assertLoaded()
677
710
  return {
678
711
  slideCount: this.#slideManager.slideCount,
679
712
  title: this.#zipManager.getCoreProperty('dc:title') || '',
@@ -682,7 +715,7 @@ class PPTXTemplater {
682
715
  modified: this.#zipManager.getCoreProperty('dcterms:modified') || '',
683
716
  slides: this.#slideManager.getAllSlideInfo(),
684
717
  mediaCount: this.#mediaManager.mediaCount,
685
- };
718
+ }
686
719
  }
687
720
 
688
721
  /**
@@ -692,8 +725,8 @@ class PPTXTemplater {
692
725
  * @returns {ValidationResult} Object with `valid`, `errors`, and `warnings` arrays.
693
726
  */
694
727
  validate() {
695
- this.#assertLoaded();
696
- return this.#slideManager.validateStructure(this.#relationshipManager, this.#zipManager);
728
+ this.#assertLoaded()
729
+ return this.#slideManager.validateStructure(this.#relationshipManager, this.#zipManager)
697
730
  }
698
731
 
699
732
  /**
@@ -703,16 +736,16 @@ class PPTXTemplater {
703
736
  * @returns {Promise<PPTXTemplater>} this (chainable)
704
737
  */
705
738
  async repair() {
706
- this.#assertLoaded();
739
+ this.#assertLoaded()
707
740
 
708
741
  // 1. Rebuild presentation.xml slide mappings
709
- this.#slideManager.rebuildPresentationSlideOrder();
742
+ this.#slideManager.rebuildPresentationSlideOrder()
710
743
 
711
744
  // 2. Remove orphan relationships
712
- this.#relationshipManager.removeOrphanRelationships(this.#zipManager);
745
+ this.#relationshipManager.removeOrphanRelationships(this.#zipManager)
713
746
 
714
- logger.info('PPTX repair complete.');
715
- return this;
747
+ logger.info('PPTX repair complete.')
748
+ return this
716
749
  }
717
750
 
718
751
  /**
@@ -720,15 +753,17 @@ class PPTXTemplater {
720
753
  * @returns {PPTXTemplater} this (chainable)
721
754
  */
722
755
  debugRelationships() {
723
- this.#assertLoaded();
724
- const files = this.#zipManager.listFiles('').filter(f => f.endsWith('.rels'));
725
- console.log('=== Relationship Graph ===');
756
+ this.#assertLoaded()
757
+ const files = this.#zipManager.listFiles('').filter(f => f.endsWith('.rels'))
758
+ console.log('=== Relationship Graph ===')
726
759
  for (const file of files) {
727
- console.log(`\n${file}:`);
728
- const rels = this.#relationshipManager.getRelationships(file.replace('_rels/', '').replace('.rels', ''));
729
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`));
760
+ console.log(`\n${file}:`)
761
+ const rels = this.#relationshipManager.getRelationships(
762
+ file.replace('_rels/', '').replace('.rels', '')
763
+ )
764
+ rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
730
765
  }
731
- return this;
766
+ return this
732
767
  }
733
768
 
734
769
  /**
@@ -737,21 +772,21 @@ class PPTXTemplater {
737
772
  * @returns {PPTXTemplater} this (chainable)
738
773
  */
739
774
  inspectSlide(slideIndex) {
740
- this.#assertLoaded();
741
- const info = this.#slideManager.getSlideInfo(slideIndex);
742
- const xml = this.#slideManager.getSlideXml(slideIndex);
743
- const rels = this.#relationshipManager.getRelationships(info.zipPath);
744
-
745
- console.log(`=== Slide ${slideIndex} Inspection ===`);
746
- console.log(`Path: ${info.zipPath}`);
747
- console.log(`ID: ${info.slideId}`);
748
- console.log(`rId: ${info.relationshipId}`);
749
- console.log(`Title: ${info.title}`);
750
- console.log(`XML Size: ${xml.length} characters`);
751
- console.log(`Relationships (${rels.length}):`);
752
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`));
753
-
754
- return this;
775
+ this.#assertLoaded()
776
+ const info = this.#slideManager.getSlideInfo(slideIndex)
777
+ const xml = this.#slideManager.getSlideXml(slideIndex)
778
+ const rels = this.#relationshipManager.getRelationships(info.zipPath)
779
+
780
+ console.log(`=== Slide ${slideIndex} Inspection ===`)
781
+ console.log(`Path: ${info.zipPath}`)
782
+ console.log(`ID: ${info.slideId}`)
783
+ console.log(`rId: ${info.relationshipId}`)
784
+ console.log(`Title: ${info.title}`)
785
+ console.log(`XML Size: ${xml.length} characters`)
786
+ console.log(`Relationships (${rels.length}):`)
787
+ rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
788
+
789
+ return this
755
790
  }
756
791
 
757
792
  /**
@@ -760,15 +795,15 @@ class PPTXTemplater {
760
795
  * @returns {Promise<PPTXTemplater>} this (chainable)
761
796
  */
762
797
  async inspectXML(xmlPath) {
763
- this.#assertLoaded();
764
- const xml = await this.#zipManager.readFile(xmlPath);
765
- console.log(`=== XML Inspection: ${xmlPath} ===`);
798
+ this.#assertLoaded()
799
+ const xml = await this.#zipManager.readFile(xmlPath)
800
+ console.log(`=== XML Inspection: ${xmlPath} ===`)
766
801
  if (!xml) {
767
- console.log('(File not found or empty)');
802
+ console.log('(File not found or empty)')
768
803
  } else {
769
- console.log(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''));
804
+ console.log(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''))
770
805
  }
771
- return this;
806
+ return this
772
807
  }
773
808
 
774
809
  /**
@@ -778,26 +813,29 @@ class PPTXTemplater {
778
813
  * @returns {Promise<Object>} Validation results for charts.
779
814
  */
780
815
  async validateCharts() {
781
- this.#assertLoaded();
782
- const issues = { valid: true, errors: [], warnings: [] };
816
+ this.#assertLoaded()
817
+ const issues = { valid: true, errors: [], warnings: [] }
783
818
 
784
819
  // We lazy require ChartRelationshipManager so we don't circularly depend if not needed
785
- const { ChartRelationshipManager } = require('../managers/charts/ChartRelationshipManager.js');
820
+ const { ChartRelationshipManager } = require('../managers/charts/ChartRelationshipManager.js')
786
821
 
787
- const chartFiles = this.#zipManager.listFiles('ppt/charts/')
788
- .filter(f => {
789
- const name = f.split('/').pop();
790
- return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels');
791
- });
822
+ const chartFiles = this.#zipManager.listFiles('ppt/charts/').filter(f => {
823
+ const name = f.split('/').pop()
824
+ return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels')
825
+ })
792
826
 
793
827
  for (const chartPath of chartFiles) {
794
- const relIssues = ChartRelationshipManager.validateChartRelationships(this.#relationshipManager, this.#zipManager, chartPath);
795
- issues.errors.push(...relIssues.errors);
796
- issues.warnings.push(...relIssues.warnings);
828
+ const relIssues = ChartRelationshipManager.validateChartRelationships(
829
+ this.#relationshipManager,
830
+ this.#zipManager,
831
+ chartPath
832
+ )
833
+ issues.errors.push(...relIssues.errors)
834
+ issues.warnings.push(...relIssues.warnings)
797
835
  }
798
836
 
799
- if (issues.errors.length > 0) issues.valid = false;
800
- return issues;
837
+ if (issues.errors.length > 0) issues.valid = false
838
+ return issues
801
839
  }
802
840
 
803
841
  /**
@@ -807,34 +845,40 @@ class PPTXTemplater {
807
845
  * @returns {Promise<PPTXTemplater>} this
808
846
  */
809
847
  async repairCharts() {
810
- this.#assertLoaded();
811
- logger.info('Repairing charts...');
848
+ this.#assertLoaded()
849
+ logger.info('Repairing charts...')
812
850
 
813
851
  // Check all charts for missing embedded workbooks
814
- const chartFiles = this.#zipManager.listFiles('ppt/charts/')
815
- .filter(f => {
816
- const name = f.split('/').pop();
817
- return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels');
818
- });
852
+ const chartFiles = this.#zipManager.listFiles('ppt/charts/').filter(f => {
853
+ const name = f.split('/').pop()
854
+ return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels')
855
+ })
819
856
  for (const chartPath of chartFiles) {
820
- const rels = this.#relationshipManager.getRelationshipsByType(chartPath, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package');
857
+ const rels = this.#relationshipManager.getRelationshipsByType(
858
+ chartPath,
859
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package'
860
+ )
821
861
  for (const rel of rels) {
822
- const xlsxPath = this.#relationshipManager.resolveTarget(chartPath, rel.target);
862
+ const xlsxPath = this.#relationshipManager.resolveTarget(chartPath, rel.target)
823
863
  if (!this.#zipManager.hasFile(xlsxPath)) {
824
- logger.warn(`Chart ${chartPath} has broken workbook reference ${rel.id}, removing to prevent repair mode.`);
825
- this.#relationshipManager.removeRelationship(chartPath, rel.id);
864
+ logger.warn(
865
+ `Chart ${chartPath} has broken workbook reference ${rel.id}, removing to prevent repair mode.`
866
+ )
867
+ this.#relationshipManager.removeRelationship(chartPath, rel.id)
826
868
 
827
869
  // Also strip c:externalData from chart XML to prevent PowerPoint looking for it
828
- const xml = await this.#zipManager.readFile(chartPath);
870
+ const xml = await this.#zipManager.readFile(chartPath)
829
871
  if (xml) {
830
- const updated = xml.replace(/<c:externalData[^>]*r:id="[^"]*"[^>]*>/, '').replace(/<\/c:externalData>/, '');
831
- this.#zipManager.writeFile(chartPath, updated);
872
+ const updated = xml
873
+ .replace(/<c:externalData[^>]*r:id="[^"]*"[^>]*>/, '')
874
+ .replace(/<\/c:externalData>/, '')
875
+ this.#zipManager.writeFile(chartPath, updated)
832
876
  }
833
877
  }
834
878
  }
835
879
  }
836
880
 
837
- return this;
881
+ return this
838
882
  }
839
883
 
840
884
  /**
@@ -843,25 +887,31 @@ class PPTXTemplater {
843
887
  * @param {string} chartId
844
888
  */
845
889
  inspectChart(chartId) {
846
- this.#assertLoaded();
847
- console.log(`=== Chart Inspection: ${chartId} ===`);
890
+ this.#assertLoaded()
891
+ console.log(`=== Chart Inspection: ${chartId} ===`)
848
892
  // Find chart across all slides to get info
849
- let found = false;
893
+ let found = false
850
894
  for (const i of this.#slideManager.getAllSlideIndices()) {
851
895
  try {
852
- const info = this.#chartManager.getChartsInSlide(i, this.#slideManager, this.#relationshipManager);
853
- const chart = info.find(c => c.zipPath.toLowerCase().includes(chartId.toLowerCase()) || c.rId === chartId);
896
+ const info = this.#chartManager.getChartsInSlide(
897
+ i,
898
+ this.#slideManager,
899
+ this.#relationshipManager
900
+ )
901
+ const chart = info.find(
902
+ c => c.zipPath.toLowerCase().includes(chartId.toLowerCase()) || c.rId === chartId
903
+ )
854
904
  if (chart) {
855
- console.log(`Found on Slide ${i}`);
856
- console.log(`ZIP Path: ${chart.zipPath}`);
857
- console.log(`Relationship ID: ${chart.rId}`);
858
- found = true;
859
- break;
905
+ console.log(`Found on Slide ${i}`)
906
+ console.log(`ZIP Path: ${chart.zipPath}`)
907
+ console.log(`Relationship ID: ${chart.rId}`)
908
+ found = true
909
+ break
860
910
  }
861
911
  } catch (e) {}
862
912
  }
863
- if (!found) console.log('Chart not found.');
864
- return this;
913
+ if (!found) console.log('Chart not found.')
914
+ return this
865
915
  }
866
916
 
867
917
  /**
@@ -870,74 +920,580 @@ class PPTXTemplater {
870
920
  * @param {string} chartFileName
871
921
  */
872
922
  async inspectChartXML(chartFileName) {
873
- const fullPath = chartFileName.includes('/') ? chartFileName : `ppt/charts/${chartFileName}`;
874
- await this.inspectXML(fullPath);
875
- return this;
923
+ const fullPath = chartFileName.includes('/') ? chartFileName : `ppt/charts/${chartFileName}`
924
+ await this.inspectXML(fullPath)
925
+ return this
876
926
  }
877
927
 
878
928
  /**
879
929
  * Logs all chart relationships.
880
930
  */
881
931
  debugChartRelationships() {
882
- this.#assertLoaded();
883
- console.log('=== Chart Relationships ===');
884
- const chartFiles = this.#zipManager.listFiles('ppt/charts/')
885
- .filter(f => {
886
- const name = f.split('/').pop();
887
- return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels');
888
- });
932
+ this.#assertLoaded()
933
+ console.log('=== Chart Relationships ===')
934
+ const chartFiles = this.#zipManager.listFiles('ppt/charts/').filter(f => {
935
+ const name = f.split('/').pop()
936
+ return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels')
937
+ })
889
938
  for (const chartPath of chartFiles) {
890
- console.log(`\n${chartPath}:`);
891
- const rels = this.#relationshipManager.getRelationships(chartPath);
892
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`));
939
+ console.log(`\n${chartPath}:`)
940
+ const rels = this.#relationshipManager.getRelationships(chartPath)
941
+ rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
893
942
  }
894
- return this;
943
+ return this
895
944
  }
896
945
 
897
946
  /**
898
947
  * Saves the modified PPTX to a file on disk.
899
948
  *
900
- * @param {string} filePath - Output file path (e.g., './output/report.pptx').
949
+ * @param {string} filePath - Output file path.
950
+ * @param {Object} [options] - Save options.
951
+ * @param {boolean} [options.strict=false] - Throw error on validation failure.
901
952
  * @returns {Promise<void>}
902
- *
903
- * @example
904
- * await ppt.saveToFile('./output/report.pptx');
905
953
  */
906
- async saveToFile(filePath) {
907
- this.#assertLoaded();
908
- await this.#outputWriter.saveToFile(filePath, this.#slideManager, this.#zipManager);
909
- logger.info(`Saved PPTX to ${filePath}`);
954
+ async saveToFile(filePath, options = {}) {
955
+ this.#assertLoaded()
956
+ const result = await this.validatePresentation()
957
+ if (!result.valid) {
958
+ if (options.strict) {
959
+ throw new PPTXError(`Validation failed before save: ${result.errors.join(', ')}`)
960
+ } else {
961
+ logger.warn(
962
+ `Validation issues found before save:\n${result.errors.map(e => ` • ${e}`).join('\n')}`
963
+ )
964
+ }
965
+ }
966
+ await this.#outputWriter.saveToFile(filePath, this.#slideManager, this.#zipManager)
967
+ logger.info(`Saved PPTX to ${filePath}`)
910
968
  }
911
969
 
912
970
  /**
913
971
  * Returns the PPTX content as a Node.js Buffer.
914
- * Useful for HTTP responses, email attachments, etc.
915
- *
916
- * @returns {Promise<Buffer>} Buffer containing PPTX binary data.
917
972
  *
918
- * @example
919
- * const buffer = await ppt.toBuffer();
920
- * res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.presentationml.presentation');
921
- * res.send(buffer);
973
+ * @returns {Promise<Buffer>}
922
974
  */
923
975
  async toBuffer() {
924
- this.#assertLoaded();
925
- return this.#outputWriter.toBuffer(this.#slideManager, this.#zipManager);
976
+ this.#assertLoaded()
977
+ return this.#outputWriter.toBuffer(this.#slideManager, this.#zipManager)
926
978
  }
927
979
 
928
980
  /**
929
981
  * Returns the PPTX content as a readable Node.js Stream.
930
- * Ideal for streaming large presentations to HTTP responses.
931
- *
932
- * @returns {Promise<NodeJS.ReadableStream>} Readable stream of PPTX data.
933
982
  *
934
- * @example
935
- * const stream = await ppt.toStream();
936
- * stream.pipe(res);
983
+ * @returns {Promise<NodeJS.ReadableStream>}
937
984
  */
938
985
  async toStream() {
939
- this.#assertLoaded();
940
- return this.#outputWriter.toStream(this.#slideManager, this.#zipManager);
986
+ this.#assertLoaded()
987
+ return this.#outputWriter.toStream(this.#slideManager, this.#zipManager)
988
+ }
989
+
990
+ // === Slide Features ===
991
+ duplicateSlide(slideIndex, atPosition) {
992
+ this.#assertLoaded()
993
+ this.#slideManager.duplicateSlide(slideIndex, atPosition, this.#relationshipManager)
994
+ return this
995
+ }
996
+
997
+ deleteSlide(slideIndex) {
998
+ this.#assertLoaded()
999
+ this.#slideManager.removeSlide(slideIndex)
1000
+ return this
1001
+ }
1002
+
1003
+ moveSlide(fromIndex, toIndex) {
1004
+ this.#assertLoaded()
1005
+ this.#slideManager.moveSlide(fromIndex, toIndex)
1006
+ return this
1007
+ }
1008
+
1009
+ insertSlide(slideIndex, options = {}) {
1010
+ this.#assertLoaded()
1011
+ this.#slideManager.insertSlide(
1012
+ slideIndex,
1013
+ options,
1014
+ this.#relationshipManager,
1015
+ this.#mediaManager
1016
+ )
1017
+ return this
1018
+ }
1019
+
1020
+ getSlides() {
1021
+ this.#assertLoaded()
1022
+ return this.#slideManager.getSlides()
1023
+ }
1024
+
1025
+ // === Table Features ===
1026
+ addTableRow(tableId, rowData) {
1027
+ this.#assertLoaded()
1028
+ const targetIndices = this.#getTargetSlideIndices()
1029
+ for (const idx of targetIndices) {
1030
+ this.#tableManager.addTableRow(idx, tableId, rowData, this.#slideManager)
1031
+ }
1032
+ return this
1033
+ }
1034
+
1035
+ removeTableRow(tableId, rowIndex) {
1036
+ this.#assertLoaded()
1037
+ const targetIndices = this.#getTargetSlideIndices()
1038
+ for (const idx of targetIndices) {
1039
+ this.#tableManager.removeTableRow(idx, tableId, rowIndex, this.#slideManager)
1040
+ }
1041
+ return this
1042
+ }
1043
+
1044
+ insertTableRow(tableId, rowIndex, rowData) {
1045
+ this.#assertLoaded()
1046
+ const targetIndices = this.#getTargetSlideIndices()
1047
+ for (const idx of targetIndices) {
1048
+ this.#tableManager.insertTableRow(idx, tableId, rowIndex, rowData, this.#slideManager)
1049
+ }
1050
+ return this
1051
+ }
1052
+
1053
+ cloneTableRow(tableId, sourceRowIndex, targetRowIndex) {
1054
+ this.#assertLoaded()
1055
+ const targetIndices = this.#getTargetSlideIndices()
1056
+ for (const idx of targetIndices) {
1057
+ this.#tableManager.cloneTableRow(
1058
+ idx,
1059
+ tableId,
1060
+ sourceRowIndex,
1061
+ targetRowIndex,
1062
+ this.#slideManager
1063
+ )
1064
+ }
1065
+ return this
1066
+ }
1067
+
1068
+ updateCell(tableId, rowIndex, colIndex, value, options = {}) {
1069
+ this.#assertLoaded()
1070
+ const targetIndices = this.#getTargetSlideIndices()
1071
+ for (const idx of targetIndices) {
1072
+ this.#tableManager.updateCell(
1073
+ idx,
1074
+ tableId,
1075
+ rowIndex,
1076
+ colIndex,
1077
+ value,
1078
+ options,
1079
+ this.#slideManager
1080
+ )
1081
+ }
1082
+ return this
1083
+ }
1084
+
1085
+ mergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
1086
+ this.#assertLoaded()
1087
+ let tableId = tableIdOrOptions
1088
+ let sRow = startRow
1089
+ let sCol = startCol
1090
+ let eRow = endRow
1091
+ let eCol = endCol
1092
+ let targetIndices = this.#getTargetSlideIndices()
1093
+
1094
+ if (tableIdOrOptions && typeof tableIdOrOptions === 'object') {
1095
+ const opt = tableIdOrOptions
1096
+ tableId = opt.tableId
1097
+ sRow = opt.startRow
1098
+ sCol = opt.startCol
1099
+ eRow = opt.endRow
1100
+ eCol = opt.endCol
1101
+ if (opt.slide !== undefined) {
1102
+ targetIndices = [opt.slide]
1103
+ }
1104
+ }
1105
+
1106
+ for (const idx of targetIndices) {
1107
+ this.#tableManager.mergeCells(idx, tableId, sRow, sCol, eRow, eCol, this.#slideManager)
1108
+ }
1109
+ return this
1110
+ }
1111
+
1112
+ unmergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
1113
+ this.#assertLoaded()
1114
+ let tableId = tableIdOrOptions
1115
+ let sRow = startRow
1116
+ let sCol = startCol
1117
+ let eRow = endRow
1118
+ let eCol = endCol
1119
+ let targetIndices = this.#getTargetSlideIndices()
1120
+ let isCellCoord = false
1121
+ let cellRow, cellCol
1122
+
1123
+ if (tableIdOrOptions && typeof tableIdOrOptions === 'object') {
1124
+ const opt = tableIdOrOptions
1125
+ tableId = opt.tableId
1126
+ sRow = opt.startRow
1127
+ sCol = opt.startCol
1128
+ eRow = opt.endRow
1129
+ eCol = opt.endCol
1130
+ if (opt.slide !== undefined) {
1131
+ targetIndices = [opt.slide]
1132
+ }
1133
+ if (opt.row !== undefined && opt.col !== undefined) {
1134
+ isCellCoord = true
1135
+ cellRow = opt.row
1136
+ cellCol = opt.col
1137
+ }
1138
+ }
1139
+
1140
+ for (const idx of targetIndices) {
1141
+ if (isCellCoord) {
1142
+ this.#tableManager.unmergeCells(idx, tableId, cellRow, cellCol, this.#slideManager)
1143
+ } else {
1144
+ this.#tableManager.unmergeCells(idx, tableId, sRow, sCol, eRow, eCol, this.#slideManager)
1145
+ }
1146
+ }
1147
+ return this
1148
+ }
1149
+
1150
+ getMergedCells(tableId) {
1151
+ this.#assertLoaded()
1152
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1153
+ return this.#tableManager.getMergedCells(slideIndex, tableId || 'first', this.#slideManager)
1154
+ }
1155
+
1156
+ validateMergeRegion(tableId, startRow, startCol, endRow, endCol) {
1157
+ this.#assertLoaded()
1158
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1159
+ return this.#tableManager.validateMergeRegion(
1160
+ slideIndex,
1161
+ tableId || 'first',
1162
+ startRow,
1163
+ startCol,
1164
+ endRow,
1165
+ endCol,
1166
+ this.#slideManager
1167
+ )
1168
+ }
1169
+
1170
+ isMergedCell(tableId, row, col) {
1171
+ this.#assertLoaded()
1172
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1173
+ return this.#tableManager.isMergedCell(
1174
+ slideIndex,
1175
+ tableId || 'first',
1176
+ row,
1177
+ col,
1178
+ this.#slideManager
1179
+ )
1180
+ }
1181
+
1182
+ getMergeParent(tableId, row, col) {
1183
+ this.#assertLoaded()
1184
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1185
+ return this.#tableManager.getMergeParent(
1186
+ slideIndex,
1187
+ tableId || 'first',
1188
+ row,
1189
+ col,
1190
+ this.#slideManager
1191
+ )
1192
+ }
1193
+
1194
+ getMergeRegion(tableId, row, col) {
1195
+ this.#assertLoaded()
1196
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1197
+ return this.#tableManager.getMergeRegion(
1198
+ slideIndex,
1199
+ tableId || 'first',
1200
+ row,
1201
+ col,
1202
+ this.#slideManager
1203
+ )
1204
+ }
1205
+
1206
+ splitMergedRegion(tableId, row, col) {
1207
+ this.#assertLoaded()
1208
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1209
+ this.#tableManager.splitMergedRegion(
1210
+ slideIndex,
1211
+ tableId || 'first',
1212
+ row,
1213
+ col,
1214
+ this.#slideManager
1215
+ )
1216
+ return this
1217
+ }
1218
+
1219
+ cloneMergedRegion(tableId, row, col, targetRow, targetCol) {
1220
+ this.#assertLoaded()
1221
+ const slideIndex = this.#getTargetSlideIndices()[0] || 1
1222
+ this.#tableManager.cloneMergedRegion(
1223
+ slideIndex,
1224
+ tableId || 'first',
1225
+ row,
1226
+ col,
1227
+ targetRow,
1228
+ targetCol,
1229
+ this.#slideManager
1230
+ )
1231
+ return this
1232
+ }
1233
+
1234
+ autoFitTable(tableId) {
1235
+ this.#assertLoaded()
1236
+ const targetIndices = this.#getTargetSlideIndices()
1237
+ for (const idx of targetIndices) {
1238
+ this.#tableManager.autoFitTable(idx, tableId, this.#slideManager)
1239
+ }
1240
+ return this
1241
+ }
1242
+
1243
+ resizeTable(tableId, width, height) {
1244
+ this.#assertLoaded()
1245
+ const targetIndices = this.#getTargetSlideIndices()
1246
+ for (const idx of targetIndices) {
1247
+ this.#tableManager.resizeTable(idx, tableId, width, height, this.#slideManager)
1248
+ }
1249
+ return this
1250
+ }
1251
+
1252
+ getTables() {
1253
+ this.#assertLoaded()
1254
+ const targetIndices = this.#getTargetSlideIndices()
1255
+ const tables = []
1256
+ for (const idx of targetIndices) {
1257
+ tables.push(...this.#tableManager.inspectTables(idx, this.#slideManager))
1258
+ }
1259
+ return tables
1260
+ }
1261
+
1262
+ // === Chart Features ===
1263
+ updateChartData(chartId, data) {
1264
+ return this.updateChart(chartId, data)
1265
+ }
1266
+
1267
+ replaceChartSeries(chartId, seriesIndex, newSeriesData) {
1268
+ this.#assertLoaded()
1269
+ const targetIndices = this.#getTargetSlideIndices()
1270
+ for (const idx of targetIndices) {
1271
+ this.#chartManager.replaceChartSeries(
1272
+ idx,
1273
+ chartId,
1274
+ seriesIndex,
1275
+ newSeriesData,
1276
+ this.#slideManager,
1277
+ this.#relationshipManager
1278
+ )
1279
+ }
1280
+ return this
1281
+ }
1282
+
1283
+ updateChartTitle(chartId, title) {
1284
+ this.#assertLoaded()
1285
+ const targetIndices = this.#getTargetSlideIndices()
1286
+ for (const idx of targetIndices) {
1287
+ this.#chartManager.updateChartTitle(
1288
+ idx,
1289
+ chartId,
1290
+ title,
1291
+ this.#slideManager,
1292
+ this.#relationshipManager
1293
+ )
1294
+ }
1295
+ return this
1296
+ }
1297
+
1298
+ updateChartCategories(chartId, categories) {
1299
+ this.#assertLoaded()
1300
+ const targetIndices = this.#getTargetSlideIndices()
1301
+ for (const idx of targetIndices) {
1302
+ this.#chartManager.updateChartCategories(
1303
+ idx,
1304
+ chartId,
1305
+ categories,
1306
+ this.#slideManager,
1307
+ this.#relationshipManager
1308
+ )
1309
+ }
1310
+ return this
1311
+ }
1312
+
1313
+ getCharts() {
1314
+ this.#assertLoaded()
1315
+ const targetIndices = this.#getTargetSlideIndices()
1316
+ const charts = []
1317
+ for (const idx of targetIndices) {
1318
+ charts.push(
1319
+ ...this.#chartManager.getChartsInSlide(idx, this.#slideManager, this.#relationshipManager)
1320
+ )
1321
+ }
1322
+ return charts
1323
+ }
1324
+
1325
+ // === Text Features ===
1326
+ replaceTextByTag(tag, value, options = {}) {
1327
+ this.#assertLoaded()
1328
+ const targetIndices = this.#getTargetSlideIndices()
1329
+ for (const idx of targetIndices) {
1330
+ this.#textManager.replaceTextByTag(
1331
+ idx,
1332
+ tag,
1333
+ value,
1334
+ options,
1335
+ this.#slideManager,
1336
+ this.#templateEngine
1337
+ )
1338
+ }
1339
+ return this
1340
+ }
1341
+
1342
+ replaceMultiple(replacements, options = {}) {
1343
+ this.#assertLoaded()
1344
+ const targetIndices = this.#getTargetSlideIndices()
1345
+ for (const idx of targetIndices) {
1346
+ this.#textManager.replaceMultiple(
1347
+ idx,
1348
+ replacements,
1349
+ options,
1350
+ this.#slideManager,
1351
+ this.#templateEngine
1352
+ )
1353
+ }
1354
+ return this
1355
+ }
1356
+
1357
+ findText(text) {
1358
+ this.#assertLoaded()
1359
+ const targetIndices = this.#getTargetSlideIndices()
1360
+ const matches = []
1361
+ for (const idx of targetIndices) {
1362
+ matches.push(...this.#textManager.findText(idx, text, this.#slideManager))
1363
+ }
1364
+ return matches
1365
+ }
1366
+
1367
+ getTextElements() {
1368
+ this.#assertLoaded()
1369
+ const targetIndices = this.#getTargetSlideIndices()
1370
+ const elements = []
1371
+ for (const idx of targetIndices) {
1372
+ elements.push(...this.#textManager.getTextElements(idx, this.#slideManager))
1373
+ }
1374
+ return elements
1375
+ }
1376
+
1377
+ // === Shape Features ===
1378
+ updateShapeText(shapeId, text) {
1379
+ this.#assertLoaded()
1380
+ const targetIndices = this.#getTargetSlideIndices()
1381
+ for (const idx of targetIndices) {
1382
+ this.#shapeManager.updateShapeText(idx, shapeId, text, this.#slideManager)
1383
+ }
1384
+ return this
1385
+ }
1386
+
1387
+ cloneShape(shapeId, newShapeId, options = {}) {
1388
+ this.#assertLoaded()
1389
+ const targetIndices = this.#getTargetSlideIndices()
1390
+ for (const idx of targetIndices) {
1391
+ this.#shapeManager.cloneShape(idx, shapeId, newShapeId, options, this.#slideManager)
1392
+ }
1393
+ return this
1394
+ }
1395
+
1396
+ deleteShape(shapeId) {
1397
+ this.#assertLoaded()
1398
+ const targetIndices = this.#getTargetSlideIndices()
1399
+ for (const idx of targetIndices) {
1400
+ this.#shapeManager.deleteShape(idx, shapeId, this.#slideManager)
1401
+ }
1402
+ return this
1403
+ }
1404
+
1405
+ getShapes() {
1406
+ this.#assertLoaded()
1407
+ const targetIndices = this.#getTargetSlideIndices()
1408
+ const shapes = []
1409
+ for (const idx of targetIndices) {
1410
+ shapes.push(...this.#shapeManager.getShapes(idx, this.#slideManager))
1411
+ }
1412
+ return shapes
1413
+ }
1414
+
1415
+ // === Image Features ===
1416
+ async replaceImage(imageIdOrName, sourcePathOrBuffer) {
1417
+ this.#assertLoaded()
1418
+ const targetIndices = this.#getTargetSlideIndices()
1419
+ for (const idx of targetIndices) {
1420
+ await this.#imageManager.replaceImage(
1421
+ idx,
1422
+ imageIdOrName,
1423
+ sourcePathOrBuffer,
1424
+ this.#slideManager,
1425
+ this.#mediaManager,
1426
+ this.#relationshipManager
1427
+ )
1428
+ }
1429
+ return this
1430
+ }
1431
+
1432
+ async addImage(sourcePathOrBuffer, options = {}) {
1433
+ this.#assertLoaded()
1434
+ const targetIndices = this.#getTargetSlideIndices()
1435
+ for (const idx of targetIndices) {
1436
+ await this.#imageManager.addImage(
1437
+ idx,
1438
+ sourcePathOrBuffer,
1439
+ options,
1440
+ this.#slideManager,
1441
+ this.#mediaManager,
1442
+ this.#relationshipManager
1443
+ )
1444
+ }
1445
+ return this
1446
+ }
1447
+
1448
+ removeImage(imageIdOrName) {
1449
+ this.#assertLoaded()
1450
+ const targetIndices = this.#getTargetSlideIndices()
1451
+ for (const idx of targetIndices) {
1452
+ this.#imageManager.removeImage(
1453
+ idx,
1454
+ imageIdOrName,
1455
+ this.#slideManager,
1456
+ this.#relationshipManager
1457
+ )
1458
+ }
1459
+ return this
1460
+ }
1461
+
1462
+ getImages() {
1463
+ this.#assertLoaded()
1464
+ const targetIndices = this.#getTargetSlideIndices()
1465
+ const images = []
1466
+ for (const idx of targetIndices) {
1467
+ images.push(
1468
+ ...this.#imageManager.getImages(idx, this.#slideManager, this.#relationshipManager)
1469
+ )
1470
+ }
1471
+ return images
1472
+ }
1473
+
1474
+ // === Validation Features ===
1475
+ async validatePresentation() {
1476
+ this.#assertLoaded()
1477
+ return await ValidationEngine.validatePresentation(this)
1478
+ }
1479
+
1480
+ async validateSlide(slideIndex) {
1481
+ this.#assertLoaded()
1482
+ return await ValidationEngine.validateSlide(this, slideIndex)
1483
+ }
1484
+
1485
+ async validateTable(tableId) {
1486
+ this.#assertLoaded()
1487
+ return await ValidationEngine.validateTable(
1488
+ this,
1489
+ this.#getTargetSlideIndices()[0] || 1,
1490
+ tableId
1491
+ )
1492
+ }
1493
+
1494
+ validateRelationships(partPath) {
1495
+ this.#assertLoaded()
1496
+ return ValidationEngine.validateRelationships(this, partPath)
941
1497
  }
942
1498
 
943
1499
  /**
@@ -945,19 +1501,46 @@ class PPTXTemplater {
945
1501
  * @type {number}
946
1502
  */
947
1503
  get slideCount() {
948
- return this.#slideManager.slideCount;
1504
+ return this.#slideManager.slideCount
949
1505
  }
950
1506
 
951
1507
  // --- Public Getters for Internal Managers ---
952
- get zipManager() { return this.#zipManager; }
953
- get xmlParser() { return this.#xmlParser; }
954
- get contentTypesManager() { return this.#contentTypesManager; }
955
- get relationshipManager() { return this.#relationshipManager; }
956
- get slideManager() { return this.#slideManager; }
957
- get chartManager() { return this.#chartManager; }
958
- get tableManager() { return this.#tableManager; }
959
- get hyperlinkManager() { return this.#hyperlinkManager; }
960
- get mediaManager() { return this.#mediaManager; }
1508
+ get zipManager() {
1509
+ return this.#zipManager
1510
+ }
1511
+ get xmlParser() {
1512
+ return this.#xmlParser
1513
+ }
1514
+ get contentTypesManager() {
1515
+ return this.#contentTypesManager
1516
+ }
1517
+ get relationshipManager() {
1518
+ return this.#relationshipManager
1519
+ }
1520
+ get slideManager() {
1521
+ return this.#slideManager
1522
+ }
1523
+ get chartManager() {
1524
+ return this.#chartManager
1525
+ }
1526
+ get tableManager() {
1527
+ return this.#tableManager
1528
+ }
1529
+ get shapeManager() {
1530
+ return this.#shapeManager
1531
+ }
1532
+ get imageManager() {
1533
+ return this.#imageManager
1534
+ }
1535
+ get textManager() {
1536
+ return this.#textManager
1537
+ }
1538
+ get hyperlinkManager() {
1539
+ return this.#hyperlinkManager
1540
+ }
1541
+ get mediaManager() {
1542
+ return this.#mediaManager
1543
+ }
961
1544
  }
962
1545
 
963
- module.exports = { PPTXTemplater };
1546
+ module.exports = { PPTXTemplater }