node-pptx-templater 1.0.3 → 1.0.5
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 +61 -3
- package/README.md +200 -300
- package/package.json +12 -3
- package/src/core/PPTXTemplater.js +270 -0
- package/src/core/ValidationEngine.js +77 -0
- package/src/index.js +2 -1
- package/src/managers/ZOrderManager.js +434 -0
- package/src/parsers/XMLParser.js +229 -2
package/README.md
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
# node-pptx-templater
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> High-performance, low-level PowerPoint (PPTX) OpenXML template engine for Node.js. Dynamically replace text, insert images, update charts (with Excel workbook data caching), and merge table cells without PowerPoint corruption or Repair Mode prompts.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/node-pptx-templater)
|
|
6
|
-
[](https://www.npmjs.com/package/node-pptx-templater)
|
|
6
|
+
[](https://github.com/jsuyog2/node-pptx-templater/actions/workflows/ci.yml)
|
|
7
|
+
[](https://bundlephobia.com/package/node-pptx-templater)
|
|
8
|
+
[](https://www.npmjs.com/package/node-pptx-templater)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](https://nodejs.org)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ⚡ Why node-pptx-templater?
|
|
15
|
+
|
|
16
|
+
Traditional PowerPoint generation libraries require building slides from scratch in code, which is verbose, hard to maintain, and strips away the power of visual design tools.
|
|
17
|
+
|
|
18
|
+
`node-pptx-templater` takes a different approach: **Design visually in PowerPoint, populate dynamically in Node.js.**
|
|
19
|
+
|
|
20
|
+
You create slide decks using PowerPoint, Google Slides, or Keynote, set your formatting, themes, animations, and layouts, and place placeholders like `{{company}}` or `{{revenue-chart}}`. `node-pptx-templater` parses the template and updates text, injects images, replaces chart values (updating both Excel workbook data caches and XML shapes), and merges tables dynamically while keeping the presentation 100% compliant with standard OpenXML guidelines.
|
|
10
21
|
|
|
11
22
|
---
|
|
12
23
|
|
|
13
24
|
## ✨ Features
|
|
14
25
|
|
|
15
|
-
- 🏗️ **Zero
|
|
16
|
-
- 🔁 **
|
|
17
|
-
- 📊 **
|
|
18
|
-
- 📋 **
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
26
|
+
- 🏗️ **Zero Native Office/Java Dependencies**: Runs on pure Javascript/Node.js, making it ideal for high-throughput cloud environments, Lambda, or serverless runtimes.
|
|
27
|
+
- 🔁 **Fragmented Placeholder Resolution**: PowerPoint often splits text runs like `{{company}}` into `<a:r>` nodes. Our engine merges and resolves fragmented tags automatically.
|
|
28
|
+
- 📊 **Full Chart Engine Integration**: Supports Bar, Column, Line, Pie, Doughnut, Area, Scatter, and Bubble charts. Automatically synchronizes chart XML properties and coordinates with the embedded Excel sheets (`ppt/embeddings/`).
|
|
29
|
+
- 📋 **Flexible Table Merging & Templating**:
|
|
30
|
+
- Horizontal column merge (`gridSpan` & `hMerge`), vertical row merge (`rowSpan` & `vMerge`), and rectangular block merges.
|
|
31
|
+
- Formats cells dynamically with inline options (`align`, `fontSize`, `fill`).
|
|
32
|
+
- Automatically handles slide table duplicates by generating unique `<a16:rowId>` 32-bit hashes to **prevent PowerPoint Repair Mode** screens.
|
|
33
|
+
- 🎨 **Shape & Image Manipulation**: Find shapes, clone layout blocks with offsets, replace image sources while keeping exact positions, or delete elements.
|
|
34
|
+
- 🎯 **Slide Management Operations**: Duplicate, reorder, delete, and import slides from external templates with automatic media asset deduplication.
|
|
35
|
+
- 🔍 **Deep Packaging Integrity Validation**: Real-time checking of relationships, XML schemas, table column numbers, and override duplicates.
|
|
22
36
|
|
|
23
37
|
---
|
|
24
38
|
|
|
@@ -32,58 +46,92 @@ npm install node-pptx-templater
|
|
|
32
46
|
|
|
33
47
|
## 🚀 Quick Start
|
|
34
48
|
|
|
49
|
+
Get up and running in under 60 seconds with this simple template rendering example:
|
|
50
|
+
|
|
35
51
|
```js
|
|
36
52
|
const { PPTXTemplater } = require('node-pptx-templater');
|
|
37
53
|
|
|
38
|
-
async function
|
|
39
|
-
// Load presentation template
|
|
40
|
-
const ppt = await PPTXTemplater.load('
|
|
54
|
+
async function main() {
|
|
55
|
+
// 1. Load your PowerPoint presentation template
|
|
56
|
+
const ppt = await PPTXTemplater.load('monthly_report_template.pptx');
|
|
41
57
|
|
|
42
|
-
//
|
|
58
|
+
// 2. Select slide 1 and execute operations
|
|
43
59
|
ppt.useSlide(1)
|
|
44
|
-
.replaceTextByTag('title', '
|
|
45
|
-
.replaceMultiple({
|
|
60
|
+
.replaceTextByTag('title', 'Quarterly Earnings Report')
|
|
61
|
+
.replaceMultiple({
|
|
62
|
+
company: 'Acme Corporation',
|
|
63
|
+
year: '2026'
|
|
64
|
+
});
|
|
46
65
|
|
|
47
|
-
// Update chart data on Slide 2
|
|
66
|
+
// 3. Update chart series data on Slide 2
|
|
48
67
|
ppt.useSlide(2)
|
|
49
68
|
.updateChartData('sales-chart', {
|
|
50
69
|
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
51
|
-
series: [
|
|
70
|
+
series: [
|
|
71
|
+
{ name: 'Target', values: [100, 120, 140, 160] },
|
|
72
|
+
{ name: 'Revenue', values: [105, 118, 145, 172] }
|
|
73
|
+
]
|
|
52
74
|
});
|
|
53
75
|
|
|
54
|
-
//
|
|
55
|
-
|
|
76
|
+
// 4. Update table with cell merging and formatting on Slide 3
|
|
77
|
+
ppt.useSlide(3)
|
|
78
|
+
.updateTable('sales-table', [
|
|
79
|
+
['Region', 'Q1 Actual', 'Q2 Actual', 'Status'],
|
|
80
|
+
['North', '120k', '140k', { value: 'On Track', align: 'ctr', fill: '10b981' }],
|
|
81
|
+
['South', '95k', '110k', { value: 'Review', align: 'ctr', fill: 'f59e0b' }]
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// 5. Save the non-corrupted PPTX back to disk
|
|
85
|
+
await ppt.saveToFile('./output/annual_earnings.pptx');
|
|
56
86
|
}
|
|
57
|
-
|
|
87
|
+
|
|
88
|
+
main().catch(err => console.error(err));
|
|
58
89
|
```
|
|
59
90
|
|
|
60
91
|
---
|
|
61
92
|
|
|
62
|
-
## 🏗️ Architecture &
|
|
93
|
+
## 🏗️ OpenXML Architecture & Internals
|
|
63
94
|
|
|
64
|
-
A
|
|
95
|
+
A `.pptx` file is an OPC (Open Packaging Convention) ZIP archive containing structured XML documents and asset folders:
|
|
65
96
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
- `
|
|
97
|
+
- `[Content_Types].xml` – Global manifest declaring content MIME types for every file part in the ZIP.
|
|
98
|
+
- `_rels/.rels` – Root-level package relationship index.
|
|
99
|
+
- `ppt/presentation.xml` – Root presentation settings and slide inventory (`sldIdLst`).
|
|
100
|
+
- `ppt/slides/slideN.xml` – Main slide canvas storing shapes, lines, tables, text runs, and layout components.
|
|
101
|
+
- `ppt/slides/_rels/slideN.xml.rels` – Relationship indexes mapping slide XML components to charts, layouts, and image assets.
|
|
70
102
|
|
|
71
|
-
###
|
|
72
|
-
|
|
73
|
-
`node-pptx-templater`
|
|
103
|
+
### Preventing PowerPoint Table Repair Errors
|
|
104
|
+
PowerPoint slide tables utilize unique 32-bit identifiers inside `<a16:rowId>` nodes for collaborative edits. Duplicating rows using naive array copy operations results in overlapping IDs, triggering Microsoft PowerPoint's **"PowerPoint found a problem with content"** repair screen on open.
|
|
105
|
+
`node-pptx-templater` intercepts all table operations (adding, cloning, inserting, or merging rows) and dynamically injects newly generated unique `rowId` hashes, ensuring a seamless, warning-free loading experience in:
|
|
106
|
+
- Microsoft PowerPoint (Desktop, Mac, Online)
|
|
107
|
+
- Google Slides
|
|
108
|
+
- LibreOffice Impress
|
|
74
109
|
|
|
75
110
|
---
|
|
76
111
|
|
|
77
|
-
##
|
|
112
|
+
## 📊 Feature Comparison Matrix
|
|
113
|
+
|
|
114
|
+
| Feature / Library | `node-pptx-templater` | `pptxgenjs` | `pptx-template` | `pptx-automizer` | `officegen` |
|
|
115
|
+
|:---|:---:|:---:|:---:|:---:|:---:|
|
|
116
|
+
| **Approach** | **Template-based** | Code-based | Template-based | Template-based | Code-based |
|
|
117
|
+
| **No PPTX Corruption / Repair Warnings** | **Yes** (Automatic Metadata Sync) | Yes | No (Fragile row duplication) | Yes | Yes (Limited layouts) |
|
|
118
|
+
| **Text Run Fragmentation Resolution** | **Yes** (Dynamic merging) | N/A | No (Placeholder breaks) | Yes | N/A |
|
|
119
|
+
| **Chart Data Workbook Sync** | **Yes** (Direct excel caching) | Yes | No (Only raw XML text) | Yes | Yes |
|
|
120
|
+
| **Horizontal & Vertical Cell Merge** | **Yes** (gridSpan, rowSpan, hMerge, vMerge) | Yes | No | No | No |
|
|
121
|
+
| **Slide Duplication & Reordering** | **Yes** | No | No | Yes | No |
|
|
122
|
+
| **External Slide Imports** | **Yes** (With asset deduplication) | No | No | Yes | No |
|
|
123
|
+
| **Dependencies** | **Zero Native Dependencies** | Zero | Zero | Zero | Node-zip, xmlbuilder |
|
|
124
|
+
|
|
125
|
+
---
|
|
78
126
|
|
|
79
|
-
|
|
127
|
+
## 📚 API Reference
|
|
80
128
|
|
|
81
|
-
### Slide
|
|
129
|
+
### Slide Operations
|
|
82
130
|
|
|
83
131
|
#### `duplicateSlide(slideIndex, atPosition)`
|
|
84
132
|
Duplicates a slide.
|
|
85
133
|
```js
|
|
86
|
-
ppt.duplicateSlide(1, 2); // Duplicate Slide 1 and insert it
|
|
134
|
+
ppt.duplicateSlide(1, 2); // Duplicate Slide 1 and insert it at position 2
|
|
87
135
|
```
|
|
88
136
|
|
|
89
137
|
#### `deleteSlide(slideIndex)`
|
|
@@ -98,363 +146,215 @@ Moves a slide to a new position.
|
|
|
98
146
|
ppt.moveSlide(1, 3); // Move Slide 1 to position 3
|
|
99
147
|
```
|
|
100
148
|
|
|
101
|
-
#### `
|
|
102
|
-
|
|
149
|
+
#### `importSlideFrom(sourcePresentation, sourceSlideIndex)`
|
|
150
|
+
Deep-copies a slide from another loaded presentation, automatically remapping layouts, shapes, charts, and deduplicating media assets.
|
|
103
151
|
```js
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
#### `getSlides()`
|
|
108
|
-
Returns metadata about all slides in the deck.
|
|
109
|
-
```js
|
|
110
|
-
const slides = ppt.getSlides();
|
|
111
|
-
console.log(slides);
|
|
152
|
+
const source = await PPTXTemplater.load('marketing_slides.pptx');
|
|
153
|
+
await ppt.importSlideFrom(source, 2); // Import Slide 2 of marketing deck
|
|
112
154
|
```
|
|
113
155
|
|
|
114
156
|
---
|
|
115
157
|
|
|
116
|
-
### Table
|
|
158
|
+
### Table Manipulation
|
|
117
159
|
|
|
118
|
-
#### `
|
|
119
|
-
|
|
160
|
+
#### `updateTable(tableId, data)`
|
|
161
|
+
Updates a table with rows data, merge rules, and cell styles.
|
|
120
162
|
```js
|
|
121
|
-
ppt.
|
|
163
|
+
ppt.updateTable('revenue-table', [
|
|
164
|
+
['Year', 'Revenue', 'Profit'],
|
|
165
|
+
['2025', '120k', '40k'],
|
|
166
|
+
['2026', '150k', { value: '60k', fill: '10b981', align: 'ctr' }]
|
|
167
|
+
]);
|
|
122
168
|
```
|
|
123
169
|
|
|
124
|
-
#### `
|
|
125
|
-
|
|
170
|
+
#### `mergeCells(options)`
|
|
171
|
+
Merges a rectangular block of cells. Supports horizontal, vertical, and block merging, concatenating all text from the merged region into the top-left cell.
|
|
126
172
|
```js
|
|
127
|
-
ppt.removeTableRow('sales-table', 1); // Delete 2nd row (0-based)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
#### `insertTableRow(tableId, rowIndex, rowData)`
|
|
131
|
-
Inserts a row at a specific index.
|
|
132
|
-
```js
|
|
133
|
-
ppt.insertTableRow('sales-table', 2, ['Q3', '100', '120', '140', '160']);
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
#### `cloneTableRow(tableId, sourceRowIndex, targetRowIndex)`
|
|
137
|
-
Clones a row style/data and inserts it.
|
|
138
|
-
```js
|
|
139
|
-
ppt.cloneTableRow('sales-table', 1, 3);
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
#### `updateCell(tableId, rowIndex, colIndex, value, options)`
|
|
143
|
-
Updates a specific cell value and styling.
|
|
144
|
-
```js
|
|
145
|
-
ppt.updateCell('sales-table', 1, 0, 'New Product Name', {
|
|
146
|
-
fill: 'FF0000', // HEX background color
|
|
147
|
-
align: 'ctr', // Text alignment: 'l', 'ctr', 'r', 'just'
|
|
148
|
-
fontSize: 14 // Font size in points
|
|
149
|
-
});
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
#### `mergeCells(options)` or `mergeCells(tableId, startRow, startCol, endRow, endCol)`
|
|
153
|
-
Merges a rectangular range of cells. Supports horizontal, vertical, and block merges, moving cell texts to the origin cell (concatenated with newlines) and setting correct OpenXML `gridSpan` and `rowSpan` attributes.
|
|
154
|
-
```js
|
|
155
|
-
// Config object signature (Recommended)
|
|
156
173
|
ppt.mergeCells({
|
|
157
|
-
slide: 3,
|
|
158
174
|
tableId: 'sales-table',
|
|
159
175
|
startRow: 1,
|
|
160
176
|
startCol: 1,
|
|
161
|
-
endRow:
|
|
162
|
-
endCol:
|
|
177
|
+
endRow: 2,
|
|
178
|
+
endCol: 2
|
|
163
179
|
});
|
|
164
|
-
|
|
165
|
-
// Legacy positional signature
|
|
166
|
-
ppt.mergeCells('sales-table', 1, 1, 3, 3);
|
|
167
180
|
```
|
|
168
181
|
|
|
169
|
-
#### `unmergeCells(options)`
|
|
170
|
-
|
|
182
|
+
#### `unmergeCells(options)`
|
|
183
|
+
Splits a merged region back to its original individual cells, removing `gridSpan`, `rowSpan`, `hMerge`, and `vMerge` attributes.
|
|
171
184
|
```js
|
|
172
|
-
// Cell-coordinate coordinate signature (Recommended)
|
|
173
185
|
ppt.unmergeCells({
|
|
174
|
-
slide: 3,
|
|
175
186
|
tableId: 'sales-table',
|
|
176
|
-
row:
|
|
177
|
-
col:
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Legacy positional signature
|
|
181
|
-
ppt.unmergeCells('sales-table', 1, 1, 3, 3);
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
#### `getMergedCells(tableId)`
|
|
185
|
-
Scans the slide table and returns all active merged regions.
|
|
186
|
-
```js
|
|
187
|
-
const merges = ppt.getMergedCells('sales-table');
|
|
188
|
-
// Output: [ { startRow: 1, startCol: 1, endRow: 3, endCol: 3 } ]
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
#### `validateMergeRegion(tableId, startRow, startCol, endRow, endCol)`
|
|
192
|
-
Checks bounds and overlaps, returning detailed validation errors.
|
|
193
|
-
```js
|
|
194
|
-
const report = ppt.validateMergeRegion('sales-table', 1, 1, 3, 3);
|
|
195
|
-
console.log(report.valid); // true or false
|
|
196
|
-
console.log(report.errors); // Array of error strings
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
#### `isMergedCell(tableId, row, col)`
|
|
200
|
-
Returns `true` if the cell at `(row, col)` is part of any merged region.
|
|
201
|
-
```js
|
|
202
|
-
const merged = ppt.isMergedCell('sales-table', 2, 2);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
#### `getMergeParent(tableId, row, col)`
|
|
206
|
-
Returns the coordinates `{ row, col }` of the top-left origin cell of the merge region containing `(row, col)`.
|
|
207
|
-
```js
|
|
208
|
-
const parent = ppt.getMergeParent('sales-table', 2, 2); // { row: 1, col: 1 }
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
#### `getMergeRegion(tableId, row, col)`
|
|
212
|
-
Returns the merged region object `{ startRow, startCol, endRow, endCol }` containing `(row, col)`.
|
|
213
|
-
```js
|
|
214
|
-
const region = ppt.getMergeRegion('sales-table', 2, 2);
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
#### `splitMergedRegion(tableId, row, col)`
|
|
218
|
-
Splits the merged region containing cell `(row, col)`.
|
|
219
|
-
```js
|
|
220
|
-
ppt.splitMergedRegion('sales-table', 2, 2);
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
#### `cloneMergedRegion(tableId, row, col, targetRow, targetCol)`
|
|
224
|
-
Clones a merged region to another starting position, preserving cell text and formatting.
|
|
225
|
-
```js
|
|
226
|
-
ppt.cloneMergedRegion('sales-table', 1, 1, 4, 1);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
#### Template-driven cell merges
|
|
230
|
-
Support cell merging inside template updates dynamically via `colSpan`/`rowSpan` or `merge` arrays:
|
|
231
|
-
```js
|
|
232
|
-
ppt.updateTable('sales-table', {
|
|
233
|
-
rows: [
|
|
234
|
-
['Header 1', 'Header 2', 'Header 3'],
|
|
235
|
-
['Row 1 Col 1', { value: 'Spanned Cell', colSpan: 2 }],
|
|
236
|
-
['Row 2 Col 1', 'Row 2 Col 2', { value: 'Spanned V', rowSpan: 2 }]
|
|
237
|
-
],
|
|
238
|
-
merge: [
|
|
239
|
-
{ startRow: 0, startCol: 0, endRow: 0, endCol: 2 }
|
|
240
|
-
]
|
|
187
|
+
row: 1,
|
|
188
|
+
col: 1
|
|
241
189
|
});
|
|
242
190
|
```
|
|
243
191
|
|
|
244
|
-
#### `autoFitTable(tableId)`
|
|
245
|
-
Resizes columns to fit text width.
|
|
246
|
-
```js
|
|
247
|
-
ppt.autoFitTable('sales-table');
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
#### `resizeTable(tableId, width, height)`
|
|
251
|
-
Resizes the bounding frame of the table. Width/height can be in EMUs or inches.
|
|
252
|
-
```js
|
|
253
|
-
ppt.resizeTable('sales-table', 8.5, 4.2); // Dimensions in inches
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
#### `getTables()`
|
|
257
|
-
Lists tables in the current slide.
|
|
258
|
-
```js
|
|
259
|
-
const tables = ppt.getTables();
|
|
260
|
-
```
|
|
261
|
-
|
|
262
192
|
---
|
|
263
193
|
|
|
264
|
-
### Chart
|
|
194
|
+
### Chart Integration
|
|
265
195
|
|
|
266
196
|
#### `updateChartData(chartId, data)`
|
|
267
|
-
|
|
197
|
+
Overwrites chart categories and series values. Updates the embedded Excel spreadsheet to ensure the chart matches perfectly on refresh.
|
|
268
198
|
```js
|
|
269
199
|
ppt.updateChartData('sales-chart', {
|
|
270
|
-
categories: ['Q1', 'Q2', 'Q3'],
|
|
271
|
-
series: [
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
#### `replaceChartSeries(chartId, seriesIndex, newSeriesData)`
|
|
276
|
-
Replaces values and name of a single series.
|
|
277
|
-
```js
|
|
278
|
-
ppt.replaceChartSeries('sales-chart', 0, {
|
|
279
|
-
name: 'Updated Series Name',
|
|
280
|
-
values: [80, 95, 110]
|
|
200
|
+
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
201
|
+
series: [
|
|
202
|
+
{ name: 'Revenue', values: [100, 150, 180, 220] }
|
|
203
|
+
]
|
|
281
204
|
});
|
|
282
205
|
```
|
|
283
206
|
|
|
284
207
|
#### `updateChartTitle(chartId, title)`
|
|
285
|
-
Updates the chart's title.
|
|
286
|
-
```js
|
|
287
|
-
ppt.updateChartTitle('sales-chart', 'Quarterly Revenue Performance');
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
#### `updateChartCategories(chartId, categories)`
|
|
291
|
-
Updates the chart categories.
|
|
292
208
|
```js
|
|
293
|
-
ppt.
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
#### `getCharts()`
|
|
297
|
-
Returns chart metadata from the slide.
|
|
298
|
-
```js
|
|
299
|
-
const charts = ppt.getCharts();
|
|
209
|
+
ppt.updateChartTitle('sales-chart', 'Revenue Growth (2026)');
|
|
300
210
|
```
|
|
301
211
|
|
|
302
212
|
---
|
|
303
213
|
|
|
304
|
-
###
|
|
214
|
+
### Z-Order (Layer Management)
|
|
305
215
|
|
|
306
|
-
|
|
307
|
-
Replaces placeholders with custom values.
|
|
308
|
-
```js
|
|
309
|
-
ppt.replaceTextByTag('username', 'Alice Cooper');
|
|
310
|
-
```
|
|
216
|
+
Control the stacking order of shapes, images, charts, tables, groups, and connectors on any slide — just like PowerPoint's **Bring Forward / Send Backward** panel. The Z-order directly maps to the XML element order inside the slide's `<p:spTree>`, which is what PowerPoint reads when rendering.
|
|
311
217
|
|
|
312
|
-
|
|
313
|
-
Performs multiple text tag replacements.
|
|
314
|
-
```js
|
|
315
|
-
ppt.replaceMultiple({
|
|
316
|
-
'date': '2026-06-02',
|
|
317
|
-
'location': 'New York'
|
|
318
|
-
});
|
|
319
|
-
```
|
|
218
|
+
All operations accept either an **options object** with a `slide` key or can be chained after `useSlide()`:
|
|
320
219
|
|
|
321
|
-
#### `findText(text)`
|
|
322
|
-
Searches for text in slide shape runs.
|
|
323
220
|
```js
|
|
324
|
-
|
|
325
|
-
|
|
221
|
+
// Option A — explicit slide number
|
|
222
|
+
ppt.bringForward({ slide: 2, objectId: 'logo' });
|
|
326
223
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
```js
|
|
330
|
-
const elements = ppt.getTextElements();
|
|
224
|
+
// Option B — fluent chain
|
|
225
|
+
ppt.useSlide(2).bringForward('logo');
|
|
331
226
|
```
|
|
332
227
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
### Shape Features
|
|
336
|
-
|
|
337
|
-
#### `updateShapeText(shapeId, text)`
|
|
338
|
-
Updates text inside a shapes run.
|
|
228
|
+
#### `getObjectOrder(slideIndex)`
|
|
229
|
+
Returns a sorted array describing every drawing element on the slide, bottom-to-top.
|
|
339
230
|
```js
|
|
340
|
-
ppt.
|
|
231
|
+
const layers = ppt.getObjectOrder(1);
|
|
232
|
+
// → [{ id: 'Background', type: 'shape', zIndex: 1 }, ...]
|
|
341
233
|
```
|
|
342
234
|
|
|
343
|
-
#### `
|
|
344
|
-
|
|
235
|
+
#### `bringForward(options)` / `sendBackward(options)`
|
|
236
|
+
Move an object one layer up or down.
|
|
345
237
|
```js
|
|
346
|
-
ppt.
|
|
347
|
-
|
|
348
|
-
offsetY: 0.5, // Offset Y in inches
|
|
349
|
-
width: 5.0, // Resize width
|
|
350
|
-
height: 2.0 // Resize height
|
|
351
|
-
});
|
|
238
|
+
ppt.bringForward({ slide: 1, objectId: 'logo' });
|
|
239
|
+
ppt.sendBackward({ slide: 1, objectId: 'logo' });
|
|
352
240
|
```
|
|
353
241
|
|
|
354
|
-
#### `
|
|
355
|
-
|
|
242
|
+
#### `bringToFront(options)` / `sendToBack(options)`
|
|
243
|
+
Move an object to the very top or very bottom of the stack.
|
|
356
244
|
```js
|
|
357
|
-
ppt.
|
|
245
|
+
ppt.bringToFront({ slide: 1, objectId: 'logo' });
|
|
246
|
+
ppt.sendToBack({ slide: 1, objectId: 'background' });
|
|
358
247
|
```
|
|
359
248
|
|
|
360
|
-
#### `
|
|
361
|
-
|
|
249
|
+
#### `setZIndex(options)`
|
|
250
|
+
Place an object at an exact 1-based stacking position.
|
|
362
251
|
```js
|
|
363
|
-
|
|
252
|
+
ppt.setZIndex({ slide: 1, objectId: 'logo', zIndex: 3 });
|
|
364
253
|
```
|
|
365
254
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
### Image Features
|
|
369
|
-
|
|
370
|
-
#### `replaceImage(imageIdOrName, sourcePathOrBuffer)`
|
|
371
|
-
Replaces an image file binary, keeping the layout.
|
|
255
|
+
#### `moveObjectBefore(options)` / `moveObjectAfter(options)`
|
|
256
|
+
Position an object immediately below or above a specific target.
|
|
372
257
|
```js
|
|
373
|
-
|
|
258
|
+
ppt.moveObjectBefore({ slide: 1, objectId: 'overlay', targetId: 'chart' });
|
|
259
|
+
ppt.moveObjectAfter({ slide: 1, objectId: 'label', targetId: 'chart' });
|
|
374
260
|
```
|
|
375
261
|
|
|
376
|
-
#### `
|
|
377
|
-
|
|
262
|
+
#### `reorderObjects(options)`
|
|
263
|
+
Bulk-reorder the entire slide stack by specifying all object names in desired bottom-to-top order.
|
|
378
264
|
```js
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
width: 3.0, // Width (inches)
|
|
383
|
-
height: 3.0 // Height (inches)
|
|
265
|
+
ppt.reorderObjects({
|
|
266
|
+
slide: 1,
|
|
267
|
+
order: ['background', 'chart', 'logo', 'title']
|
|
384
268
|
});
|
|
385
269
|
```
|
|
386
270
|
|
|
387
|
-
#### `
|
|
388
|
-
|
|
271
|
+
#### `applyZOrder(slideIndex, configs)`
|
|
272
|
+
Apply multiple stacking rules in a single call. Operations are executed sequentially.
|
|
389
273
|
```js
|
|
390
|
-
ppt.
|
|
274
|
+
ppt.applyZOrder(1, [
|
|
275
|
+
{ id: 'background', sendToBack: true },
|
|
276
|
+
{ id: 'overlay', zIndex: 2 },
|
|
277
|
+
{ id: 'logo', bringToFront: true },
|
|
278
|
+
]);
|
|
391
279
|
```
|
|
392
280
|
|
|
393
|
-
#### `
|
|
394
|
-
|
|
281
|
+
#### `swapObjects(slideIndex, objectId1, objectId2)`
|
|
282
|
+
Exchange the stacking positions of two objects.
|
|
395
283
|
```js
|
|
396
|
-
|
|
284
|
+
ppt.swapObjects(1, 'logo', 'chart');
|
|
397
285
|
```
|
|
398
286
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
### Validation System
|
|
402
|
-
|
|
403
|
-
#### `validatePresentation()`
|
|
404
|
-
Audits the complete presentation.
|
|
287
|
+
#### `sortObjects(slideIndex, compareFn)`
|
|
288
|
+
Sort the layer stack using a custom comparator (receives `{ id, type, zIndex }` objects).
|
|
405
289
|
```js
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
console.log('Warnings:', report.warnings);
|
|
290
|
+
// Alphabetical ascending by name
|
|
291
|
+
ppt.sortObjects(1, (a, b) => a.id.localeCompare(b.id));
|
|
409
292
|
```
|
|
410
293
|
|
|
411
|
-
#### `
|
|
412
|
-
|
|
294
|
+
#### `getTopMostObject(slideIndex)` / `getBottomMostObject(slideIndex)`
|
|
295
|
+
Retrieve metadata for the topmost or bottommost element.
|
|
413
296
|
```js
|
|
414
|
-
const
|
|
297
|
+
const top = ppt.getTopMostObject(1); // { id: 'logo', type: 'image', zIndex: 5 }
|
|
298
|
+
const bottom = ppt.getBottomMostObject(1); // { id: 'background', type: 'shape', zIndex: 1 }
|
|
415
299
|
```
|
|
416
300
|
|
|
417
|
-
#### `
|
|
418
|
-
|
|
301
|
+
#### `normalizeZOrder(slideIndex)`
|
|
302
|
+
Re-derives the Z-order directly from the current XML element order. Useful after manual XML edits or imports to reset the internal ordering state.
|
|
419
303
|
```js
|
|
420
|
-
|
|
304
|
+
ppt.normalizeZOrder(1);
|
|
421
305
|
```
|
|
422
306
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
307
|
+
**Supported element types:**
|
|
308
|
+
|
|
309
|
+
| PowerPoint Type | XML Tag | `type` Value |
|
|
310
|
+
|:---|:---|:---|
|
|
311
|
+
| Shape / Text Box | `p:sp` | `shape` / `text` |
|
|
312
|
+
| Image | `p:pic` | `image` |
|
|
313
|
+
| Chart | `p:graphicFrame` + chart URI | `chart` |
|
|
314
|
+
| Table | `p:graphicFrame` + table URI | `table` |
|
|
315
|
+
| SmartArt | `p:graphicFrame` + diagram URI | `smartart` |
|
|
316
|
+
| Group | `p:grpSp` | `group` |
|
|
317
|
+
| Connector | `p:cxnSp` | `connector` |
|
|
428
318
|
|
|
429
319
|
---
|
|
430
320
|
|
|
431
321
|
## ⚡ Performance Benchmarks
|
|
432
322
|
|
|
433
|
-
Tested on a 50-slide presentation template:
|
|
323
|
+
Tested on a standard 50-slide enterprise presentation template:
|
|
434
324
|
|
|
435
|
-
| Operation |
|
|
436
|
-
|
|
437
|
-
| Load
|
|
438
|
-
| Replace 20
|
|
439
|
-
|
|
|
440
|
-
|
|
|
441
|
-
|
|
|
325
|
+
| Operation | Execution Duration |
|
|
326
|
+
|:---|:---|
|
|
327
|
+
| Load PPTX Template | ~110ms |
|
|
328
|
+
| Find & Replace 20 Text Placeholders | ~2.5ms |
|
|
329
|
+
| XML Schema & Integrity Validation Check | ~14ms |
|
|
330
|
+
| Dynamic Row Insertion & Merging (15 rows) | ~3ms |
|
|
331
|
+
| Save and Re-package to PPTX ZIP | ~78ms |
|
|
442
332
|
|
|
443
333
|
---
|
|
444
334
|
|
|
445
|
-
## ❓ FAQ
|
|
335
|
+
## ❓ FAQ & Troubleshooting
|
|
446
336
|
|
|
447
|
-
|
|
448
|
-
|
|
337
|
+
### PowerPoint displays a "Repair" prompt when opening my generated file
|
|
338
|
+
This is commonly caused by:
|
|
339
|
+
1. **Missing overridden content type**: A new slide or chart XML was added but not registered in `[Content_Types].xml`.
|
|
340
|
+
2. **Duplicate row identifiers**: If table rows are duplicated without generating a new unique `rowId` under `<a16:rowId>`.
|
|
341
|
+
3. **Invalid relationship mapping**: An asset (like an image or worksheet) is referenced in slide XML but is missing from the slide's `.rels` file.
|
|
449
342
|
|
|
450
|
-
|
|
451
|
-
|
|
343
|
+
*Fix*: Ensure you always use the public `saveToFile()` or `toBuffer()` helper functions, which automatically execute structural verification passes and update relationship chains.
|
|
344
|
+
|
|
345
|
+
### My text placeholders are not replacing
|
|
346
|
+
PowerPoint text editors segment formatting runs into separate XML elements. The text `{{title}}` may look normal in PowerPoint, but in XML it could be split into `<a:t>{{ti</a:t><a:t>tle}}</a:t>`.
|
|
347
|
+
*Fix*: You can enable logger output to see split tags:
|
|
348
|
+
```bash
|
|
349
|
+
PPTX_LOG_LEVEL=debug node app.js
|
|
350
|
+
```
|
|
351
|
+
To fix this in PowerPoint, highlight the entire placeholder block, cut it, and paste it back as "Keep Text Only" to unify the XML text runs.
|
|
452
352
|
|
|
453
353
|
---
|
|
454
354
|
|
|
455
355
|
## 🤝 Contributing
|
|
456
356
|
|
|
457
|
-
|
|
357
|
+
We welcome contributions! Please check out [CONTRIBUTING.md](./CONTRIBUTING.md) to get started.
|
|
458
358
|
|
|
459
359
|
```bash
|
|
460
360
|
git clone https://github.com/jsuyog2/node-pptx-templater.git
|
|
@@ -467,4 +367,4 @@ npm test
|
|
|
467
367
|
|
|
468
368
|
## 📄 License
|
|
469
369
|
|
|
470
|
-
MIT © [node-pptx-templater contributors](./LICENSE)
|
|
370
|
+
Licensed under the MIT License. © [node-pptx-templater contributors](./LICENSE)
|