node-pptx-templater 1.0.1 → 1.0.3
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/README.md +336 -281
- package/package.json +6 -6
- package/src/cli/commands/build.js +32 -31
- package/src/cli/commands/debug.js +25 -24
- package/src/cli/commands/extract.js +23 -21
- package/src/cli/commands/inspect.js +25 -23
- package/src/cli/commands/validate.js +19 -17
- package/src/cli/index.js +45 -43
- package/src/core/OutputWriter.js +81 -78
- package/src/core/PPTXTemplater.js +859 -274
- package/src/core/TemplateEngine.js +69 -71
- package/src/core/ValidationEngine.js +246 -0
- package/src/index.js +51 -15
- package/src/managers/ChartManager.js +197 -70
- package/src/managers/ContentTypesManager.js +51 -45
- package/src/managers/HyperlinkManager.js +148 -142
- package/src/managers/ImageManager.js +336 -0
- package/src/managers/MediaManager.js +64 -81
- package/src/managers/RelationshipManager.js +102 -96
- package/src/managers/ShapeManager.js +340 -0
- package/src/managers/SlideManager.js +410 -311
- package/src/managers/TableManager.js +981 -262
- package/src/managers/TextManager.js +197 -0
- package/src/managers/ZipManager.js +71 -69
- package/src/managers/charts/ChartCacheGenerator.js +77 -58
- package/src/managers/charts/ChartParser.js +11 -13
- package/src/managers/charts/ChartRelationshipManager.js +14 -10
- package/src/managers/charts/ChartWorkbookUpdater.js +61 -56
- package/src/parsers/XMLParser.js +50 -49
- package/src/templates/blankPptx.js +3 -1
- package/src/templates/slideTemplate.js +31 -32
- package/src/utils/contentTypesHelper.js +41 -53
- package/src/utils/errors.js +33 -23
- package/src/utils/idUtils.js +23 -15
- package/src/utils/logger.js +21 -15
- package/src/utils/relationshipUtils.js +28 -22
- package/src/utils/xmlUtils.js +37 -29
package/src/core/OutputWriter.js
CHANGED
|
@@ -8,33 +8,31 @@
|
|
|
8
8
|
* 4. Write to file, buffer, or stream
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const { writeFile, ensureDir } = fsExtra
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const logger = createLogger('OutputWriter');
|
|
11
|
+
const fsExtra = require('fs-extra')
|
|
12
|
+
const { writeFile, ensureDir } = fsExtra
|
|
13
|
+
const path = require('path')
|
|
14
|
+
const { XMLParser } = require('../parsers/XMLParser.js')
|
|
15
|
+
const { createLogger } = require('../utils/logger.js')
|
|
16
|
+
const { PPTXError } = require('../utils/errors.js')
|
|
17
|
+
const logger = createLogger('OutputWriter')
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* @class OutputWriter
|
|
23
21
|
* @description Serializes the modified PPTX to various output formats.
|
|
24
22
|
*/
|
|
25
|
-
|
|
23
|
+
class OutputWriter {
|
|
26
24
|
/** @private @type {ZipManager} */
|
|
27
|
-
#zipManager
|
|
25
|
+
#zipManager
|
|
28
26
|
/** @private @type {ContentTypesManager} */
|
|
29
|
-
#contentTypesManager
|
|
27
|
+
#contentTypesManager
|
|
30
28
|
|
|
31
29
|
/**
|
|
32
30
|
* @param {ZipManager} zipManager
|
|
33
31
|
* @param {ContentTypesManager} contentTypesManager
|
|
34
32
|
*/
|
|
35
33
|
constructor(zipManager, contentTypesManager) {
|
|
36
|
-
this.#zipManager = zipManager
|
|
37
|
-
this.#contentTypesManager = contentTypesManager
|
|
34
|
+
this.#zipManager = zipManager
|
|
35
|
+
this.#contentTypesManager = contentTypesManager
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
/**
|
|
@@ -48,14 +46,14 @@ export class OutputWriter {
|
|
|
48
46
|
*/
|
|
49
47
|
async saveToFile(filePath, slideManager, zipManager) {
|
|
50
48
|
try {
|
|
51
|
-
const buffer = await this.toBuffer(slideManager, zipManager)
|
|
52
|
-
const dir = path.dirname(filePath)
|
|
53
|
-
await ensureDir(dir)
|
|
54
|
-
await writeFile(filePath, buffer)
|
|
55
|
-
logger.info(`Saved to ${filePath} (${(buffer.length / 1024).toFixed(1)} KB)`)
|
|
49
|
+
const buffer = await this.toBuffer(slideManager, zipManager)
|
|
50
|
+
const dir = path.dirname(filePath)
|
|
51
|
+
await ensureDir(dir)
|
|
52
|
+
await writeFile(filePath, buffer)
|
|
53
|
+
logger.info(`Saved to ${filePath} (${(buffer.length / 1024).toFixed(1)} KB)`)
|
|
56
54
|
} catch (err) {
|
|
57
|
-
if (err instanceof PPTXError) throw err
|
|
58
|
-
throw new PPTXError(`Failed to save file to ${filePath}: ${err.message}`, err)
|
|
55
|
+
if (err instanceof PPTXError) throw err
|
|
56
|
+
throw new PPTXError(`Failed to save file to ${filePath}: ${err.message}`, err)
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
@@ -68,17 +66,17 @@ export class OutputWriter {
|
|
|
68
66
|
*/
|
|
69
67
|
async toBuffer(slideManager, zipManager) {
|
|
70
68
|
// Ensure all slides are flushed to the ZIP
|
|
71
|
-
await this.#flushAllSlides(slideManager, zipManager)
|
|
69
|
+
await this.#flushAllSlides(slideManager, zipManager)
|
|
72
70
|
|
|
73
71
|
// Flush Content Types safely
|
|
74
|
-
this.#contentTypesManager.flush(zipManager)
|
|
72
|
+
this.#contentTypesManager.flush(zipManager)
|
|
75
73
|
|
|
76
74
|
// Wait for any queued asynchronous writes (like content types, media hashing)
|
|
77
|
-
await zipManager.waitForPendingWrites()
|
|
75
|
+
await zipManager.waitForPendingWrites()
|
|
78
76
|
|
|
79
|
-
const buffer = await zipManager.toBuffer()
|
|
80
|
-
logger.debug(`Generated buffer: ${(buffer.length / 1024).toFixed(1)} KB`)
|
|
81
|
-
return buffer
|
|
77
|
+
const buffer = await zipManager.toBuffer()
|
|
78
|
+
logger.debug(`Generated buffer: ${(buffer.length / 1024).toFixed(1)} KB`)
|
|
79
|
+
return buffer
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
/**
|
|
@@ -89,14 +87,14 @@ export class OutputWriter {
|
|
|
89
87
|
* @returns {Promise<Readable>}
|
|
90
88
|
*/
|
|
91
89
|
async toStream(slideManager, zipManager) {
|
|
92
|
-
await this.#flushAllSlides(slideManager, zipManager)
|
|
90
|
+
await this.#flushAllSlides(slideManager, zipManager)
|
|
93
91
|
|
|
94
92
|
// Flush Content Types safely
|
|
95
|
-
this.#contentTypesManager.flush(zipManager)
|
|
93
|
+
this.#contentTypesManager.flush(zipManager)
|
|
96
94
|
|
|
97
|
-
await zipManager.waitForPendingWrites()
|
|
98
|
-
const nodeStream = await zipManager.toStream()
|
|
99
|
-
return nodeStream
|
|
95
|
+
await zipManager.waitForPendingWrites()
|
|
96
|
+
const nodeStream = await zipManager.toStream()
|
|
97
|
+
return nodeStream
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
/**
|
|
@@ -111,71 +109,76 @@ export class OutputWriter {
|
|
|
111
109
|
async #flushAllSlides(slideManager, zipManager) {
|
|
112
110
|
// SlideManager already writes to zipManager via setSlideXml,
|
|
113
111
|
// so this is mostly a no-op with a validation step.
|
|
114
|
-
const info = slideManager.getAllSlideInfo()
|
|
112
|
+
const info = slideManager.getAllSlideInfo()
|
|
115
113
|
|
|
116
114
|
for (const slide of info) {
|
|
117
115
|
if (!zipManager.hasFile(slide.zipPath)) {
|
|
118
|
-
logger.warn(`Slide file missing in ZIP: ${slide.zipPath}`)
|
|
116
|
+
logger.warn(`Slide file missing in ZIP: ${slide.zipPath}`)
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
// Update the slide count and titles in docProps/app.xml to prevent repair mode issues
|
|
123
121
|
if (zipManager.hasFile('docProps/app.xml')) {
|
|
124
122
|
zipManager.addPendingPromise(
|
|
125
|
-
zipManager.rawZip
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
countVar
|
|
123
|
+
zipManager.rawZip
|
|
124
|
+
.file('docProps/app.xml')
|
|
125
|
+
.async('text')
|
|
126
|
+
.then(content => {
|
|
127
|
+
const parser = new XMLParser()
|
|
128
|
+
const appObj = parser.parse(content, 'app.xml')
|
|
129
|
+
const properties = appObj.Properties
|
|
130
|
+
|
|
131
|
+
if (properties) {
|
|
132
|
+
// 1. Update Slides count
|
|
133
|
+
properties.Slides = info.length
|
|
134
|
+
|
|
135
|
+
// 2. Find old slide titles count and update HeadingPairs
|
|
136
|
+
let oldSlideTitlesCount = 0
|
|
137
|
+
const variants = properties.HeadingPairs?.['vt:vector']?.['vt:variant']
|
|
138
|
+
if (Array.isArray(variants)) {
|
|
139
|
+
for (let i = 0; i < variants.length; i++) {
|
|
140
|
+
if (variants[i]['vt:lpstr'] === 'Slide Titles') {
|
|
141
|
+
const countVar = variants[i + 1]
|
|
142
|
+
if (countVar) {
|
|
143
|
+
oldSlideTitlesCount = parseInt(countVar['vt:i4'], 10) || 0
|
|
144
|
+
countVar['vt:i4'] = info.length
|
|
145
|
+
}
|
|
146
|
+
break
|
|
144
147
|
}
|
|
145
|
-
break;
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
|
-
}
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
// 3. Update TitlesOfParts
|
|
152
|
+
const titlesVector = properties.TitlesOfParts?.['vt:vector']
|
|
153
|
+
if (titlesVector) {
|
|
154
|
+
let lpstrs = titlesVector['vt:lpstr']
|
|
155
|
+
if (lpstrs) {
|
|
156
|
+
if (!Array.isArray(lpstrs)) lpstrs = [lpstrs]
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
// Remove the old slide titles (which are at the end)
|
|
159
|
+
if (oldSlideTitlesCount > 0 && lpstrs.length >= oldSlideTitlesCount) {
|
|
160
|
+
lpstrs = lpstrs.slice(0, lpstrs.length - oldSlideTitlesCount)
|
|
161
|
+
}
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
// Append new slide titles
|
|
164
|
+
const newSlideTitles = info.map(slide => slide.title || `Slide ${slide.index}`)
|
|
165
|
+
lpstrs.push(...newSlideTitles)
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
titlesVector['vt:lpstr'] = lpstrs
|
|
168
|
+
titlesVector['@_size'] = String(lpstrs.length)
|
|
169
|
+
}
|
|
168
170
|
}
|
|
169
|
-
}
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
172
|
+
const declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
|
173
|
+
const updatedXml = parser.build(appObj, declaration)
|
|
174
|
+
zipManager.writeFile('docProps/app.xml', updatedXml)
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
)
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
logger.debug(`Flushed ${info.length} slide(s) to ZIP`)
|
|
180
|
+
logger.debug(`Flushed ${info.length} slide(s) to ZIP`)
|
|
180
181
|
}
|
|
181
182
|
}
|
|
183
|
+
|
|
184
|
+
module.exports = { OutputWriter }
|