@@ -23,6 +23,8 @@ import {
2323import { ContentTracker } from '../helper/content-tracker' ;
2424import {
2525 ElementInfo ,
26+ ModifyXmlCallback ,
27+ PlaceholderInfo ,
2628 RelationshipAttribute ,
2729 SlideListAttribute ,
2830 TemplateSlideInfo ,
@@ -39,8 +41,9 @@ import { XmlSlideHelper } from '../helper/xml-slide-helper';
3941import { OLEObject } from '../shapes/ole' ;
4042import { Hyperlink } from '../shapes/hyperlink' ;
4143import { HyperlinkProcessor } from '../helper/hyperlink-processor' ;
42- import { vd } from '../helper/general-helper' ;
4344import { Diagram } from '../shapes/diagram' ;
45+ import { ISlide } from '../interfaces/islide' ;
46+ import { GeneralHelper , vd } from '../helper/general-helper' ;
4447
4548export default class HasShapes {
4649 /**
@@ -83,6 +86,11 @@ export default class HasShapes {
8386 * @internal
8487 */
8588 targetPath : string ;
89+ /**
90+ * Preparations of root template slide
91+ * @internal
92+ */
93+ preparations : SlideModificationCallback [ ] ;
8694 /**
8795 * Modifications of root template slide
8896 * @internal
@@ -92,7 +100,7 @@ export default class HasShapes {
92100 * Modifications of slide relations
93101 * @internal
94102 */
95- relModifications : SlideModificationCallback [ ] ;
103+ relModifications : ModifyXmlCallback [ ] ;
96104 /**
97105 * Import elements of slide
98106 * @internal
@@ -144,6 +152,7 @@ export default class HasShapes {
144152 ] ;
145153 targetType : ShapeTargetType ;
146154 params : AutomizerParams ;
155+ presentation : IPresentationProps ;
147156
148157 cleanupPlaceholders = false ;
149158
@@ -153,11 +162,14 @@ export default class HasShapes {
153162 } ) {
154163 this . sourceTemplate = params . template ;
155164
165+ this . preparations = [ ] ;
156166 this . modifications = [ ] ;
157167 this . relModifications = [ ] ;
158168 this . importElements = [ ] ;
159169 this . generateElements = [ ] ;
160170
171+ this . presentation = params . presentation ;
172+
161173 this . status = params . presentation . status ;
162174 this . content = params . presentation . content ;
163175
@@ -180,17 +192,17 @@ export default class HasShapes {
180192 /**
181193 * Asynchronously retrieves all elements from the slide.
182194 * @param filterTags Use an array of strings to filter parent tags (e.g. 'sp')
183- * @param slideInfo Use placeholder position from layout as fallback
195+ * @param layoutPlaceholders
184196 * @returns {Promise<ElementInfo[]> } A promise that resolves to an array of ElementInfo objects.
185197 */
186198 async getAllElements (
187199 filterTags ?: string [ ] ,
188- slideInfo ?: TemplateSlideInfo ,
200+ layoutPlaceholders ?: PlaceholderInfo [ ] ,
189201 ) : Promise < ElementInfo [ ] > {
190202 const xmlSlideHelper = await this . getSlideHelper ( ) ;
191203
192204 // Get all ElementInfo objects
193- return xmlSlideHelper . getAllElements ( filterTags , slideInfo ) ;
205+ return xmlSlideHelper . getAllElements ( filterTags , layoutPlaceholders ) ;
194206 }
195207
196208 /**
@@ -219,30 +231,48 @@ export default class HasShapes {
219231 * @returns {Promise<XmlSlideHelper> } An instance of XmlSlideHelper.
220232 */
221233 async getSlideHelper ( ) : Promise < XmlSlideHelper > {
234+ return this . getSlideHelperInstance (
235+ this . sourceTemplate . archive ,
236+ this . sourcePath ,
237+ this . sourceNumber ,
238+ ) ;
239+ }
240+
241+ async getSlideHelperInstance (
242+ archive : IArchive ,
243+ path : string ,
244+ number : number ,
245+ ) : Promise < XmlSlideHelper > {
222246 try {
223247 // Retrieve the slide XML data
224- const slideXml = await XmlHelper . getXmlFromArchive (
225- this . sourceTemplate . archive ,
226- this . sourcePath ,
227- ) ;
248+ const slideXml = await XmlHelper . getXmlFromArchive ( archive , path ) ;
228249
229250 const sourceLayoutId = await XmlRelationshipHelper . getSlideLayoutNumber (
230- this . sourceTemplate . archive ,
231- this . sourceNumber ,
251+ archive ,
252+ number ,
232253 ) ;
233254
234255 // Initialize the XmlSlideHelper
235256 return new XmlSlideHelper ( slideXml , {
236- sourceArchive : this . sourceTemplate . archive ,
237- slideNumber : this . sourceNumber ,
238- sourceLayoutId
257+ sourceArchive : archive ,
258+ slideNumber : number ,
259+ sourceLayoutId,
239260 } ) ;
240261 } catch ( error ) {
241262 // Log the error message
242263 throw new Error ( error . message ) ;
243264 }
244265 }
245266
267+ /**
268+ * Push preparations list
269+ * @internal
270+ * @param callback
271+ */
272+ prepare ( callback : SlideModificationCallback ) : void {
273+ this . preparations . push ( callback ) ;
274+ }
275+
246276 /**
247277 * Push modifications list
248278 * @internal
@@ -257,7 +287,7 @@ export default class HasShapes {
257287 * @internal
258288 * @param callback
259289 */
260- modifyRelations ( callback : SlideModificationCallback ) : void {
290+ modifyRelations ( callback : ModifyXmlCallback ) : void {
261291 this . relModifications . push ( callback ) ;
262292 }
263293
@@ -369,55 +399,6 @@ export default class HasShapes {
369399 } ) ;
370400 }
371401
372- /**
373- * Checks if an element with the same selector has already been imported or modified.
374- * This function helps to apply placeholder modifications properly.
375- *
376- * @param {FindElementSelector } selector - The selector used to identify an element.
377- * Can be a string or an object with name and optional creationId/nameIdx.
378- * @returns {ImportElement|undefined } The existing element if found, otherwise undefined.
379- */
380- getAlreadyModifiedElement (
381- selector : FindElementSelector ,
382- ) : ImportElement | undefined {
383- // Search through previously imported/modified elements
384- return this . importElements . find ( ( element ) => {
385- // Skip comparison if either selector is not an object
386- if (
387- typeof selector !== 'object' ||
388- typeof element . selector !== 'object'
389- ) {
390- return false ;
391- }
392-
393- // Case 1: Element without creationId - match by name and nameIdx
394- if ( ! selector . creationId ) {
395- return (
396- selector . name === element . selector . name &&
397- selector . nameIdx === element . selector . nameIdx
398- ) ;
399- }
400-
401- // Case 2: Element with creationId - match by name and normalized creationId
402- if ( selector . creationId && element . selector ?. creationId ) {
403- // Normalize creationIds by removing curly braces
404- const normalizedSelectorId = selector . creationId . replace ( / { | } / g, '' ) ;
405- const normalizedElementId = element . selector . creationId . replace (
406- / { | } / g,
407- '' ,
408- ) ;
409-
410- return (
411- selector . name === element . selector . name &&
412- normalizedSelectorId === normalizedElementId
413- ) ;
414- }
415-
416- // No match found for this element
417- return false ;
418- } ) ;
419- }
420-
421402 /**
422403 * ToDo: Implement creationIds as well for slideMasters
423404 *
@@ -456,13 +437,19 @@ export default class HasShapes {
456437 }
457438
458439 /**
459- * Imported selected elements
440+ * Imported selected elements while merging multiple element modifications
460441 * @internal
461442 */
462443 async importedSelectedElements ( ) : Promise < void > {
444+ await this . getUniqueImportedElements ( ) ;
445+
463446 for ( const element of this . importElements ) {
464- const info = await this . getElementInfo ( element ) ;
447+ if ( ! element . info ) {
448+ // Element has already been modified, skipping...
449+ continue ;
450+ }
465451
452+ const info = element . info ;
466453 switch ( info ?. type ) {
467454 case ElementType . Chart :
468455 await new Chart ( info , this . targetType ) [ info . mode ] (
@@ -515,6 +502,35 @@ export default class HasShapes {
515502 }
516503 }
517504
505+ /**
506+ * Processes and updates the list of imported elements by ensuring their uniqueness based on a generated hash.
507+ * If duplicate elements are found, their callbacks are merged.
508+ *
509+ * @return {Promise<void> } Resolves when the process of identifying and updating unique imported elements is complete.
510+ */
511+ async getUniqueImportedElements ( ) : Promise < void > {
512+ for ( const element of this . importElements ) {
513+ const info = await this . getElementInfo ( element ) ;
514+ const eleHash =
515+
516+ XmlHelper . createHashFromXmlElement ( info . sourceElement , element ) ;
517+
518+ const alreadyImported = this . importElements . find (
519+ ( ele ) => ele . info ?. hash === eleHash ,
520+ ) ;
521+ if ( alreadyImported ) {
522+ alreadyImported . callback = GeneralHelper . arrayify (
523+ alreadyImported . callback ,
524+ ) ;
525+ const pushCallbacks = GeneralHelper . arrayify ( element . callback ) ;
526+ alreadyImported . callback . push ( ...pushCallbacks ) ;
527+ } else {
528+ info . hash = eleHash ;
529+ element . info = info ;
530+ }
531+ }
532+ }
533+
518534 /**
519535 * Gets element info
520536 * @internal
@@ -898,7 +914,10 @@ export default class HasShapes {
898914 ) . modifyOnAddedSlide ( this . targetTemplate , this . targetNumber ) ;
899915 }
900916
901- const diagrams = await Diagram . getAllOnSlide ( this . sourceArchive , this . relsPath ) ;
917+ const diagrams = await Diagram . getAllOnSlide (
918+ this . sourceArchive ,
919+ this . relsPath ,
920+ ) ;
902921 for ( const diagram of diagrams ) {
903922 await new Diagram (
904923 {
@@ -1082,7 +1101,25 @@ export default class HasShapes {
10821101 }
10831102
10841103 /**
1085- * Applys modifications
1104+ * Applys slide preparation callbacks
1105+ * Will be executed before any shape modifications callback
1106+ * @internal
1107+ * @returns modifications
1108+ */
1109+ async applyPreparations ( ) : Promise < void > {
1110+ for ( const modification of this . preparations ) {
1111+ const xml = await XmlHelper . getXmlFromArchive (
1112+ this . targetArchive ,
1113+ this . targetPath ,
1114+ ) ;
1115+ await modification ( xml , this ) ;
1116+ XmlHelper . writeXmlToArchive ( this . targetArchive , this . targetPath , xml ) ;
1117+ }
1118+ }
1119+
1120+ /**
1121+ * Applys slide modification callbacks
1122+ * Will be executed after all shape modifications callbacks
10861123 * @internal
10871124 * @returns modifications
10881125 */
@@ -1092,7 +1129,7 @@ export default class HasShapes {
10921129 this . targetArchive ,
10931130 this . targetPath ,
10941131 ) ;
1095- modification ( xml ) ;
1132+ await modification ( xml , this ) ;
10961133 XmlHelper . writeXmlToArchive ( this . targetArchive , this . targetPath , xml ) ;
10971134 }
10981135 }
0 commit comments