node-pptx-templater 1.0.16 → 1.0.18
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 +178 -4
- package/package.json +1 -1
- package/src/core/OutputWriter.js +12 -14
- package/src/core/PPTXTemplater.js +205 -5
- package/src/managers/ChartManager.js +0 -3
- package/src/managers/ImageManager.js +14 -18
- package/src/managers/MediaManager.js +151 -35
- package/src/managers/ShapeManager.js +19 -28
- package/src/managers/SlideManager.js +247 -4
- package/src/managers/TableManager.js +56 -87
- package/src/managers/ZOrderManager.js +0 -5
- package/src/managers/ZipManager.js +120 -14
- package/src/managers/charts/ChartWorkbookUpdater.js +1 -1
- package/src/utils/contentTypesHelper.js +6 -9
- package/src/utils/imageMetadata.js +227 -0
|
@@ -41,10 +41,7 @@ class ImageManager {
|
|
|
41
41
|
mediaManager,
|
|
42
42
|
relationshipManager
|
|
43
43
|
) {
|
|
44
|
-
const
|
|
45
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
46
|
-
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
47
|
-
const picRes = this.#findPicRecursive(spTree, imageIdOrName)
|
|
44
|
+
const picRes = slideManager.getSlidePic(slideIndex, imageIdOrName)
|
|
48
45
|
|
|
49
46
|
if (!picRes) {
|
|
50
47
|
throw new PPTXError(`Image shape "${imageIdOrName}" not found on slide ${slideIndex}`)
|
|
@@ -85,9 +82,11 @@ class ImageManager {
|
|
|
85
82
|
relationshipManager
|
|
86
83
|
) {
|
|
87
84
|
const slideInfo = slideManager.getSlideInfo(slideIndex)
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
85
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
86
|
+
const spTree =
|
|
87
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
88
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
89
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
91
90
|
|
|
92
91
|
if (!spTree) {
|
|
93
92
|
throw new PPTXError(`Invalid slide structure for slide ${slideIndex}`)
|
|
@@ -155,8 +154,7 @@ class ImageManager {
|
|
|
155
154
|
}
|
|
156
155
|
spTree['p:pic'].push(picObj)
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
157
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
160
158
|
logger.debug(`Added image "${name}" with ID ${newId} and rId ${rId} to slide ${slideIndex}`)
|
|
161
159
|
}
|
|
162
160
|
|
|
@@ -169,10 +167,7 @@ class ImageManager {
|
|
|
169
167
|
* @param {RelationshipManager} relationshipManager
|
|
170
168
|
*/
|
|
171
169
|
removeImage(slideIndex, imageIdOrName, slideManager, relationshipManager) {
|
|
172
|
-
const
|
|
173
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
174
|
-
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
175
|
-
const picRes = this.#findPicRecursive(spTree, imageIdOrName)
|
|
170
|
+
const picRes = slideManager.getSlidePic(slideIndex, imageIdOrName)
|
|
176
171
|
|
|
177
172
|
if (!picRes) {
|
|
178
173
|
throw new PPTXError(`Image shape "${imageIdOrName}" not found on slide ${slideIndex}`)
|
|
@@ -195,8 +190,7 @@ class ImageManager {
|
|
|
195
190
|
relationshipManager.removeRelationship(slideInfo.zipPath, rId)
|
|
196
191
|
}
|
|
197
192
|
|
|
198
|
-
|
|
199
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
193
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
200
194
|
logger.debug(`Removed image "${imageIdOrName}" from slide ${slideIndex}`)
|
|
201
195
|
}
|
|
202
196
|
|
|
@@ -210,9 +204,11 @@ class ImageManager {
|
|
|
210
204
|
*/
|
|
211
205
|
getImages(slideIndex, slideManager, relationshipManager) {
|
|
212
206
|
const slideInfo = slideManager.getSlideInfo(slideIndex)
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
207
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
208
|
+
const spTree =
|
|
209
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
210
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
211
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
216
212
|
|
|
217
213
|
const imagesInfo = []
|
|
218
214
|
this.#collectImagesInfo(spTree, slideInfo.zipPath, relationshipManager, imagesInfo)
|
|
@@ -40,9 +40,29 @@ const { createHash } = require('crypto')
|
|
|
40
40
|
const { createLogger } = require('../utils/logger.js')
|
|
41
41
|
const { PPTXError } = require('../utils/errors.js')
|
|
42
42
|
const fsExtra = require('fs-extra')
|
|
43
|
+
const fs = require('fs')
|
|
43
44
|
|
|
44
45
|
const logger = createLogger('MediaManager')
|
|
45
46
|
|
|
47
|
+
function getFileHash(filePath) {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const hash = createHash('sha1')
|
|
50
|
+
const stream = fs.createReadStream(filePath)
|
|
51
|
+
stream.on('data', chunk => hash.update(chunk))
|
|
52
|
+
stream.on('end', () => resolve(hash.digest('hex')))
|
|
53
|
+
stream.on('error', reject)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function streamToBuffer(stream) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const chunks = []
|
|
60
|
+
stream.on('data', chunk => chunks.push(chunk))
|
|
61
|
+
stream.on('end', () => resolve(Buffer.concat(chunks)))
|
|
62
|
+
stream.on('error', reject)
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
/**
|
|
47
67
|
* Extension to MIME type mapping.
|
|
48
68
|
*/
|
|
@@ -106,30 +126,37 @@ class MediaManager {
|
|
|
106
126
|
this.#zipManager = zipManager
|
|
107
127
|
const mediaFiles = zipManager.listFiles('ppt/media/')
|
|
108
128
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
// Register all existing media files without loading/hashing them yet
|
|
130
|
+
for (const mediaPath of mediaFiles) {
|
|
131
|
+
const ext = mediaPath.split('.').pop().toLowerCase()
|
|
132
|
+
const mimeType = EXT_TO_MIME[ext] || 'application/octet-stream'
|
|
133
|
+
|
|
134
|
+
const mediaInfo = { zipPath: mediaPath, hash: null, mimeType, size: null }
|
|
135
|
+
this.#mediaRegistry.set(mediaPath, mediaInfo)
|
|
136
|
+
|
|
137
|
+
// Track the highest media ID to avoid collisions
|
|
138
|
+
const numMatch = /\d+/.exec(mediaPath.split('/').pop())
|
|
139
|
+
if (numMatch) {
|
|
140
|
+
const num = parseInt(numMatch[0], 10)
|
|
141
|
+
if (num >= this.#nextMediaId) this.#nextMediaId = num + 1
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logger.debug(`Registered ${this.#mediaRegistry.size} media file(s) (lazy loading enabled)`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async #ensureAllMediaHashed() {
|
|
149
|
+
for (const [mediaPath, mediaInfo] of this.#mediaRegistry.entries()) {
|
|
150
|
+
if (mediaInfo.hash === null) {
|
|
151
|
+
const data = await this.#zipManager.readBinaryFile(mediaPath)
|
|
113
152
|
if (data) {
|
|
114
153
|
const hash = this.#hashBytes(data)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const mediaInfo = { zipPath: mediaPath, hash, mimeType, size: data.length }
|
|
154
|
+
mediaInfo.hash = hash
|
|
155
|
+
mediaInfo.size = data.length
|
|
119
156
|
this.#mediaHashIndex.set(hash, mediaPath)
|
|
120
|
-
this.#mediaRegistry.set(mediaPath, mediaInfo)
|
|
121
|
-
|
|
122
|
-
// Track the highest media ID to avoid collisions
|
|
123
|
-
const numMatch = /\d+/.exec(mediaPath.split('/').pop())
|
|
124
|
-
if (numMatch) {
|
|
125
|
-
const num = parseInt(numMatch[0], 10)
|
|
126
|
-
if (num >= this.#nextMediaId) this.#nextMediaId = num + 1
|
|
127
|
-
}
|
|
128
157
|
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
logger.debug(`Indexed ${this.#mediaRegistry.size} media file(s)`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
/**
|
|
@@ -152,41 +179,130 @@ class MediaManager {
|
|
|
152
179
|
async embedImage(source, mimeType) {
|
|
153
180
|
let data
|
|
154
181
|
let ext
|
|
182
|
+
let hash
|
|
183
|
+
let size
|
|
184
|
+
const isStream = source && typeof source.on === 'function' && typeof source.pipe === 'function'
|
|
185
|
+
let streamForZip = null
|
|
186
|
+
|
|
187
|
+
if (isStream) {
|
|
188
|
+
if (source.path) {
|
|
189
|
+
// It's a file stream (fs.createReadStream)
|
|
190
|
+
const filePath = source.path
|
|
191
|
+
hash = await getFileHash(filePath)
|
|
192
|
+
ext = filePath.split('.').pop().toLowerCase()
|
|
193
|
+
mimeType = mimeType || EXT_TO_MIME[ext] || 'image/png'
|
|
194
|
+
|
|
195
|
+
// Check for duplicate (content-addressable dedup)
|
|
196
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
197
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
198
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
199
|
+
return existingPath
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Ensure all media from template is hashed to check for duplicates
|
|
203
|
+
await this.#ensureAllMediaHashed()
|
|
204
|
+
|
|
205
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
206
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
207
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
208
|
+
return existingPath
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const stat = await fsExtra.stat(filePath)
|
|
212
|
+
size = stat.size
|
|
213
|
+
streamForZip = fs.createReadStream(filePath)
|
|
214
|
+
} else {
|
|
215
|
+
// Generic stream - we must buffer it to hash and reuse
|
|
216
|
+
data = await streamToBuffer(source)
|
|
217
|
+
hash = this.#hashBytes(data)
|
|
218
|
+
ext = this.#detectExtension(data)
|
|
219
|
+
mimeType = mimeType || EXT_TO_MIME[ext] || 'image/png'
|
|
220
|
+
size = data.length
|
|
221
|
+
|
|
222
|
+
// Check for duplicate (content-addressable dedup)
|
|
223
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
224
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
225
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
226
|
+
return existingPath
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Ensure all media from template is hashed to check for duplicates
|
|
230
|
+
await this.#ensureAllMediaHashed()
|
|
155
231
|
|
|
156
|
-
|
|
232
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
233
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
234
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
235
|
+
return existingPath
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else if (typeof source === 'string') {
|
|
157
239
|
// Load from file path
|
|
158
|
-
|
|
240
|
+
hash = await getFileHash(source)
|
|
159
241
|
ext = source.split('.').pop().toLowerCase()
|
|
160
242
|
mimeType = mimeType || EXT_TO_MIME[ext] || 'image/png'
|
|
243
|
+
|
|
244
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
245
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
246
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
247
|
+
return existingPath
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Ensure all media from template is hashed to check for duplicates
|
|
251
|
+
await this.#ensureAllMediaHashed()
|
|
252
|
+
|
|
253
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
254
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
255
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
256
|
+
return existingPath
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const stat = await fsExtra.stat(source)
|
|
260
|
+
size = stat.size
|
|
261
|
+
streamForZip = fs.createReadStream(source)
|
|
161
262
|
} else if (Buffer.isBuffer(source) || source instanceof Uint8Array) {
|
|
162
263
|
data = source
|
|
163
|
-
// Detect format from magic bytes
|
|
164
264
|
ext = this.#detectExtension(data)
|
|
165
265
|
mimeType = mimeType || EXT_TO_MIME[ext] || 'image/png'
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
266
|
+
hash = this.#hashBytes(data)
|
|
267
|
+
size = data.length
|
|
268
|
+
|
|
269
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
270
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
271
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
272
|
+
return existingPath
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Ensure all media from template is hashed to check for duplicates
|
|
276
|
+
await this.#ensureAllMediaHashed()
|
|
169
277
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
278
|
+
if (this.#mediaHashIndex.has(hash)) {
|
|
279
|
+
const existingPath = this.#mediaHashIndex.get(hash)
|
|
280
|
+
logger.debug(`Reusing existing media: ${existingPath} (hash: ${hash.substring(0, 8)}...)`)
|
|
281
|
+
return existingPath
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
throw new PPTXError(
|
|
285
|
+
'embedImage: source must be a file path string, Buffer, or Readable Stream'
|
|
286
|
+
)
|
|
176
287
|
}
|
|
177
288
|
|
|
178
289
|
// Create a new media file
|
|
179
290
|
const mediaId = this.#nextMediaId++
|
|
180
291
|
const zipPath = `ppt/media/image${mediaId}.${ext}`
|
|
181
292
|
|
|
182
|
-
|
|
293
|
+
if (streamForZip) {
|
|
294
|
+
this.#zipManager.writeBinaryFile(zipPath, streamForZip)
|
|
295
|
+
} else {
|
|
296
|
+
this.#zipManager.writeBinaryFile(zipPath, data)
|
|
297
|
+
}
|
|
298
|
+
|
|
183
299
|
this.#mediaHashIndex.set(hash, zipPath)
|
|
184
|
-
this.#mediaRegistry.set(zipPath, { zipPath, hash, mimeType, size
|
|
300
|
+
this.#mediaRegistry.set(zipPath, { zipPath, hash, mimeType, size })
|
|
185
301
|
|
|
186
302
|
// Register content type
|
|
187
303
|
this.#registerContentType(ext, mimeType)
|
|
188
304
|
|
|
189
|
-
logger.debug(`Embedded new media: ${zipPath} (${
|
|
305
|
+
logger.debug(`Embedded new media: ${zipPath} (${size} bytes)`)
|
|
190
306
|
return zipPath
|
|
191
307
|
}
|
|
192
308
|
|
|
@@ -31,19 +31,14 @@ class ShapeManager {
|
|
|
31
31
|
* @param {SlideManager} slideManager
|
|
32
32
|
*/
|
|
33
33
|
updateShapeText(slideIndex, shapeId, text, slideManager) {
|
|
34
|
-
const
|
|
35
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
36
|
-
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
37
|
-
const res = this.findShapeRecursive(spTree, shapeId)
|
|
34
|
+
const res = slideManager.getSlideShape(slideIndex, shapeId)
|
|
38
35
|
|
|
39
36
|
if (!res) {
|
|
40
37
|
throw new PPTXError(`Shape "${shapeId}" not found in slide ${slideIndex}`)
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
this.#setShapeTextObj(res.shape, text)
|
|
44
|
-
|
|
45
|
-
const decl = this.#xmlParser.extractDeclaration(slideXml)
|
|
46
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
41
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
47
42
|
logger.debug(`Updated text for shape "${shapeId}" on slide ${slideIndex}`)
|
|
48
43
|
}
|
|
49
44
|
|
|
@@ -60,10 +55,7 @@ class ShapeManager {
|
|
|
60
55
|
* @param {SlideManager} slideManager
|
|
61
56
|
*/
|
|
62
57
|
updateShapePosition(slideIndex, shapeId, options = {}, slideManager) {
|
|
63
|
-
const
|
|
64
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
65
|
-
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
66
|
-
const res = this.findShapeRecursive(spTree, shapeId)
|
|
58
|
+
const res = slideManager.getSlideShape(slideIndex, shapeId)
|
|
67
59
|
|
|
68
60
|
if (!res) {
|
|
69
61
|
throw new PPTXError(`Shape "${shapeId}" not found in slide ${slideIndex}`)
|
|
@@ -108,8 +100,7 @@ class ShapeManager {
|
|
|
108
100
|
xfrm['a:ext']['@_cy'] = '0'
|
|
109
101
|
}
|
|
110
102
|
|
|
111
|
-
|
|
112
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
103
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
113
104
|
logger.debug(`Updated position/dimensions for shape "${shapeId}" on slide ${slideIndex}`)
|
|
114
105
|
}
|
|
115
106
|
|
|
@@ -146,10 +137,8 @@ class ShapeManager {
|
|
|
146
137
|
* @param {SlideManager} slideManager
|
|
147
138
|
*/
|
|
148
139
|
cloneShape(slideIndex, shapeId, newShapeId, options = {}, slideManager) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
152
|
-
const res = this.findShapeRecursive(spTree, shapeId)
|
|
140
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
141
|
+
const res = slideManager.getSlideShape(slideIndex, shapeId)
|
|
153
142
|
|
|
154
143
|
if (!res) {
|
|
155
144
|
throw new PPTXError(`Shape "${shapeId}" not found in slide ${slideIndex}`)
|
|
@@ -158,6 +147,11 @@ class ShapeManager {
|
|
|
158
147
|
const newShape = this.#xmlParser.deepClone(res.shape)
|
|
159
148
|
const cNvPr = newShape['p:nvSpPr']?.['p:cNvPr']
|
|
160
149
|
|
|
150
|
+
const spTree =
|
|
151
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
152
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
153
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
154
|
+
|
|
161
155
|
const existingIds = this.#getAllShapeIds(spTree)
|
|
162
156
|
const maxId = existingIds.length > 0 ? Math.max(...existingIds) : 1000
|
|
163
157
|
const newId = maxId + 1
|
|
@@ -204,8 +198,7 @@ class ShapeManager {
|
|
|
204
198
|
}
|
|
205
199
|
parent['p:sp'].push(newShape)
|
|
206
200
|
|
|
207
|
-
|
|
208
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
201
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
209
202
|
logger.debug(`Cloned shape "${shapeId}" as "${newShapeId}"`)
|
|
210
203
|
}
|
|
211
204
|
|
|
@@ -217,10 +210,7 @@ class ShapeManager {
|
|
|
217
210
|
* @param {SlideManager} slideManager
|
|
218
211
|
*/
|
|
219
212
|
deleteShape(slideIndex, shapeId, slideManager) {
|
|
220
|
-
const
|
|
221
|
-
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
222
|
-
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
223
|
-
const res = this.findShapeRecursive(spTree, shapeId)
|
|
213
|
+
const res = slideManager.getSlideShape(slideIndex, shapeId)
|
|
224
214
|
|
|
225
215
|
if (!res) {
|
|
226
216
|
throw new PPTXError(`Shape "${shapeId}" not found in slide ${slideIndex}`)
|
|
@@ -235,8 +225,7 @@ class ShapeManager {
|
|
|
235
225
|
}
|
|
236
226
|
}
|
|
237
227
|
|
|
238
|
-
|
|
239
|
-
slideManager.setSlideXml(slideIndex, this.#xmlParser.build(slideObj, decl))
|
|
228
|
+
slideManager.markSlideObjDirty(slideIndex)
|
|
240
229
|
logger.debug(`Deleted shape "${shapeId}" from slide ${slideIndex}`)
|
|
241
230
|
}
|
|
242
231
|
|
|
@@ -248,9 +237,11 @@ class ShapeManager {
|
|
|
248
237
|
* @returns {Array<Object>}
|
|
249
238
|
*/
|
|
250
239
|
getShapes(slideIndex, slideManager) {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
|
|
240
|
+
const slideObj = slideManager.getSlideObj(slideIndex)
|
|
241
|
+
const spTree =
|
|
242
|
+
slideObj?.['p:sld']?.['p:cSld']?.['p:spTree'] ||
|
|
243
|
+
slideObj?.['p:sldLayout']?.['p:cSld']?.['p:spTree'] ||
|
|
244
|
+
slideObj?.['p:sldMaster']?.['p:cSld']?.['p:spTree']
|
|
254
245
|
|
|
255
246
|
const shapesInfo = []
|
|
256
247
|
this.#collectShapesInfo(spTree, shapesInfo)
|