node-pptx-templater 1.0.21 → 1.1.1
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 +169 -2230
- package/package.json +12 -2
- package/src/core/PPTXTemplater.js +992 -42
- package/src/index.js +3 -1
- package/src/managers/TableManager.js +81 -12
- 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 {
|
|
@@ -1030,13 +1137,13 @@ class PPTXTemplater {
|
|
|
1030
1137
|
debugRelationships() {
|
|
1031
1138
|
this.#assertLoaded()
|
|
1032
1139
|
const files = this.#zipManager.listFiles('').filter(f => f.endsWith('.rels'))
|
|
1033
|
-
|
|
1140
|
+
logger.info('=== Relationship Graph ===')
|
|
1034
1141
|
for (const file of files) {
|
|
1035
|
-
|
|
1142
|
+
logger.info(`\n${file}:`)
|
|
1036
1143
|
const rels = this.#relationshipManager.getRelationships(
|
|
1037
1144
|
file.replace('_rels/', '').replace('.rels', '')
|
|
1038
1145
|
)
|
|
1039
|
-
rels.forEach(r =>
|
|
1146
|
+
rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
|
|
1040
1147
|
}
|
|
1041
1148
|
return this
|
|
1042
1149
|
}
|
|
@@ -1052,14 +1159,14 @@ class PPTXTemplater {
|
|
|
1052
1159
|
const xml = this.#slideManager.getSlideXml(slideIndex)
|
|
1053
1160
|
const rels = this.#relationshipManager.getRelationships(info.zipPath)
|
|
1054
1161
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
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}`))
|
|
1063
1170
|
|
|
1064
1171
|
return this
|
|
1065
1172
|
}
|
|
@@ -1072,11 +1179,11 @@ class PPTXTemplater {
|
|
|
1072
1179
|
async inspectXML(xmlPath) {
|
|
1073
1180
|
this.#assertLoaded()
|
|
1074
1181
|
const xml = await this.#zipManager.readFile(xmlPath)
|
|
1075
|
-
|
|
1182
|
+
logger.info(`=== XML Inspection: ${xmlPath} ===`)
|
|
1076
1183
|
if (!xml) {
|
|
1077
|
-
|
|
1184
|
+
logger.info('(File not found or empty)')
|
|
1078
1185
|
} else {
|
|
1079
|
-
|
|
1186
|
+
logger.info(xml.substring(0, 1500) + (xml.length > 1500 ? '...\n[Truncated]' : ''))
|
|
1080
1187
|
}
|
|
1081
1188
|
return this
|
|
1082
1189
|
}
|
|
@@ -1163,8 +1270,7 @@ class PPTXTemplater {
|
|
|
1163
1270
|
*/
|
|
1164
1271
|
inspectChart(chartId) {
|
|
1165
1272
|
this.#assertLoaded()
|
|
1166
|
-
|
|
1167
|
-
// Find chart across all slides to get info
|
|
1273
|
+
logger.info(`=== Chart Inspection: ${chartId} ===`)
|
|
1168
1274
|
let found = false
|
|
1169
1275
|
for (const i of this.#slideManager.getAllSlideIndices()) {
|
|
1170
1276
|
try {
|
|
@@ -1177,15 +1283,15 @@ class PPTXTemplater {
|
|
|
1177
1283
|
c => c.zipPath.toLowerCase().includes(chartId.toLowerCase()) || c.rId === chartId
|
|
1178
1284
|
)
|
|
1179
1285
|
if (chart) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1286
|
+
logger.info(`Found on Slide ${i}`)
|
|
1287
|
+
logger.info(`ZIP Path: ${chart.zipPath}`)
|
|
1288
|
+
logger.info(`Relationship ID: ${chart.rId}`)
|
|
1183
1289
|
found = true
|
|
1184
1290
|
break
|
|
1185
1291
|
}
|
|
1186
1292
|
} catch (e) {}
|
|
1187
1293
|
}
|
|
1188
|
-
if (!found)
|
|
1294
|
+
if (!found) logger.info('Chart not found.')
|
|
1189
1295
|
return this
|
|
1190
1296
|
}
|
|
1191
1297
|
|
|
@@ -1205,15 +1311,15 @@ class PPTXTemplater {
|
|
|
1205
1311
|
*/
|
|
1206
1312
|
debugChartRelationships() {
|
|
1207
1313
|
this.#assertLoaded()
|
|
1208
|
-
|
|
1314
|
+
logger.info('=== Chart Relationships ===')
|
|
1209
1315
|
const chartFiles = this.#zipManager.listFiles('ppt/charts/').filter(f => {
|
|
1210
1316
|
const name = f.split('/').pop()
|
|
1211
1317
|
return name.startsWith('chart') && name.endsWith('.xml') && !f.includes('_rels')
|
|
1212
1318
|
})
|
|
1213
1319
|
for (const chartPath of chartFiles) {
|
|
1214
|
-
|
|
1320
|
+
logger.info(`\n${chartPath}:`)
|
|
1215
1321
|
const rels = this.#relationshipManager.getRelationships(chartPath)
|
|
1216
|
-
rels.forEach(r =>
|
|
1322
|
+
rels.forEach(r => logger.info(` - ${r.id} [${r.type.split('/').pop()}] -> ${r.target}`))
|
|
1217
1323
|
}
|
|
1218
1324
|
return this
|
|
1219
1325
|
}
|
|
@@ -1345,24 +1451,64 @@ class PPTXTemplater {
|
|
|
1345
1451
|
}
|
|
1346
1452
|
|
|
1347
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
|
+
*/
|
|
1348
1464
|
duplicateSlide(slideIndex, atPosition) {
|
|
1349
1465
|
this.#assertLoaded()
|
|
1350
1466
|
this.#slideManager.duplicateSlide(slideIndex, atPosition, this.#relationshipManager)
|
|
1351
1467
|
return this
|
|
1352
1468
|
}
|
|
1353
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
|
+
*/
|
|
1354
1479
|
deleteSlide(slideIndex) {
|
|
1355
1480
|
this.#assertLoaded()
|
|
1356
1481
|
this.#slideManager.removeSlide(slideIndex)
|
|
1357
1482
|
return this
|
|
1358
1483
|
}
|
|
1359
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
|
+
*/
|
|
1360
1495
|
moveSlide(fromIndex, toIndex) {
|
|
1361
1496
|
this.#assertLoaded()
|
|
1362
1497
|
this.#slideManager.moveSlide(fromIndex, toIndex)
|
|
1363
1498
|
return this
|
|
1364
1499
|
}
|
|
1365
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
|
+
*/
|
|
1366
1512
|
insertSlide(slideIndex, options = {}) {
|
|
1367
1513
|
this.#assertLoaded()
|
|
1368
1514
|
this.#slideManager.insertSlide(
|
|
@@ -1374,12 +1520,44 @@ class PPTXTemplater {
|
|
|
1374
1520
|
return this
|
|
1375
1521
|
}
|
|
1376
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
|
+
*/
|
|
1377
1532
|
getSlides() {
|
|
1378
1533
|
this.#assertLoaded()
|
|
1379
1534
|
return this.#slideManager.getSlides()
|
|
1380
1535
|
}
|
|
1381
1536
|
|
|
1382
1537
|
// === Table Features ===
|
|
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
|
+
*/
|
|
1383
1561
|
getTableRows(tableId, options = {}) {
|
|
1384
1562
|
this.#assertLoaded()
|
|
1385
1563
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1390,6 +1568,25 @@ class PPTXTemplater {
|
|
|
1390
1568
|
return this.#tableManager.getTableRows(idx, tableId, options, this.#slideManager)
|
|
1391
1569
|
}
|
|
1392
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
|
+
*/
|
|
1393
1590
|
addTableRow(tableId, rowData, options = {}) {
|
|
1394
1591
|
this.#assertLoaded()
|
|
1395
1592
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1399,6 +1596,16 @@ class PPTXTemplater {
|
|
|
1399
1596
|
return this
|
|
1400
1597
|
}
|
|
1401
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
|
+
*/
|
|
1402
1609
|
removeTableRow(tableId, rowIndex) {
|
|
1403
1610
|
this.#assertLoaded()
|
|
1404
1611
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1408,6 +1615,17 @@ class PPTXTemplater {
|
|
|
1408
1615
|
return this
|
|
1409
1616
|
}
|
|
1410
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
|
+
*/
|
|
1411
1629
|
insertTableRow(tableId, rowIndex, rowData) {
|
|
1412
1630
|
this.#assertLoaded()
|
|
1413
1631
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1417,6 +1635,17 @@ class PPTXTemplater {
|
|
|
1417
1635
|
return this
|
|
1418
1636
|
}
|
|
1419
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
|
+
*/
|
|
1420
1649
|
cloneTableRow(tableId, sourceRowIndex, targetRowIndex) {
|
|
1421
1650
|
this.#assertLoaded()
|
|
1422
1651
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1432,6 +1661,25 @@ class PPTXTemplater {
|
|
|
1432
1661
|
return this
|
|
1433
1662
|
}
|
|
1434
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
|
+
*/
|
|
1435
1683
|
updateCell(tableId, rowIndex, colIndex, value, options = {}) {
|
|
1436
1684
|
this.#assertLoaded()
|
|
1437
1685
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1449,6 +1697,22 @@ class PPTXTemplater {
|
|
|
1449
1697
|
return this
|
|
1450
1698
|
}
|
|
1451
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
|
+
*/
|
|
1452
1716
|
mergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
|
|
1453
1717
|
this.#assertLoaded()
|
|
1454
1718
|
let tableId = tableIdOrOptions
|
|
@@ -1476,6 +1740,22 @@ class PPTXTemplater {
|
|
|
1476
1740
|
return this
|
|
1477
1741
|
}
|
|
1478
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
|
+
*/
|
|
1479
1759
|
unmergeCells(tableIdOrOptions, startRow, startCol, endRow, endCol) {
|
|
1480
1760
|
this.#assertLoaded()
|
|
1481
1761
|
let tableId = tableIdOrOptions
|
|
@@ -1514,12 +1794,37 @@ class PPTXTemplater {
|
|
|
1514
1794
|
return this
|
|
1515
1795
|
}
|
|
1516
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
|
+
*/
|
|
1517
1807
|
getMergedCells(tableId) {
|
|
1518
1808
|
this.#assertLoaded()
|
|
1519
1809
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
1520
1810
|
return this.#tableManager.getMergedCells(slideIndex, tableId || 'first', this.#slideManager)
|
|
1521
1811
|
}
|
|
1522
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
|
+
*/
|
|
1523
1828
|
validateMergeRegion(tableId, startRow, startCol, endRow, endCol) {
|
|
1524
1829
|
this.#assertLoaded()
|
|
1525
1830
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
@@ -1534,6 +1839,19 @@ class PPTXTemplater {
|
|
|
1534
1839
|
)
|
|
1535
1840
|
}
|
|
1536
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
|
+
*/
|
|
1537
1855
|
isMergedCell(tableId, row, col) {
|
|
1538
1856
|
this.#assertLoaded()
|
|
1539
1857
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
@@ -1546,6 +1864,19 @@ class PPTXTemplater {
|
|
|
1546
1864
|
)
|
|
1547
1865
|
}
|
|
1548
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
|
+
*/
|
|
1549
1880
|
getMergeParent(tableId, row, col) {
|
|
1550
1881
|
this.#assertLoaded()
|
|
1551
1882
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
@@ -1558,6 +1889,18 @@ class PPTXTemplater {
|
|
|
1558
1889
|
)
|
|
1559
1890
|
}
|
|
1560
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
|
+
*/
|
|
1561
1904
|
getMergeRegion(tableId, row, col) {
|
|
1562
1905
|
this.#assertLoaded()
|
|
1563
1906
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
@@ -1570,6 +1913,17 @@ class PPTXTemplater {
|
|
|
1570
1913
|
)
|
|
1571
1914
|
}
|
|
1572
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
|
+
*/
|
|
1573
1927
|
splitMergedRegion(tableId, row, col) {
|
|
1574
1928
|
this.#assertLoaded()
|
|
1575
1929
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
@@ -1583,6 +1937,19 @@ class PPTXTemplater {
|
|
|
1583
1937
|
return this
|
|
1584
1938
|
}
|
|
1585
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
|
+
*/
|
|
1586
1953
|
cloneMergedRegion(tableId, row, col, targetRow, targetCol) {
|
|
1587
1954
|
this.#assertLoaded()
|
|
1588
1955
|
const slideIndex = this.#getTargetSlideIndices()[0] || 1
|
|
@@ -1598,6 +1965,15 @@ class PPTXTemplater {
|
|
|
1598
1965
|
return this
|
|
1599
1966
|
}
|
|
1600
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
|
+
*/
|
|
1601
1977
|
autoFitTable(tableId) {
|
|
1602
1978
|
this.#assertLoaded()
|
|
1603
1979
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1607,6 +1983,18 @@ class PPTXTemplater {
|
|
|
1607
1983
|
return this
|
|
1608
1984
|
}
|
|
1609
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
|
+
*/
|
|
1610
1998
|
resizeTable(tableId, width, height) {
|
|
1611
1999
|
this.#assertLoaded()
|
|
1612
2000
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1616,6 +2004,15 @@ class PPTXTemplater {
|
|
|
1616
2004
|
return this
|
|
1617
2005
|
}
|
|
1618
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
|
+
*/
|
|
1619
2016
|
getTables() {
|
|
1620
2017
|
this.#assertLoaded()
|
|
1621
2018
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1627,10 +2024,34 @@ class PPTXTemplater {
|
|
|
1627
2024
|
}
|
|
1628
2025
|
|
|
1629
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
|
+
*/
|
|
1630
2040
|
updateChartData(chartId, data) {
|
|
1631
2041
|
return this.updateChart(chartId, data)
|
|
1632
2042
|
}
|
|
1633
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
|
+
*/
|
|
1634
2055
|
replaceChartSeries(chartId, seriesIndex, newSeriesData) {
|
|
1635
2056
|
this.#assertLoaded()
|
|
1636
2057
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1647,6 +2068,16 @@ class PPTXTemplater {
|
|
|
1647
2068
|
return this
|
|
1648
2069
|
}
|
|
1649
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
|
+
*/
|
|
1650
2081
|
updateChartTitle(chartId, title) {
|
|
1651
2082
|
this.#assertLoaded()
|
|
1652
2083
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1662,6 +2093,16 @@ class PPTXTemplater {
|
|
|
1662
2093
|
return this
|
|
1663
2094
|
}
|
|
1664
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
|
+
*/
|
|
1665
2106
|
updateChartCategories(chartId, categories) {
|
|
1666
2107
|
this.#assertLoaded()
|
|
1667
2108
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1677,6 +2118,26 @@ class PPTXTemplater {
|
|
|
1677
2118
|
return this
|
|
1678
2119
|
}
|
|
1679
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
|
+
*/
|
|
1680
2141
|
updateDataLabels(chartId, options) {
|
|
1681
2142
|
this.#assertLoaded()
|
|
1682
2143
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1692,6 +2153,18 @@ class PPTXTemplater {
|
|
|
1692
2153
|
return this
|
|
1693
2154
|
}
|
|
1694
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
|
+
*/
|
|
1695
2168
|
async getDataLabels(chartId, options = {}) {
|
|
1696
2169
|
this.#assertLoaded()
|
|
1697
2170
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1706,6 +2179,17 @@ class PPTXTemplater {
|
|
|
1706
2179
|
)
|
|
1707
2180
|
}
|
|
1708
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
|
+
*/
|
|
1709
2193
|
async validateDataLabels(chartId, options = {}) {
|
|
1710
2194
|
this.#assertLoaded()
|
|
1711
2195
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1714,6 +2198,16 @@ class PPTXTemplater {
|
|
|
1714
2198
|
return ValidationEngine.validateDataLabels(this, idx, chartId, options)
|
|
1715
2199
|
}
|
|
1716
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
|
+
*/
|
|
1717
2211
|
async validateChartLabels(chartId, options = {}) {
|
|
1718
2212
|
this.#assertLoaded()
|
|
1719
2213
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1728,6 +2222,16 @@ class PPTXTemplater {
|
|
|
1728
2222
|
)
|
|
1729
2223
|
}
|
|
1730
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
|
+
*/
|
|
1731
2235
|
async validateSeriesNameLabels(chartId, options = {}) {
|
|
1732
2236
|
this.#assertLoaded()
|
|
1733
2237
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1742,6 +2246,15 @@ class PPTXTemplater {
|
|
|
1742
2246
|
)
|
|
1743
2247
|
}
|
|
1744
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
|
+
*/
|
|
1745
2258
|
getCharts() {
|
|
1746
2259
|
this.#assertLoaded()
|
|
1747
2260
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1851,6 +2364,21 @@ class PPTXTemplater {
|
|
|
1851
2364
|
* @param {string|Object} data - String value or list configuration object.
|
|
1852
2365
|
* @returns {PPTXTemplater} this (chainable)
|
|
1853
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
|
+
*/
|
|
1854
2382
|
updateText(tag, data) {
|
|
1855
2383
|
this.#assertLoaded()
|
|
1856
2384
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1866,6 +2394,16 @@ class PPTXTemplater {
|
|
|
1866
2394
|
* @param {string} tag - Shape name/ID or placeholder tag.
|
|
1867
2395
|
* @returns {Array} Nested list structure of items.
|
|
1868
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
|
+
*/
|
|
1869
2407
|
getList(tag) {
|
|
1870
2408
|
this.#assertLoaded()
|
|
1871
2409
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1884,6 +2422,20 @@ class PPTXTemplater {
|
|
|
1884
2422
|
}
|
|
1885
2423
|
|
|
1886
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
|
+
*/
|
|
1887
2439
|
replaceTextByTag(tag, value, options = {}) {
|
|
1888
2440
|
this.#assertLoaded()
|
|
1889
2441
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1900,6 +2452,21 @@ class PPTXTemplater {
|
|
|
1900
2452
|
return this
|
|
1901
2453
|
}
|
|
1902
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
|
+
*/
|
|
1903
2470
|
replaceMultiple(replacements, options = {}) {
|
|
1904
2471
|
this.#assertLoaded()
|
|
1905
2472
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1915,6 +2482,16 @@ class PPTXTemplater {
|
|
|
1915
2482
|
return this
|
|
1916
2483
|
}
|
|
1917
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
|
+
*/
|
|
1918
2495
|
findText(text) {
|
|
1919
2496
|
this.#assertLoaded()
|
|
1920
2497
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1925,6 +2502,15 @@ class PPTXTemplater {
|
|
|
1925
2502
|
return matches
|
|
1926
2503
|
}
|
|
1927
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
|
+
*/
|
|
1928
2514
|
getTextElements() {
|
|
1929
2515
|
this.#assertLoaded()
|
|
1930
2516
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1936,6 +2522,16 @@ class PPTXTemplater {
|
|
|
1936
2522
|
}
|
|
1937
2523
|
|
|
1938
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
|
+
*/
|
|
1939
2535
|
updateShapeText(shapeId, text) {
|
|
1940
2536
|
this.#assertLoaded()
|
|
1941
2537
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1960,7 +2556,11 @@ class PPTXTemplater {
|
|
|
1960
2556
|
this.#assertLoaded()
|
|
1961
2557
|
const targetIndices = this.#getTargetSlideIndices()
|
|
1962
2558
|
for (const idx of targetIndices) {
|
|
1963
|
-
|
|
2559
|
+
let resolvedOptions = options
|
|
2560
|
+
if (options.alignToCell) {
|
|
2561
|
+
resolvedOptions = this.#resolveAlignToCell(idx, options, shapeId, true)
|
|
2562
|
+
}
|
|
2563
|
+
this.#shapeManager.updateShapePosition(idx, shapeId, resolvedOptions, this.#slideManager)
|
|
1964
2564
|
}
|
|
1965
2565
|
return this
|
|
1966
2566
|
}
|
|
@@ -1985,6 +2585,19 @@ class PPTXTemplater {
|
|
|
1985
2585
|
return this
|
|
1986
2586
|
}
|
|
1987
2587
|
|
|
2588
|
+
/**
|
|
2589
|
+
* Duplicates an existing shape with a new ID, optionally at a different position.
|
|
2590
|
+
*
|
|
2591
|
+
* @param {string} shapeId - Source shape name or ID.
|
|
2592
|
+
* @param {string} newShapeId - Name/ID for the cloned shape.
|
|
2593
|
+
* @param {Object} [options] - Position overrides for the clone.
|
|
2594
|
+
* @param {number} [options.x] - X offset for the clone (EMUs).
|
|
2595
|
+
* @param {number} [options.y] - Y offset for the clone (EMUs).
|
|
2596
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
2597
|
+
*
|
|
2598
|
+
* @example
|
|
2599
|
+
* ppt.cloneShape('logo', 'logo-copy', { x: 2000000, y: 500000 });
|
|
2600
|
+
*/
|
|
1988
2601
|
cloneShape(shapeId, newShapeId, options = {}) {
|
|
1989
2602
|
this.#assertLoaded()
|
|
1990
2603
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -1994,6 +2607,15 @@ class PPTXTemplater {
|
|
|
1994
2607
|
return this
|
|
1995
2608
|
}
|
|
1996
2609
|
|
|
2610
|
+
/**
|
|
2611
|
+
* Removes a shape from the targeted slide(s). Alias for `removeShape()`.
|
|
2612
|
+
*
|
|
2613
|
+
* @param {string} shapeId - Shape name or ID to delete.
|
|
2614
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
2615
|
+
*
|
|
2616
|
+
* @example
|
|
2617
|
+
* ppt.deleteShape('temp-banner');
|
|
2618
|
+
*/
|
|
1997
2619
|
deleteShape(shapeId) {
|
|
1998
2620
|
this.#assertLoaded()
|
|
1999
2621
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -2003,6 +2625,15 @@ class PPTXTemplater {
|
|
|
2003
2625
|
return this
|
|
2004
2626
|
}
|
|
2005
2627
|
|
|
2628
|
+
/**
|
|
2629
|
+
* Returns metadata for all shapes on the targeted slide(s).
|
|
2630
|
+
*
|
|
2631
|
+
* @returns {Array<{id: string, name: string, type: string, x: number, y: number, width: number, height: number}>}
|
|
2632
|
+
*
|
|
2633
|
+
* @example
|
|
2634
|
+
* const shapes = ppt.getShapes();
|
|
2635
|
+
* shapes.forEach(s => console.log(`${s.name}: ${s.type} at (${s.x}, ${s.y})`));
|
|
2636
|
+
*/
|
|
2006
2637
|
getShapes() {
|
|
2007
2638
|
this.#assertLoaded()
|
|
2008
2639
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -2023,17 +2654,22 @@ class PPTXTemplater {
|
|
|
2023
2654
|
return this.#shapeManager.validateShape(options)
|
|
2024
2655
|
}
|
|
2025
2656
|
|
|
2026
|
-
|
|
2027
|
-
* Adds a new shape dynamically to the targeted slide(s).
|
|
2028
|
-
*
|
|
2029
|
-
* @param {Object} options Shape configuration options.
|
|
2030
|
-
* @returns {this} The chainable presentation templater instance.
|
|
2031
|
-
*/
|
|
2032
|
-
async addShape(options) {
|
|
2657
|
+
async addShape(typeOrOptions, options = {}) {
|
|
2033
2658
|
this.#assertLoaded()
|
|
2659
|
+
let resolvedOptions = {}
|
|
2660
|
+
if (typeof typeOrOptions === 'string') {
|
|
2661
|
+
resolvedOptions = { ...options, type: typeOrOptions }
|
|
2662
|
+
} else {
|
|
2663
|
+
resolvedOptions = { ...typeOrOptions }
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2034
2666
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2035
2667
|
for (const idx of targetIndices) {
|
|
2036
|
-
|
|
2668
|
+
let finalOptions = resolvedOptions
|
|
2669
|
+
if (resolvedOptions.alignToCell) {
|
|
2670
|
+
finalOptions = this.#resolveAlignToCell(idx, resolvedOptions)
|
|
2671
|
+
}
|
|
2672
|
+
this.#shapeManager.addShape(idx, finalOptions, this.#slideManager)
|
|
2037
2673
|
}
|
|
2038
2674
|
return this
|
|
2039
2675
|
}
|
|
@@ -2049,7 +2685,11 @@ class PPTXTemplater {
|
|
|
2049
2685
|
this.#assertLoaded()
|
|
2050
2686
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2051
2687
|
for (const idx of targetIndices) {
|
|
2052
|
-
|
|
2688
|
+
let resolvedOptions = options
|
|
2689
|
+
if (options.alignToCell) {
|
|
2690
|
+
resolvedOptions = this.#resolveAlignToCell(idx, options, shapeId)
|
|
2691
|
+
}
|
|
2692
|
+
this.#shapeManager.updateShape(idx, shapeId, resolvedOptions, this.#slideManager)
|
|
2053
2693
|
}
|
|
2054
2694
|
return this
|
|
2055
2695
|
}
|
|
@@ -2195,13 +2835,17 @@ class PPTXTemplater {
|
|
|
2195
2835
|
/**
|
|
2196
2836
|
* Retrieves final rendered bounds of a table cell in pixels.
|
|
2197
2837
|
*
|
|
2198
|
-
* @param {string}
|
|
2838
|
+
* @param {string|Object} tableIdOrObj - Table name, shape ID, or table object.
|
|
2199
2839
|
* @param {number} rowIndex - 0-based row index.
|
|
2200
2840
|
* @param {number} colIndex - 0-based column index.
|
|
2201
2841
|
* @returns {Object|null} Cell bounds { x, y, width, height } in pixels, or null.
|
|
2202
2842
|
*/
|
|
2203
|
-
getCellBounds(
|
|
2843
|
+
getCellBounds(tableIdOrObj, rowIndex, colIndex) {
|
|
2204
2844
|
this.#assertLoaded()
|
|
2845
|
+
const tableId =
|
|
2846
|
+
typeof tableIdOrObj === 'object'
|
|
2847
|
+
? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
|
|
2848
|
+
: tableIdOrObj
|
|
2205
2849
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2206
2850
|
for (const idx of targetIndices) {
|
|
2207
2851
|
try {
|
|
@@ -2224,14 +2868,21 @@ class PPTXTemplater {
|
|
|
2224
2868
|
|
|
2225
2869
|
/**
|
|
2226
2870
|
* Retrieves final rendered position of a table cell in pixels.
|
|
2871
|
+
* Optionally calculates centered top-left coordinates for a shape of given dimensions.
|
|
2227
2872
|
*
|
|
2228
|
-
* @param {string}
|
|
2873
|
+
* @param {string|Object} tableIdOrObj - Table name, shape ID, or table object.
|
|
2229
2874
|
* @param {number} rowIndex - 0-based row index.
|
|
2230
2875
|
* @param {number} colIndex - 0-based column index.
|
|
2231
|
-
* @
|
|
2876
|
+
* @param {number|Object} [shapeWidthOrOptions] - Width of the shape in pixels, or options object.
|
|
2877
|
+
* @param {number} [shapeHeight] - Height of the shape in pixels.
|
|
2878
|
+
* @returns {Object|null} Cell position { row, column, x, y, width, height } in pixels, or null.
|
|
2232
2879
|
*/
|
|
2233
|
-
getCellPosition(
|
|
2880
|
+
getCellPosition(tableIdOrObj, rowIndex, colIndex, shapeWidthOrOptions, shapeHeight) {
|
|
2234
2881
|
this.#assertLoaded()
|
|
2882
|
+
const tableId =
|
|
2883
|
+
typeof tableIdOrObj === 'object'
|
|
2884
|
+
? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
|
|
2885
|
+
: tableIdOrObj
|
|
2235
2886
|
const targetIndices = this.#getTargetSlideIndices()
|
|
2236
2887
|
for (const idx of targetIndices) {
|
|
2237
2888
|
try {
|
|
@@ -2240,7 +2891,9 @@ class PPTXTemplater {
|
|
|
2240
2891
|
tableId,
|
|
2241
2892
|
rowIndex,
|
|
2242
2893
|
colIndex,
|
|
2243
|
-
this.#slideManager
|
|
2894
|
+
this.#slideManager,
|
|
2895
|
+
shapeWidthOrOptions,
|
|
2896
|
+
shapeHeight
|
|
2244
2897
|
)
|
|
2245
2898
|
if (pos) return pos
|
|
2246
2899
|
} catch (err) {
|
|
@@ -2253,6 +2906,16 @@ class PPTXTemplater {
|
|
|
2253
2906
|
}
|
|
2254
2907
|
|
|
2255
2908
|
// === Image Features ===
|
|
2909
|
+
/**
|
|
2910
|
+
* Replaces an existing image in the presentation by shape name or relationship ID.
|
|
2911
|
+
*
|
|
2912
|
+
* @param {string} imageIdOrName - Shape name, alt text, or relationship ID of the image.
|
|
2913
|
+
* @param {string|Buffer} sourcePathOrBuffer - Path to the replacement image file, or a Buffer.
|
|
2914
|
+
* @returns {Promise<PPTXTemplater>} this (chainable)
|
|
2915
|
+
*
|
|
2916
|
+
* @example
|
|
2917
|
+
* await ppt.replaceImage('company-logo', './new-logo.png');
|
|
2918
|
+
*/
|
|
2256
2919
|
async replaceImage(imageIdOrName, sourcePathOrBuffer) {
|
|
2257
2920
|
this.#assertLoaded()
|
|
2258
2921
|
const t0 = performance.now()
|
|
@@ -2271,6 +2934,30 @@ class PPTXTemplater {
|
|
|
2271
2934
|
return this
|
|
2272
2935
|
}
|
|
2273
2936
|
|
|
2937
|
+
/**
|
|
2938
|
+
* Adds a new image to the targeted slide(s) at the specified position.
|
|
2939
|
+
*
|
|
2940
|
+
* @param {string|Buffer} sourcePathOrBuffer - Path to the image file, or a Buffer.
|
|
2941
|
+
* @param {Object} [options] - Positioning and display options.
|
|
2942
|
+
* @param {number} [options.x] - X offset in EMUs (1 inch = 914,400 EMUs).
|
|
2943
|
+
* @param {number} [options.y] - Y offset in EMUs.
|
|
2944
|
+
* @param {number} [options.width] - Width in EMUs.
|
|
2945
|
+
* @param {number} [options.height] - Height in EMUs.
|
|
2946
|
+
* @param {number} [options.rotation] - Rotation in degrees (0–360).
|
|
2947
|
+
* @param {number} [options.opacity] - Opacity (0–100).
|
|
2948
|
+
* @param {string} [options.name] - Shape name for the image.
|
|
2949
|
+
* @param {Object} [options.cropTo] - Crop percentages `{ l, r, t, b }` (0–100000).
|
|
2950
|
+
* @returns {Promise<PPTXTemplater>} this (chainable)
|
|
2951
|
+
*
|
|
2952
|
+
* @example
|
|
2953
|
+
* await ppt.addImage('./photo.jpg', {
|
|
2954
|
+
* x: 914400, // 1 inch
|
|
2955
|
+
* y: 914400, // 1 inch
|
|
2956
|
+
* width: 3657600, // 4 inches
|
|
2957
|
+
* height: 2743200, // 3 inches
|
|
2958
|
+
* name: 'hero-image'
|
|
2959
|
+
* });
|
|
2960
|
+
*/
|
|
2274
2961
|
async addImage(sourcePathOrBuffer, options = {}) {
|
|
2275
2962
|
this.#assertLoaded()
|
|
2276
2963
|
const t0 = performance.now()
|
|
@@ -2289,6 +2976,15 @@ class PPTXTemplater {
|
|
|
2289
2976
|
return this
|
|
2290
2977
|
}
|
|
2291
2978
|
|
|
2979
|
+
/**
|
|
2980
|
+
* Removes an image from the targeted slide(s) by shape name or relationship ID.
|
|
2981
|
+
*
|
|
2982
|
+
* @param {string} imageIdOrName - Shape name, alt text, or relationship ID of the image.
|
|
2983
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
2984
|
+
*
|
|
2985
|
+
* @example
|
|
2986
|
+
* ppt.removeImage('old-logo');
|
|
2987
|
+
*/
|
|
2292
2988
|
removeImage(imageIdOrName) {
|
|
2293
2989
|
this.#assertLoaded()
|
|
2294
2990
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -2303,6 +2999,15 @@ class PPTXTemplater {
|
|
|
2303
2999
|
return this
|
|
2304
3000
|
}
|
|
2305
3001
|
|
|
3002
|
+
/**
|
|
3003
|
+
* Returns metadata for all images found on the targeted slide(s).
|
|
3004
|
+
*
|
|
3005
|
+
* @returns {Array<{rId: string, name: string, x: number, y: number, width: number, height: number, mediaPath: string}>}
|
|
3006
|
+
*
|
|
3007
|
+
* @example
|
|
3008
|
+
* const images = ppt.getImages();
|
|
3009
|
+
* images.forEach(img => console.log(`${img.name}: ${img.width}×${img.height} EMUs`));
|
|
3010
|
+
*/
|
|
2306
3011
|
getImages() {
|
|
2307
3012
|
this.#assertLoaded()
|
|
2308
3013
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -2315,7 +3020,19 @@ class PPTXTemplater {
|
|
|
2315
3020
|
return images
|
|
2316
3021
|
}
|
|
2317
3022
|
|
|
2318
|
-
|
|
3023
|
+
/**
|
|
3024
|
+
* Performs a comprehensive validation of the entire PPTX structure.
|
|
3025
|
+
* Checks slide XML, relationships, content types, slide masters, and layouts.
|
|
3026
|
+
*
|
|
3027
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>} Validation report.
|
|
3028
|
+
*
|
|
3029
|
+
* @example
|
|
3030
|
+
* const result = await ppt.validatePresentation();
|
|
3031
|
+
* if (!result.valid) {
|
|
3032
|
+
* console.error('Errors:', result.errors);
|
|
3033
|
+
* console.warn('Warnings:', result.warnings);
|
|
3034
|
+
* }
|
|
3035
|
+
*/
|
|
2319
3036
|
async validatePresentation() {
|
|
2320
3037
|
this.#assertLoaded()
|
|
2321
3038
|
return await ValidationEngine.validatePresentation(this)
|
|
@@ -2409,11 +3126,31 @@ class PPTXTemplater {
|
|
|
2409
3126
|
}
|
|
2410
3127
|
}
|
|
2411
3128
|
|
|
3129
|
+
/**
|
|
3130
|
+
* Validates the XML structure of a specific slide.
|
|
3131
|
+
*
|
|
3132
|
+
* @param {number} slideIndex - 1-based slide index to validate.
|
|
3133
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
|
|
3134
|
+
*
|
|
3135
|
+
* @example
|
|
3136
|
+
* const result = await ppt.validateSlide(1);
|
|
3137
|
+
* if (!result.valid) console.error(result.errors);
|
|
3138
|
+
*/
|
|
2412
3139
|
async validateSlide(slideIndex) {
|
|
2413
3140
|
this.#assertLoaded()
|
|
2414
3141
|
return await ValidationEngine.validateSlide(this, slideIndex)
|
|
2415
3142
|
}
|
|
2416
3143
|
|
|
3144
|
+
/**
|
|
3145
|
+
* Validates the XML structure of a specific table on the active slide.
|
|
3146
|
+
*
|
|
3147
|
+
* @param {string} tableId - Table name or shape ID.
|
|
3148
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
|
|
3149
|
+
*
|
|
3150
|
+
* @example
|
|
3151
|
+
* const result = await ppt.validateTable('SalesTable');
|
|
3152
|
+
* if (!result.valid) console.error(result.errors);
|
|
3153
|
+
*/
|
|
2417
3154
|
async validateTable(tableId) {
|
|
2418
3155
|
this.#assertLoaded()
|
|
2419
3156
|
return await ValidationEngine.validateTable(
|
|
@@ -2423,17 +3160,46 @@ class PPTXTemplater {
|
|
|
2423
3160
|
)
|
|
2424
3161
|
}
|
|
2425
3162
|
|
|
3163
|
+
/**
|
|
3164
|
+
* Validates the internal ZIP archive structure of the PPTX file.
|
|
3165
|
+
* Checks that all files referenced in the archive are accessible and uncorrupted.
|
|
3166
|
+
* Throws if critical structural issues are found.
|
|
3167
|
+
*
|
|
3168
|
+
* @returns {Promise<PPTXTemplater>} this (chainable)
|
|
3169
|
+
*
|
|
3170
|
+
* @example
|
|
3171
|
+
* await ppt.validateArchive(); // Throws PPTXError if the ZIP is corrupt
|
|
3172
|
+
*/
|
|
2426
3173
|
async validateArchive() {
|
|
2427
3174
|
this.#assertLoaded()
|
|
2428
3175
|
await this.#zipManager.validateArchive()
|
|
2429
3176
|
return this
|
|
2430
3177
|
}
|
|
2431
3178
|
|
|
3179
|
+
/**
|
|
3180
|
+
* Enables ZIP debug output. When enabled, every call to `toBuffer()` or `toStream()`
|
|
3181
|
+
* will log all ZIP entries (name, compression method, sizes, CRC).
|
|
3182
|
+
*
|
|
3183
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
3184
|
+
*
|
|
3185
|
+
* @example
|
|
3186
|
+
* ppt.enableDebugZip();
|
|
3187
|
+
* const buffer = await ppt.toBuffer(); // Logs ZIP entries to debug output
|
|
3188
|
+
*/
|
|
2432
3189
|
enableDebugZip() {
|
|
2433
3190
|
this.#outputWriter.debugZip = true
|
|
2434
3191
|
return this
|
|
2435
3192
|
}
|
|
2436
3193
|
|
|
3194
|
+
/**
|
|
3195
|
+
* Validates relationships for a specific part path inside the ZIP.
|
|
3196
|
+
*
|
|
3197
|
+
* @param {string} partPath - ZIP path to validate (e.g. `'ppt/slides/slide1.xml'`).
|
|
3198
|
+
* @returns {Object} Validation result with `valid`, `errors`, and `warnings`.
|
|
3199
|
+
*
|
|
3200
|
+
* @example
|
|
3201
|
+
* const result = ppt.validateRelationships('ppt/slides/slide1.xml');
|
|
3202
|
+
*/
|
|
2437
3203
|
validateRelationships(partPath) {
|
|
2438
3204
|
this.#assertLoaded()
|
|
2439
3205
|
return ValidationEngine.validateRelationships(this, partPath)
|
|
@@ -2658,6 +3424,17 @@ class PPTXTemplater {
|
|
|
2658
3424
|
/**
|
|
2659
3425
|
* Gets the ordered metadata of all objects on the slide.
|
|
2660
3426
|
*/
|
|
3427
|
+
/**
|
|
3428
|
+
* Returns an ordered array of all slide objects (shapes, images, charts, tables)
|
|
3429
|
+
* from bottom to top of the stacking order.
|
|
3430
|
+
*
|
|
3431
|
+
* @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
|
|
3432
|
+
* @returns {Array<{id: string, name: string, type: string, zIndex: number}>}
|
|
3433
|
+
*
|
|
3434
|
+
* @example
|
|
3435
|
+
* const order = ppt.getObjectOrder(1);
|
|
3436
|
+
* order.forEach(o => console.log(`[${o.zIndex}] ${o.name}`));
|
|
3437
|
+
*/
|
|
2661
3438
|
getObjectOrder(slideIndex) {
|
|
2662
3439
|
this.#assertLoaded()
|
|
2663
3440
|
const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
|
|
@@ -2684,6 +3461,16 @@ class PPTXTemplater {
|
|
|
2684
3461
|
/**
|
|
2685
3462
|
* Retrieves the info of the top-most object on the slide.
|
|
2686
3463
|
*/
|
|
3464
|
+
/**
|
|
3465
|
+
* Returns the top-most (front) object on the slide.
|
|
3466
|
+
*
|
|
3467
|
+
* @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
|
|
3468
|
+
* @returns {{id: string, name: string, type: string, zIndex: number}|null}
|
|
3469
|
+
*
|
|
3470
|
+
* @example
|
|
3471
|
+
* const top = ppt.getTopMostObject(1);
|
|
3472
|
+
* console.log('Front-most shape:', top.name);
|
|
3473
|
+
*/
|
|
2687
3474
|
getTopMostObject(slideIndex) {
|
|
2688
3475
|
this.#assertLoaded()
|
|
2689
3476
|
const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
|
|
@@ -2693,6 +3480,16 @@ class PPTXTemplater {
|
|
|
2693
3480
|
/**
|
|
2694
3481
|
* Retrieves the info of the bottom-most object on the slide.
|
|
2695
3482
|
*/
|
|
3483
|
+
/**
|
|
3484
|
+
* Returns the bottom-most (back) object on the slide.
|
|
3485
|
+
*
|
|
3486
|
+
* @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
|
|
3487
|
+
* @returns {{id: string, name: string, type: string, zIndex: number}|null}
|
|
3488
|
+
*
|
|
3489
|
+
* @example
|
|
3490
|
+
* const bottom = ppt.getBottomMostObject(1);
|
|
3491
|
+
* console.log('Back-most shape:', bottom.name);
|
|
3492
|
+
*/
|
|
2696
3493
|
getBottomMostObject(slideIndex) {
|
|
2697
3494
|
this.#assertLoaded()
|
|
2698
3495
|
const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
|
|
@@ -2702,6 +3499,18 @@ class PPTXTemplater {
|
|
|
2702
3499
|
/**
|
|
2703
3500
|
* Swaps stacking positions of two slide objects.
|
|
2704
3501
|
*/
|
|
3502
|
+
/**
|
|
3503
|
+
* Swaps the stacking positions of two slide objects.
|
|
3504
|
+
*
|
|
3505
|
+
* @param {number|string} slideIndexOrId1 - Slide index (if 3 args) or first object ID (if 2 args).
|
|
3506
|
+
* @param {string} id1OrId2 - First object ID (if 3 args) or second object ID (if 2 args).
|
|
3507
|
+
* @param {string} [id2] - Second object ID (only if slide index is provided as first arg).
|
|
3508
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
3509
|
+
*
|
|
3510
|
+
* @example
|
|
3511
|
+
* ppt.swapObjects(1, 'logo', 'background'); // On slide 1, swap 'logo' and 'background'
|
|
3512
|
+
* ppt.swapObjects('logo', 'background'); // On active slide
|
|
3513
|
+
*/
|
|
2705
3514
|
swapObjects(slideIndexOrId1, id1OrId2, id2) {
|
|
2706
3515
|
this.#assertLoaded()
|
|
2707
3516
|
let slideIndex, objectId1, objectId2
|
|
@@ -2721,6 +3530,17 @@ class PPTXTemplater {
|
|
|
2721
3530
|
/**
|
|
2722
3531
|
* Sorts stacking order using a custom comparison function.
|
|
2723
3532
|
*/
|
|
3533
|
+
/**
|
|
3534
|
+
* Sorts all slide objects using a custom comparison function.
|
|
3535
|
+
*
|
|
3536
|
+
* @param {number|Function} slideIndexOrCompareFn - Slide index (if 2 args) or compare function (if 1 arg).
|
|
3537
|
+
* @param {Function} [compareFnOption] - Compare function when slide index is provided.
|
|
3538
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
3539
|
+
*
|
|
3540
|
+
* @example
|
|
3541
|
+
* // Sort alphabetically by name on the active slide
|
|
3542
|
+
* ppt.sortObjects((a, b) => a.name.localeCompare(b.name));
|
|
3543
|
+
*/
|
|
2724
3544
|
sortObjects(slideIndexOrCompareFn, compareFnOption) {
|
|
2725
3545
|
this.#assertLoaded()
|
|
2726
3546
|
let slideIndex, compareFn
|
|
@@ -2738,12 +3558,142 @@ class PPTXTemplater {
|
|
|
2738
3558
|
/**
|
|
2739
3559
|
* Cleans up and normalizes stacking order consistency.
|
|
2740
3560
|
*/
|
|
3561
|
+
/**
|
|
3562
|
+
* Normalizes the stacking order of all objects on a slide, removing gaps
|
|
3563
|
+
* and ensuring Z-index values are sequential (1, 2, 3, ...).
|
|
3564
|
+
*
|
|
3565
|
+
* @param {number} [slideIndex] - 1-based slide index. Defaults to the active slide.
|
|
3566
|
+
* @returns {PPTXTemplater} this (chainable)
|
|
3567
|
+
*
|
|
3568
|
+
* @example
|
|
3569
|
+
* ppt.normalizeZOrder(1); // Clean up stacking order on slide 1
|
|
3570
|
+
*/
|
|
2741
3571
|
normalizeZOrder(slideIndex) {
|
|
2742
3572
|
this.#assertLoaded()
|
|
2743
3573
|
const targetIdx = slideIndex !== undefined ? slideIndex : this.#getTargetSlideIndices()[0] || 1
|
|
2744
3574
|
this.#zOrderManager.normalizeZOrder(targetIdx, this.#slideManager)
|
|
2745
3575
|
return this
|
|
2746
3576
|
}
|
|
3577
|
+
|
|
3578
|
+
/**
|
|
3579
|
+
* Aligns an existing shape to a table cell's position.
|
|
3580
|
+
*
|
|
3581
|
+
* @param {string} shapeId - Unique shape name/id in the template.
|
|
3582
|
+
* @param {string|Object} tableIdOrObj - Table ID string, or table object.
|
|
3583
|
+
* @param {number} rowIndex - 0-based row index.
|
|
3584
|
+
* @param {number} colIndex - 0-based column index.
|
|
3585
|
+
* @param {Object} [options] - Alignment options.
|
|
3586
|
+
* @param {'left'|'center'|'right'} [options.horizontal='center'] - Horizontal alignment.
|
|
3587
|
+
* @param {'top'|'middle'|'bottom'} [options.vertical='middle'] - Vertical alignment.
|
|
3588
|
+
* @returns {this} The chainable presentation templater instance.
|
|
3589
|
+
*/
|
|
3590
|
+
alignShapeToCell(shapeId, tableIdOrObj, rowIndex, colIndex, options = {}) {
|
|
3591
|
+
const tableId =
|
|
3592
|
+
typeof tableIdOrObj === 'object'
|
|
3593
|
+
? tableIdOrObj.id || tableIdOrObj.name || tableIdOrObj.tableId
|
|
3594
|
+
: tableIdOrObj
|
|
3595
|
+
this.updateShapePosition(shapeId, {
|
|
3596
|
+
alignToCell: {
|
|
3597
|
+
table: tableId,
|
|
3598
|
+
row: rowIndex,
|
|
3599
|
+
col: colIndex,
|
|
3600
|
+
horizontal: options.horizontal || 'center',
|
|
3601
|
+
vertical: options.vertical || 'middle',
|
|
3602
|
+
},
|
|
3603
|
+
})
|
|
3604
|
+
return this
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
#resolveAlignToCell(slideIndex, options, shapeId, convertToEmus = false) {
|
|
3608
|
+
const align = options.alignToCell
|
|
3609
|
+
if (!align || !align.table) return options
|
|
3610
|
+
|
|
3611
|
+
const tableId =
|
|
3612
|
+
typeof align.table === 'object'
|
|
3613
|
+
? align.table.id || align.table.name || align.table.tableId
|
|
3614
|
+
: align.table
|
|
3615
|
+
const row = align.row !== undefined ? align.row : 0
|
|
3616
|
+
const col = align.col !== undefined ? align.col : 0
|
|
3617
|
+
|
|
3618
|
+
// Get cell bounds
|
|
3619
|
+
const bounds = this.#tableManager.getCellBounds(
|
|
3620
|
+
slideIndex,
|
|
3621
|
+
tableId,
|
|
3622
|
+
row,
|
|
3623
|
+
col,
|
|
3624
|
+
this.#slideManager
|
|
3625
|
+
)
|
|
3626
|
+
|
|
3627
|
+
if (!bounds) return options
|
|
3628
|
+
|
|
3629
|
+
// Determine shape dimensions
|
|
3630
|
+
let shapeWidth = options.width
|
|
3631
|
+
let shapeHeight = options.height
|
|
3632
|
+
|
|
3633
|
+
if (convertToEmus) {
|
|
3634
|
+
if (shapeWidth !== undefined) shapeWidth = Math.round(shapeWidth / 9525)
|
|
3635
|
+
if (shapeHeight !== undefined) shapeHeight = Math.round(shapeHeight / 9525)
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
if (shapeWidth === undefined || shapeHeight === undefined) {
|
|
3639
|
+
if (options.type === 'square' && options.size !== undefined) {
|
|
3640
|
+
shapeWidth = options.size
|
|
3641
|
+
shapeHeight = options.size
|
|
3642
|
+
} else if (options.type === 'circle' && options.radius !== undefined) {
|
|
3643
|
+
shapeWidth = options.radius * 2
|
|
3644
|
+
shapeHeight = options.radius * 2
|
|
3645
|
+
} else if (shapeId) {
|
|
3646
|
+
// Try getting existing shape dimensions
|
|
3647
|
+
const existing = this.#shapeManager.getShape(slideIndex, shapeId, this.#slideManager)
|
|
3648
|
+
if (existing) {
|
|
3649
|
+
shapeWidth = existing.width
|
|
3650
|
+
shapeHeight = existing.height
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
// Default to fallback dimensions if still undefined
|
|
3656
|
+
if (shapeWidth === undefined) shapeWidth = 100
|
|
3657
|
+
if (shapeHeight === undefined) shapeHeight = 100
|
|
3658
|
+
|
|
3659
|
+
// Align horizontally
|
|
3660
|
+
let horiz = align.horizontal || align.alignX || 'center'
|
|
3661
|
+
horiz = String(horiz).toLowerCase()
|
|
3662
|
+
if (horiz === 'middle') horiz = 'center'
|
|
3663
|
+
|
|
3664
|
+
let x = bounds.x
|
|
3665
|
+
if (horiz === 'center') {
|
|
3666
|
+
x = bounds.x + (bounds.width - shapeWidth) / 2
|
|
3667
|
+
} else if (horiz === 'right') {
|
|
3668
|
+
x = bounds.x + bounds.width - shapeWidth
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
// Align vertically
|
|
3672
|
+
let vert = align.vertical || align.alignY || 'middle'
|
|
3673
|
+
vert = String(vert).toLowerCase()
|
|
3674
|
+
if (vert === 'center') vert = 'middle'
|
|
3675
|
+
|
|
3676
|
+
let y = bounds.y
|
|
3677
|
+
if (vert === 'middle') {
|
|
3678
|
+
y = bounds.y + (bounds.height - shapeHeight) / 2
|
|
3679
|
+
} else if (vert === 'bottom') {
|
|
3680
|
+
y = bounds.y + bounds.height - shapeHeight
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
const resolved = { ...options }
|
|
3684
|
+
if (convertToEmus) {
|
|
3685
|
+
resolved.x = Math.round(x * 9525)
|
|
3686
|
+
resolved.y = Math.round(y * 9525)
|
|
3687
|
+
} else {
|
|
3688
|
+
resolved.x = Math.round(x)
|
|
3689
|
+
resolved.y = Math.round(y)
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
// Remove alignToCell to prevent it polluting lower levels
|
|
3693
|
+
delete resolved.alignToCell
|
|
3694
|
+
|
|
3695
|
+
return resolved
|
|
3696
|
+
}
|
|
2747
3697
|
}
|
|
2748
3698
|
|
|
2749
3699
|
module.exports = { PPTXTemplater }
|