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.
Files changed (37) hide show
  1. package/README.md +336 -281
  2. package/package.json +6 -6
  3. package/src/cli/commands/build.js +32 -31
  4. package/src/cli/commands/debug.js +25 -24
  5. package/src/cli/commands/extract.js +23 -21
  6. package/src/cli/commands/inspect.js +25 -23
  7. package/src/cli/commands/validate.js +19 -17
  8. package/src/cli/index.js +45 -43
  9. package/src/core/OutputWriter.js +81 -78
  10. package/src/core/PPTXTemplater.js +859 -274
  11. package/src/core/TemplateEngine.js +69 -71
  12. package/src/core/ValidationEngine.js +246 -0
  13. package/src/index.js +51 -15
  14. package/src/managers/ChartManager.js +197 -70
  15. package/src/managers/ContentTypesManager.js +51 -45
  16. package/src/managers/HyperlinkManager.js +148 -142
  17. package/src/managers/ImageManager.js +336 -0
  18. package/src/managers/MediaManager.js +64 -81
  19. package/src/managers/RelationshipManager.js +102 -96
  20. package/src/managers/ShapeManager.js +340 -0
  21. package/src/managers/SlideManager.js +410 -311
  22. package/src/managers/TableManager.js +981 -262
  23. package/src/managers/TextManager.js +197 -0
  24. package/src/managers/ZipManager.js +71 -69
  25. package/src/managers/charts/ChartCacheGenerator.js +77 -58
  26. package/src/managers/charts/ChartParser.js +11 -13
  27. package/src/managers/charts/ChartRelationshipManager.js +14 -10
  28. package/src/managers/charts/ChartWorkbookUpdater.js +61 -56
  29. package/src/parsers/XMLParser.js +50 -49
  30. package/src/templates/blankPptx.js +3 -1
  31. package/src/templates/slideTemplate.js +31 -32
  32. package/src/utils/contentTypesHelper.js +41 -53
  33. package/src/utils/errors.js +33 -23
  34. package/src/utils/idUtils.js +23 -15
  35. package/src/utils/logger.js +21 -15
  36. package/src/utils/relationshipUtils.js +28 -22
  37. package/src/utils/xmlUtils.js +37 -29
@@ -8,33 +8,31 @@
8
8
  * 4. Write to file, buffer, or stream
9
9
  */
10
10
 
11
- import fsExtra from 'fs-extra';
12
- const { writeFile, ensureDir } = fsExtra;
13
- import path from 'path';
14
- import { XMLParser } from '../parsers/XMLParser.js';
15
- import { createLogger } from '../utils/logger.js';
16
- import { PPTXError } from '../utils/errors.js';
17
- import { Readable } from 'stream';
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
- export class OutputWriter {
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.file('docProps/app.xml').async('text').then(content => {
126
- const parser = new XMLParser();
127
- const appObj = parser.parse(content, 'app.xml');
128
- const properties = appObj.Properties;
129
-
130
- if (properties) {
131
- // 1. Update Slides count
132
- properties.Slides = info.length;
133
-
134
- // 2. Find old slide titles count and update HeadingPairs
135
- let oldSlideTitlesCount = 0;
136
- const variants = properties.HeadingPairs?.['vt:vector']?.['vt:variant'];
137
- if (Array.isArray(variants)) {
138
- for (let i = 0; i < variants.length; i++) {
139
- if (variants[i]['vt:lpstr'] === 'Slide Titles') {
140
- const countVar = variants[i + 1];
141
- if (countVar) {
142
- oldSlideTitlesCount = parseInt(countVar['vt:i4'], 10) || 0;
143
- countVar['vt:i4'] = info.length;
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
- // 3. Update TitlesOfParts
151
- const titlesVector = properties.TitlesOfParts?.['vt:vector'];
152
- if (titlesVector) {
153
- let lpstrs = titlesVector['vt:lpstr'];
154
- if (lpstrs) {
155
- if (!Array.isArray(lpstrs)) lpstrs = [lpstrs];
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
- // Remove the old slide titles (which are at the end)
158
- if (oldSlideTitlesCount > 0 && lpstrs.length >= oldSlideTitlesCount) {
159
- lpstrs = lpstrs.slice(0, lpstrs.length - oldSlideTitlesCount);
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
- // Append new slide titles
163
- const newSlideTitles = info.map(slide => slide.title || `Slide ${slide.index}`);
164
- lpstrs.push(...newSlideTitles);
163
+ // Append new slide titles
164
+ const newSlideTitles = info.map(slide => slide.title || `Slide ${slide.index}`)
165
+ lpstrs.push(...newSlideTitles)
165
166
 
166
- titlesVector['vt:lpstr'] = lpstrs;
167
- titlesVector['@_size'] = String(lpstrs.length);
167
+ titlesVector['vt:lpstr'] = lpstrs
168
+ titlesVector['@_size'] = String(lpstrs.length)
169
+ }
168
170
  }
169
- }
170
171
 
171
- const declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
172
- const updatedXml = parser.build(appObj, declaration);
173
- zipManager.writeFile('docProps/app.xml', updatedXml);
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 }