node-pptx-templater 1.0.5 → 1.0.7
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 +32 -0
- package/README.md +1305 -225
- package/package.json +95 -3
- package/src/core/PPTXTemplater.js +52 -1
- package/src/core/ValidationEngine.js +93 -0
- package/src/index.js +14 -1
- package/src/managers/ChartManager.js +352 -21
- package/src/managers/charts/ChartCacheGenerator.js +371 -0
- package/src/managers/charts/ChartWorkbookUpdater.js +187 -34
- package/src/parsers/XMLParser.js +38 -1
- package/src/utils/xmlUtils.js +285 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-pptx-templater",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -52,11 +52,103 @@
|
|
|
52
52
|
"mail-merge",
|
|
53
53
|
"report-generator",
|
|
54
54
|
"xml-manipulation",
|
|
55
|
-
"pptx-editor"
|
|
55
|
+
"pptx-editor",
|
|
56
|
+
"ppt",
|
|
57
|
+
"slide-cloning",
|
|
58
|
+
"z-index",
|
|
59
|
+
"z-order",
|
|
60
|
+
"bring-to-front",
|
|
61
|
+
"send-to-back",
|
|
62
|
+
"xlsx",
|
|
63
|
+
"excel-caching",
|
|
64
|
+
"jszip",
|
|
65
|
+
"slide-import",
|
|
66
|
+
"slide-relationships",
|
|
67
|
+
"content-types",
|
|
68
|
+
"xml-repair",
|
|
69
|
+
"xml-security",
|
|
70
|
+
"xml-bomb-protection",
|
|
71
|
+
"xxe-prevention",
|
|
72
|
+
"billion-laughs-fix",
|
|
73
|
+
"entity-expansion-fix",
|
|
74
|
+
"safe-xml-parser",
|
|
75
|
+
"fast-xml-parser",
|
|
76
|
+
"office-automation",
|
|
77
|
+
"docx",
|
|
78
|
+
"drawingml",
|
|
79
|
+
"presentationml",
|
|
80
|
+
"slide-deck",
|
|
81
|
+
"slide-generator",
|
|
82
|
+
"ppt-generator",
|
|
83
|
+
"generate-pptx",
|
|
84
|
+
"create-pptx",
|
|
85
|
+
"modify-pptx",
|
|
86
|
+
"edit-pptx",
|
|
87
|
+
"parse-pptx",
|
|
88
|
+
"read-pptx",
|
|
89
|
+
"write-pptx",
|
|
90
|
+
"pptx-parser",
|
|
91
|
+
"pptx-builder",
|
|
92
|
+
"pptx-writer",
|
|
93
|
+
"template-engine",
|
|
94
|
+
"placeholders",
|
|
95
|
+
"replacement-engine",
|
|
96
|
+
"run-fragmentation",
|
|
97
|
+
"text-runs",
|
|
98
|
+
"fonts",
|
|
99
|
+
"colors",
|
|
100
|
+
"shapes",
|
|
101
|
+
"cloning-shapes",
|
|
102
|
+
"copy-shapes",
|
|
103
|
+
"tables-formatting",
|
|
104
|
+
"charts-excel",
|
|
105
|
+
"xlsx-caching",
|
|
106
|
+
"workbook-update",
|
|
107
|
+
"bar-chart",
|
|
108
|
+
"pie-chart",
|
|
109
|
+
"line-chart",
|
|
110
|
+
"bar-charts",
|
|
111
|
+
"pie-charts",
|
|
112
|
+
"line-charts",
|
|
113
|
+
"scatter-chart",
|
|
114
|
+
"bubble-chart",
|
|
115
|
+
"area-chart",
|
|
116
|
+
"doughnut-chart",
|
|
117
|
+
"slide-masters",
|
|
118
|
+
"slide-layouts",
|
|
119
|
+
"custom-xml",
|
|
120
|
+
"relationship-remapping",
|
|
121
|
+
"rowid-generation",
|
|
122
|
+
"powerpoint-repair-fix",
|
|
123
|
+
"non-corrupting",
|
|
124
|
+
"zero-dependency",
|
|
125
|
+
"lambda-compatible",
|
|
126
|
+
"serverless-pptx",
|
|
127
|
+
"aws-lambda",
|
|
128
|
+
"vercel-edge",
|
|
129
|
+
"google-cloud-functions",
|
|
130
|
+
"pure-js-pptx",
|
|
131
|
+
"no-java",
|
|
132
|
+
"no-office",
|
|
133
|
+
"pptx-merge",
|
|
134
|
+
"pptx-split",
|
|
135
|
+
"pptx-reorder",
|
|
136
|
+
"pptx-stacking",
|
|
137
|
+
"pptx-layers",
|
|
138
|
+
"pptx-hyperlinks",
|
|
139
|
+
"pptx-action-links",
|
|
140
|
+
"pptx-navigation"
|
|
56
141
|
],
|
|
57
142
|
"author": {
|
|
58
|
-
"name": "node-pptx-templater contributors"
|
|
143
|
+
"name": "node-pptx-templater contributors",
|
|
144
|
+
"url": "https://github.com/jsuyog2/node-pptx-templater/graphs/contributors"
|
|
59
145
|
},
|
|
146
|
+
"funding": [
|
|
147
|
+
{
|
|
148
|
+
"type": "github",
|
|
149
|
+
"url": "https://github.com/sponsors/jsuyog2"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
60
152
|
"license": "MIT",
|
|
61
153
|
"repository": {
|
|
62
154
|
"type": "git",
|
|
@@ -359,20 +359,34 @@ class PPTXTemplater {
|
|
|
359
359
|
* Updates chart data in the selected slide(s).
|
|
360
360
|
* Finds charts by their name/ID and updates categories, series, and values.
|
|
361
361
|
* Preserves original chart styles, themes, and formatting.
|
|
362
|
+
* Supports inline custom data labels by passing objects in the format `{ data: number, label: string }` instead of numbers.
|
|
362
363
|
*
|
|
363
364
|
* @param {string} chartId - Chart name or relationship ID.
|
|
364
365
|
* @param {ChartData} data - New chart data.
|
|
365
366
|
* @param {string[]} data.categories - Category labels (X-axis).
|
|
366
367
|
* @param {SeriesData[]} data.series - Data series array.
|
|
367
368
|
* @param {string} data.series[].name - Series name.
|
|
368
|
-
* @param {number[]} data.series[].values - Data values.
|
|
369
|
+
* @param {number[]|object[]} data.series[].values - Data values (numbers or label objects).
|
|
369
370
|
* @returns {PPTXTemplater} this (chainable)
|
|
370
371
|
*
|
|
371
372
|
* @example
|
|
373
|
+
* // Simple numeric values:
|
|
372
374
|
* ppt.updateChart('sales-chart', {
|
|
373
375
|
* categories: ['Jan', 'Feb', 'Mar'],
|
|
374
376
|
* series: [{ name: 'Revenue', values: [120, 150, 180] }]
|
|
375
377
|
* });
|
|
378
|
+
*
|
|
379
|
+
* // Custom inline data labels:
|
|
380
|
+
* ppt.updateChart('sales-chart', {
|
|
381
|
+
* categories: ['Q1', 'Q2'],
|
|
382
|
+
* series: [{
|
|
383
|
+
* name: 'Revenue',
|
|
384
|
+
* values: [
|
|
385
|
+
* { data: 120, label: '120 (40%)' },
|
|
386
|
+
* { data: 180, label: '180 (60%)' }
|
|
387
|
+
* ]
|
|
388
|
+
* }]
|
|
389
|
+
* });
|
|
376
390
|
*/
|
|
377
391
|
updateChart(chartId, data) {
|
|
378
392
|
this.#assertLoaded()
|
|
@@ -1320,6 +1334,43 @@ class PPTXTemplater {
|
|
|
1320
1334
|
return this
|
|
1321
1335
|
}
|
|
1322
1336
|
|
|
1337
|
+
updateDataLabels(chartId, options) {
|
|
1338
|
+
this.#assertLoaded()
|
|
1339
|
+
const targetIndices = this.#getTargetSlideIndices()
|
|
1340
|
+
for (const idx of targetIndices) {
|
|
1341
|
+
this.#chartManager.updateDataLabels(
|
|
1342
|
+
idx,
|
|
1343
|
+
chartId,
|
|
1344
|
+
options,
|
|
1345
|
+
this.#slideManager,
|
|
1346
|
+
this.#relationshipManager
|
|
1347
|
+
)
|
|
1348
|
+
}
|
|
1349
|
+
return this
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
async getDataLabels(chartId, options = {}) {
|
|
1353
|
+
this.#assertLoaded()
|
|
1354
|
+
const targetIndices = this.#getTargetSlideIndices()
|
|
1355
|
+
if (targetIndices.length === 0) return []
|
|
1356
|
+
const idx = targetIndices[0]
|
|
1357
|
+
return this.#chartManager.getDataLabels(
|
|
1358
|
+
idx,
|
|
1359
|
+
chartId,
|
|
1360
|
+
options,
|
|
1361
|
+
this.#slideManager,
|
|
1362
|
+
this.#relationshipManager
|
|
1363
|
+
)
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
async validateDataLabels(chartId, options = {}) {
|
|
1367
|
+
this.#assertLoaded()
|
|
1368
|
+
const targetIndices = this.#getTargetSlideIndices()
|
|
1369
|
+
if (targetIndices.length === 0) return { valid: true, errors: [] }
|
|
1370
|
+
const idx = targetIndices[0]
|
|
1371
|
+
return ValidationEngine.validateDataLabels(this, idx, chartId, options)
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1323
1374
|
getCharts() {
|
|
1324
1375
|
this.#assertLoaded()
|
|
1325
1376
|
const targetIndices = this.#getTargetSlideIndices()
|
|
@@ -318,6 +318,99 @@ class ValidationEngine {
|
|
|
318
318
|
warnings,
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Validates data label configurations for a chart.
|
|
324
|
+
*
|
|
325
|
+
* @param {PPTXTemplater} ppt
|
|
326
|
+
* @param {number} slideIndex
|
|
327
|
+
* @param {string} chartId
|
|
328
|
+
* @param {Object} options
|
|
329
|
+
* @returns {Promise<Object>} report
|
|
330
|
+
*/
|
|
331
|
+
static async validateDataLabels(ppt, slideIndex, chartId, options = {}) {
|
|
332
|
+
const errors = []
|
|
333
|
+
const warnings = []
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const chartInfo = ppt.chartManager.findChartInSlide(
|
|
337
|
+
slideIndex,
|
|
338
|
+
chartId,
|
|
339
|
+
ppt.slideManager,
|
|
340
|
+
ppt.relationshipManager
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if (!chartInfo) {
|
|
344
|
+
errors.push(`Chart "${chartId}" not found in slide ${slideIndex}`)
|
|
345
|
+
return { valid: false, errors, warnings }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const chartType = await ppt.chartManager.getChartTypeAsync(
|
|
349
|
+
slideIndex,
|
|
350
|
+
chartId,
|
|
351
|
+
ppt.slideManager,
|
|
352
|
+
ppt.relationshipManager
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
const supportedTypes = [
|
|
356
|
+
'bar',
|
|
357
|
+
'column',
|
|
358
|
+
'line',
|
|
359
|
+
'pie',
|
|
360
|
+
'doughnut',
|
|
361
|
+
'area',
|
|
362
|
+
'scatter',
|
|
363
|
+
'combo',
|
|
364
|
+
'unknown',
|
|
365
|
+
]
|
|
366
|
+
if (!supportedTypes.includes(chartType)) {
|
|
367
|
+
errors.push(`Unsupported chart type "${chartType}" for data labels`)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const xml = await ppt.zipManager.readFile(chartInfo.zipPath)
|
|
371
|
+
let ptsCount = 0
|
|
372
|
+
const catMatch = /<c:cat>([\s\S]*?)<\/c:cat>/.exec(xml)
|
|
373
|
+
const valMatch = /<c:val>([\s\S]*?)<\/c:val>/.exec(xml)
|
|
374
|
+
const targetBlock = catMatch ? catMatch[1] : valMatch ? valMatch[1] : ''
|
|
375
|
+
const ptCountMatch = /<c:ptCount val="(\d+)"\/>/.exec(targetBlock)
|
|
376
|
+
if (ptCountMatch) {
|
|
377
|
+
ptsCount = parseInt(ptCountMatch[1], 10)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (options.labels) {
|
|
381
|
+
if (ptsCount > 0 && options.labels.length !== ptsCount) {
|
|
382
|
+
errors.push(
|
|
383
|
+
`Label count (${options.labels.length}) does not match chart data points count (${ptsCount})`
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
options.labels.forEach((lbl, i) => {
|
|
388
|
+
if (lbl === null || lbl === undefined || String(lbl).trim() === '') {
|
|
389
|
+
warnings.push(`Label at index ${i} is empty`)
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (options.labelsFromCells) {
|
|
395
|
+
const range = options.labelsFromCells
|
|
396
|
+
const parts = range.split('!')
|
|
397
|
+
const rangePart = parts.length > 1 ? parts[1] : parts[0]
|
|
398
|
+
|
|
399
|
+
const rangeRegex = /^\$?[A-Z]+\$?\d+(?::\$?[A-Z]+\$?\d+)?$/i
|
|
400
|
+
if (!rangeRegex.test(rangePart)) {
|
|
401
|
+
errors.push(`Invalid range format: "${options.labelsFromCells}"`)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
errors.push(`Data labels validation error: ${err.message}`)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
valid: errors.length === 0,
|
|
410
|
+
errors,
|
|
411
|
+
warnings,
|
|
412
|
+
}
|
|
413
|
+
}
|
|
321
414
|
}
|
|
322
415
|
|
|
323
416
|
module.exports = { ValidationEngine }
|
package/src/index.js
CHANGED
|
@@ -42,7 +42,15 @@ const { ValidationEngine } = require('./core/ValidationEngine.js')
|
|
|
42
42
|
|
|
43
43
|
// Utility exports
|
|
44
44
|
const { generateRelationshipId, parseRelationshipId } = require('./utils/relationshipUtils.js')
|
|
45
|
-
const {
|
|
45
|
+
const {
|
|
46
|
+
validateXml,
|
|
47
|
+
validateXML,
|
|
48
|
+
safeParseXml,
|
|
49
|
+
repairXML,
|
|
50
|
+
scanForEntities,
|
|
51
|
+
analyzeXmlFile,
|
|
52
|
+
reportXmlComplexity,
|
|
53
|
+
} = require('./utils/xmlUtils.js')
|
|
46
54
|
const { createLogger } = require('./utils/logger.js')
|
|
47
55
|
const {
|
|
48
56
|
PPTXError,
|
|
@@ -70,8 +78,13 @@ module.exports = {
|
|
|
70
78
|
ValidationEngine,
|
|
71
79
|
generateRelationshipId,
|
|
72
80
|
parseRelationshipId,
|
|
81
|
+
validateXml,
|
|
73
82
|
validateXML,
|
|
83
|
+
safeParseXml,
|
|
74
84
|
repairXML,
|
|
85
|
+
scanForEntities,
|
|
86
|
+
analyzeXmlFile,
|
|
87
|
+
reportXmlComplexity,
|
|
75
88
|
createLogger,
|
|
76
89
|
PPTXError,
|
|
77
90
|
SlideNotFoundError,
|