diff --git a/src/ShapeClipboard.ts b/src/ShapeClipboard.ts index 5900d9c..053cd59 100644 --- a/src/ShapeClipboard.ts +++ b/src/ShapeClipboard.ts @@ -42,6 +42,7 @@ interface PasteContext { binaryModifications: Map; contentTypesDocument: XMLDocument; clonedParts: Map; + parsedDocuments: Map; } export function createSlideObjectClipboard( @@ -122,7 +123,8 @@ export async function pasteSlideObjects( textModifications: new Map(), binaryModifications: new Map(), contentTypesDocument, - clonedParts: new Map() + clonedParts: new Map(), + parsedDocuments: new Map() }; const destinationShapeTree = getShapeTree(destinationSlideDocument); const shapeIndexes: number[] = []; @@ -134,11 +136,15 @@ export async function pasteSlideObjects( offsetShape(clonedShape, offsetEmu, offsetEmu); assignUniqueNonVisualIds(destinationSlideDocument, clonedShape); - await copyShapeRelationships(sourceSlidePath, destinationSlidePath, clonedShape, context); + copyShapeRelationships(sourceSlidePath, destinationSlidePath, clonedShape, context); destinationShapeTree.appendChild(clonedShape); shapeIndexes.push(getShapeChildren(destinationShapeTree).length - 1); } + for (const [partPath, doc] of context.parsedDocuments) { + context.textModifications.set(partPath, serializeXml(doc)); + } + context.textModifications.set(destinationSlidePath, serializeXml(destinationSlideDocument)); context.textModifications.set('[Content_Types].xml', serializeXml(contentTypesDocument)); const buffer = await buildZip( @@ -224,12 +230,29 @@ function fallbackUuid(): string { }); } -async function copyShapeRelationships( +function getOrParseRelationshipsDocument( + destinationRelationshipsPath: string, + context: PasteContext +): XMLDocument { + const existing = context.parsedDocuments.get(destinationRelationshipsPath); + if (existing) return existing; + + const xmlText = + context.textModifications.get(destinationRelationshipsPath) ?? + context.destination.textFiles.get(destinationRelationshipsPath); + const doc = xmlText + ? parseXml(xmlText, destinationRelationshipsPath) + : createRelationshipsDocument(); + context.parsedDocuments.set(destinationRelationshipsPath, doc); + return doc; +} + +function copyShapeRelationships( sourceSlidePath: string, destinationSlidePath: string, clonedShape: Element, context: PasteContext -): Promise { +): void { const sourceRelationshipsPath = getRelationshipsPath(sourceSlidePath); const sourceRelationshipsXml = context.source.textFiles.get(sourceRelationshipsPath); const relationshipAttributes = getRelationshipAttributes(clonedShape); @@ -237,9 +260,7 @@ async function copyShapeRelationships( const sourceRelationships = parseXml(sourceRelationshipsXml, sourceRelationshipsPath); const destinationRelationshipsPath = getRelationshipsPath(destinationSlidePath); - const destinationRelationships = context.destination.textFiles.has(destinationRelationshipsPath) - ? parseXml(getRequiredTextFile(context.destination, destinationRelationshipsPath), destinationRelationshipsPath) - : createRelationshipsDocument(); + const destinationRelationships = getOrParseRelationshipsDocument(destinationRelationshipsPath, context); for (const attribute of relationshipAttributes) { const sourceRelationship = findRelationship(sourceRelationships, attribute.value); @@ -253,7 +274,7 @@ async function copyShapeRelationships( const target = clonedRelationship.getAttribute('Target'); if (target && clonedRelationship.getAttribute('TargetMode') !== 'External') { const sourceTargetPath = resolvePartPath(sourceSlidePath, target); - const destinationTargetPath = await ensureRelatedPart( + const destinationTargetPath = ensureRelatedPart( sourceTargetPath, context, isChartRelationship(clonedRelationship) @@ -263,8 +284,6 @@ async function copyShapeRelationships( destinationRelationships.documentElement.appendChild(clonedRelationship); } - - context.textModifications.set(destinationRelationshipsPath, serializeXml(destinationRelationships)); } function getRelationshipAttributes(element: Element): Attr[] { @@ -300,11 +319,11 @@ function isChartRelationship(relationship: Element): boolean { return relationship.getAttribute('Type')?.endsWith('/chart') ?? false; } -async function ensureRelatedPart( +function ensureRelatedPart( sourcePartPath: string, context: PasteContext, forceClone: boolean -): Promise { +): string { const cached = context.clonedParts.get(sourcePartPath); if (cached) return cached; @@ -336,16 +355,16 @@ async function ensureRelatedPart( if (!target || relationship.getAttribute('TargetMode') === 'External') continue; const sourceTargetPath = resolvePartPath(sourcePartPath, target); - const destinationTargetPath = await ensureRelatedPart( + const destinationTargetPath = ensureRelatedPart( sourceTargetPath, context, shouldCloneDependency(sourcePartPath, sourceTargetPath) ); relationship.setAttribute('Target', getRelativePartPath(destinationPartPath, destinationTargetPath)); } - context.textModifications.set( + context.parsedDocuments.set( getRelationshipsPath(destinationPartPath), - serializeXml(relationships) + relationships ); }