Merging PowerPoint 2007 Decks Together by Using the Open XML SDK 2.0 for Microsoft Office

Summary: Learn how to use the Open XML SDK 2.0 for Microsoft Office to merge Microsoft Office PowerPoint 2007 decks together programmatically. (17 printed pages)

Office Visual How To

**Applies to:**2007 Microsoft Office system, Microsoft Office PowerPoint 2007, , Open XML SDK 2.0 for Microsoft Office

Joel Krist, iSoftStone

August 2009

Overview

The Open XMLSoftware Development Kit 2.0 for Microsoft Office makes it possible to create and manipulate Microsoft Office Word 2007, Microsoft Office Excel 2007, and Microsoft Office PowerPoint 2007 documents programmatically via the Microsoft Office Open XML Formats. The typesafe classes included with the SDK provide a layer of abstraction between the developer and the Microsoft Office Open XML Formats, simplifying the process of working with Office 2007 documents and enabling the creation of solutions that are not dependent on the presence of the Office client applications to handle document creation.

The sample code in this visual how-to article shows how to merge Microsoft Office PowerPoint 2007 decks together programmatically. The code uses the PresentationDocument, PresentationPart, SlidePart, SlideMasterPart, SlideMasterId, and SlideId classes from the Open XML SDK 2.0 for Microsoft Office to create the required document parts and to merge the decks.

See It Video startup screen

Watch the Video

Length: 10:06 | Size: 13.20 MB | Type: WMV file

Code It | Read It | Explore It

Code It

Download sample code

The sample code in this visual how-to article creates a PowerPoint 2007 presentation by merging the slides from multiple presentations into a single presentation. The ideas and the code are based on the approach and the sample code in the blog post by Zeyad Rajabi, How to Assemble Multiple PowerPoint Decks.

As it proceeds, this section walks through the following steps:

  1. Creating a Windows console application solution in .

  2. Adding references to the DocumentFormat.OpenXml and WindowsBase assemblies.

  3. Adding the sample code to the solution.

Creating a Windows Console Application in Visual Studio 2008

This visual how-to article uses a Windows console application to provide the framework for the sample code. However, you could use the same approach that is illustrated here with other application types as well.

To create a Windows Console Application in Visual Studio 2008

  1. Start Microsoft Visual Studio 2008.

  2. On the File menu, point to New, and then click Project.

  3. In the New Project dialog box select the Visual C# Windows type in the Project types pane.

  4. Select Console Application in the Templates pane, and then name the project MergeDecks.

    Figure 1. Create new solution in the New Project dialog box

    Create new solution in the New Project dialog box

     

  5. Click OK to create the solution.

Adding References to the DocumentFormat.OpenXml and WindowsBase Assemblies

The sample code uses the classes and enumerations that are in the DocumentFormat.OpenXml.dll assembly that is installed with the Open XML SDK 2.0 for Microsoft Office. To add the reference to the assembly in the following steps or to build the sample code that accompanies this visual how-to, you must first download and install the Open XML SDK 2.0 for Microsoft Office so that the assembly is available.

To add References to the DocumentFormat.OpenXml and WindowsBase Assemblies

  1. Add a reference to the DocumentFormat.OpenXml assembly by doing the following:

    1. On the Project menu in Visual Studio, click Add Reference to open the Add Reference dialog box.

    2. Select the .NET tab, scroll down to DocumenFormat.OpenXml, select it, and then click OK.

      Figure 2. Add Reference to DocumentFormat.OpenXML

      Add Reference to DocumentFormat.OpenXml

       

  2. The classes in the DocumentFormat.OpenXml assembly use the System.IO.Packaging.Package class that is defined in the WindowsBase assembly. Add a reference to the WindowsBase assembly by doing the following:

    1. On the Project menu in Visual Studio, click Add Reference to open the Add Reference dialog box.

    2. Select the .NET tab, scroll down to WindowsBase, select it, and then click OK.

      Figure 3. Add Reference to WindowsBase

      Add Reference to WindowsBase

       

Adding the Sample Code to the Solution

Replace the contents of the Program.cs source file with the following code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation;

class Program
{
  // This id is used for both the slide master id and slide
  // layout id lists.
  static uint uniqueId;

  static void Main(string[] args)
  {
    string mergedPresentation = "MergedPresentation.pptx";
    string presentationTemplate = "PresentationTemplate.pptx";
    string[] sourcePresentations = new string[]
    { "SourcePresentation1.pptx",
      "SourcePresentation2.pptx",
      "SourcePresentation3.pptx" };
    string presentationFolder = @"C:\Dev\VHT\MergeDecks\SampleFiles\";

    // Make a copy of the template presentation. This will throw an
    // exception if the template presentation does not exist.
    File.Copy(presentationFolder + presentationTemplate,
      presentationFolder + mergedPresentation, true);

    // Loop through each source presentation and merge the slides 
    // into the merged presentation.
    foreach (string sourcePresentation in sourcePresentations)
      MergeSlides(presentationFolder, sourcePresentation,
        mergedPresentation);

    // Validate the merged presentation.
    OpenXmlValidator validator = new OpenXmlValidator();

    var errors =
      validator.Validate(presentationFolder + mergedPresentation);

    if (errors.Count() > 0)
    {
      Console.WriteLine("The merge process completed but the " +
        "merged presentation failed to validate.");
      Console.WriteLine("There are " + errors.Count() +
        " errors:\r\n");

      DisplayValidationErrors(errors);
    }
    else
      Console.WriteLine("The merge process completed and the " +
        "merged presentation validated with 0 errors.");
  }

  static void MergeSlides(string presentationFolder,
    string sourcePresentation, string destPresentation)
  {
    int id = 0;

    // Open the destination presentation.
    using (PresentationDocument myDestDeck =
      PresentationDocument.Open(presentationFolder + destPresentation,
      true))
    {
      PresentationPart destPresPart = myDestDeck.PresentationPart;

      // If the merged presentation does not have a SlideIdList 
      // element yet, add it.
      if (destPresPart.Presentation.SlideIdList == null)
        destPresPart.Presentation.SlideIdList = new SlideIdList();

      // Open the source presentation. This will throw an exception if
      // the source presentation does not exist.
      using (PresentationDocument mySourceDeck =
        PresentationDocument.Open(
          presentationFolder + sourcePresentation, false))
      {
        PresentationPart sourcePresPart =
          mySourceDeck.PresentationPart;

        // Get unique ids for the slide master and slide lists
        // for use later.
        uniqueId =
          GetMaxSlideMasterId(
            destPresPart.Presentation.SlideMasterIdList);

        uint maxSlideId =
          GetMaxSlideId(destPresPart.Presentation.SlideIdList);

        // Copy each slide in the source presentation, in order, to 
        // the destination presentation.
        foreach (SlideId slideId in
          sourcePresPart.Presentation.SlideIdList)
        {
          SlidePart sp;
          SlidePart destSp;
          SlideMasterPart destMasterPart;
          string relId;
          SlideMasterId newSlideMasterId;
          SlideId newSlideId;

          // Create a unique relationship id.
          id++;
          sp =
            (SlidePart)sourcePresPart.GetPartById(
              slideId.RelationshipId);

          relId =
            sourcePresentation.Remove(
              sourcePresentation.IndexOf('.')) + id;

          // Add the slide part to the destination presentation.
          destSp = destPresPart.AddPart<SlidePart>(sp, relId);

          // The slide master part was added. Make sure the
          // relationship between the main presentation part and
          // the slide master part is in place.
          destMasterPart = destSp.SlideLayoutPart.SlideMasterPart;
          destPresPart.AddPart(destMasterPart);

          // Add the slide master id to the slide master id list.
          uniqueId++;
          newSlideMasterId = new SlideMasterId();
          newSlideMasterId.RelationshipId =
            destPresPart.GetIdOfPart(destMasterPart);
          newSlideMasterId.Id = uniqueId;

          destPresPart.Presentation.SlideMasterIdList.Append(
            newSlideMasterId);

          // Add the slide id to the slide id list.
          maxSlideId++;
          newSlideId = new SlideId();
          newSlideId.RelationshipId = relId;
          newSlideId.Id = maxSlideId;

          destPresPart.Presentation.SlideIdList.Append(newSlideId);
        }

        // Make sure that all slide layout ids are unique.
        FixSlideLayoutIds(destPresPart);
      }

      // Save the changes to the destination deck.
      destPresPart.Presentation.Save();
    }
  }

  static void FixSlideLayoutIds(PresentationPart presPart)
  {
    // Make sure that all slide layouts have unique ids.
    foreach (SlideMasterPart slideMasterPart in
      presPart.SlideMasterParts)
    {
      foreach (SlideLayoutId slideLayoutId in
        slideMasterPart.SlideMaster.SlideLayoutIdList)
      {
        uniqueId++;
        slideLayoutId.Id = (uint)uniqueId;
      }

      slideMasterPart.SlideMaster.Save();
    }
  }

  static uint GetMaxSlideId(SlideIdList slideIdList)
  {
    // Slide identifiers have a minimum value of greater than or
    // equal to 256 and a maximum value of less than 2147483648. 
    uint max = 256;

    if (slideIdList != null)
      // Get the maximum id value from the current set of children.
      foreach (SlideId child in slideIdList.Elements<SlideId>())
      {
        uint id = child.Id;

        if (id > max)
          max = id;
      }

    return max;
  }

  static uint GetMaxSlideMasterId(SlideMasterIdList slideMasterIdList)
  {
    // Slide master identifiers have a minimum value of greater than
    // or equal to 2147483648. 
    uint max = 2147483648;

    if (slideMasterIdList != null)
      // Get the maximum id value from the current set of children.
      foreach (SlideMasterId child in
        slideMasterIdList.Elements<SlideMasterId>())
      {
        uint id = child.Id;

        if (id > max)
          max = id;
      }

    return max;
  }

  static void DisplayValidationErrors(
    IEnumerable<ValidationErrorInfo> errors)
  {
    int errorIndex = 1;

    foreach (ValidationErrorInfo errorInfo in errors)
    {
      Console.WriteLine(errorInfo.Description);
      Console.WriteLine(errorInfo.Path.XPath);

      if (++errorIndex <= errors.Count())
        Console.WriteLine("================");
    }
  }
}

 

Build and run the solution in Visual Studio by pressing CTRL+F5. When you build and run the sample code, it creates a presentation in the C:\Temp folder named MergedPresentation.pptx. The merged presentation contains all of the slides from three source presentations that are also located in the C:\Temp folder.

To change the names or location of the source or target presentations, modify the sample code and change the value of the mergedPresentation, presentationTemplate, sourcePresentations, or presentationFolder variables that are defined in the Main method. The presentations that you want to merge and the template presentation must exist for the solution to run successfully. To create the source and template presentations, open Microsoft Office PowerPoint 2007, create and name the presentations, and then save them to the folder that you specified in the presentationFolder variable.

Read It

The sample code in this visual how-to article illustrates how to create a PowerPoint 2007 presentation by merging the slides from multiple presentations into a single presentation. To accomplish that task, the sample code performs the following actions:

  • Opens a new merged presentation that is based on an existing template presentation.

  • Accesses the main presentation part of the merged presentation.

  • Opens each source presentation and accesses the main presentation part of that source presentation.

  • Adds each and every slide in the source presentation, in order, to the merged presentation.

  • For each and every slide that it adds to the merged presentation, the code verifies that there is a relationship between the merged main presentation part and the copied master slide layout part.

  • For each and every added slide part, the code verifies that there is a reference to that part within the merged main presentation part.

  • Performs cleanup work to ensure that all references to slide layout parts have unique ids.

  • Saves changes that it makes to the merged presentation.

  • Validates the merged presentation.

This section uses code snippets from the Code It section to describe the approach to the solution.

The sample code creates the merged presentation by making a copy of a presentation template, which can just be an empty presentation. Because you provide a presentation template, the code does not have to create the base presentation parts and relationships that are required to create a new merged presentation from scratch.

After it makes a copy of the template, the code calls the MergeSlides method for each source slide.

// Make a copy of the template presentation. This throws an
// exception if the template presentation does not exist.
File.Copy(presentationFolder + presentationTemplate,
  presentationFolder + mergedPresentation, true);

// Loop through each source presentation and merge the slides into 
// the merged presentation.
foreach (string sourcePresentation in sourcePresentations)
  MergeSlides(presentationFolder, sourcePresentation,
    mergedPresentation);

 

The MergeSlides method opens the source and destination presentations, adding a Slide ID List to the destination presentation if one does not already exist. It next iterates through the Slide ID List in the source presentation to ensure that all of the slides from the source presentation are added to the destination presentation in order. In addition, the code takes advantage of AddPart, which you can use to not only add a slide part, but to add all the parts referenced by that slide part as well.

static void MergeSlides(string presentationFolder,
  string sourcePresentation, string destPresentation)
{
  int id = 0;

  // Open the destination presentation.
  using (PresentationDocument myDestDeck =
    PresentationDocument.Open(presentationFolder + destPresentation,
    true))
  {
    PresentationPart destPresPart = myDestDeck.PresentationPart;

    // If the merged presentation does not have a SlideIdList 
    // element yet, add it.
    if (destPresPart.Presentation.SlideIdList == null)
      destPresPart.Presentation.SlideIdList = new SlideIdList();

    // Open the source presentation. This will throw an exception if
    // the source presentation does not exist.
    using (PresentationDocument mySourceDeck =
      PresentationDocument.Open(
        presentationFolder + sourcePresentation, false))
    {
      PresentationPart sourcePresPart = mySourceDeck.PresentationPart;

      // Get unique ids for the slide master and slide lists
      // for use later.
      uniqueId =
        GetMaxSlideMasterId(
          destPresPart.Presentation.SlideMasterIdList);

      uint maxSlideId =
        GetMaxSlideId(destPresPart.Presentation.SlideIdList);

      // Copy each slide in the source presentation, in order, to 
      // the destination presentation.
      foreach (SlideId slideId in
        sourcePresPart.Presentation.SlideIdList)
      {
        SlidePart sp;
        SlidePart destSp;
        SlideMasterPart destMasterPart;
        string relId;
        SlideMasterId newSlideMasterId;
        SlideId newSlideId;

        // Create a unique relationship id.
        id++;
        sp =
          (SlidePart)sourcePresPart.GetPartById(
            slideId.RelationshipId);

        relId =
          sourcePresentation.Remove(
            sourcePresentation.IndexOf('.')) + id;

        // Add the slide part to the destination presentation.
        destSp = destPresPart.AddPart<SlidePart>(sp, relId);
        …
      }
      …
    }
    …
  }
}

 

At this point the code has added the imported slide part to the destination presentation. The following code fixes up the relationship between the main presentation part and the added slide master part. Again, the code takes advantage of AddPart to add this relationship. AddPart will fix up relationships if the part already exists in the package.

// The slide master part was added. Make sure that the
// relationship between the main presentation part and
// the slide master part is in place.
destMasterPart = destSp.SlideLayoutPart.SlideMasterPart;
destPresPart.AddPart(destMasterPart);

 

Next, the code adds the Slide Master ID and the Slide ID to the appropriate lists in the destination main presentation part. The IDs that are referenced in each of these lists must be unique, and those unique ID values must be in a specific range. Slide IDs must be greater than or equal to 256 and less than 2147483648. Slide Master IDs must be greater than or equal to 2147483684. The sample code uses two methods that can return the current maximum ID value in the lists, and the code uses those capabilities to comply with the uniqueness and range constraints. Specifically, when the code adds a new item to these lists, it uses these methods to get the current maximum ID value in the lists, and then adds 1 to that value.

static uint GetMaxSlideId(SlideIdList slideIdList)
{
  // Slide identifiers have a minimum value of greater than or
  // equal to 256 and a maximum value of less than 2147483648. 
  uint max = 256;

  if (slideIdList != null)
    // Get the maximum id value from the current set of children.
    foreach (SlideId child in slideIdList.Elements<SlideId>())
    {
      uint id = child.Id;

      if (id > max)
        max = id;
    }

  return max;
}

static uint GetMaxSlideMasterId(SlideMasterIdList slideMasterIdList)
{
  // Slide master identifiers have a minimum value of greater than
  // or equal to 2147483648. 
  uint max = 2147483648;

  if (slideMasterIdList != null)
    // Get the maximum id value from the current set of children.
    foreach (SlideMasterId child in 
      slideMasterIdList.Elements<SlideMasterId>())
    {
      uint id = child.Id;

      if (id > max)
        max = id;
    }

  return max;
}

 

The GetMaxSlideMasterId and GetMaxSlideId methods are called before the loop that processes each slide in order to get the appropriate unique IDs for the Slide Master ID and Slide ID Lists. Once the unique IDs have been calculated, the code adds the Slide Master ID and Slide ID to the appropriate lists in the destination main presentation part.

// Add the slide master id to the slide master id list.
uniqueId++;
newSlideMasterId = new SlideMasterId();
newSlideMasterId.RelationshipId =
  destPresPart.GetIdOfPart(destMasterPart);
newSlideMasterId.Id = uniqueId;

destPresPart.Presentation.SlideMasterIdList.Append(
  newSlideMasterId);

// Add the slide id to the slide id list.
maxSlideId++;
newSlideId = new SlideId();
newSlideId.RelationshipId = relId;
newSlideId.Id = maxSlideId;

destPresPart.Presentation.SlideIdList.Append(newSlideId);

 

The next step ensures that all Slide Layout IDs are unique. Again, these ID values cannot conflict with the ID values of the slide Master Part ID list. In addition, the IDs must be greater than 2147483684. The FixSlideLayoutIds method goes through all slide master parts and fixes up all referenced Slide Layout IDs by incrementing the ID values based on the maximum value seen thus far.

static void FixSlideLayoutIds(PresentationPart presPart)
{
  // Make sure that all slide layouts have unique ids.
  foreach (SlideMasterPart slideMasterPart in
    presPart.SlideMasterParts)
  {
    foreach (SlideLayoutId slideLayoutId in
      slideMasterPart.SlideMaster.SlideLayoutIdList)
    {
      uniqueId++;
      slideLayoutId.Id = (uint)uniqueId;
    }

    slideMasterPart.SlideMaster.Save();
  }
}

 

Finally, the code validates the new merged presentation by using the OpenXmlValidator class. If there are validation errors, the code calls the DisplayValidationErrors helper method, passing the collection of ValidationErrorInfo objects.

// Validate the merged presentation.
OpenXmlValidator validator = new OpenXmlValidator();

var errors =
  validator.Validate(presentationFolder + mergedPresentation);

if (errors.Count() > 0)
{
  Console.WriteLine("The merge process completed but the merged " +
    "presentation failed to validate.");
  Console.WriteLine("There are " + errors.Count() +
    " errors:\r\n");

  DisplayValidationErrors(errors);
}
else
  Console.WriteLine("The merge process completed and the merged " +
    "presentation validated with 0 errors.");

 

The DisplayValidationErrors method loops through the passed ValidationErrorInfo objects and displays the error description and the path to the part where the error occurred.

static void DisplayValidationErrors(
  IEnumerable<ValidationErrorInfo> errors)
{
  int errorIndex = 1;

  foreach (ValidationErrorInfo errorInfo in errors)
  {
    Console.WriteLine(errorInfo.Description);
    Console.WriteLine(errorInfo.Path.XPath);

    if (++errorIndex <= errors.Count())
      Console.WriteLine("================");
  }
}

 

The following figures show the results after running the solution using source presentations that are based on the 2008, 2009, and 2010 calendar presentation templates that are available for download from Microsoft Office Online.

Figure 4. Source presentations displayed separately

Source presentations displayed separately

 

Figure 5. Merged presentations displayed together

Merged presentations displayed together

 

NoteNote

When you use the Open XML SDK 2.0 for Microsoft Office to create a document-generation solution, it is best practice to create a template document first, and then use DocumentReflector, a tool that comes with the SDK. DocumentReflector can generate C# code that uses the SDK typesafe classes to reproduce your template document and the functionality that it contains. You can then use that code to help you add functionality or to help you understand the Open XML document parts and relationships that are required to implement a specific document feature. For more information about best practices and the Open XML SDK 2.0 for Microsoft Office, see Erika Ehrli's blog entry Getting Started Best Practices.

Explore It