node-pptx-templater 1.0.20 → 1.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.
@@ -40,7 +40,7 @@ const { ZOrderManager } = require('../managers/ZOrderManager.js')
40
40
  const { ValidationEngine } = require('./ValidationEngine.js')
41
41
  const { OutputWriter } = require('./OutputWriter.js')
42
42
  const { TemplateEngine } = require('./TemplateEngine.js')
43
- const { createLogger } = require('../utils/logger.js')
43
+ const { createLogger, setGlobalLogLevel } = require('../utils/logger.js')
44
44
  const { PPTXError } = require('../utils/errors.js')
45
45
  const { performance } = require('perf_hooks')
46
46
 
@@ -217,12 +217,46 @@ class PPTXTemplater {
217
217
  * const buffer = fs.readFileSync('./template.pptx');
218
218
  * const ppt = await PPTXTemplater.load(buffer);
219
219
  */
220
- static async load(source) {
220
+ static async load(source, options = {}) {
221
+ if (options.logLevel) {
222
+ setGlobalLogLevel(options.logLevel)
223
+ }
221
224
  const engine = new PPTXTemplater()
222
225
  await engine.#initialize(source)
223
226
  return engine
224
227
  }
225
228
 
229
+ /**
230
+ * Sets the global log level for all PPTXTemplater logger instances.
231
+ * This is equivalent to setting the PPTX_LOG_LEVEL environment variable,
232
+ * but works at runtime without restarting the process.
233
+ *
234
+ * @static
235
+ * @param {'verbose'|'debug'|'info'|'warn'|'error'|'silent'} level - Log level.
236
+ * @returns {void}
237
+ *
238
+ * @example
239
+ * PPTXTemplater.setLogLevel('debug'); // Enable full debug output
240
+ * PPTXTemplater.setLogLevel('silent'); // Suppress all output
241
+ */
242
+ static setLogLevel(level) {
243
+ setGlobalLogLevel(level)
244
+ }
245
+
246
+ /**
247
+ * Preloads a PPTX template into an in-memory cache for fast repeated generation.
248
+ * Call this once at startup; subsequent `fromCache()` calls read from memory
249
+ * with zero disk I/O.
250
+ *
251
+ * @static
252
+ * @param {string|Buffer|Object} source - File path, Buffer, or folder object.
253
+ * @returns {Promise<Map>} The cache Map keyed by file paths.
254
+ *
255
+ * @example
256
+ * await PPTXTemplater.preload('./template.pptx');
257
+ * // Later, in request handlers:
258
+ * const ppt = await PPTXTemplater.fromCache('./template.pptx');
259
+ */
226
260
  static async preload(source) {
227
261
  let key = source
228
262
  if (Buffer.isBuffer(source)) {
@@ -256,10 +290,33 @@ class PPTXTemplater {
256
290
  return cachedFiles
257
291
  }
258
292
 
293
+ /**
294
+ * Alias for `preload()`. Caches a PPTX template in memory.
295
+ *
296
+ * @static
297
+ * @param {string|Buffer|Object} source - File path, Buffer, or folder object.
298
+ * @returns {Promise<Map>} The cache Map.
299
+ *
300
+ * @example
301
+ * await PPTXTemplater.cache('./template.pptx');
302
+ */
259
303
  static async cache(source) {
260
304
  return PPTXTemplater.preload(source)
261
305
  }
262
306
 
307
+ /**
308
+ * Creates an engine instance from a previously preloaded cache entry.
309
+ * Falls back to preloading if the source has not been cached yet.
310
+ *
311
+ * @static
312
+ * @param {string|Buffer|Object} source - Same source used with `preload()`.
313
+ * @returns {Promise<PPTXTemplater>} Initialized engine from cache.
314
+ *
315
+ * @example
316
+ * await PPTXTemplater.preload('./template.pptx');
317
+ * const ppt = await PPTXTemplater.fromCache('./template.pptx');
318
+ * // ppt is ready — no disk I/O on the load
319
+ */
263
320
  static async fromCache(source) {
264
321
  let key = source
265
322
  if (Buffer.isBuffer(source)) {
@@ -279,16 +336,66 @@ class PPTXTemplater {
279
336
  return engine
280
337
  }
281
338
 
339
+ /**
340
+ * Clears all preloaded templates from the in-memory cache.
341
+ * Call this to free memory or force templates to be reloaded from disk.
342
+ *
343
+ * @static
344
+ * @returns {void}
345
+ *
346
+ * @example
347
+ * PPTXTemplater.clearCache(); // Force fresh reload on next fromCache()
348
+ */
282
349
  static clearCache() {
283
350
  PPTXTemplater.#templateCache.clear()
284
351
  }
285
352
 
353
+ /**
354
+ * Enables internal performance profiling.
355
+ * After calling this, use `getPerformanceMetrics()` to read timing data.
356
+ *
357
+ * @returns {PPTXTemplater} this (chainable)
358
+ *
359
+ * @example
360
+ * ppt.enablePerformanceProfile();
361
+ * // ... do work ...
362
+ * console.log(ppt.getPerformanceMetrics());
363
+ */
286
364
  enablePerformanceProfile() {
287
365
  this.#profiler.enabled = true
288
366
  this.#profiler.startTime = performance.now()
289
367
  return this
290
368
  }
291
369
 
370
+ /**
371
+ * Enables debug-level logging for this session.
372
+ * Shortcut for `PPTXTemplater.setLogLevel('debug')`.
373
+ *
374
+ * @returns {PPTXTemplater} this (chainable)
375
+ *
376
+ * @example
377
+ * const ppt = await PPTXTemplater.load('./template.pptx');
378
+ * ppt.enableDebug(); // Shows all debug output
379
+ */
380
+ enableDebug() {
381
+ setGlobalLogLevel('debug')
382
+ return this
383
+ }
384
+
385
+ /**
386
+ * Returns performance metrics collected since `enablePerformanceProfile()` was called.
387
+ * Includes timing for template load, XML parse, chart update, image update,
388
+ * ZIP generation, total elapsed time, and memory usage.
389
+ *
390
+ * @returns {Object} Metrics object with `templateLoadMs`, `parseMs`, `chartUpdateMs`,
391
+ * `imageUpdateMs`, `zipGenerationMs`, `totalMs`, `memoryUsedMB`.
392
+ *
393
+ * @example
394
+ * ppt.enablePerformanceProfile();
395
+ * await ppt.updateChart('chart1', data);
396
+ * const metrics = ppt.getPerformanceMetrics();
397
+ * console.log(`Total: ${metrics.totalMs}ms, Memory: ${metrics.memoryUsedMB}MB`);
398
+ */
292
399
  getPerformanceMetrics() {
293
400
  if (!this.#profiler.enabled) {
294
401
  return {
@@ -340,6 +447,89 @@ class PPTXTemplater {
340
447
  return engine
341
448
  }
342
449
 
450
+ /**
451
+ * Extracts a PPTX file into an unzipped OpenXML folder structure.
452
+ *
453
+ * @static
454
+ * @param {string} pptxPath - Path to the source PPTX file.
455
+ * @param {string} outputPath - Path to the destination folder.
456
+ * @param {Object} [options] - Options (e.g. { overwrite: true }).
457
+ * @returns {Promise<void>}
458
+ */
459
+ static async extractPptx(pptxPath, outputPath, options = {}) {
460
+ const fs = require('fs-extra')
461
+ const path = require('path')
462
+
463
+ const resolvedPptx = path.resolve(pptxPath)
464
+ const resolvedOut = path.resolve(outputPath)
465
+
466
+ if (!fs.existsSync(resolvedPptx)) {
467
+ throw new PPTXError(`Source PPTX file not found: ${pptxPath}`)
468
+ }
469
+
470
+ if (fs.existsSync(resolvedOut)) {
471
+ const stats = fs.statSync(resolvedOut)
472
+ if (stats.isFile()) {
473
+ throw new PPTXError(`Destination is a file: ${outputPath}`)
474
+ }
475
+ const files = fs.readdirSync(resolvedOut)
476
+ if (files.length > 0 && !options.overwrite) {
477
+ throw new PPTXError(
478
+ `Destination directory "${outputPath}" is not empty. Set overwrite: true to overwrite.`
479
+ )
480
+ }
481
+ } else {
482
+ await fs.ensureDir(resolvedOut)
483
+ }
484
+
485
+ const engine = await PPTXTemplater.load(resolvedPptx)
486
+ await engine.#zipManager.toFolder(resolvedOut)
487
+
488
+ // Validation
489
+ const criticalParts = ['ppt/presentation.xml', 'ppt/slides', 'ppt/_rels', '[Content_Types].xml']
490
+
491
+ for (const part of criticalParts) {
492
+ const p = path.join(resolvedOut, part)
493
+ if (!fs.existsSync(p)) {
494
+ throw new PPTXError(`Extracted structure is missing critical part: ${part}`)
495
+ }
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Rebuilds a PPTX file from an unzipped OpenXML folder structure.
501
+ *
502
+ * @static
503
+ * @param {string} folderPath - Path to the source folder structure.
504
+ * @param {string} pptxPath - Path to the destination PPTX file.
505
+ * @returns {Promise<void>}
506
+ */
507
+ static async buildPptx(folderPath, pptxPath) {
508
+ const fs = require('fs-extra')
509
+ const path = require('path')
510
+
511
+ const resolvedFolder = path.resolve(folderPath)
512
+ const resolvedPptx = path.resolve(pptxPath)
513
+
514
+ if (!fs.existsSync(resolvedFolder)) {
515
+ throw new PPTXError(`Source folder not found: ${folderPath}`)
516
+ }
517
+
518
+ // Validation of the source folder
519
+ const criticalParts = ['ppt/presentation.xml', 'ppt/slides', 'ppt/_rels', '[Content_Types].xml']
520
+
521
+ for (const part of criticalParts) {
522
+ const p = path.join(resolvedFolder, part)
523
+ if (!fs.existsSync(p)) {
524
+ throw new PPTXError(`Source folder is missing critical OpenXML part: ${part}`)
525
+ }
526
+ }
527
+
528
+ const engine = await PPTXTemplater.load(resolvedFolder)
529
+ await fs.ensureDir(path.dirname(resolvedPptx))
530
+ await engine.saveToFile(resolvedPptx)
531
+ }
532
+
343
533
  /**
344
534
  * Initializes the engine by loading a PPTX file/buffer.
345
535
  * @private
@@ -947,13 +1137,13 @@ class PPTXTemplater {
947
1137
  debugRelationships() {
948
1138
  this.#assertLoaded()
949
1139
  const files = this.#zipManager.listFiles('').filter(f => f.endsWith('.rels'))
950
- console.log('=== Relationship Graph ===')
1140
+ logger.info('=== Relationship Graph ===')
951
1141
  for (const file of files) {
952
- console.log(`\n${file}:`)
1142
+ logger.info(`\n${file}:`)
953
1143
  const rels = this.#relationshipManager.getRelationships(
954
1144
  file.replace('_rels/', '').replace('.rels', '')
955
1145
  )
956
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1146
+ rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
957
1147
  }
958
1148
  return this
959
1149
  }
@@ -969,14 +1159,14 @@ class PPTXTemplater {
969
1159
  const xml = this.#slideManager.getSlideXml(slideIndex)
970
1160
  const rels = this.#relationshipManager.getRelationships(info.zipPath)
971
1161
 
972
- console.log(`=== Slide ${slideIndex} Inspection ===`)
973
- console.log(`Path: ${info.zipPath}`)
974
- console.log(`ID: ${info.slideId}`)
975
- console.log(`rId: ${info.relationshipId}`)
976
- console.log(`Title: ${info.title}`)
977
- console.log(`XML Size: ${xml.length} characters`)
978
- console.log(`Relationships (${rels.length}):`)
979
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1162
+ logger.info(`=== Slide ${slideIndex} Inspection ===`)
1163
+ logger.info(`Path: ${info.zipPath}`)
1164
+ logger.info(`ID: ${info.slideId}`)
1165
+ logger.info(`rId: ${info.relationshipId}`)
1166
+ logger.info(`Title: ${info.title}`)
1167
+ logger.info(`XML Size: ${xml.length} characters`)
1168
+ logger.info(`Relationships (${rels.length}):`)
1169
+ rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
980
1170
 
981
1171
  return this
982
1172
  }
@@ -989,11 +1179,11 @@ class PPTXTemplater {
989
1179
  async inspectXML(xmlPath) {
990
1180
  this.#assertLoaded()
991
1181
  const xml = await this.#zipManager.readFile(xmlPath)
992
- console.log(`=== XML Inspection: ${xmlPath} ===`)
1182
+ logger.info(`=== XML Inspection: ${xmlPath} ===`)
993
1183
  if (!xml) {
994
- console.log('(File not found or empty)')
1184
+ logger.info('(File not found or empty)')
995
1185
  } else {
996
- console.log(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''))
1186
+ logger.info(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''))
997
1187
  }
998
1188
  return this
999
1189
  }
@@ -1080,8 +1270,7 @@ class PPTXTemplater {
1080
1270
  */
1081
1271
  inspectChart(chartId) {
1082
1272
  this.#assertLoaded()
1083
- console.log(`=== Chart Inspection: ${chartId} ===`)
1084
- // Find chart across all slides to get info
1273
+ logger.info(`=== Chart Inspection: ${chartId} ===`)
1085
1274
  let found = false
1086
1275
  for (const i of this.#slideManager.getAllSlideIndices()) {
1087
1276
  try {
@@ -1094,15 +1283,15 @@ class PPTXTemplater {
1094
1283
  c => c.zipPath.toLowerCase().includes(chartId.toLowerCase()) || c.rId === chartId
1095
1284
  )
1096
1285
  if (chart) {
1097
- console.log(`Found on Slide ${i}`)
1098
- console.log(`ZIP Path: ${chart.zipPath}`)
1099
- console.log(`Relationship ID: ${chart.rId}`)
1286
+ logger.info(`Found on Slide ${i}`)
1287
+ logger.info(`ZIP Path: ${chart.zipPath}`)
1288
+ logger.info(`Relationship ID: ${chart.rId}`)
1100
1289
  found = true
1101
1290
  break
1102
1291
  }
1103
1292
  } catch (e) {}
1104
1293
  }
1105
- if (!found) console.log('Chart not found.')
1294
+ if (!found) logger.info('Chart not found.')
1106
1295
  return this
1107
1296
  }
1108
1297
 
@@ -1122,15 +1311,15 @@ class PPTXTemplater {
1122
1311
  */
1123
1312
  debugChartRelationships() {
1124
1313
  this.#assertLoaded()
1125
- console.log('=== Chart Relationships ===')
1314
+ logger.info('=== Chart Relationships ===')
1126
1315
  const chartFiles = this.#zipManager.listFiles('ppt/charts/').filter(f => {
1127
1316
  const name = f.split('/').pop()
1128
1317
  return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels')
1129
1318
  })
1130
1319
  for (const chartPath of chartFiles) {
1131
- console.log(`\n${chartPath}:`)
1320
+ logger.info(`\n${chartPath}:`)
1132
1321
  const rels = this.#relationshipManager.getRelationships(chartPath)
1133
- rels.forEach(r => console.log(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1322
+ rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
1134
1323
  }
1135
1324
  return this
1136
1325
  }
@@ -1262,24 +1451,64 @@ class PPTXTemplater {
1262
1451
  }
1263
1452
 
1264
1453
  // === Slide Features ===
1454
+ /**
1455
+ * Duplicates an existing slide and inserts the copy at the specified position.
1456
+ *
1457
+ * @param {number} slideIndex - 1-based index of the slide to duplicate.
1458
+ * @param {number} [atPosition] - 1-based position to insert the copy. Defaults to end.
1459
+ * @returns {PPTXTemplater} this (chainable)
1460
+ *
1461
+ * @example
1462
+ * ppt.duplicateSlide(1, 2); // Copy slide 1 and insert it as slide 2
1463
+ */
1265
1464
  duplicateSlide(slideIndex, atPosition) {
1266
1465
  this.#assertLoaded()
1267
1466
  this.#slideManager.duplicateSlide(slideIndex, atPosition, this.#relationshipManager)
1268
1467
  return this
1269
1468
  }
1270
1469
 
1470
+ /**
1471
+ * Removes a slide from the presentation. Alias for `removeSlide()`.
1472
+ *
1473
+ * @param {number} slideIndex - 1-based index of the slide to delete.
1474
+ * @returns {PPTXTemplater} this (chainable)
1475
+ *
1476
+ * @example
1477
+ * ppt.deleteSlide(3); // Remove slide 3
1478
+ */
1271
1479
  deleteSlide(slideIndex) {
1272
1480
  this.#assertLoaded()
1273
1481
  this.#slideManager.removeSlide(slideIndex)
1274
1482
  return this
1275
1483
  }
1276
1484
 
1485
+ /**
1486
+ * Moves a slide from one position to another within the presentation.
1487
+ *
1488
+ * @param {number} fromIndex - 1-based source slide index.
1489
+ * @param {number} toIndex - 1-based target slide index.
1490
+ * @returns {PPTXTemplater} this (chainable)
1491
+ *
1492
+ * @example
1493
+ * ppt.moveSlide(5, 1); // Move slide 5 to position 1 (make it first)
1494
+ */
1277
1495
  moveSlide(fromIndex, toIndex) {
1278
1496
  this.#assertLoaded()
1279
1497
  this.#slideManager.moveSlide(fromIndex, toIndex)
1280
1498
  return this
1281
1499
  }
1282
1500
 
1501
+ /**
1502
+ * Inserts a new blank slide at the specified position.
1503
+ *
1504
+ * @param {number} slideIndex - 1-based position to insert the slide at.
1505
+ * @param {Object} [options] - Insert options.
1506
+ * @param {number} [options.layoutIndex] - Slide layout index to apply.
1507
+ * @returns {PPTXTemplater} this (chainable)
1508
+ *
1509
+ * @example
1510
+ * ppt.insertSlide(2, { layoutIndex: 1 }); // Insert blank slide at position 2
1511
+ */
1283
1512
  insertSlide(slideIndex, options = {}) {
1284
1513
  this.#assertLoaded()
1285
1514
  this.#slideManager.insertSlide(
@@ -1291,21 +1520,92 @@ class PPTXTemplater {
1291
1520
  return this
1292
1521
  }
1293
1522
 
1523
+ /**
1524
+ * Returns an array of all slides in the presentation with their metadata.
1525
+ *
1526
+ * @returns {Array<{index: number, slideId: string, title: string, zipPath: string}>}
1527
+ *
1528
+ * @example
1529
+ * const slides = ppt.getSlides();
1530
+ * slides.forEach(s => console.log(`Slide ${s.index}: ${s.title}`));
1531
+ */
1294
1532
  getSlides() {
1295
1533
  this.#assertLoaded()
1296
1534
  return this.#slideManager.getSlides()
1297
1535
  }
1298
1536
 
1299
1537
  // === Table Features ===
1300
- addTableRow(tableId, rowData) {
1538
+ /**
1539
+ * Extracts table data from the active slide as structured JSON.
1540
+ * The first row of the table is treated as the header row.
1541
+ *
1542
+ * @param {string} tableId - Table name or shape ID.
1543
+ * @param {Object} [options] - Extraction options.
1544
+ * @param {boolean} [options.raw=false] - Return `string[][]` instead of object array.
1545
+ * @param {boolean} [options.includeMetadata=false] - Return `{rows, rowCount, columnCount, mergedCells}`.
1546
+ * @returns {Array<Object>|Array<Array<string>>|Object} Extracted table data.
1547
+ *
1548
+ * @example
1549
+ * // Object mode (default)
1550
+ * const rows = await ppt.getTableRows('SalesTable');
1551
+ * // → [{ region: 'North', sales: '1200' }, ...]
1552
+ *
1553
+ * // Raw mode
1554
+ * const raw = await ppt.getTableRows('SalesTable', { raw: true });
1555
+ * // → [['North', '1200'], ...]
1556
+ *
1557
+ * // Metadata mode
1558
+ * const meta = await ppt.getTableRows('SalesTable', { includeMetadata: true });
1559
+ * // → { rows: [...], rowCount: 5, columnCount: 3, mergedCells: [] }
1560
+ */
1561
+ getTableRows(tableId, options = {}) {
1562
+ this.#assertLoaded()
1563
+ const targetIndices = this.#getTargetSlideIndices()
1564
+ if (targetIndices.length === 0) {
1565
+ throw new PPTXError('No slides active/loaded')
1566
+ }
1567
+ const idx = targetIndices[0]
1568
+ return this.#tableManager.getTableRows(idx, tableId, options, this.#slideManager)
1569
+ }
1570
+
1571
+ /**
1572
+ * Appends one or more rows to a table. Supports flat arrays and nested arrays
1573
+ * for rowspan-merged cells.
1574
+ *
1575
+ * @param {string} tableId - Table name or shape ID.
1576
+ * @param {Array<string|Array<string>>} rowData - Row data. Nested arrays create rowspan cells.
1577
+ * @param {Object} [options] - Row insertion options.
1578
+ * @param {'rowspan'|'auto'|'none'} [options.mergeStrategy='rowspan'] - How to handle nested arrays.
1579
+ * `'rowspan'` creates OpenXML vertical spans, `'auto'` merges identical adjacent values,
1580
+ * `'none'` expands nested arrays into multiple flat rows.
1581
+ * @returns {PPTXTemplater} this (chainable)
1582
+ *
1583
+ * @example
1584
+ * // Simple flat row
1585
+ * ppt.addTableRow('SalesTable', ['North', '1200', '15%']);
1586
+ *
1587
+ * // Nested row with rowspan
1588
+ * ppt.addTableRow('SalesTable', ['Region', ['Q1', 'Q2'], '$5K'], { mergeStrategy: 'rowspan' });
1589
+ */
1590
+ addTableRow(tableId, rowData, options = {}) {
1301
1591
  this.#assertLoaded()
1302
1592
  const targetIndices = this.#getTargetSlideIndices()
1303
1593
  for (const idx of targetIndices) {
1304
- this.#tableManager.addTableRow(idx, tableId, rowData, this.#slideManager)
1594
+ this.#tableManager.addTableRow(idx, tableId, rowData, this.#slideManager, options)
1305
1595
  }
1306
1596
  return this
1307
1597
  }
1308
1598
 
1599
+ /**
1600
+ * Removes a row from a table by its 0-based row index.
1601
+ *
1602
+ * @param {string} tableId - Table name or shape ID.
1603
+ * @param {number} rowIndex - 0-based row index to remove.
1604
+ * @returns {PPTXTemplater} this (chainable)
1605
+ *
1606
+ * @example
1607
+ * ppt.removeTableRow('SalesTable', 2); // Remove the third row (0-based)
1608
+ */
1309
1609
  removeTableRow(tableId, rowIndex) {
1310
1610
  this.#assertLoaded()
1311
1611
  const targetIndices = this.#getTargetSlideIndices()
@@ -1315,6 +1615,17 @@ class PPTXTemplater {
1315
1615
  return this
1316
1616
  }
1317
1617
 
1618
+ /**
1619
+ * Inserts a new row at the specified 0-based index, shifting existing rows down.
1620
+ *
1621
+ * @param {string} tableId - Table name or shape ID.
1622
+ * @param {number} rowIndex - 0-based index at which to insert the new row.
1623
+ * @param {Array<string>} rowData - Cell values for the new row.
1624
+ * @returns {PPTXTemplater} this (chainable)
1625
+ *
1626
+ * @example
1627
+ * ppt.insertTableRow('SalesTable', 1, ['East', '980', '8%']); // Insert at row 1
1628
+ */
1318
1629
  insertTableRow(tableId, rowIndex, rowData) {
1319
1630
  this.#assertLoaded()
1320
1631
  const targetIndices = this.#getTargetSlideIndices()
@@ -1324,6 +1635,17 @@ class PPTXTemplater {
1324
1635
  return this
1325
1636
  }
1326
1637
 
1638
+ /**
1639
+ * Clones a row and inserts the copy at a target position.
1640
+ *
1641
+ * @param {string} tableId - Table name or shape ID.
1642
+ * @param {number} sourceRowIndex - 0-based index of the row to clone.
1643
+ * @param {number} targetRowIndex - 0-based index where the clone is inserted.
1644
+ * @returns {PPTXTemplater} this (chainable)
1645
+ *
1646
+ * @example
1647
+ * ppt.cloneTableRow('SalesTable', 0, 3); // Clone row 0 to position 3
1648
+ */
1327
1649
  cloneTableRow(tableId, sourceRowIndex, targetRowIndex) {
1328
1650
  this.#assertLoaded()
1329
1651
  const targetIndices = this.#getTargetSlideIndices()
@@ -1339,6 +1661,25 @@ class PPTXTemplater {
1339
1661
  return this
1340
1662
  }
1341
1663
 
1664
+ /**
1665
+ * Updates the text and optional formatting of a single table cell.
1666
+ *
1667
+ * @param {string} tableId - Table name or shape ID.
1668
+ * @param {number} rowIndex - 0-based row index.
1669
+ * @param {number} colIndex - 0-based column index.
1670
+ * @param {string} value - New cell text content.
1671
+ * @param {Object} [options] - Cell formatting options.
1672
+ * @param {boolean} [options.bold] - Bold text.
1673
+ * @param {boolean} [options.italic] - Italic text.
1674
+ * @param {number} [options.fontSize] - Font size in points.
1675
+ * @param {'left'|'center'|'right'} [options.align] - Text alignment.
1676
+ * @param {string} [options.fill] - Cell background color (hex, e.g. '#FF0000').
1677
+ * @param {string} [options.color] - Text color (hex).
1678
+ * @returns {PPTXTemplater} this (chainable)
1679
+ *
1680
+ * @example
1681
+ * ppt.updateCell('SalesTable', 1, 2, '$9,800', { bold: true, color: '#10B981' });
1682
+ */
1342
1683
  updateCell(tableId, rowIndex, colIndex, value, options = {}) {
1343
1684
  this.#assertLoaded()
1344
1685
  const targetIndices = this.#getTargetSlideIndices()
@@ -1356,6 +1697,22 @@ class PPTXTemplater {
1356
1697
  return this
1357
1698
  }
1358
1699
 
1700
+ /**
1701
+ * Merges a rectangular region of table cells into a single merged cell.
1702
+ * Supports both positional arguments and an options object.
1703
+ *
1704
+ * @param {string|Object} tableIdOrOptions - Table ID string, or options object with all fields.
1705
+ * @param {number} [startRow] - 0-based start row.
1706
+ * @param {number} [startCol] - 0-based start column.
1707
+ * @param {number} [endRow] - 0-based end row (inclusive).
1708
+ * @param {number} [endCol] - 0-based end column (inclusive).
1709
+ * @returns {PPTXTemplater} this (chainable)
1710
+ *
1711
+ * @example
1712
+ * ppt.mergeCells('SalesTable', 0, 0, 0, 2); // Merge first 3 cells of row 0
1713
+ * // Or with options object:
1714
+ * ppt.mergeCells({ tableId: 'SalesTable', startRow: 0, startCol: 0, endRow: 0, endCol: 2 });
1715
+ */
1359
1716
  mergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
1360
1717
  this.#assertLoaded()
1361
1718
  let tableId = tableIdOrOptions
@@ -1383,6 +1740,22 @@ class PPTXTemplater {
1383
1740
  return this
1384
1741
  }
1385
1742
 
1743
+ /**
1744
+ * Unmerges (splits) a previously merged cell region.
1745
+ * Supports both positional arguments and an options object.
1746
+ *
1747
+ * @param {string|Object} tableIdOrOptions - Table ID string, or options object.
1748
+ * @param {number} [startRow] - 0-based start row of the merged region.
1749
+ * @param {number} [startCol] - 0-based start column.
1750
+ * @param {number} [endRow] - 0-based end row.
1751
+ * @param {number} [endCol] - 0-based end column.
1752
+ * @returns {PPTXTemplater} this (chainable)
1753
+ *
1754
+ * @example
1755
+ * ppt.unmergeCells('SalesTable', 0, 0, 0, 2); // Unmerge region
1756
+ * // Via cell coordinate:
1757
+ * ppt.unmergeCells({ tableId: 'SalesTable', row: 0, col: 0 });
1758
+ */
1386
1759
  unmergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
1387
1760
  this.#assertLoaded()
1388
1761
  let tableId = tableIdOrOptions
@@ -1421,12 +1794,37 @@ class PPTXTemplater {
1421
1794
  return this
1422
1795
  }
1423
1796
 
1797
+ /**
1798
+ * Returns an array of all merged cell regions in a table.
1799
+ *
1800
+ * @param {string} [tableId] - Table name or shape ID. Defaults to the first table found.
1801
+ * @returns {Array<{startRow: number, startCol: number, endRow: number, endCol: number}>}
1802
+ *
1803
+ * @example
1804
+ * const merges = ppt.getMergedCells('SalesTable');
1805
+ * merges.forEach(m => console.log(`Merged: row ${m.startRow}-${m.endRow}, col ${m.startCol}-${m.endCol}`));
1806
+ */
1424
1807
  getMergedCells(tableId) {
1425
1808
  this.#assertLoaded()
1426
1809
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
1427
1810
  return this.#tableManager.getMergedCells(slideIndex, tableId || 'first', this.#slideManager)
1428
1811
  }
1429
1812
 
1813
+ /**
1814
+ * Validates whether a merge region is valid for the given table dimensions.
1815
+ * Checks for overlapping merges, out-of-bounds coordinates, etc.
1816
+ *
1817
+ * @param {string} tableId - Table name or shape ID.
1818
+ * @param {number} startRow - 0-based start row.
1819
+ * @param {number} startCol - 0-based start column.
1820
+ * @param {number} endRow - 0-based end row.
1821
+ * @param {number} endCol - 0-based end column.
1822
+ * @returns {{valid: boolean, errors: string[]}} Validation result.
1823
+ *
1824
+ * @example
1825
+ * const result = ppt.validateMergeRegion('SalesTable', 0, 0, 1, 2);
1826
+ * if (!result.valid) console.error(result.errors);
1827
+ */
1430
1828
  validateMergeRegion(tableId, startRow, startCol, endRow, endCol) {
1431
1829
  this.#assertLoaded()
1432
1830
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1441,6 +1839,19 @@ class PPTXTemplater {
1441
1839
  )
1442
1840
  }
1443
1841
 
1842
+ /**
1843
+ * Checks whether a specific table cell is part of a merged region.
1844
+ *
1845
+ * @param {string} tableId - Table name or shape ID.
1846
+ * @param {number} row - 0-based row index.
1847
+ * @param {number} col - 0-based column index.
1848
+ * @returns {boolean} `true` if the cell is merged (parent or continuation).
1849
+ *
1850
+ * @example
1851
+ * if (ppt.isMergedCell('SalesTable', 0, 0)) {
1852
+ * console.log('Cell is part of a merged region');
1853
+ * }
1854
+ */
1444
1855
  isMergedCell(tableId, row, col) {
1445
1856
  this.#assertLoaded()
1446
1857
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1453,6 +1864,19 @@ class PPTXTemplater {
1453
1864
  )
1454
1865
  }
1455
1866
 
1867
+ /**
1868
+ * Returns the anchor (parent) cell coordinates of a merged region
1869
+ * that contains the given cell.
1870
+ *
1871
+ * @param {string} tableId - Table name or shape ID.
1872
+ * @param {number} row - 0-based row index of any cell in the merged region.
1873
+ * @param {number} col - 0-based column index.
1874
+ * @returns {{row: number, col: number}|null} Anchor cell coordinates, or null if not merged.
1875
+ *
1876
+ * @example
1877
+ * const parent = ppt.getMergeParent('SalesTable', 0, 1);
1878
+ * // → { row: 0, col: 0 } if cells (0,0)-(0,2) are merged
1879
+ */
1456
1880
  getMergeParent(tableId, row, col) {
1457
1881
  this.#assertLoaded()
1458
1882
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1465,6 +1889,18 @@ class PPTXTemplater {
1465
1889
  )
1466
1890
  }
1467
1891
 
1892
+ /**
1893
+ * Returns the full extent of the merged region containing a given cell.
1894
+ *
1895
+ * @param {string} tableId - Table name or shape ID.
1896
+ * @param {number} row - 0-based row index of any cell in the merged region.
1897
+ * @param {number} col - 0-based column index.
1898
+ * @returns {{startRow: number, startCol: number, endRow: number, endCol: number}|null}
1899
+ *
1900
+ * @example
1901
+ * const region = ppt.getMergeRegion('SalesTable', 0, 1);
1902
+ * // → { startRow: 0, startCol: 0, endRow: 0, endCol: 2 }
1903
+ */
1468
1904
  getMergeRegion(tableId, row, col) {
1469
1905
  this.#assertLoaded()
1470
1906
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1477,6 +1913,17 @@ class PPTXTemplater {
1477
1913
  )
1478
1914
  }
1479
1915
 
1916
+ /**
1917
+ * Splits a previously merged cell region back into individual cells.
1918
+ *
1919
+ * @param {string} tableId - Table name or shape ID.
1920
+ * @param {number} row - 0-based row of the merged region anchor.
1921
+ * @param {number} col - 0-based column of the merged region anchor.
1922
+ * @returns {PPTXTemplater} this (chainable)
1923
+ *
1924
+ * @example
1925
+ * ppt.splitMergedRegion('SalesTable', 0, 0); // Split merge starting at row 0, col 0
1926
+ */
1480
1927
  splitMergedRegion(tableId, row, col) {
1481
1928
  this.#assertLoaded()
1482
1929
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1490,6 +1937,19 @@ class PPTXTemplater {
1490
1937
  return this
1491
1938
  }
1492
1939
 
1940
+ /**
1941
+ * Clones an existing merged region to a new anchor position in the table.
1942
+ *
1943
+ * @param {string} tableId - Table name or shape ID.
1944
+ * @param {number} row - 0-based row of the source merged region.
1945
+ * @param {number} col - 0-based column of the source merged region.
1946
+ * @param {number} targetRow - 0-based target row.
1947
+ * @param {number} targetCol - 0-based target column.
1948
+ * @returns {PPTXTemplater} this (chainable)
1949
+ *
1950
+ * @example
1951
+ * ppt.cloneMergedRegion('SalesTable', 0, 0, 4, 0);
1952
+ */
1493
1953
  cloneMergedRegion(tableId, row, col, targetRow, targetCol) {
1494
1954
  this.#assertLoaded()
1495
1955
  const slideIndex = this.#getTargetSlideIndices()[0] || 1
@@ -1505,6 +1965,15 @@ class PPTXTemplater {
1505
1965
  return this
1506
1966
  }
1507
1967
 
1968
+ /**
1969
+ * Automatically adjusts column widths to fit the content of each cell.
1970
+ *
1971
+ * @param {string} tableId - Table name or shape ID.
1972
+ * @returns {PPTXTemplater} this (chainable)
1973
+ *
1974
+ * @example
1975
+ * ppt.autoFitTable('SalesTable');
1976
+ */
1508
1977
  autoFitTable(tableId) {
1509
1978
  this.#assertLoaded()
1510
1979
  const targetIndices = this.#getTargetSlideIndices()
@@ -1514,6 +1983,18 @@ class PPTXTemplater {
1514
1983
  return this
1515
1984
  }
1516
1985
 
1986
+ /**
1987
+ * Resizes a table to the specified width and height in EMUs.
1988
+ * 1 inch = 914,400 EMUs.
1989
+ *
1990
+ * @param {string} tableId - Table name or shape ID.
1991
+ * @param {number} width - New width in EMUs.
1992
+ * @param {number} height - New height in EMUs.
1993
+ * @returns {PPTXTemplater} this (chainable)
1994
+ *
1995
+ * @example
1996
+ * ppt.resizeTable('SalesTable', 6858000, 1371600); // 7.5" wide × 1.5" tall
1997
+ */
1517
1998
  resizeTable(tableId, width, height) {
1518
1999
  this.#assertLoaded()
1519
2000
  const targetIndices = this.#getTargetSlideIndices()
@@ -1523,6 +2004,15 @@ class PPTXTemplater {
1523
2004
  return this
1524
2005
  }
1525
2006
 
2007
+ /**
2008
+ * Returns metadata for all tables on the targeted slide(s).
2009
+ *
2010
+ * @returns {Array<{name: string, rows: number, cols: number, zipPath: string}>}
2011
+ *
2012
+ * @example
2013
+ * const tables = ppt.getTables();
2014
+ * tables.forEach(t => console.log(`${t.name}: ${t.rows}×${t.cols}`));
2015
+ */
1526
2016
  getTables() {
1527
2017
  this.#assertLoaded()
1528
2018
  const targetIndices = this.#getTargetSlideIndices()
@@ -1534,10 +2024,34 @@ class PPTXTemplater {
1534
2024
  }
1535
2025
 
1536
2026
  // === Chart Features ===
2027
+ /**
2028
+ * Alias for `updateChart()`. Updates chart data for a named chart.
2029
+ *
2030
+ * @param {string} chartId - Chart name or shape ID.
2031
+ * @param {Object} data - Chart data object with `categories` and `series`.
2032
+ * @returns {PPTXTemplater} this (chainable)
2033
+ *
2034
+ * @example
2035
+ * ppt.updateChartData('revenue-chart', {
2036
+ * categories: ['Q1', 'Q2', 'Q3'],
2037
+ * series: [{ name: 'Revenue', values: [100, 150, 200] }]
2038
+ * });
2039
+ */
1537
2040
  updateChartData(chartId, data) {
1538
2041
  return this.updateChart(chartId, data)
1539
2042
  }
1540
2043
 
2044
+ /**
2045
+ * Replaces a specific data series in a chart.
2046
+ *
2047
+ * @param {string} chartId - Chart name or shape ID.
2048
+ * @param {number} seriesIndex - 0-based index of the series to replace.
2049
+ * @param {Object} newSeriesData - New series data `{ name, values }`.
2050
+ * @returns {PPTXTemplater} this (chainable)
2051
+ *
2052
+ * @example
2053
+ * ppt.replaceChartSeries('revenue-chart', 0, { name: 'Revenue', values: [100, 200, 300] });
2054
+ */
1541
2055
  replaceChartSeries(chartId, seriesIndex, newSeriesData) {
1542
2056
  this.#assertLoaded()
1543
2057
  const targetIndices = this.#getTargetSlideIndices()
@@ -1554,6 +2068,16 @@ class PPTXTemplater {
1554
2068
  return this
1555
2069
  }
1556
2070
 
2071
+ /**
2072
+ * Updates only the title text of a chart.
2073
+ *
2074
+ * @param {string} chartId - Chart name or shape ID.
2075
+ * @param {string} title - New chart title.
2076
+ * @returns {PPTXTemplater} this (chainable)
2077
+ *
2078
+ * @example
2079
+ * ppt.updateChartTitle('revenue-chart', 'Q2 2026 Revenue');
2080
+ */
1557
2081
  updateChartTitle(chartId, title) {
1558
2082
  this.#assertLoaded()
1559
2083
  const targetIndices = this.#getTargetSlideIndices()
@@ -1569,6 +2093,16 @@ class PPTXTemplater {
1569
2093
  return this
1570
2094
  }
1571
2095
 
2096
+ /**
2097
+ * Updates only the category labels (X-axis) of a chart, keeping values unchanged.
2098
+ *
2099
+ * @param {string} chartId - Chart name or shape ID.
2100
+ * @param {string[]} categories - Array of category label strings.
2101
+ * @returns {PPTXTemplater} this (chainable)
2102
+ *
2103
+ * @example
2104
+ * ppt.updateChartCategories('revenue-chart', ['Jan', 'Feb', 'Mar', 'Apr']);
2105
+ */
1572
2106
  updateChartCategories(chartId, categories) {
1573
2107
  this.#assertLoaded()
1574
2108
  const targetIndices = this.#getTargetSlideIndices()
@@ -1584,6 +2118,26 @@ class PPTXTemplater {
1584
2118
  return this
1585
2119
  }
1586
2120
 
2121
+ /**
2122
+ * Updates data labels for a specific chart series.
2123
+ * Supports custom arrays, label maps, template strings, and cell references.
2124
+ *
2125
+ * @param {string} chartId - Chart name or shape ID.
2126
+ * @param {Object} options - Data label options.
2127
+ * @param {number} [options.series=0] - 0-based series index.
2128
+ * @param {string[]} [options.labels] - Array of custom label strings.
2129
+ * @param {Object} [options.labelMap] - Map of `{ categoryValue: label }`.
2130
+ * @param {string} [options.template] - Template string with `{value}`, `{category}`, `{percentage}` tokens.
2131
+ * @param {string} [options.labelsFromCells] - Excel cell range (e.g. `'Sheet1!$C$2:$C$6'`).
2132
+ * @param {boolean} [options.showSeriesNameInBar] - Prepend series name to bar chart labels.
2133
+ * @returns {PPTXTemplater} this (chainable)
2134
+ *
2135
+ * @example
2136
+ * ppt.updateDataLabels('revenue-chart', {
2137
+ * series: 0,
2138
+ * template: '{value} ({percentage}%)',
2139
+ * });
2140
+ */
1587
2141
  updateDataLabels(chartId, options) {
1588
2142
  this.#assertLoaded()
1589
2143
  const targetIndices = this.#getTargetSlideIndices()
@@ -1599,6 +2153,18 @@ class PPTXTemplater {
1599
2153
  return this
1600
2154
  }
1601
2155
 
2156
+ /**
2157
+ * Retrieves the current data labels configuration for a specific chart series.
2158
+ *
2159
+ * @param {string} chartId - Chart name or shape ID.
2160
+ * @param {Object} [options] - Options.
2161
+ * @param {number} [options.series=0] - 0-based series index.
2162
+ * @returns {Promise<Object>} Current data label settings.
2163
+ *
2164
+ * @example
2165
+ * const labels = await ppt.getDataLabels('revenue-chart', { series: 0 });
2166
+ * console.log(labels.showValue, labels.position);
2167
+ */
1602
2168
  async getDataLabels(chartId, options = {}) {
1603
2169
  this.#assertLoaded()
1604
2170
  const targetIndices = this.#getTargetSlideIndices()
@@ -1613,6 +2179,17 @@ class PPTXTemplater {
1613
2179
  )
1614
2180
  }
1615
2181
 
2182
+ /**
2183
+ * Validates the data labels configuration for a chart series against the chart XML.
2184
+ *
2185
+ * @param {string} chartId - Chart name or shape ID.
2186
+ * @param {Object} [options] - Options (same as `updateDataLabels`).
2187
+ * @returns {Promise<{valid: boolean, errors: string[]}>}
2188
+ *
2189
+ * @example
2190
+ * const result = await ppt.validateDataLabels('revenue-chart');
2191
+ * if (!result.valid) console.error(result.errors);
2192
+ */
1616
2193
  async validateDataLabels(chartId, options = {}) {
1617
2194
  this.#assertLoaded()
1618
2195
  const targetIndices = this.#getTargetSlideIndices()
@@ -1621,6 +2198,16 @@ class PPTXTemplater {
1621
2198
  return ValidationEngine.validateDataLabels(this, idx, chartId, options)
1622
2199
  }
1623
2200
 
2201
+ /**
2202
+ * Validates chart data labels across all series, including cell reference checks.
2203
+ *
2204
+ * @param {string} chartId - Chart name or shape ID.
2205
+ * @param {Object} [options] - Options.
2206
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
2207
+ *
2208
+ * @example
2209
+ * const result = await ppt.validateChartLabels('revenue-chart');
2210
+ */
1624
2211
  async validateChartLabels(chartId, options = {}) {
1625
2212
  this.#assertLoaded()
1626
2213
  const targetIndices = this.#getTargetSlideIndices()
@@ -1635,6 +2222,16 @@ class PPTXTemplater {
1635
2222
  )
1636
2223
  }
1637
2224
 
2225
+ /**
2226
+ * Validates series name labels (the labels showing series names inside bar chart bars).
2227
+ *
2228
+ * @param {string} chartId - Chart name or shape ID.
2229
+ * @param {Object} [options] - Options.
2230
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
2231
+ *
2232
+ * @example
2233
+ * const result = await ppt.validateSeriesNameLabels('bar-chart');
2234
+ */
1638
2235
  async validateSeriesNameLabels(chartId, options = {}) {
1639
2236
  this.#assertLoaded()
1640
2237
  const targetIndices = this.#getTargetSlideIndices()
@@ -1649,6 +2246,15 @@ class PPTXTemplater {
1649
2246
  )
1650
2247
  }
1651
2248
 
2249
+ /**
2250
+ * Returns an array of all charts found on the targeted slide(s).
2251
+ *
2252
+ * @returns {Array<{rId: string, zipPath: string}>} Chart info objects.
2253
+ *
2254
+ * @example
2255
+ * const charts = ppt.getCharts();
2256
+ * charts.forEach(c => console.log(c.zipPath));
2257
+ */
1652
2258
  getCharts() {
1653
2259
  this.#assertLoaded()
1654
2260
  const targetIndices = this.#getTargetSlideIndices()
@@ -1758,6 +2364,21 @@ class PPTXTemplater {
1758
2364
  * @param {string|Object} data - String value or list configuration object.
1759
2365
  * @returns {PPTXTemplater} this (chainable)
1760
2366
  */
2367
+ /**
2368
+ * Updates text content or list items in a named shape or text box.
2369
+ * Supports plain strings, bullet lists, numbered lists, and nested lists.
2370
+ *
2371
+ * @param {string} tag - Shape name/ID or placeholder tag.
2372
+ * @param {string|Object} data - Text string, or list configuration object.
2373
+ * @returns {PPTXTemplater} this (chainable)
2374
+ *
2375
+ * @example
2376
+ * // Plain text
2377
+ * ppt.updateText('SubtitleBox', 'Updated subtitle');
2378
+ *
2379
+ * // Bullet list
2380
+ * ppt.updateText('BulletBox', { items: ['Item A', 'Item B', 'Item C'] });
2381
+ */
1761
2382
  updateText(tag, data) {
1762
2383
  this.#assertLoaded()
1763
2384
  const targetIndices = this.#getTargetSlideIndices()
@@ -1773,6 +2394,16 @@ class PPTXTemplater {
1773
2394
  * @param {string} tag - Shape name/ID or placeholder tag.
1774
2395
  * @returns {Array} Nested list structure of items.
1775
2396
  */
2397
+ /**
2398
+ * Retrieves list items from a shape or text box.
2399
+ *
2400
+ * @param {string} tag - Shape name/ID or placeholder tag.
2401
+ * @returns {Array} Nested list structure of items.
2402
+ *
2403
+ * @example
2404
+ * const items = ppt.getList('BulletBox');
2405
+ * console.log(items); // ['Item A', ['Nested', 'Sub-item'], 'Item B']
2406
+ */
1776
2407
  getList(tag) {
1777
2408
  this.#assertLoaded()
1778
2409
  const targetIndices = this.#getTargetSlideIndices()
@@ -1791,6 +2422,20 @@ class PPTXTemplater {
1791
2422
  }
1792
2423
 
1793
2424
  // === Text Features ===
2425
+ /**
2426
+ * Replaces a text placeholder tag across all targeted shapes on selected slides.
2427
+ * Finds shapes containing `{{tag}}` or `tag` and replaces the placeholder value.
2428
+ *
2429
+ * @param {string} tag - Placeholder tag name (e.g. `'{{name}}'` or `'name'`).
2430
+ * @param {string} value - Replacement value.
2431
+ * @param {Object} [options] - Options.
2432
+ * @param {number} [options.slide] - Target a specific slide index (overrides `useSlide`).
2433
+ * @returns {PPTXTemplater} this (chainable)
2434
+ *
2435
+ * @example
2436
+ * ppt.replaceTextByTag('{{company}}', 'Acme Corp');
2437
+ * ppt.replaceTextByTag('{{date}}', '2026-06-12');
2438
+ */
1794
2439
  replaceTextByTag(tag, value, options = {}) {
1795
2440
  this.#assertLoaded()
1796
2441
  const targetIndices = this.#getTargetSlideIndices()
@@ -1807,6 +2452,21 @@ class PPTXTemplater {
1807
2452
  return this
1808
2453
  }
1809
2454
 
2455
+ /**
2456
+ * Replaces multiple text placeholder tags in a single pass.
2457
+ * More efficient than calling `replaceTextByTag()` repeatedly.
2458
+ *
2459
+ * @param {Object<string, string>} replacements - Map of `{ tag: value }` pairs.
2460
+ * @param {Object} [options] - Options (same as `replaceTextByTag`).
2461
+ * @returns {PPTXTemplater} this (chainable)
2462
+ *
2463
+ * @example
2464
+ * ppt.replaceMultiple({
2465
+ * '{{title}}': 'Q2 Report',
2466
+ * '{{date}}': 'June 2026',
2467
+ * '{{company}}': 'Acme Corp'
2468
+ * });
2469
+ */
1810
2470
  replaceMultiple(replacements, options = {}) {
1811
2471
  this.#assertLoaded()
1812
2472
  const targetIndices = this.#getTargetSlideIndices()
@@ -1822,6 +2482,16 @@ class PPTXTemplater {
1822
2482
  return this
1823
2483
  }
1824
2484
 
2485
+ /**
2486
+ * Searches for all occurrences of a text string across the targeted slides.
2487
+ *
2488
+ * @param {string} text - Text to search for.
2489
+ * @returns {Array<{slideIndex: number, shapeName: string, text: string}>} Array of matches.
2490
+ *
2491
+ * @example
2492
+ * const matches = ppt.findText('Revenue');
2493
+ * matches.forEach(m => console.log(`Found on slide ${m.slideIndex} in "${m.shapeName}"`));
2494
+ */
1825
2495
  findText(text) {
1826
2496
  this.#assertLoaded()
1827
2497
  const targetIndices = this.#getTargetSlideIndices()
@@ -1832,6 +2502,15 @@ class PPTXTemplater {
1832
2502
  return matches
1833
2503
  }
1834
2504
 
2505
+ /**
2506
+ * Returns all text elements (paragraphs) across the targeted slides.
2507
+ *
2508
+ * @returns {Array<{slideIndex: number, shapeName: string, text: string}>}
2509
+ *
2510
+ * @example
2511
+ * const elements = ppt.getTextElements();
2512
+ * elements.forEach(el => console.log(`Slide ${el.slideIndex}: ${el.text}`))
2513
+ */
1835
2514
  getTextElements() {
1836
2515
  this.#assertLoaded()
1837
2516
  const targetIndices = this.#getTargetSlideIndices()
@@ -1843,6 +2522,16 @@ class PPTXTemplater {
1843
2522
  }
1844
2523
 
1845
2524
  // === Shape Features ===
2525
+ /**
2526
+ * Sets the text content of an existing shape by name or ID.
2527
+ *
2528
+ * @param {string} shapeId - Shape name or ID.
2529
+ * @param {string} text - New text content.
2530
+ * @returns {PPTXTemplater} this (chainable)
2531
+ *
2532
+ * @example
2533
+ * ppt.updateShapeText('CalloutBox', 'Important note here');
2534
+ */
1846
2535
  updateShapeText(shapeId, text) {
1847
2536
  this.#assertLoaded()
1848
2537
  const targetIndices = this.#getTargetSlideIndices()
@@ -1892,6 +2581,19 @@ class PPTXTemplater {
1892
2581
  return this
1893
2582
  }
1894
2583
 
2584
+ /**
2585
+ * Duplicates an existing shape with a new ID, optionally at a different position.
2586
+ *
2587
+ * @param {string} shapeId - Source shape name or ID.
2588
+ * @param {string} newShapeId - Name/ID for the cloned shape.
2589
+ * @param {Object} [options] - Position overrides for the clone.
2590
+ * @param {number} [options.x] - X offset for the clone (EMUs).
2591
+ * @param {number} [options.y] - Y offset for the clone (EMUs).
2592
+ * @returns {PPTXTemplater} this (chainable)
2593
+ *
2594
+ * @example
2595
+ * ppt.cloneShape('logo', 'logo-copy', { x: 2000000, y: 500000 });
2596
+ */
1895
2597
  cloneShape(shapeId, newShapeId, options = {}) {
1896
2598
  this.#assertLoaded()
1897
2599
  const targetIndices = this.#getTargetSlideIndices()
@@ -1901,6 +2603,15 @@ class PPTXTemplater {
1901
2603
  return this
1902
2604
  }
1903
2605
 
2606
+ /**
2607
+ * Removes a shape from the targeted slide(s). Alias for `removeShape()`.
2608
+ *
2609
+ * @param {string} shapeId - Shape name or ID to delete.
2610
+ * @returns {PPTXTemplater} this (chainable)
2611
+ *
2612
+ * @example
2613
+ * ppt.deleteShape('temp-banner');
2614
+ */
1904
2615
  deleteShape(shapeId) {
1905
2616
  this.#assertLoaded()
1906
2617
  const targetIndices = this.#getTargetSlideIndices()
@@ -1910,6 +2621,15 @@ class PPTXTemplater {
1910
2621
  return this
1911
2622
  }
1912
2623
 
2624
+ /**
2625
+ * Returns metadata for all shapes on the targeted slide(s).
2626
+ *
2627
+ * @returns {Array<{id: string, name: string, type: string, x: number, y: number, width: number, height: number}>}
2628
+ *
2629
+ * @example
2630
+ * const shapes = ppt.getShapes();
2631
+ * shapes.forEach(s => console.log(`${s.name}: ${s.type} at (${s.x}, ${s.y})`));
2632
+ */
1913
2633
  getShapes() {
1914
2634
  this.#assertLoaded()
1915
2635
  const targetIndices = this.#getTargetSlideIndices()
@@ -2099,7 +2819,77 @@ class PPTXTemplater {
2099
2819
  return null
2100
2820
  }
2101
2821
 
2822
+ /**
2823
+ * Retrieves final rendered bounds of a table cell in pixels.
2824
+ *
2825
+ * @param {string} tableId - Table name or shape ID.
2826
+ * @param {number} rowIndex - 0-based row index.
2827
+ * @param {number} colIndex - 0-based column index.
2828
+ * @returns {Object|null} Cell bounds { x, y, width, height } in pixels, or null.
2829
+ */
2830
+ getCellBounds(tableId, rowIndex, colIndex) {
2831
+ this.#assertLoaded()
2832
+ const targetIndices = this.#getTargetSlideIndices()
2833
+ for (const idx of targetIndices) {
2834
+ try {
2835
+ const bounds = this.#tableManager.getCellBounds(
2836
+ idx,
2837
+ tableId,
2838
+ rowIndex,
2839
+ colIndex,
2840
+ this.#slideManager
2841
+ )
2842
+ if (bounds) return bounds
2843
+ } catch (err) {
2844
+ logger.debug(
2845
+ `Could not get cell bounds for table ${tableId} on slide ${idx}: ${err.message}`
2846
+ )
2847
+ }
2848
+ }
2849
+ return null
2850
+ }
2851
+
2852
+ /**
2853
+ * Retrieves final rendered position of a table cell in pixels.
2854
+ *
2855
+ * @param {string} tableId - Table name or shape ID.
2856
+ * @param {number} rowIndex - 0-based row index.
2857
+ * @param {number} colIndex - 0-based column index.
2858
+ * @returns {Object|null} Cell position { row, column, x, y } in pixels, or null.
2859
+ */
2860
+ getCellPosition(tableId, rowIndex, colIndex) {
2861
+ this.#assertLoaded()
2862
+ const targetIndices = this.#getTargetSlideIndices()
2863
+ for (const idx of targetIndices) {
2864
+ try {
2865
+ const pos = this.#tableManager.getCellPosition(
2866
+ idx,
2867
+ tableId,
2868
+ rowIndex,
2869
+ colIndex,
2870
+ this.#slideManager
2871
+ )
2872
+ if (pos) return pos
2873
+ } catch (err) {
2874
+ logger.debug(
2875
+ `Could not get cell position for table ${tableId} on slide ${idx}: ${err.message}`
2876
+ )
2877
+ }
2878
+ }
2879
+ return null
2880
+ }
2881
+
2102
2882
  // === Image Features ===
2883
+ /**
2884
+ * Replaces an existing image in the presentation by shape name or relationship ID.
2885
+ *
2886
+ * @param {string} imageIdOrName - Shape name, alt text, or relationship ID of the image.
2887
+ * @param {string|Buffer} sourcePathOrBuffer - Path to the replacement image file, or a Buffer.
2888
+ * @returns {Promise<PPTXTemplater>} this (chainable)
2889
+ *
2890
+ * @example
2891
+ * await ppt.replaceImage('company-logo', './new-logo.png');
2892
+ */
2103
2893
  async replaceImage(imageIdOrName, sourcePathOrBuffer) {
2104
2894
  this.#assertLoaded()
2105
2895
  const t0 = performance.now()
@@ -2118,6 +2908,30 @@ class PPTXTemplater {
2118
2908
  return this
2119
2909
  }
2120
2910
 
2911
+ /**
2912
+ * Adds a new image to the targeted slide(s) at the specified position.
2913
+ *
2914
+ * @param {string|Buffer} sourcePathOrBuffer - Path to the image file, or a Buffer.
2915
+ * @param {Object} [options] - Positioning and display options.
2916
+ * @param {number} [options.x] - X offset in EMUs (1 inch = 914,400 EMUs).
2917
+ * @param {number} [options.y] - Y offset in EMUs.
2918
+ * @param {number} [options.width] - Width in EMUs.
2919
+ * @param {number} [options.height] - Height in EMUs.
2920
+ * @param {number} [options.rotation] - Rotation in degrees (0–360).
2921
+ * @param {number} [options.opacity] - Opacity (0–100).
2922
+ * @param {string} [options.name] - Shape name for the image.
2923
+ * @param {Object} [options.cropTo] - Crop percentages `{ l, r, t, b }` (0–100000).
2924
+ * @returns {Promise<PPTXTemplater>} this (chainable)
2925
+ *
2926
+ * @example
2927
+ * await ppt.addImage('./photo.jpg', {
2928
+ * x: 914400, // 1 inch
2929
+ * y: 914400, // 1 inch
2930
+ * width: 3657600, // 4 inches
2931
+ * height: 2743200, // 3 inches
2932
+ * name: 'hero-image'
2933
+ * });
2934
+ */
2121
2935
  async addImage(sourcePathOrBuffer, options = {}) {
2122
2936
  this.#assertLoaded()
2123
2937
  const t0 = performance.now()
@@ -2136,6 +2950,15 @@ class PPTXTemplater {
2136
2950
  return this
2137
2951
  }
2138
2952
 
2953
+ /**
2954
+ * Removes an image from the targeted slide(s) by shape name or relationship ID.
2955
+ *
2956
+ * @param {string} imageIdOrName - Shape name, alt text, or relationship ID of the image.
2957
+ * @returns {PPTXTemplater} this (chainable)
2958
+ *
2959
+ * @example
2960
+ * ppt.removeImage('old-logo');
2961
+ */
2139
2962
  removeImage(imageIdOrName) {
2140
2963
  this.#assertLoaded()
2141
2964
  const targetIndices = this.#getTargetSlideIndices()
@@ -2150,6 +2973,15 @@ class PPTXTemplater {
2150
2973
  return this
2151
2974
  }
2152
2975
 
2976
+ /**
2977
+ * Returns metadata for all images found on the targeted slide(s).
2978
+ *
2979
+ * @returns {Array<{rId: string, name: string, x: number, y: number, width: number, height: number, mediaPath: string}>}
2980
+ *
2981
+ * @example
2982
+ * const images = ppt.getImages();
2983
+ * images.forEach(img => console.log(`${img.name}: ${img.width}×${img.height} EMUs`));
2984
+ */
2153
2985
  getImages() {
2154
2986
  this.#assertLoaded()
2155
2987
  const targetIndices = this.#getTargetSlideIndices()
@@ -2162,7 +2994,19 @@ class PPTXTemplater {
2162
2994
  return images
2163
2995
  }
2164
2996
 
2165
- // === Validation Features ===
2997
+ /**
2998
+ * Performs a comprehensive validation of the entire PPTX structure.
2999
+ * Checks slide XML, relationships, content types, slide masters, and layouts.
3000
+ *
3001
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>} Validation report.
3002
+ *
3003
+ * @example
3004
+ * const result = await ppt.validatePresentation();
3005
+ * if (!result.valid) {
3006
+ * console.error('Errors:', result.errors);
3007
+ * console.warn('Warnings:', result.warnings);
3008
+ * }
3009
+ */
2166
3010
  async validatePresentation() {
2167
3011
  this.#assertLoaded()
2168
3012
  return await ValidationEngine.validatePresentation(this)
@@ -2256,11 +3100,31 @@ class PPTXTemplater {
2256
3100
  }
2257
3101
  }
2258
3102
 
3103
+ /**
3104
+ * Validates the XML structure of a specific slide.
3105
+ *
3106
+ * @param {number} slideIndex - 1-based slide index to validate.
3107
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
3108
+ *
3109
+ * @example
3110
+ * const result = await ppt.validateSlide(1);
3111
+ * if (!result.valid) console.error(result.errors);
3112
+ */
2259
3113
  async validateSlide(slideIndex) {
2260
3114
  this.#assertLoaded()
2261
3115
  return await ValidationEngine.validateSlide(this, slideIndex)
2262
3116
  }
2263
3117
 
3118
+ /**
3119
+ * Validates the XML structure of a specific table on the active slide.
3120
+ *
3121
+ * @param {string} tableId - Table name or shape ID.
3122
+ * @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
3123
+ *
3124
+ * @example
3125
+ * const result = await ppt.validateTable('SalesTable');
3126
+ * if (!result.valid) console.error(result.errors);
3127
+ */
2264
3128
  async validateTable(tableId) {
2265
3129
  this.#assertLoaded()
2266
3130
  return await ValidationEngine.validateTable(
@@ -2270,17 +3134,46 @@ class PPTXTemplater {
2270
3134
  )
2271
3135
  }
2272
3136
 
3137
+ /**
3138
+ * Validates the internal ZIP archive structure of the PPTX file.
3139
+ * Checks that all files referenced in the archive are accessible and uncorrupted.
3140
+ * Throws if critical structural issues are found.
3141
+ *
3142
+ * @returns {Promise<PPTXTemplater>} this (chainable)
3143
+ *
3144
+ * @example
3145
+ * await ppt.validateArchive(); // Throws PPTXError if the ZIP is corrupt
3146
+ */
2273
3147
  async validateArchive() {
2274
3148
  this.#assertLoaded()
2275
3149
  await this.#zipManager.validateArchive()
2276
3150
  return this
2277
3151
  }
2278
3152
 
3153
+ /**
3154
+ * Enables ZIP debug output. When enabled, every call to `toBuffer()` or `toStream()`
3155
+ * will log all ZIP entries (name, compression method, sizes, CRC).
3156
+ *
3157
+ * @returns {PPTXTemplater} this (chainable)
3158
+ *
3159
+ * @example
3160
+ * ppt.enableDebugZip();
3161
+ * const buffer = await ppt.toBuffer(); // Logs ZIP entries to debug output
3162
+ */
2279
3163
  enableDebugZip() {
2280
3164
  this.#outputWriter.debugZip = true
2281
3165
  return this
2282
3166
  }
2283
3167
 
3168
+ /**
3169
+ * Validates relationships for a specific part path inside the ZIP.
3170
+ *
3171
+ * @param {string} partPath - ZIP path to validate (e.g. `'ppt/slides/slide1.xml'`).
3172
+ * @returns {Object} Validation result with `valid`, `errors`, and `warnings`.
3173
+ *
3174
+ * @example
3175
+ * const result = ppt.validateRelationships('ppt/slides/slide1.xml');
3176
+ */
2284
3177
  validateRelationships(partPath) {
2285
3178
  this.#assertLoaded()
2286
3179
  return ValidationEngine.validateRelationships(this, partPath)
@@ -2505,6 +3398,17 @@ class PPTXTemplater {
2505
3398
  /**
2506
3399
  * Gets the ordered metadata of all objects on the slide.
2507
3400
  */
3401
+ /**
3402
+ * Returns an ordered array of all slide objects (shapes, images, charts, tables)
3403
+ * from bottom to top of the stacking order.
3404
+ *
3405
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3406
+ * @returns {Array<{id: string, name: string, type: string, zIndex: number}>}
3407
+ *
3408
+ * @example
3409
+ * const order = ppt.getObjectOrder(1);
3410
+ * order.forEach(o => console.log(`[${o.zIndex}] ${o.name}`));
3411
+ */
2508
3412
  getObjectOrder(slideIndex) {
2509
3413
  this.#assertLoaded()
2510
3414
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
@@ -2531,6 +3435,16 @@ class PPTXTemplater {
2531
3435
  /**
2532
3436
  * Retrieves the info of the top-most object on the slide.
2533
3437
  */
3438
+ /**
3439
+ * Returns the top-most (front) object on the slide.
3440
+ *
3441
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3442
+ * @returns {{id: string, name: string, type: string, zIndex: number}|null}
3443
+ *
3444
+ * @example
3445
+ * const top = ppt.getTopMostObject(1);
3446
+ * console.log('Front-most shape:', top.name);
3447
+ */
2534
3448
  getTopMostObject(slideIndex) {
2535
3449
  this.#assertLoaded()
2536
3450
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
@@ -2540,6 +3454,16 @@ class PPTXTemplater {
2540
3454
  /**
2541
3455
  * Retrieves the info of the bottom-most object on the slide.
2542
3456
  */
3457
+ /**
3458
+ * Returns the bottom-most (back) object on the slide.
3459
+ *
3460
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3461
+ * @returns {{id: string, name: string, type: string, zIndex: number}|null}
3462
+ *
3463
+ * @example
3464
+ * const bottom = ppt.getBottomMostObject(1);
3465
+ * console.log('Back-most shape:', bottom.name);
3466
+ */
2543
3467
  getBottomMostObject(slideIndex) {
2544
3468
  this.#assertLoaded()
2545
3469
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
@@ -2549,6 +3473,18 @@ class PPTXTemplater {
2549
3473
  /**
2550
3474
  * Swaps stacking positions of two slide objects.
2551
3475
  */
3476
+ /**
3477
+ * Swaps the stacking positions of two slide objects.
3478
+ *
3479
+ * @param {number|string} slideIndexOrId1 - Slide index (if 3 args) or first object ID (if 2 args).
3480
+ * @param {string} id1OrId2 - First object ID (if 3 args) or second object ID (if 2 args).
3481
+ * @param {string} [id2] - Second object ID (only if slide index is provided as first arg).
3482
+ * @returns {PPTXTemplater} this (chainable)
3483
+ *
3484
+ * @example
3485
+ * ppt.swapObjects(1, 'logo', 'background'); // On slide 1, swap 'logo' and 'background'
3486
+ * ppt.swapObjects('logo', 'background'); // On active slide
3487
+ */
2552
3488
  swapObjects(slideIndexOrId1, id1OrId2, id2) {
2553
3489
  this.#assertLoaded()
2554
3490
  let slideIndex, objectId1, objectId2
@@ -2568,6 +3504,17 @@ class PPTXTemplater {
2568
3504
  /**
2569
3505
  * Sorts stacking order using a custom comparison function.
2570
3506
  */
3507
+ /**
3508
+ * Sorts all slide objects using a custom comparison function.
3509
+ *
3510
+ * @param {number|Function} slideIndexOrCompareFn - Slide index (if 2 args) or compare function (if 1 arg).
3511
+ * @param {Function} [compareFnOption] - Compare function when slide index is provided.
3512
+ * @returns {PPTXTemplater} this (chainable)
3513
+ *
3514
+ * @example
3515
+ * // Sort alphabetically by name on the active slide
3516
+ * ppt.sortObjects((a, b) => a.name.localeCompare(b.name));
3517
+ */
2571
3518
  sortObjects(slideIndexOrCompareFn, compareFnOption) {
2572
3519
  this.#assertLoaded()
2573
3520
  let slideIndex, compareFn
@@ -2585,6 +3532,16 @@ class PPTXTemplater {
2585
3532
  /**
2586
3533
  * Cleans up and normalizes stacking order consistency.
2587
3534
  */
3535
+ /**
3536
+ * Normalizes the stacking order of all objects on a slide, removing gaps
3537
+ * and ensuring Z-index values are sequential (1, 2, 3, ...).
3538
+ *
3539
+ * @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
3540
+ * @returns {PPTXTemplater} this (chainable)
3541
+ *
3542
+ * @example
3543
+ * ppt.normalizeZOrder(1); // Clean up stacking order on slide 1
3544
+ */
2588
3545
  normalizeZOrder(slideIndex) {
2589
3546
  this.#assertLoaded()
2590
3547
  const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1