Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copying OLE Objects from one slide to another corrupts the resulting PowerPoint

I have code that copies the content of one PowerPoint slide into another. Below is an example of how images are processed.

foreach (OpenXmlElement element in sourceSlide.CommonSlideData.ShapeTree.ChildElements.ToList())
{
    string elementType = element.GetType().ToString();

    if (elementType.EndsWith(".Picture"))
    {
        // Deep clone the element.
        elementClone = element.CloneNode(true);
        var picture = (Picture)elementClone;

        // Get the picture's original rId
        var blip = picture.BlipFill.Blip;
        string rId = blip.Embed.Value;
        
        // Retrieve the ImagePart from the original slide by rId
        ImagePart sourceImagePart = (ImagePart)sourceSlide.SlidePart.GetPartById(rId);

        // Add the image part to the new slide, letting OpenXml generate the new rId
        ImagePart targetImagePart = targetSlidePart.AddImagePart(sourceImagePart.ContentType);

        // And copy the image data.
        targetImagePart.FeedData(sourceImagePart.GetStream());

        // Retrieve the new ID from the target image part,
        string id = targetSlidePart.GetIdOfPart(targetImagePart);

        // and assign it to the picture.
        blip.Embed.Value = id;

        // Get the shape tree that we're adding the clone to and append to it.
        ShapeTree shapeTree = targetSlide.CommonSlideData.ShapeTree;
        shapeTree.Append(elementClone);
    }

This code works fine. For other scenarios like Graphic Frames, it looks a bit different, because each graphic frame can contain multiple picture objects.

// Go thru all the Picture objects in this GraphicFrame.
foreach (var sourcePicture in element.Descendants<Picture>())
{
    string rId = sourcePicture.BlipFill.Blip.Embed.Value;
    ImagePart sourceImagePart = (ImagePart)sourceSlide.SlidePart.GetPartById(rId);
    var contentType = sourceImagePart.ContentType;

    var targetPicture = elementClone.Descendants<Picture>().First(x => x.BlipFill.Blip.Embed.Value == rId);
    var targetBlip = targetPicture.BlipFill.Blip;

    ImagePart targetImagePart = targetSlidePart.AddImagePart(contentType);
    targetImagePart.FeedData(sourceImagePart.GetStream());
    string id = targetSlidePart.GetIdOfPart(targetImagePart);
    targetBlip.Embed.Value = id;
}

Now I need to do the same thing with OLE objects.

// Go thru all the embedded objects in this GraphicFrame.
foreach (var oleObject in element.Descendants<OleObject>())
{
    // Get the rId of the embedded OLE object.
    string rId = oleObject.Id;

    // Get the EmbeddedPart from the source slide.
    var embeddedOleObj = sourceSlide.SlidePart.GetPartById(rId);

    // Get the content type.
    var contentType = embeddedOleObj.ContentType;

    // Create the Target Part.  Let OpenXML assign an rId.
    var targetObjectPart = targetSlide.SlidePart.AddNewPart<EmbeddedObjectPart>(contentType, null);

    // Get the embedded OLE object data from the original object.
    var objectStream = embeddedOleObj.GetStream();

    // And give it to the ObjectPart.
    targetObjectPart.FeedData(objectStream);

    // Get the new rId and assign it to the OLE Object.
    string id = targetSlidePart.GetIdOfPart(targetObjectPart);
    oleObject.Id = id;
}

But it didn't work. The resulting PowerPoint is corrupted.

What am I doing wrong?


NOTE: All of the code works except for the rId handling in the OLE Object. I know it works because if I simply pass the original rId from the source object to the target Object Part, like this:

var targetObjectPart = targetSlide.SlidePart
   .AddNewPart<EmbeddedObjectPart>(contentType, rId);

it will function properly, so long as that rId doesn't already exist in the target slide, which will obviously not work every time like I need it to.

The source slide and target slide are coming from different PPTX files. We're using OpenXML, not Office Interop.

like image 918
Robert Harvey Avatar asked Aug 31 '20 17:08

Robert Harvey


People also ask

How do I copy slides from one PowerPoint to another without losing formatting?

Right-click the selected slide(s), and then click Copy. Right-click the thumbnail you want your copied slides to follow in the second presentation, and under Paste Options, do one of the following: To take the theme of the presentation you're pasting into, click Use Destination Theme.

Why does PowerPoint change color when copy and paste?

What happens when copying slides. When we copy slides onto a new PowerPoint presentation deck the slides will default to the new theme style. This means that backgrounds, fonts and colours on your slides can change to the new theme style.

How to copy and paste slides from one PowerPoint presentation to another?

Copy and Paste Method in PowerPoint A quick way to use slides from one presentation in another presentation is to copy the slides that you want to use and paste those slides into the new presentation. Open both presentations to show them at the same time on the screen.

How can I isolate this issue in a PowerPoint presentation?

For us to isolate this issue, we suggest that you create a new PowerPoint file and copy the slides that you have in presentation A. Once done, check if you will still experience the same issue. In addition, you can check the articles below on how to copy your slides. Please post back with the result for further assistance. Was this reply helpful?

How do you manipulate objects in PowerPoint?

If you have a PowerPoint presentation already made and this is what you want to manipulate, then open the presentation. If you don’t have a ready presentation, just open a new presentation and insert any object. In our example, we will create a rectangular shape and position it anywhere in the slide.

Why can't I copy and paste from slides with comments?

I found that slides with review comments are the culprit. If a slide contains a review comment, it allows you to copy it, and paste options are available, but nothing happens. If only one slide in a group of 10 slides I want to copy has a comment, the entire copy job fails.


1 Answers

Since you did not provide the full code, it is difficult to tell what's wrong.
My guess would be that you are not modifying the correct object.

In your code example for Pictures, you are creating and modifying elementClone.
In your code example for ole objects, you are working with and modifying oleObject (which is a descendant of element) and it is not exacly clear from the context, whether it is a part of the source document or of the target document.


You can try this minimal example:

  • use a new pptx with one embedded ole object for c:\testdata\input.pptx
  • use a new pptx (a blank one) for c:\testdata\output.pptx

After running the code, I was able to open the embedded ole object in the output document.

using DocumentFormat.OpenXml.Presentation;
using DocumentFormat.OpenXml.Packaging;
using System.Linq;

namespace ooxml
{
    class Program
    {
        static void Main(string[] args)
        {            
            CopyOle("c:\\testdata\\input.pptx", "c:\\testdata\\output.pptx");
        }

        private static void CopyOle(string inputFile, string outputFile)
        {
            using (PresentationDocument sourceDocument = PresentationDocument.Open(inputFile, true))
            {
                using (PresentationDocument targetDocument = PresentationDocument.Open(outputFile, true))
                {
                    var sourceSlidePart = sourceDocument.PresentationPart.SlideParts.First();
                    var targetSlidePart = targetDocument.PresentationPart.SlideParts.First();

                    
                    foreach (var element in sourceSlidePart.Slide.CommonSlideData.ShapeTree.ChildElements)
                    {
                        //clones an element, does not copy the actual relationship target (e.g. ppt\embeddings\oleObject1.bin)
                        var elementClone = element.CloneNode(true);                      
                        
                        //for each cloned OleObject, fix its relationship
                        foreach(var clonedOleObject in elementClone.Descendants<OleObject>())
                        {
                            //find the original EmbeddedObjectPart in the source document
                            //(we can use the id from the clonedOleObject to do that, since it contained the same id
                            // as the source ole object)
                            var sourceObjectPart = sourceSlidePart.GetPartById(clonedOleObject.Id);

                            //create a new EmbeddedObjectPart in the target document and copy the data from the original EmbeddedObjectPart
                            var targetObjectPart = targetSlidePart.AddEmbeddedObjectPart(sourceObjectPart.ContentType);
                            targetObjectPart.FeedData(sourceObjectPart.GetStream());

                            //update the relationship target on the clonedOleObject to point to the newly created EmbeddedObjectPath
                            clonedOleObject.Id = targetSlidePart.GetIdOfPart(targetObjectPart);
                        }

                        //add cloned element to the document
                        targetSlidePart.Slide.CommonSlideData.ShapeTree.Append(elementClone);
                    }
                    targetDocument.PresentationPart.Presentation.Save();
                }
            }
        }
    }
}

As for troubleshooting, the OOXML Tools chrome extension was helpful.
It allows to compare the structure of two documents, so it is way easier to analyze what went wrong.

Examples:

  • if you were to only clone all elements, you could see that /ppt/embeddings/* and /ppt/media/* would be missing enter image description here
  • or you can check whether the relationships are correct (e.g. input document uses "rId1" to reference the embedded data and the output document uses "R3a2fa0c37eaa42b5") enter image description here

    enter image description here
like image 183
mcernak Avatar answered Sep 20 '22 06:09

mcernak