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.
- package/CHANGELOG.md +39 -0
- package/README.md +170 -2073
- package/package.json +11 -1
- package/src/core/PPTXTemplater.js +985 -28
- package/src/index.js +3 -1
- package/src/managers/TableManager.js +714 -115
- package/src/utils/logger.js +76 -15
|
@@ -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
|
-
|
|
1140
|
+
logger.info('=== Relationship Graph ===')
|
|
951
1141
|
for (const file of files) {
|
|
952
|
-
|
|
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 =>
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
rels.forEach(r =>
|
|
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
|
-
|
|
1182
|
+
logger.info(`=== XML Inspection: ${xmlPath} ===`)
|
|
993
1183
|
if (!xml) {
|
|
994
|
-
|
|
1184
|
+
logger.info('(File not found or empty)')
|
|
995
1185
|
} else {
|
|
996
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
1320
|
+
logger.info(`\n${chartPath}:`)
|
|
1132
1321
|
const rels = this.#relationshipManager.getRelationships(chartPath)
|
|
1133
|
-
rels.forEach(r =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|