node-pptx-templater 1.0.2 → 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 +1 -1
- package/src/cli/commands/build.js +30 -31
- package/src/cli/commands/debug.js +23 -23
- package/src/cli/commands/extract.js +21 -21
- package/src/cli/commands/inspect.js +23 -23
- package/src/cli/commands/validate.js +17 -17
- package/src/cli/index.js +39 -36
- package/src/core/OutputWriter.js +79 -78
- package/src/core/PPTXTemplater.js +856 -273
- package/src/core/TemplateEngine.js +67 -71
- package/src/core/ValidationEngine.js +246 -0
- package/src/index.js +30 -17
- package/src/managers/ChartManager.js +195 -70
- package/src/managers/ContentTypesManager.js +49 -45
- package/src/managers/HyperlinkManager.js +146 -142
- package/src/managers/ImageManager.js +336 -0
- package/src/managers/MediaManager.js +62 -81
- package/src/managers/RelationshipManager.js +99 -95
- package/src/managers/ShapeManager.js +340 -0
- package/src/managers/SlideManager.js +408 -311
- package/src/managers/TableManager.js +979 -262
- package/src/managers/TextManager.js +197 -0
- package/src/managers/ZipManager.js +69 -69
- package/src/managers/charts/ChartCacheGenerator.js +75 -58
- package/src/managers/charts/ChartParser.js +9 -13
- package/src/managers/charts/ChartRelationshipManager.js +12 -10
- package/src/managers/charts/ChartWorkbookUpdater.js +59 -56
- package/src/parsers/XMLParser.js +47 -50
- package/src/templates/blankPptx.js +3 -2
- package/src/templates/slideTemplate.js +28 -34
- package/src/utils/contentTypesHelper.js +40 -54
- package/src/utils/errors.js +18 -18
- package/src/utils/idUtils.js +16 -14
- package/src/utils/logger.js +18 -16
- package/src/utils/relationshipUtils.js +19 -20
- package/src/utils/xmlUtils.js +26 -26
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
* Use <a:hlinkMouseOver r:id="..."/> instead of hlinkClick.
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
const { createLogger } = require('../utils/logger.js')
|
|
35
|
-
const { PPTXError } = require('../utils/errors.js')
|
|
36
|
-
const { REL_TYPES } = require('./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
|
|
@@ -43,17 +43,17 @@ const logger = createLogger('HyperlinkManager');
|
|
|
43
43
|
*/
|
|
44
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 @@ 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 @@ 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 @@ 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 @@ 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(
|
|
146
|
+
logger.debug(
|
|
147
|
+
`Linked text "${text}" on slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`
|
|
148
|
+
)
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
/**
|
|
@@ -155,23 +157,31 @@ class HyperlinkManager {
|
|
|
155
157
|
* @param {SlideManager} slideManager
|
|
156
158
|
* @param {RelationshipManager} relationshipManager
|
|
157
159
|
*/
|
|
158
|
-
addShapeSlideLink(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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(
|
|
182
|
+
logger.debug(
|
|
183
|
+
`Linked shape "${shapeName}" on slide ${sourceSlideIndex} → slide ${targetSlideIndex} (${rId})`
|
|
184
|
+
)
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
/**
|
|
@@ -184,19 +194,19 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ class HyperlinkManager {
|
|
|
390
388
|
.replace(/</g, '<')
|
|
391
389
|
.replace(/>/g, '>')
|
|
392
390
|
.replace(/"/g, '"')
|
|
393
|
-
.replace(/'/g, ''')
|
|
391
|
+
.replace(/'/g, ''')
|
|
394
392
|
}
|
|
395
393
|
|
|
396
394
|
/**
|
|
@@ -402,11 +400,11 @@ 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 @@ 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(
|
|
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 @@ 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':
|
|
435
|
+
case 'next':
|
|
436
|
+
return 'ppaction://hlinkshowjump?s=nextslide'
|
|
436
437
|
case 'previous':
|
|
437
|
-
case 'prev':
|
|
438
|
-
|
|
439
|
-
case '
|
|
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,8 +450,8 @@ 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
|
}
|
|
452
456
|
|
|
453
|
-
module.exports = { HyperlinkManager }
|
|
457
|
+
module.exports = { HyperlinkManager }
|