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
@@ -31,29 +31,29 @@
31
31
  * Use <a:hlinkMouseOver r:id="..."/> instead of hlinkClick.
32
32
  */
33
33
 
34
- import { createLogger } from '../utils/logger.js';
35
- import { PPTXError } from '../utils/errors.js';
36
- import { REL_TYPES } from './RelationshipManager.js';
34
+ const { createLogger } = require('../utils/logger.js')
35
+ const { PPTXError } = require('../utils/errors.js')
36
+ const { REL_TYPES } = require('./RelationshipManager.js')
37
37
 
38
- const logger = createLogger('HyperlinkManager');
38
+ const logger = createLogger('HyperlinkManager')
39
39
 
40
40
  /**
41
41
  * @class HyperlinkManager
42
42
  * @description Manages hyperlink creation and modification in PPTX slides.
43
43
  */
44
- export class HyperlinkManager {
44
+ class HyperlinkManager {
45
45
  /** @private @type {XMLParser} */
46
- #xmlParser;
46
+ #xmlParser
47
47
  /** @private @type {RelationshipManager} */
48
- #relationshipManager;
48
+ #relationshipManager
49
49
 
50
50
  /**
51
51
  * @param {XMLParser} xmlParser
52
52
  * @param {RelationshipManager} relationshipManager
53
53
  */
54
54
  constructor(xmlParser, relationshipManager) {
55
- this.#xmlParser = xmlParser;
56
- this.#relationshipManager = relationshipManager;
55
+ this.#xmlParser = xmlParser
56
+ this.#relationshipManager = relationshipManager
57
57
  }
58
58
 
59
59
  /**
@@ -68,9 +68,9 @@ export class HyperlinkManager {
68
68
  * @param {RelationshipManager} relationshipManager
69
69
  */
70
70
  addExternalHyperlink(slideIndex, options, slideManager, relationshipManager) {
71
- const { text, url, tooltip } = options;
72
- const slideInfo = slideManager.getSlideInfo(slideIndex);
73
- const slideXml = slideManager.getSlideXml(slideIndex);
71
+ const { text, url, tooltip } = options
72
+ const slideInfo = slideManager.getSlideInfo(slideIndex)
73
+ const slideXml = slideManager.getSlideXml(slideIndex)
74
74
 
75
75
  // Add the hyperlink relationship to the slide's .rels file
76
76
  const rId = relationshipManager.addRelationship(
@@ -78,13 +78,13 @@ export class HyperlinkManager {
78
78
  REL_TYPES.HYPERLINK,
79
79
  url,
80
80
  'External'
81
- );
81
+ )
82
82
 
83
83
  // Update the slide XML to reference the new rId
84
- const updatedXml = this.#injectHyperlinkOnText(slideXml, text, rId, tooltip);
85
- slideManager.setSlideXml(slideIndex, updatedXml);
84
+ const updatedXml = this.#injectHyperlinkOnText(slideXml, text, rId, tooltip)
85
+ slideManager.setSlideXml(slideIndex, updatedXml)
86
86
 
87
- logger.debug(`Added hyperlink to "${text}" → ${url} (${rId}) in slide ${slideIndex}`);
87
+ logger.debug(`Added hyperlink to "${text}" → ${url} (${rId}) in slide ${slideIndex}`)
88
88
  }
89
89
 
90
90
  /**
@@ -96,25 +96,25 @@ export class HyperlinkManager {
96
96
  * @param {RelationshipManager} relationshipManager
97
97
  */
98
98
  addSlideHyperlink(sourceSlideIndex, targetSlideIndex, slideManager, relationshipManager) {
99
- const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex);
100
- const targetInfo = slideManager.getSlideInfo(targetSlideIndex);
99
+ const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex)
100
+ const targetInfo = slideManager.getSlideInfo(targetSlideIndex)
101
101
 
102
102
  // Build relative target path from source to target slide
103
- const relativePath = `../slides/${targetInfo.zipPath.split('/').pop()}`;
103
+ const relativePath = `../slides/${targetInfo.zipPath.split('/').pop()}`
104
104
 
105
105
  // Add relationship pointing to the target slide
106
106
  const rId = relationshipManager.addRelationship(
107
107
  sourceInfo.zipPath,
108
108
  REL_TYPES.SLIDE,
109
109
  relativePath
110
- );
110
+ )
111
111
 
112
112
  // Add hlinkClick with slide jump action to the slide number placeholder
113
- const slideXml = slideManager.getSlideXml(sourceSlideIndex);
114
- const updatedXml = this.#injectSlideJumpHyperlink(slideXml, rId);
115
- slideManager.setSlideXml(sourceSlideIndex, updatedXml);
113
+ const slideXml = slideManager.getSlideXml(sourceSlideIndex)
114
+ const updatedXml = this.#injectSlideJumpHyperlink(slideXml, rId)
115
+ slideManager.setSlideXml(sourceSlideIndex, updatedXml)
116
116
 
117
- logger.debug(`Linked slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`);
117
+ logger.debug(`Linked slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`)
118
118
  }
119
119
 
120
120
  /**
@@ -127,23 +127,25 @@ export class HyperlinkManager {
127
127
  * @param {RelationshipManager} relationshipManager
128
128
  */
129
129
  addTextSlideLink(sourceSlideIndex, text, targetSlideIndex, slideManager, relationshipManager) {
130
- const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex);
131
- const targetInfo = slideManager.getSlideInfo(targetSlideIndex);
130
+ const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex)
131
+ const targetInfo = slideManager.getSlideInfo(targetSlideIndex)
132
132
 
133
- const relativePath = `../slides/${targetInfo.zipPath.split('/').pop()}`;
133
+ const relativePath = `../slides/${targetInfo.zipPath.split('/').pop()}`
134
134
  const rId = relationshipManager.addRelationship(
135
135
  sourceInfo.zipPath,
136
136
  REL_TYPES.SLIDE,
137
137
  relativePath
138
- );
138
+ )
139
139
 
140
- const slideXml = slideManager.getSlideXml(sourceSlideIndex);
140
+ const slideXml = slideManager.getSlideXml(sourceSlideIndex)
141
141
  // Use injectHyperlinkOnText but append action attribute
142
- const actionAttr = 'action="ppaction://hlinksldjump"';
143
- const updatedXml = this.#injectHyperlinkOnText(slideXml, text, rId, null, actionAttr);
144
- slideManager.setSlideXml(sourceSlideIndex, updatedXml);
142
+ const actionAttr = 'action="ppaction://hlinksldjump"'
143
+ const updatedXml = this.#injectHyperlinkOnText(slideXml, text, rId, null, actionAttr)
144
+ slideManager.setSlideXml(sourceSlideIndex, updatedXml)
145
145
 
146
- logger.debug(`Linked text "${text}" on slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`);
146
+ logger.debug(
147
+ `Linked text "${text}" on slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`
148
+ )
147
149
  }
148
150
 
149
151
  /**
@@ -155,23 +157,31 @@ export class HyperlinkManager {
155
157
  * @param {SlideManager} slideManager
156
158
  * @param {RelationshipManager} relationshipManager
157
159
  */
158
- addShapeSlideLink(sourceSlideIndex, shapeName, targetSlideIndex, slideManager, relationshipManager) {
159
- const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex);
160
- const targetInfo = slideManager.getSlideInfo(targetSlideIndex);
161
-
162
- const relativePath = `../slides/${targetInfo.zipPath.split('/').pop()}`;
160
+ addShapeSlideLink(
161
+ sourceSlideIndex,
162
+ shapeName,
163
+ targetSlideIndex,
164
+ slideManager,
165
+ relationshipManager
166
+ ) {
167
+ const sourceInfo = slideManager.getSlideInfo(sourceSlideIndex)
168
+ const targetInfo = slideManager.getSlideInfo(targetSlideIndex)
169
+
170
+ const relativePath = `../slides/${targetInfo.zipPath.split('/').pop()}`
163
171
  const rId = relationshipManager.addRelationship(
164
172
  sourceInfo.zipPath,
165
173
  REL_TYPES.SLIDE,
166
174
  relativePath
167
- );
175
+ )
168
176
 
169
- const slideXml = slideManager.getSlideXml(sourceSlideIndex);
170
- const actionAttr = 'action="ppaction://hlinksldjump"';
171
- const updatedXml = this.#injectHyperlinkOnShape(slideXml, shapeName, rId, actionAttr);
172
- slideManager.setSlideXml(sourceSlideIndex, updatedXml);
177
+ const slideXml = slideManager.getSlideXml(sourceSlideIndex)
178
+ const actionAttr = 'action="ppaction://hlinksldjump"'
179
+ const updatedXml = this.#injectHyperlinkOnShape(slideXml, shapeName, rId, actionAttr)
180
+ slideManager.setSlideXml(sourceSlideIndex, updatedXml)
173
181
 
174
- logger.debug(`Linked shape "${shapeName}" on slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`);
182
+ logger.debug(
183
+ `Linked shape "${shapeName}" on slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`
184
+ )
175
185
  }
176
186
 
177
187
  /**
@@ -184,19 +194,19 @@ export class HyperlinkManager {
184
194
  * @param {RelationshipManager} relationshipManager
185
195
  */
186
196
  addShapeHyperlink(slideIndex, shapeName, url, slideManager, relationshipManager) {
187
- const slideInfo = slideManager.getSlideInfo(slideIndex);
188
- const slideXml = slideManager.getSlideXml(slideIndex);
197
+ const slideInfo = slideManager.getSlideInfo(slideIndex)
198
+ const slideXml = slideManager.getSlideXml(slideIndex)
189
199
 
190
200
  const rId = relationshipManager.addRelationship(
191
201
  slideInfo.zipPath,
192
202
  REL_TYPES.HYPERLINK,
193
203
  url,
194
204
  'External'
195
- );
205
+ )
196
206
 
197
- const updatedXml = this.#injectHyperlinkOnShape(slideXml, shapeName, rId);
198
- slideManager.setSlideXml(slideIndex, updatedXml);
199
- logger.debug(`Added shape hyperlink on "${shapeName}" → ${url}`);
207
+ const updatedXml = this.#injectHyperlinkOnShape(slideXml, shapeName, rId)
208
+ slideManager.setSlideXml(slideIndex, updatedXml)
209
+ logger.debug(`Added shape hyperlink on "${shapeName}" → ${url}`)
200
210
  }
201
211
 
202
212
  /**
@@ -208,24 +218,24 @@ export class HyperlinkManager {
208
218
  * @param {RelationshipManager} relationshipManager
209
219
  */
210
220
  removeHyperlink(slideIndex, text, slideManager, relationshipManager) {
211
- const slideXml = slideManager.getSlideXml(slideIndex);
212
- const slideInfo = slideManager.getSlideInfo(slideIndex);
221
+ const slideXml = slideManager.getSlideXml(slideIndex)
222
+ const slideInfo = slideManager.getSlideInfo(slideIndex)
213
223
 
214
224
  // Find the rId for this hyperlink
215
225
  const hlinkPattern = new RegExp(
216
226
  `<a:hlinkClick[^>]*r:id="(rId\\d+)"[^/]*/>[\\s\\S]*?<a:t>${this.#escapeRegex(text)}</a:t>`
217
- );
218
- const match = hlinkPattern.exec(slideXml);
227
+ )
228
+ const match = hlinkPattern.exec(slideXml)
219
229
 
220
230
  if (match) {
221
- const rId = match[1];
231
+ const rId = match[1]
222
232
  // Remove the hlinkClick attribute from the rPr
223
233
  const updatedXml = slideXml.replace(
224
234
  new RegExp(`<a:hlinkClick[^>]*r:id="${rId}"[^/]*/>`, 'g'),
225
235
  ''
226
- );
227
- slideManager.setSlideXml(slideIndex, updatedXml);
228
- relationshipManager.removeRelationship(slideInfo.zipPath, rId);
236
+ )
237
+ slideManager.setSlideXml(slideIndex, updatedXml)
238
+ relationshipManager.removeRelationship(slideInfo.zipPath, rId)
229
239
  }
230
240
  }
231
241
 
@@ -241,75 +251,69 @@ export class HyperlinkManager {
241
251
  * @returns {string} Updated slide XML.
242
252
  */
243
253
  #injectHyperlinkOnText(slideXml, text, rId, tooltip, actionAttr = '') {
244
- const escapedText = this.#escapeXml(text);
245
- const textPattern = new RegExp(`(<a:t>)(${this.#escapeRegex(escapedText)})(<\/a:t>)`, 'g');
254
+ const escapedText = this.#escapeXml(text)
255
+ const textPattern = new RegExp(`(<a:t>)(${this.#escapeRegex(escapedText)})(<\/a:t>)`, 'g')
246
256
 
247
257
  if (!textPattern.test(slideXml)) {
248
- logger.warn(`Text "${text}" not found in slide XML`);
249
- return slideXml;
258
+ logger.warn(`Text "${text}" not found in slide XML`)
259
+ return slideXml
250
260
  }
251
261
 
252
- const tipAttr = tooltip ? ` tooltip="${this.#escapeXml(tooltip)}"` : '';
253
- const actAttr = actionAttr ? ` ${actionAttr}` : '';
254
- const rIdAttr = rId ? ` r:id="${rId}"` : '';
255
- const hlinkXml = `<a:hlinkClick xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"${rIdAttr}${tipAttr}${actAttr}/>`;
262
+ const tipAttr = tooltip ? ` tooltip="${this.#escapeXml(tooltip)}"` : ''
263
+ const actAttr = actionAttr ? ` ${actionAttr}` : ''
264
+ const rIdAttr = rId ? ` r:id="${rId}"` : ''
265
+ const hlinkXml = `<a:hlinkClick xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"${rIdAttr}${tipAttr}${actAttr}/>`
256
266
 
257
267
  // We need to add the hlinkClick INSIDE the a:rPr of the text run containing our text
258
- let updated = slideXml;
268
+ let updated = slideXml
259
269
 
260
270
  // Find the text node
261
- const tStart = slideXml.indexOf(`<a:t>${escapedText}</a:t>`);
271
+ const tStart = slideXml.indexOf(`<a:t>${escapedText}</a:t>`)
262
272
  if (tStart === -1) {
263
- const tStartPlain = slideXml.indexOf(`<a:t>${text}</a:t>`);
273
+ const tStartPlain = slideXml.indexOf(`<a:t>${text}</a:t>`)
264
274
  if (tStartPlain === -1) {
265
- logger.warn(`Could not locate text "${text}" in slide XML`);
266
- return slideXml;
275
+ logger.warn(`Could not locate text "${text}" in slide XML`)
276
+ return slideXml
267
277
  }
268
278
  }
269
279
 
270
280
  // Find the containing <a:r> tag
271
- const rStart = updated.lastIndexOf('<a:r>', tStart);
272
- const rEnd = updated.indexOf('</a:r>', tStart);
281
+ const rStart = updated.lastIndexOf('<a:r>', tStart)
282
+ const rEnd = updated.indexOf('</a:r>', tStart)
273
283
 
274
284
  if (rStart === -1 || rEnd === -1) {
275
- return slideXml;
285
+ return slideXml
276
286
  }
277
287
 
278
- const runXml = updated.substring(rStart, rEnd + '</a:r>'.length);
288
+ const runXml = updated.substring(rStart, rEnd + '</a:r>'.length)
279
289
 
280
290
  // Check if rPr exists
281
291
  if (runXml.includes('<a:rPr')) {
282
- const rPrEnd = runXml.indexOf('>', runXml.indexOf('<a:rPr'));
283
- const rPrIsSelfClosing = runXml[rPrEnd - 1] === '/';
292
+ const rPrEnd = runXml.indexOf('>', runXml.indexOf('<a:rPr'))
293
+ const rPrIsSelfClosing = runXml[rPrEnd - 1] === '/'
284
294
 
285
- let newRunXml;
295
+ let newRunXml
286
296
  if (rPrIsSelfClosing) {
287
- const rPrStart = runXml.indexOf('<a:rPr');
288
- const rPrFull = runXml.substring(rPrStart, rPrEnd + 1);
289
- const rPrAttribs = rPrFull.replace('/>', '');
290
- newRunXml = runXml.replace(
291
- rPrFull,
292
- `${rPrAttribs}>${hlinkXml}</a:rPr>`
293
- );
297
+ const rPrStart = runXml.indexOf('<a:rPr')
298
+ const rPrFull = runXml.substring(rPrStart, rPrEnd + 1)
299
+ const rPrAttribs = rPrFull.replace('/>', '')
300
+ newRunXml = runXml.replace(rPrFull, `${rPrAttribs}>${hlinkXml}</a:rPr>`)
294
301
  } else {
295
- const rPrClose = runXml.indexOf('</a:rPr>');
296
- newRunXml =
297
- runXml.substring(0, rPrClose) + hlinkXml + runXml.substring(rPrClose);
302
+ const rPrClose = runXml.indexOf('</a:rPr>')
303
+ newRunXml = runXml.substring(0, rPrClose) + hlinkXml + runXml.substring(rPrClose)
298
304
  }
299
305
 
300
- updated =
301
- updated.substring(0, rStart) + newRunXml + updated.substring(rEnd + '</a:r>'.length);
306
+ updated = updated.substring(0, rStart) + newRunXml + updated.substring(rEnd + '</a:r>'.length)
302
307
  } else {
303
- const tTagStart = runXml.indexOf('<a:t>');
308
+ const tTagStart = runXml.indexOf('<a:t>')
304
309
  const newRunXml =
305
310
  runXml.substring(0, tTagStart) +
306
311
  `<a:rPr lang="en-US" dirty="0">${hlinkXml}</a:rPr>` +
307
- runXml.substring(tTagStart);
308
- updated =
309
- updated.substring(0, rStart) + newRunXml + updated.substring(rEnd + '</a:r>'.length);
312
+ runXml.substring(tTagStart)
313
+ updated = updated.substring(0, rStart) + newRunXml + updated.substring(rEnd + '</a:r>'.length)
310
314
  }
311
315
 
312
- return updated;
316
+ return updated
313
317
  }
314
318
 
315
319
  /**
@@ -317,20 +321,17 @@ export class HyperlinkManager {
317
321
  * @private
318
322
  */
319
323
  #injectSlideJumpHyperlink(slideXml, rId) {
320
- const hlinkXml = `<a:hlinkClick xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="${rId}" action="ppaction://hlinksldjump"/>`;
324
+ const hlinkXml = `<a:hlinkClick xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" r:id="${rId}" action="ppaction://hlinksldjump"/>`
321
325
 
322
326
  // Look for slide number field (<a:fld type="slidenum">) or first text run
323
- const fldPattern = /<a:fld[^>]*type="slidenum"[^>]*>/;
327
+ const fldPattern = /<a:fld[^>]*type="slidenum"[^>]*>/
324
328
  if (fldPattern.test(slideXml)) {
325
329
  // Add action to the fld element
326
- return slideXml.replace(
327
- fldPattern,
328
- match => match.replace('>', `>${hlinkXml}`)
329
- );
330
+ return slideXml.replace(fldPattern, match => match.replace('>', `>${hlinkXml}`))
330
331
  }
331
332
 
332
333
  // Fallback: add to first text in slide
333
- return this.#injectHyperlinkOnFirstText(slideXml, rId);
334
+ return this.#injectHyperlinkOnFirstText(slideXml, rId)
334
335
  }
335
336
 
336
337
  /**
@@ -338,11 +339,11 @@ export class HyperlinkManager {
338
339
  * @private
339
340
  */
340
341
  #injectHyperlinkOnFirstText(slideXml, rId) {
341
- const firstT = slideXml.indexOf('<a:t>');
342
- if (firstT === -1) return slideXml;
342
+ const firstT = slideXml.indexOf('<a:t>')
343
+ if (firstT === -1) return slideXml
343
344
 
344
- const text = slideXml.substring(firstT + 5, slideXml.indexOf('</a:t>', firstT));
345
- return this.#injectHyperlinkOnText(slideXml, text, rId);
345
+ const text = slideXml.substring(firstT + 5, slideXml.indexOf('</a:t>', firstT))
346
+ return this.#injectHyperlinkOnText(slideXml, text, rId)
346
347
  }
347
348
 
348
349
  /**
@@ -350,35 +351,32 @@ export class HyperlinkManager {
350
351
  * @private
351
352
  */
352
353
  #injectHyperlinkOnShape(slideXml, shapeName, rId, actionAttr = '') {
353
- const actAttr = actionAttr ? ` ${actionAttr}` : '';
354
- const rIdAttr = rId ? ` r:id="${rId}"` : '';
355
- const hlinkXml = `<a:hlinkClick xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"${rIdAttr}${actAttr}/>`;
354
+ const actAttr = actionAttr ? ` ${actionAttr}` : ''
355
+ const rIdAttr = rId ? ` r:id="${rId}"` : ''
356
+ const hlinkXml = `<a:hlinkClick xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"${rIdAttr}${actAttr}/>`
356
357
 
357
358
  // Find the shape by name
358
- const namePattern = new RegExp(`name="${this.#escapeRegex(shapeName)}"`);
359
- const nameMatch = namePattern.exec(slideXml);
359
+ const namePattern = new RegExp(`name="${this.#escapeRegex(shapeName)}"`)
360
+ const nameMatch = namePattern.exec(slideXml)
360
361
  if (!nameMatch) {
361
- logger.warn(`Shape "${shapeName}" not found`);
362
- return slideXml;
362
+ logger.warn(`Shape "${shapeName}" not found`)
363
+ return slideXml
363
364
  }
364
365
 
365
366
  // Find the p:sp containing this shape and inject into nvSpPr
366
- const spStart = slideXml.lastIndexOf('<p:sp>', nameMatch.index);
367
- const spEnd = slideXml.indexOf('</p:sp>', nameMatch.index);
367
+ const spStart = slideXml.lastIndexOf('<p:sp>', nameMatch.index)
368
+ const spEnd = slideXml.indexOf('</p:sp>', nameMatch.index)
368
369
 
369
- if (spStart === -1 || spEnd === -1) return slideXml;
370
+ if (spStart === -1 || spEnd === -1) return slideXml
370
371
 
371
- const spXml = slideXml.substring(spStart, spEnd + '</p:sp>'.length);
372
- const cNvSpPrEnd = spXml.indexOf('</p:cNvSpPr>');
372
+ const spXml = slideXml.substring(spStart, spEnd + '</p:sp>'.length)
373
+ const cNvSpPrEnd = spXml.indexOf('</p:cNvSpPr>')
373
374
 
374
- if (cNvSpPrEnd === -1) return slideXml;
375
+ if (cNvSpPrEnd === -1) return slideXml
375
376
 
376
- const newSpXml =
377
- spXml.substring(0, cNvSpPrEnd) + hlinkXml + spXml.substring(cNvSpPrEnd);
377
+ const newSpXml = spXml.substring(0, cNvSpPrEnd) + hlinkXml + spXml.substring(cNvSpPrEnd)
378
378
 
379
- return (
380
- slideXml.substring(0, spStart) + newSpXml + slideXml.substring(spEnd + '</p:sp>'.length)
381
- );
379
+ return slideXml.substring(0, spStart) + newSpXml + slideXml.substring(spEnd + '</p:sp>'.length)
382
380
  }
383
381
 
384
382
  /**
@@ -390,7 +388,7 @@ export class HyperlinkManager {
390
388
  .replace(/</g, '&lt;')
391
389
  .replace(/>/g, '&gt;')
392
390
  .replace(/"/g, '&quot;')
393
- .replace(/'/g, '&apos;');
391
+ .replace(/'/g, '&apos;')
394
392
  }
395
393
 
396
394
  /**
@@ -402,11 +400,11 @@ export class HyperlinkManager {
402
400
  * @param {SlideManager} slideManager
403
401
  */
404
402
  addTextNavigationLink(slideIndex, text, navType, slideManager) {
405
- const action = this.#getNavigationAction(navType);
406
- const slideXml = slideManager.getSlideXml(slideIndex);
407
- const updatedXml = this.#injectHyperlinkOnText(slideXml, text, '', null, `action="${action}"`);
408
- slideManager.setSlideXml(slideIndex, updatedXml);
409
- logger.debug(`Added navigation link (${navType}) to "${text}" in slide ${slideIndex}`);
403
+ const action = this.#getNavigationAction(navType)
404
+ const slideXml = slideManager.getSlideXml(slideIndex)
405
+ const updatedXml = this.#injectHyperlinkOnText(slideXml, text, '', null, `action="${action}"`)
406
+ slideManager.setSlideXml(slideIndex, updatedXml)
407
+ logger.debug(`Added navigation link (${navType}) to "${text}" in slide ${slideIndex}`)
410
408
  }
411
409
 
412
410
  /**
@@ -418,11 +416,13 @@ export class HyperlinkManager {
418
416
  * @param {SlideManager} slideManager
419
417
  */
420
418
  addShapeNavigationLink(slideIndex, shapeName, navType, slideManager) {
421
- const action = this.#getNavigationAction(navType);
422
- const slideXml = slideManager.getSlideXml(slideIndex);
423
- const updatedXml = this.#injectHyperlinkOnShape(slideXml, shapeName, '', `action="${action}"`);
424
- slideManager.setSlideXml(slideIndex, updatedXml);
425
- logger.debug(`Added navigation link (${navType}) to shape "${shapeName}" in slide ${slideIndex}`);
419
+ const action = this.#getNavigationAction(navType)
420
+ const slideXml = slideManager.getSlideXml(slideIndex)
421
+ const updatedXml = this.#injectHyperlinkOnShape(slideXml, shapeName, '', `action="${action}"`)
422
+ slideManager.setSlideXml(slideIndex, updatedXml)
423
+ logger.debug(
424
+ `Added navigation link (${navType}) to shape "${shapeName}" in slide ${slideIndex}`
425
+ )
426
426
  }
427
427
 
428
428
  /**
@@ -430,15 +430,19 @@ export class HyperlinkManager {
430
430
  * @private
431
431
  */
432
432
  #getNavigationAction(navType) {
433
- const type = String(navType).toLowerCase();
433
+ const type = String(navType).toLowerCase()
434
434
  switch (type) {
435
- case 'next': return 'ppaction://hlinkshowjump?s=nextslide';
435
+ case 'next':
436
+ return 'ppaction://hlinkshowjump?s=nextslide'
436
437
  case 'previous':
437
- case 'prev': return 'ppaction://hlinkshowjump?s=prevslide';
438
- case 'first': return 'ppaction://hlinkshowjump?s=firstslide';
439
- case 'last': return 'ppaction://hlinkshowjump?s=lastslide';
438
+ case 'prev':
439
+ return 'ppaction://hlinkshowjump?s=prevslide'
440
+ case 'first':
441
+ return 'ppaction://hlinkshowjump?s=firstslide'
442
+ case 'last':
443
+ return 'ppaction://hlinkshowjump?s=lastslide'
440
444
  default:
441
- throw new PPTXError(`Invalid navigation type: ${navType}`);
445
+ throw new PPTXError(`Invalid navigation type: ${navType}`)
442
446
  }
443
447
  }
444
448
 
@@ -446,6 +450,8 @@ export class HyperlinkManager {
446
450
  * @private
447
451
  */
448
452
  #escapeRegex(str) {
449
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
453
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
450
454
  }
451
455
  }
456
+
457
+ module.exports = { HyperlinkManager }