Cloning Slides including Images and Charts in PowerPoint presentations & Using Open XML SDK 2.0 Productivity Tool

by wenyuz 21. March 2011 12:37

Since Microsoft used Open XML as their document format for the office products since 2007. You can rename the file to a .zip file and then explore the content of the office. Programming against office files is now more of an XML manipulation. Unless you want to purchase additional controls such as Apose which is currently support to up to 2007, Open XML SDK productivity tool is definitely your best friend.

I was working on cloning slides for a complex slide deck this past a couple of weeks which I need to clone the content of the slide as well as charts and images. So I was looking through resources online and I found a couple of good resources such as http://www.prowareness.com/blog/?p=392 and also http://msdn.microsoft.com/en-us/library/dd469465(v=office.12).aspx. However the Chartspace cloning part did not work for me in Anand’s blog, and I couldn’t find any other good resources to clone the charts. In Anand’s blog, he uses the ExternalData in the chartspace which does not resolve in my code, and I also tried to get the node and set the ID as a work around, however it does not work either. The PowerPoint slide would run an error when you try to open the file and requests a repair, and after repair, the content is no longer valid. So as a work around, I used the Open XML SDK Productivity Tool.

Here is what you can do to generate the charts on the fly. In cloning the slides, you need to clone all aspects of the slide. If you have two charts in the slide, you would need to clone both of them for the slide to work, chartspace as well as the external embedded excel worksheet for each of the chart. When you open the slide in the Productivity Tool, you need to explore to the chart xml that you want to clone. Normally if there is only one slide, then you need to open the node under ppt/presentation.xml -> ppt/slides/slide1.xml -> /ppt/charts/chart1.xml. When you select /ppt/charts/chart1.xml, and then click Reflect Code. In the reflect code pane, you will be able to find the code that you would need to create the chart on the fly. It would include CreateChartPart(ChartPart part) function that would call something similar to these five lines of code. And of course you would need to include the rest of the functions in the reflect code pane that the CreateChartPart function calls. If you have multiple charts in the slide that you need to clone, then you would need to get the CreateChartPart function for the second chart as well. This only works if you are cloning the charts onto a separate slide. Because in openxml , charts are indexed within the slide, so you can use the same code in the previous slide including the indexing.

public void CreateChartPart(ChartPart part) {
ChartDrawingPart chartDrawingPart1 = part.AddNewPart("rId2");
GenerateChartDrawingPart1Content(chartDrawingPart1);
EmbeddedPackagePart embeddedPackagePart1 = part.AddNewPart("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "rId1");
GenerateEmbeddedPackagePart1Content(embeddedPackagePart1);
GeneratePartContent(part); }

Here is the function to clone the charts, images and slides:

public static SlidePart CloneSlidePartWithImagesAndCharts(PresentationPart presentationPart, SlidePart slideTemplate) {
int i = presentationPart.SlideParts.Count(); //Create a new slide part in the presentation.
SlidePart newSlidePart = presentationPart.AddNewPart(”newSlide” + i);
i++; //Add the source slide content into the new slide.
newSlidePart.FeedData(slideTemplate.GetStream(FileMode.Open)); //Make sure the new slide references the proper slide layout.
newSlidePart.AddPart(slideTemplate.SlideLayoutPart, slideTemplate.GetIdOfPart(slideTemplate.SlideLayoutPart)); // copy the image parts
foreach (ImagePart ipart in slideTemplate.ImageParts) {
ImagePart newipart = newSlidePart.AddImagePart(ipart.ContentType, slideTemplate.GetIdOfPart(ipart));
newipart.FeedData(ipart.GetStream()); } // copy the chart parts
ChartPart cpart1 = slideTemplate.ChartParts.FirstOrDefault();
ChartPart newcpart1 = newSlidePart.AddNewPart(slideTemplate.GetIdOfPart(cpart1));
CreateChartPart(newcpart1);
newcpart1.ChartSpace.Save(); //Get the list of slide ids.
SlideIdList slideIdList = presentationPart.Presentation.SlideIdList; //Deternmine where to add the next slide (find max number of slides).
uint maxSlideId = 1;
SlideId prevSlideId = null;
foreach (SlideId slideId in slideIdList.ChildElements) {
if (slideId.Id > maxSlideId) {
maxSlideId = slideId.Id;
prevSlideId = slideId; } }
maxSlideId++; //Add the new slide at the end of the deck.
SlideId newSlideId = slideIdList.InsertAfter(new SlideId(), prevSlideId); //Make sure the id and relid are set appropriately.
newSlideId.Id = maxSlideId; newSlideId.RelationshipId = presentationPart.GetIdOfPart(newSlidePart);
newSlidePart.Slide.Save();
return newSlidePart; }

After you clone the slide, you probably would need to edit the chart data, one way to edit each of the chart series is to remove the series and then create the series on the fly. If you have a line chart, you can get the code for creating the linechart by browsing to the ChartSpace, then c:chart (Chart) -> c:linechart (LineChart) -> LineChartSeries, and click Reflect code. If there are multiple LineChartSeries, then you would need to get the code for all series. Most of the code for generating the LineChartSeries are very similar. You could consolidate the functions into one function, however, you would need to make sure that the index for each LineChartSeries is correct, if there are not correct, the slide would not work. You would need to modify the function to include the new data for the new charts on the fly at the GenerateLineChartSeries function. So here is how you can remove the series and then add a new series in it’s place. However, one draw-back for updating the chart series this way is that the backend xslx for each chart is not updated.

ChartPart ChartPart1 = Slide1.ChartParts.FirstOrDefault();
Drawing.Charts.Chart Chart1 = ChartPart1.ChartSpace.Descendants().First();
Drawing.Charts.LineChart LineChart1 = Chart1.Descendants().First();
Drawing.Charts.LineChartSeries ChartSeries1 = Chart1.Descendants().First();
LineChart1.RemoveChild(ChartSeries1);
Drawing.Charts.LineChartSeries NewChartSeries1 = GenerateLineChartSeries(); //You would need to modify the function to either take in parameters in the function call and then add in the data according.
LineChart1.AppendChild(NewChartSeries1);
ChartPart1.ChartSpace.Save();

Tag cloud