node-pptx-templater 1.0.2 → 1.0.4

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 (38) hide show
  1. package/CHANGELOG.md +28 -3
  2. package/README.md +175 -327
  3. package/package.json +12 -3
  4. package/src/cli/commands/build.js +30 -31
  5. package/src/cli/commands/debug.js +23 -23
  6. package/src/cli/commands/extract.js +21 -21
  7. package/src/cli/commands/inspect.js +23 -23
  8. package/src/cli/commands/validate.js +17 -17
  9. package/src/cli/index.js +39 -36
  10. package/src/core/OutputWriter.js +79 -78
  11. package/src/core/PPTXTemplater.js +856 -273
  12. package/src/core/TemplateEngine.js +67 -71
  13. package/src/core/ValidationEngine.js +246 -0
  14. package/src/index.js +30 -17
  15. package/src/managers/ChartManager.js +195 -70
  16. package/src/managers/ContentTypesManager.js +49 -45
  17. package/src/managers/HyperlinkManager.js +146 -142
  18. package/src/managers/ImageManager.js +336 -0
  19. package/src/managers/MediaManager.js +62 -81
  20. package/src/managers/RelationshipManager.js +99 -95
  21. package/src/managers/ShapeManager.js +340 -0
  22. package/src/managers/SlideManager.js +408 -311
  23. package/src/managers/TableManager.js +979 -262
  24. package/src/managers/TextManager.js +197 -0
  25. package/src/managers/ZipManager.js +69 -69
  26. package/src/managers/charts/ChartCacheGenerator.js +75 -58
  27. package/src/managers/charts/ChartParser.js +9 -13
  28. package/src/managers/charts/ChartRelationshipManager.js +12 -10
  29. package/src/managers/charts/ChartWorkbookUpdater.js +59 -56
  30. package/src/parsers/XMLParser.js +47 -50
  31. package/src/templates/blankPptx.js +3 -2
  32. package/src/templates/slideTemplate.js +28 -34
  33. package/src/utils/contentTypesHelper.js +40 -54
  34. package/src/utils/errors.js +18 -18
  35. package/src/utils/idUtils.js +16 -14
  36. package/src/utils/logger.js +18 -16
  37. package/src/utils/relationshipUtils.js +19 -20
  38. package/src/utils/xmlUtils.js +26 -26
@@ -39,13 +39,13 @@
39
39
  * We update both to ensure compatibility with both cached and live data.
40
40
  */
41
41
 
42
- const { createLogger } = require('../utils/logger.js');
43
- const { ChartNotFoundError } = require('../utils/errors.js');
44
- const { REL_TYPES } = require('./RelationshipManager.js');
45
- const { ChartWorkbookUpdater } = require('./charts/ChartWorkbookUpdater.js');
46
- const { ChartCacheGenerator } = require('./charts/ChartCacheGenerator.js');
42
+ const { createLogger } = require('../utils/logger.js')
43
+ const { ChartNotFoundError } = require('../utils/errors.js')
44
+ const { REL_TYPES } = require('./RelationshipManager.js')
45
+ const { ChartWorkbookUpdater } = require('./charts/ChartWorkbookUpdater.js')
46
+ const { ChartCacheGenerator } = require('./charts/ChartCacheGenerator.js')
47
47
 
48
- const logger = createLogger('ChartManager');
48
+ const logger = createLogger('ChartManager')
49
49
 
50
50
  /**
51
51
  * Supported chart types and their XML element names.
@@ -60,7 +60,7 @@ const CHART_TYPE_MAP = {
60
60
  radar: 'c:radarChart',
61
61
  bubble: 'c:bubbleChart',
62
62
  stock: 'c:stockChart',
63
- };
63
+ }
64
64
 
65
65
  /**
66
66
  * @class ChartManager
@@ -71,21 +71,21 @@ const CHART_TYPE_MAP = {
71
71
  */
72
72
  class ChartManager {
73
73
  /** @private @type {XMLParser} */
74
- #xmlParser;
74
+ #xmlParser
75
75
  /** @private @type {ZipManager} */
76
- #zipManager;
76
+ #zipManager
77
77
 
78
78
  /**
79
79
  * Cache of chart ZIP paths: maps chartName → { zipPath, slideIndex }
80
80
  * @private @type {Map<string, { zipPath: string, slideIndex: number }>}
81
81
  */
82
- #chartRegistry = new Map();
82
+ #chartRegistry = new Map()
83
83
 
84
84
  /**
85
85
  * @param {XMLParser} xmlParser
86
86
  */
87
87
  constructor(xmlParser) {
88
- this.#xmlParser = xmlParser;
88
+ this.#xmlParser = xmlParser
89
89
  }
90
90
 
91
91
  /**
@@ -95,16 +95,18 @@ class ChartManager {
95
95
  * @returns {Promise<void>}
96
96
  */
97
97
  async initialize(zipManager) {
98
- this.#zipManager = zipManager;
99
- const chartFiles = zipManager.listFiles('ppt/charts/').filter(f => f.endsWith('.xml') && !f.includes('_rels'));
98
+ this.#zipManager = zipManager
99
+ const chartFiles = zipManager
100
+ .listFiles('ppt/charts/')
101
+ .filter(f => f.endsWith('.xml') && !f.includes('_rels'))
100
102
 
101
103
  for (const chartPath of chartFiles) {
102
104
  // Chart name is inferred from file name
103
- const chartName = chartPath.split('/').pop().replace('.xml', '');
104
- this.#chartRegistry.set(chartName, { zipPath: chartPath, slideIndex: null });
105
+ const chartName = chartPath.split('/').pop().replace('.xml', '')
106
+ this.#chartRegistry.set(chartName, { zipPath: chartPath, slideIndex: null })
105
107
  }
106
108
 
107
- logger.debug(`Found ${chartFiles.length} chart file(s)`);
109
+ logger.debug(`Found ${chartFiles.length} chart file(s)`)
108
110
  }
109
111
 
110
112
  /**
@@ -118,14 +120,14 @@ class ChartManager {
118
120
  * @throws {ChartNotFoundError} If the chart cannot be found.
119
121
  */
120
122
  updateChart(slideIndex, chartId, data, slideManager, relationshipManager) {
121
- const chartInfo = this.#findChartInSlide(slideIndex, chartId, slideManager, relationshipManager);
123
+ const chartInfo = this.#findChartInSlide(slideIndex, chartId, slideManager, relationshipManager)
122
124
 
123
125
  if (!chartInfo) {
124
- throw new ChartNotFoundError(`Chart "${chartId}" not found in slide ${slideIndex}`);
126
+ throw new ChartNotFoundError(`Chart "${chartId}" not found in slide ${slideIndex}`)
125
127
  }
126
128
 
127
- logger.debug(`Updating chart "${chartId}" at ${chartInfo.zipPath}`);
128
- this.#updateChartXml(chartInfo.zipPath, data, relationshipManager);
129
+ logger.debug(`Updating chart "${chartId}" at ${chartInfo.zipPath}`)
130
+ this.#updateChartXml(chartInfo.zipPath, data, relationshipManager)
129
131
  }
130
132
 
131
133
  /**
@@ -137,13 +139,13 @@ class ChartManager {
137
139
  * @returns {Array<{name: string, zipPath: string}>}
138
140
  */
139
141
  getChartsInSlide(slideIndex, slideManager, relationshipManager) {
140
- const slideInfo = slideManager.getSlideInfo(slideIndex);
141
- const rels = relationshipManager.getRelationshipsByType(slideInfo.zipPath, REL_TYPES.CHART);
142
+ const slideInfo = slideManager.getSlideInfo(slideIndex)
143
+ const rels = relationshipManager.getRelationshipsByType(slideInfo.zipPath, REL_TYPES.CHART)
142
144
 
143
145
  return rels.map(rel => {
144
- const chartPath = relationshipManager.resolveTarget(slideInfo.zipPath, rel.target);
145
- return { rId: rel.id, zipPath: chartPath };
146
- });
146
+ const chartPath = relationshipManager.resolveTarget(slideInfo.zipPath, rel.target)
147
+ return { rId: rel.id, zipPath: chartPath }
148
+ })
147
149
  }
148
150
 
149
151
  /**
@@ -157,58 +159,58 @@ class ChartManager {
157
159
  * @returns {{ zipPath: string }|null}
158
160
  */
159
161
  #findChartInSlide(slideIndex, chartId, slideManager, relationshipManager) {
160
- const slideInfo = slideManager.getSlideInfo(slideIndex);
161
- const slideXml = slideManager.getSlideXml(slideIndex);
162
+ const slideInfo = slideManager.getSlideInfo(slideIndex)
163
+ const slideXml = slideManager.getSlideXml(slideIndex)
162
164
 
163
165
  // Strategy 1: Look for shape with matching name (cNvPr name attribute)
164
166
  const shapeNamePattern = new RegExp(
165
167
  `<p:cNvPr[^>]*name="${chartId}"[^>]*>(?:.*?)<c:chart[^>]*r:id="(rId\\d+)"`,
166
168
  's'
167
- );
168
- const rIdMatch = shapeNamePattern.exec(slideXml);
169
+ )
170
+ const rIdMatch = shapeNamePattern.exec(slideXml)
169
171
 
170
172
  // Strategy 2: Find graphicFrame shapes and match chart rIds
171
173
  if (!rIdMatch) {
172
- const chartRIdPattern = /<c:chart[^>]*r:id="(rId\d+)"/g;
173
- let chartMatch;
174
+ const chartRIdPattern = /<c:chart[^>]*r:id="(rId\d+)"/g
175
+ let chartMatch
174
176
  while ((chartMatch = chartRIdPattern.exec(slideXml)) !== null) {
175
- const rId = chartMatch[1];
176
- const rel = relationshipManager.getRelationshipById(slideInfo.zipPath, rId);
177
+ const rId = chartMatch[1]
178
+ const rel = relationshipManager.getRelationshipById(slideInfo.zipPath, rId)
177
179
  if (rel) {
178
- const chartPath = relationshipManager.resolveTarget(slideInfo.zipPath, rel.target);
180
+ const chartPath = relationshipManager.resolveTarget(slideInfo.zipPath, rel.target)
179
181
  // Check if chart file name matches chartId
180
182
  if (chartPath.includes(chartId) || rel.id === chartId) {
181
- return { zipPath: chartPath };
183
+ return { zipPath: chartPath }
182
184
  }
183
185
  // For the first chart found, if chartId looks like a chart file name
184
186
  if (chartId.startsWith('chart')) {
185
- return { zipPath: chartPath };
187
+ return { zipPath: chartPath }
186
188
  }
187
189
  }
188
190
  }
189
191
  }
190
192
 
191
193
  if (rIdMatch) {
192
- const rId = rIdMatch[1];
193
- const rel = relationshipManager.getRelationshipById(slideInfo.zipPath, rId);
194
+ const rId = rIdMatch[1]
195
+ const rel = relationshipManager.getRelationshipById(slideInfo.zipPath, rId)
194
196
  if (rel) {
195
- const chartPath = relationshipManager.resolveTarget(slideInfo.zipPath, rel.target);
196
- return { zipPath: chartPath };
197
+ const chartPath = relationshipManager.resolveTarget(slideInfo.zipPath, rel.target)
198
+ return { zipPath: chartPath }
197
199
  }
198
200
  }
199
201
 
200
202
  // Strategy 3: Direct chart registry lookup
201
203
  if (this.#chartRegistry.has(chartId)) {
202
- return this.#chartRegistry.get(chartId);
204
+ return this.#chartRegistry.get(chartId)
203
205
  }
204
206
 
205
207
  // Strategy 4: Try chartN naming convention
206
- const chartPath = `ppt/charts/${chartId}.xml`;
208
+ const chartPath = `ppt/charts/${chartId}.xml`
207
209
  if (this.#zipManager.hasFile(chartPath)) {
208
- return { zipPath: chartPath };
210
+ return { zipPath: chartPath }
209
211
  }
210
212
 
211
- return null;
213
+ return null
212
214
  }
213
215
 
214
216
  /**
@@ -222,13 +224,13 @@ class ChartManager {
222
224
  */
223
225
  #updateChartXml(chartZipPath, data, relationshipManager) {
224
226
  if (!this.#zipManager.hasFile(chartZipPath)) {
225
- throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`);
227
+ throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`)
226
228
  }
227
229
 
228
230
  // Register async update to ensure it completes before saving
229
231
  this.#zipManager.addPendingPromise(
230
232
  this.updateChartAsync(chartZipPath, data, relationshipManager)
231
- );
233
+ )
232
234
  }
233
235
 
234
236
  /**
@@ -241,29 +243,29 @@ class ChartManager {
241
243
  */
242
244
  async updateChartAsync(chartZipPath, data, relationshipManager) {
243
245
  // 1. Read Chart XML
244
- const xml = await this.#zipManager.readFile(chartZipPath);
245
- if (!xml) throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`);
246
+ const xml = await this.#zipManager.readFile(chartZipPath)
247
+ if (!xml) throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`)
246
248
 
247
249
  // 2. Apply Chart XML Updates
248
- const updatedXml = this.#applyChartData(xml, data, chartZipPath);
249
- this.#zipManager.writeFile(chartZipPath, updatedXml);
250
+ const updatedXml = this.#applyChartData(xml, data, chartZipPath)
251
+ this.#zipManager.writeFile(chartZipPath, updatedXml)
250
252
 
251
253
  // 3. Find and Update Embedded Workbook
252
254
  if (relationshipManager) {
253
- const rels = relationshipManager.getRelationshipsByType(chartZipPath, REL_TYPES.PACKAGE);
255
+ const rels = relationshipManager.getRelationshipsByType(chartZipPath, REL_TYPES.PACKAGE)
254
256
  for (const rel of rels) {
255
- const xlsxPath = relationshipManager.resolveTarget(chartZipPath, rel.target);
256
- const xlsxData = this.#zipManager.rawZip.file(xlsxPath);
257
+ const xlsxPath = relationshipManager.resolveTarget(chartZipPath, rel.target)
258
+ const xlsxData = this.#zipManager.rawZip.file(xlsxPath)
257
259
  if (xlsxData) {
258
- console.log(`Found embedded workbook: ${xlsxPath}`);
259
- const buffer = await xlsxData.async('nodebuffer');
260
- const updatedXlsx = await ChartWorkbookUpdater.updateWorkbook(buffer, data);
260
+ console.log(`Found embedded workbook: ${xlsxPath}`)
261
+ const buffer = await xlsxData.async('nodebuffer')
262
+ const updatedXlsx = await ChartWorkbookUpdater.updateWorkbook(buffer, data)
261
263
  if (updatedXlsx) {
262
- console.log(`Writing updated workbook to: ${xlsxPath}, size: ${updatedXlsx.length}`);
263
- this.#zipManager.writeBinaryFile(xlsxPath, updatedXlsx);
264
+ console.log(`Writing updated workbook to: ${xlsxPath}, size: ${updatedXlsx.length}`)
265
+ this.#zipManager.writeBinaryFile(xlsxPath, updatedXlsx)
264
266
  }
265
267
  } else {
266
- console.log(`Could not find workbook at: ${xlsxPath}`);
268
+ console.log(`Could not find workbook at: ${xlsxPath}`)
267
269
  }
268
270
  }
269
271
  }
@@ -279,27 +281,150 @@ class ChartManager {
279
281
  * @returns {string} Updated XML.
280
282
  */
281
283
  #applyChartData(xml, data, context) {
282
- const { categories, series } = data;
284
+ const { categories, series } = data
283
285
 
284
286
  // Detect chart type
285
- const chartType = this.#detectChartType(xml);
286
- logger.debug(`Updating ${chartType} chart at ${context}`);
287
+ const chartType = this.#detectChartType(xml)
288
+ logger.debug(`Updating ${chartType} chart at ${context}`)
287
289
 
288
- let updatedXml = xml;
290
+ let updatedXml = xml
289
291
 
290
292
  if (series && series.length > 0) {
291
- updatedXml = ChartCacheGenerator.appendDynamicSeries(updatedXml, series.length);
293
+ updatedXml = ChartCacheGenerator.appendDynamicSeries(updatedXml, series.length)
292
294
  }
293
295
 
294
296
  if (categories && categories.length > 0) {
295
- updatedXml = ChartCacheGenerator.updateCategories(updatedXml, categories);
297
+ updatedXml = ChartCacheGenerator.updateCategories(updatedXml, categories)
296
298
  }
297
299
 
298
300
  if (series && series.length > 0) {
299
- updatedXml = ChartCacheGenerator.updateSeries(updatedXml, series, categories ? categories.length : null);
301
+ updatedXml = ChartCacheGenerator.updateSeries(
302
+ updatedXml,
303
+ series,
304
+ categories ? categories.length : null
305
+ )
306
+ }
307
+
308
+ return updatedXml
309
+ }
310
+
311
+ /**
312
+ * Updates only chart categories.
313
+ */
314
+ updateChartCategories(slideIndex, chartId, categories, slideManager, relationshipManager) {
315
+ const chartInfo = this.#findChartInSlide(slideIndex, chartId, slideManager, relationshipManager)
316
+ if (!chartInfo) {
317
+ throw new ChartNotFoundError(`Chart "${chartId}" not found in slide ${slideIndex}`)
318
+ }
319
+ this.#zipManager.addPendingPromise(
320
+ this.updateChartCategoriesAsync(chartInfo.zipPath, categories, relationshipManager)
321
+ )
322
+ }
323
+
324
+ async updateChartCategoriesAsync(chartZipPath, categories, relationshipManager) {
325
+ const xml = await this.#zipManager.readFile(chartZipPath)
326
+ if (!xml) throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`)
327
+ const data = this.#extractChartData(xml)
328
+ data.categories = categories
329
+ await this.updateChartAsync(chartZipPath, data, relationshipManager)
330
+ }
331
+
332
+ /**
333
+ * Replaces a specific chart series.
334
+ */
335
+ replaceChartSeries(
336
+ slideIndex,
337
+ chartId,
338
+ seriesIndex,
339
+ newSeriesData,
340
+ slideManager,
341
+ relationshipManager
342
+ ) {
343
+ const chartInfo = this.#findChartInSlide(slideIndex, chartId, slideManager, relationshipManager)
344
+ if (!chartInfo) {
345
+ throw new ChartNotFoundError(`Chart "${chartId}" not found in slide ${slideIndex}`)
346
+ }
347
+ this.#zipManager.addPendingPromise(
348
+ this.replaceChartSeriesAsync(
349
+ chartInfo.zipPath,
350
+ seriesIndex,
351
+ newSeriesData,
352
+ relationshipManager
353
+ )
354
+ )
355
+ }
356
+
357
+ async replaceChartSeriesAsync(chartZipPath, seriesIndex, newSeriesData, relationshipManager) {
358
+ const xml = await this.#zipManager.readFile(chartZipPath)
359
+ if (!xml) throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`)
360
+ const data = this.#extractChartData(xml)
361
+ data.series[seriesIndex] = newSeriesData
362
+ await this.updateChartAsync(chartZipPath, data, relationshipManager)
363
+ }
364
+
365
+ /**
366
+ * Updates the chart title.
367
+ */
368
+ updateChartTitle(slideIndex, chartId, title, slideManager, relationshipManager) {
369
+ const chartInfo = this.#findChartInSlide(slideIndex, chartId, slideManager, relationshipManager)
370
+ if (!chartInfo) {
371
+ throw new ChartNotFoundError(`Chart "${chartId}" not found in slide ${slideIndex}`)
372
+ }
373
+ this.#zipManager.addPendingPromise(this.updateChartTitleAsync(chartInfo.zipPath, title))
374
+ }
375
+
376
+ async updateChartTitleAsync(chartZipPath, title) {
377
+ const xml = await this.#zipManager.readFile(chartZipPath)
378
+ if (!xml) throw new ChartNotFoundError(`Chart file not found: ${chartZipPath}`)
379
+ const updatedXml = ChartCacheGenerator.updateTitle(xml, title)
380
+ this.#zipManager.writeFile(chartZipPath, updatedXml)
381
+ }
382
+
383
+ /**
384
+ * Helper to extract data from chart XML.
385
+ */
386
+ #extractChartData(xml) {
387
+ const categories = []
388
+ const series = []
389
+
390
+ const catMatch = /<c:cat>([\s\S]*?)<\/c:cat>/.exec(xml)
391
+ if (catMatch) {
392
+ const catXml = catMatch[1]
393
+ const ptPattern = /<c:pt idx="\d+">\s*<c:v>([^<]*)<\/c:v>/g
394
+ let match
395
+ while ((match = ptPattern.exec(catXml)) !== null) {
396
+ categories.push(match[1])
397
+ }
398
+ }
399
+
400
+ const serPattern = /<c:ser>([\s\S]*?)<\/c:ser>/g
401
+ let serMatch
402
+ let idx = 0
403
+ while ((serMatch = serPattern.exec(xml)) !== null) {
404
+ const serXml = serMatch[1]
405
+
406
+ let name = `Series ${idx + 1}`
407
+ const txMatch = /<c:tx>([\s\S]*?)<\/c:tx>/.exec(serXml)
408
+ if (txMatch) {
409
+ const nameValMatch = /<c:v>([^<]*)<\/c:v>/.exec(txMatch[1])
410
+ if (nameValMatch) name = nameValMatch[1]
411
+ }
412
+
413
+ const values = []
414
+ const valMatch = /<c:val>([\s\S]*?)<\/c:val>/.exec(serXml)
415
+ if (valMatch) {
416
+ const ptPattern = /<c:pt idx="\d+">\s*<c:v>([^<]*)<\/c:v>/g
417
+ let match
418
+ while ((match = ptPattern.exec(valMatch[1])) !== null) {
419
+ values.push(Number(match[1]) || 0)
420
+ }
421
+ }
422
+
423
+ series.push({ name, values })
424
+ idx++
300
425
  }
301
426
 
302
- return updatedXml;
427
+ return { categories, series }
303
428
  }
304
429
 
305
430
  /**
@@ -310,10 +435,10 @@ class ChartManager {
310
435
  */
311
436
  #detectChartType(xml) {
312
437
  for (const [name, element] of Object.entries(CHART_TYPE_MAP)) {
313
- if (xml.includes(element)) return name;
438
+ if (xml.includes(element)) return name
314
439
  }
315
- return 'unknown';
440
+ return 'unknown'
316
441
  }
317
442
  }
318
443
 
319
- module.exports = { ChartManager };
444
+ module.exports = { ChartManager }
@@ -4,25 +4,25 @@
4
4
  * Implements structured, XML-safe manipulation of the OPC manifest.
5
5
  */
6
6
 
7
- const { createLogger } = require('../utils/logger.js');
8
- const { PPTXError } = require('../utils/errors.js');
7
+ const { createLogger } = require('../utils/logger.js')
8
+ const { PPTXError } = require('../utils/errors.js')
9
9
 
10
- const logger = createLogger('ContentTypesManager');
10
+ const logger = createLogger('ContentTypesManager')
11
11
 
12
- const TYPES_XML_PATH = '[Content_Types].xml';
12
+ const TYPES_XML_PATH = '[Content_Types].xml'
13
13
 
14
14
  class ContentTypesManager {
15
15
  /** @private @type {XMLParser} */
16
- #xmlParser;
16
+ #xmlParser
17
17
 
18
18
  /** @private @type {Object} */
19
- #contentTypesObj = null;
19
+ #contentTypesObj = null
20
20
 
21
21
  /**
22
22
  * @param {XMLParser} xmlParser
23
23
  */
24
24
  constructor(xmlParser) {
25
- this.#xmlParser = xmlParser;
25
+ this.#xmlParser = xmlParser
26
26
  }
27
27
 
28
28
  /**
@@ -32,36 +32,38 @@ class ContentTypesManager {
32
32
  * @returns {Promise<void>}
33
33
  */
34
34
  async initialize(zipManager) {
35
- const content = await zipManager.readFile(TYPES_XML_PATH);
35
+ const content = await zipManager.readFile(TYPES_XML_PATH)
36
36
  if (!content) {
37
- throw new PPTXError(`${TYPES_XML_PATH} is missing from the archive.`);
37
+ throw new PPTXError(`${TYPES_XML_PATH} is missing from the archive.`)
38
38
  }
39
39
 
40
- this.#contentTypesObj = this.#xmlParser.parse(content, TYPES_XML_PATH);
40
+ this.#contentTypesObj = this.#xmlParser.parse(content, TYPES_XML_PATH)
41
41
 
42
42
  // Ensure structure is correct
43
43
  if (!this.#contentTypesObj.Types) {
44
44
  this.#contentTypesObj.Types = {
45
45
  '@_xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types',
46
46
  Default: [],
47
- Override: []
48
- };
47
+ Override: [],
48
+ }
49
49
  }
50
50
 
51
51
  // Ensure array properties
52
52
  if (!this.#contentTypesObj.Types.Default) {
53
- this.#contentTypesObj.Types.Default = [];
53
+ this.#contentTypesObj.Types.Default = []
54
54
  } else if (!Array.isArray(this.#contentTypesObj.Types.Default)) {
55
- this.#contentTypesObj.Types.Default = [this.#contentTypesObj.Types.Default];
55
+ this.#contentTypesObj.Types.Default = [this.#contentTypesObj.Types.Default]
56
56
  }
57
57
 
58
58
  if (!this.#contentTypesObj.Types.Override) {
59
- this.#contentTypesObj.Types.Override = [];
59
+ this.#contentTypesObj.Types.Override = []
60
60
  } else if (!Array.isArray(this.#contentTypesObj.Types.Override)) {
61
- this.#contentTypesObj.Types.Override = [this.#contentTypesObj.Types.Override];
61
+ this.#contentTypesObj.Types.Override = [this.#contentTypesObj.Types.Override]
62
62
  }
63
63
 
64
- logger.debug(`Loaded [Content_Types].xml with ${this.#contentTypesObj.Types.Default.length} Defaults and ${this.#contentTypesObj.Types.Override.length} Overrides`);
64
+ logger.debug(
65
+ `Loaded [Content_Types].xml with ${this.#contentTypesObj.Types.Default.length} Defaults and ${this.#contentTypesObj.Types.Override.length} Overrides`
66
+ )
65
67
  }
66
68
 
67
69
  /**
@@ -71,18 +73,18 @@ class ContentTypesManager {
71
73
  * @param {string} contentType - The MIME type.
72
74
  */
73
75
  addDefault(extension, contentType) {
74
- const extLower = extension.toLowerCase();
75
- const defaults = this.#contentTypesObj.Types.Default;
76
+ const extLower = extension.toLowerCase()
77
+ const defaults = this.#contentTypesObj.Types.Default
76
78
 
77
- const existing = defaults.find(d => d['@_Extension']?.toLowerCase() === extLower);
79
+ const existing = defaults.find(d => d['@_Extension']?.toLowerCase() === extLower)
78
80
  if (existing) {
79
- existing['@_ContentType'] = contentType;
81
+ existing['@_ContentType'] = contentType
80
82
  } else {
81
83
  defaults.push({
82
84
  '@_Extension': extLower,
83
- '@_ContentType': contentType
84
- });
85
- logger.debug(`Registered default content type for extension .${extLower} -> ${contentType}`);
85
+ '@_ContentType': contentType,
86
+ })
87
+ logger.debug(`Registered default content type for extension .${extLower} -> ${contentType}`)
86
88
  }
87
89
  }
88
90
 
@@ -93,18 +95,18 @@ class ContentTypesManager {
93
95
  * @param {string} contentType - The MIME type.
94
96
  */
95
97
  addOverride(partName, contentType) {
96
- const normalizedPart = partName.startsWith('/') ? partName : `/${partName}`;
97
- const overrides = this.#contentTypesObj.Types.Override;
98
+ const normalizedPart = partName.startsWith('/') ? partName : `/${partName}`
99
+ const overrides = this.#contentTypesObj.Types.Override
98
100
 
99
- const existing = overrides.find(o => o['@_PartName'] === normalizedPart);
101
+ const existing = overrides.find(o => o['@_PartName'] === normalizedPart)
100
102
  if (existing) {
101
- existing['@_ContentType'] = contentType;
103
+ existing['@_ContentType'] = contentType
102
104
  } else {
103
105
  overrides.push({
104
106
  '@_PartName': normalizedPart,
105
- '@_ContentType': contentType
106
- });
107
- logger.debug(`Registered override content type for ${normalizedPart} -> ${contentType}`);
107
+ '@_ContentType': contentType,
108
+ })
109
+ logger.debug(`Registered override content type for ${normalizedPart} -> ${contentType}`)
108
110
  }
109
111
  }
110
112
 
@@ -114,13 +116,13 @@ class ContentTypesManager {
114
116
  * @param {string} partName - Absolute part path (e.g., '/ppt/slides/slide1.xml').
115
117
  */
116
118
  removeOverride(partName) {
117
- const normalizedPart = partName.startsWith('/') ? partName : `/${partName}`;
118
- const overrides = this.#contentTypesObj.Types.Override;
119
+ const normalizedPart = partName.startsWith('/') ? partName : `/${partName}`
120
+ const overrides = this.#contentTypesObj.Types.Override
119
121
 
120
- const filtered = overrides.filter(o => o['@_PartName'] !== normalizedPart);
122
+ const filtered = overrides.filter(o => o['@_PartName'] !== normalizedPart)
121
123
  if (filtered.length !== overrides.length) {
122
- this.#contentTypesObj.Types.Override = filtered;
123
- logger.debug(`Removed content type override for ${normalizedPart}`);
124
+ this.#contentTypesObj.Types.Override = filtered
125
+ logger.debug(`Removed content type override for ${normalizedPart}`)
124
126
  }
125
127
  }
126
128
 
@@ -131,8 +133,10 @@ class ContentTypesManager {
131
133
  * @returns {boolean}
132
134
  */
133
135
  hasDefault(extension) {
134
- const extLower = extension.toLowerCase();
135
- return this.#contentTypesObj.Types.Default.some(d => d['@_Extension']?.toLowerCase() === extLower);
136
+ const extLower = extension.toLowerCase()
137
+ return this.#contentTypesObj.Types.Default.some(
138
+ d => d['@_Extension']?.toLowerCase() === extLower
139
+ )
136
140
  }
137
141
 
138
142
  /**
@@ -142,8 +146,8 @@ class ContentTypesManager {
142
146
  * @returns {boolean}
143
147
  */
144
148
  hasOverride(partName) {
145
- const normalizedPart = partName.startsWith('/') ? partName : `/${partName}`;
146
- return this.#contentTypesObj.Types.Override.some(o => o['@_PartName'] === normalizedPart);
149
+ const normalizedPart = partName.startsWith('/') ? partName : `/${partName}`
150
+ return this.#contentTypesObj.Types.Override.some(o => o['@_PartName'] === normalizedPart)
147
151
  }
148
152
 
149
153
  /**
@@ -152,11 +156,11 @@ class ContentTypesManager {
152
156
  * @param {ZipManager} zipManager
153
157
  */
154
158
  flush(zipManager) {
155
- const declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
156
- const xml = this.#xmlParser.build(this.#contentTypesObj, declaration);
157
- zipManager.writeFile(TYPES_XML_PATH, xml);
158
- logger.debug(`Flushed ${TYPES_XML_PATH}`);
159
+ const declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
160
+ const xml = this.#xmlParser.build(this.#contentTypesObj, declaration)
161
+ zipManager.writeFile(TYPES_XML_PATH, xml)
162
+ logger.debug(`Flushed ${TYPES_XML_PATH}`)
159
163
  }
160
164
  }
161
165
 
162
- module.exports = { ContentTypesManager };
166
+ module.exports = { ContentTypesManager }