Export (0) Print
Expand All

Office Open XML Formats: Replacing PowerPoint 2007 Slide Images

Office 2007

Summary: Replace PowerPoint 2007 slide images programmatically using code snippets for use with Visual Studio 2005.

Office Visual How To

Applies to:2007 Microsoft Office system, Microsoft Office PowerPoint 2007, Visual Studio 2005

Ken Getz, MCW Technologies, LLC

March 2007

Overview

Imagine that you need to replace a single image on a specific slide in one or more PowerPoint 2007 presentations. Perhaps a logo changed, or a particular common graphic element was updated. The ability to perform this operation without requiring you to load PowerPoint 2007 and then load the presentations, one after another, can be an incredible time saver. The Office Open XML File Formats make this task possible. Working with the Office Open XML File Formats requires knowledge of the way PowerPoint stores the content, the System.IO.Packaging API, and XML programming.

See It

Bb332455.6cd7d961-2525-469f-9a4b-eaad80d9df96(en-us,office.12).jpg

Watch the Video

Length: 09:30 | Size: 7.0 MB | Type: WMV file

Code It | Read It | Explore It

To help you get started, you can download a set of forty code snippets for Microsoft Visual Studio 2005, each of which demonstrates various techniques working with the 2007 Office System Sample: Open XML File Format Code Snippets for Visual Studio 2005. After installing the code snippets, create a sample PowerPoint presentation to test. (See Read It section for reference). Create a Windows Application project in Visual Studio 2005, open the code editor, right-click and select Insert Snippet, and select the PowerPoint: Replace image snippet from the list of available 2007 Office system snippets. If you are using , inserting the snippet inserts a reference to WindowsBase.dll and adds the following Imports statements:

Imports System.IO
Imports System.IO.Packaging
Imports System.Xml

If you use Microsoft Visual C#, you need to add the reference to the WindowsBase.dll assembly and the corresponding using statements, so that you can compile the code. (Code snippets in C# cannot set references and insert using statements for you.) If the Windowsbase.dll reference does not appear on the .NET tab of the Add Reference dialog box, click the Browse tab, locate the C:\Program Files\Reference assemblies\Microsoft\Framework\v3.0 folder, and then click WindowsBase.dll.

The PPTReplaceImageOnSlide snippet demonstrates how to replace an image on a slide with another image that you specify. To test it, store your sample presentation somewhere easy to find (for example, C:\Test.pptx). In the Windows application project that you created, insert the PPTReplaceImageOnSlide snippet and then call it using this code:

PPTReplaceImageOnSlide("C:\\test.pptx", 
  "Image Slide", "C:\\WinterLeaves.jpg");

("Image Slide" is the title of the slide that has the image you want to replace and "C:\WinterLeaves.jpg" is the path to the replacement image.)

The first part of the snippet creates a) constants that represent the various namespaces required to search through the XML and b) the various relationship types needed to find the parts within the presentation. The next part of the snippet opens a Package object representing the contents of the presentation and uses the GetRelationsByType method to locate the document part, in this case, the XML part representing the entire presentation:

public void PPTReplaceImageOnSlide(
  string fileName, string slideTitle, string imagePath)
{
  //  Given a slide deck name and a slide title within the deck,
  //  replace the first image on the slide with the supplied image.
  //  If the slide does not have an image on it, do nothing.

  const string documentRelationshipType = 
    "http://schemas.openxmlformats.org/officeDocument/" + 
    "2006/relationships/officeDocument";
  const string slideRelationshipType = 
    "http://schemas.openxmlformats.org/officeDocument/" + 
    "2006/relationships/slide";
  const string imageRelationshipType = 
    "http://schemas.openxmlformats.org/officeDocument/" + 
    "2006/relationships/image";
  const string imageContentType = "image/jpeg";

  const string presentationmlNamespace = 
    "http://schemas.openxmlformats.org/presentationml/2006/main";
  const string drawingmlNamespace = 
    "http://schemas.openxmlformats.org/drawingml/2006/main";
  const string relationshipNamespace = 
    "http://schemas.openxmlformats.org/officeDocument/" + 
    "2006/relationships";

  PackagePart documentPart = null;
  using (Package pptPackage = 
    Package.Open(fileName, FileMode.Open, FileAccess.ReadWrite))
  {
    //  Get the main document part (presentation.xml).
    foreach (System.IO.Packaging.PackageRelationship 
      documentRelationship in 
      pptPackage.GetRelationshipsByType(documentRelationshipType))
    {
      Uri documentUri = PackUriHelper.ResolvePartUri(
        new Uri("/", UriKind.Relative), 
        documentRelationship.TargetUri);
      documentPart = pptPackage.GetPart(documentUri);
      //  There is only one document.
      break;
    }

    // Next code block goes here.

  }
}

The snippet must create and fill an XmlNamespaceManager instance because the snippet searches through XML content in an XmlDocument instance, and the XmlNamespaceManager has the namespace definitions that the snippet needs to perform the search. In this case, the namespace manager includes an entry for each namespace that the snippet needs as it searches for specific XML elements later on:

//  Manage namespaces to perform Xml XPath queries.
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("p", presentationmlNamespace);
nsManager.AddNamespace("a", drawingmlNamespace);
nsManager.AddNamespace("r", relationshipNamespace);

// Next code block goes here.

The next block searches for the first slide whose title matches the specified title. The code uses the PackagePart.GetRelationshipsByType method to retrieve a list of relationships that match the slide relationship type. For each of these, the code retrieves the slide part, stores it in an XmlDocument instance, retrieves the title (if it exists), and compares the title to the specified value:

PackagePart slidePart = null;
Uri slideUri = null;

//  Select each slide document part (slides/slideX.xml)
//  using the relationship from the officeDocument part.
foreach (System.IO.Packaging.PackageRelationship 
  slidePartRelation in 
  documentPart.GetRelationshipsByType(slideRelationshipType))
{
  slideUri = PackUriHelper.ResolvePartUri(
    documentPart.Uri, slidePartRelation.TargetUri);
  slidePart = pptPackage.GetPart(slideUri);

  //  Get the slide part from the package.
  XmlDocument slideDoc = new XmlDocument(nt);
  slideDoc.Load(slidePart.GetStream());

  //  Locate the slide title using XPath.
  XmlNode titleNode = slideDoc.SelectSingleNode(
    "//p:sp//p:ph[@type='title' or @type='ctrTitle']", 
    nsManager);
  if (titleNode != null)
  {
    string titleText = 
      titleNode.ParentNode.ParentNode.ParentNode.InnerText;

    //  Perform a case-insensitive comparison.
    if (string.Compare(titleText, slideTitle, true) == 0)
    {

      // Next code block goes here.

    }
  }
}

When the code finds a match for the requested title, the code then checks to determine if the slide contains an image by calling the GetRelationshipsByType method, passing the image relationship type constant. If it finds at least one image, the code calls the PackagePart.GetPart method to retrieve the first image part (this snippet only replaces the first matching image on a slide). If the image part is successfully retrieved, the snippet creates an image part by using the new image file name as the name of the part. (Note that the URI cannot contain spaces; if your image file name contains spaces, the CreatePart method fails.)

The next block of code resets the stream corresponding to the slide part, and writes its contents back out to the package. Note that this block does not delete the old image part, because other slides might be using it. Instead, it leaves the reference. The next time the presentation is loaded and resaved, PowerPoint deletes the image and its relationship if PowerPoint determines that the image is not used in any other slide. Because this snippet only replaces the first matching slide, the snippet exits the search loop after the first slide is replaced.

PackagePart imagePart = null;

//  Look through the slide's relationships to see 
// if there is an image.
foreach (System.IO.Packaging.PackageRelationship 
  imagePartRelation in 
  slidePart.GetRelationshipsByType(
  imageRelationshipType))
{
  Uri imageUri = PackUriHelper.ResolvePartUri(
    slidePart.Uri, imagePartRelation.TargetUri);
  imagePart = pptPackage.GetPart(imageUri);
  if (imagePart != null)
  {
    //  You have an image.
    string oldRelID = imagePartRelation.Id;

    //  Create a part for the new image
    //  (Important: If the package already contains 
    //  a part with this name (unlikely), you cannot 
    //  create the new part):
    string imageFile = Path.GetFileName(imagePath);
    Uri newImageUri = 
      new Uri("/ppt/media/" + imageFile, 
      UriKind.Relative);
    PackagePart newImagePart = 
      pptPackage.CreatePart(
      newImageUri, imageContentType);

    //  Now copy the bytes into the new part...
    using (Stream outputStream = 
      newImagePart.GetStream(
      FileMode.Create, FileAccess.Write))
    {
      using (FileStream inputStream = 
        new FileStream(imagePath, 
        FileMode.Open, FileAccess.Read))
      {
        //  This fails if the image is large 
        // (more bytes than an int can contain)
        int len = Convert.ToInt32(inputStream.Length);
        byte[] bytes = new byte[len];
        int bytesRead = inputStream.Read(bytes, 0, len);
        if (bytesRead == len)
        {
          outputStream.Write(bytes, 0, len);
        }
      }
    }

    //  Create a relationship to the new part.
    PackageRelationship newRelation = 
      slidePart.CreateRelationship(
      newImageUri, TargetMode.Internal, 
      imageRelationshipType);
    string newRelId = newRelation.Id;

    //  Update the relationship in the slide XML.
    string searchString = string.Format(
      "//p:pic//a:blip[@r:embed='{0}']", oldRelID);
    XmlNode relNode = 
      slideDoc.SelectSingleNode(searchString, nsManager);
    if (relNode != null)
    {
      relNode.Attributes["r:embed"].Value = newRelId;
    }
    // Delete the old relationship
    // PowerPoint will clean up the orphaned 
    // image part on the next save.
    slidePart.DeleteRelationship(oldRelID);

    //  Only update the first image you run across.
    break;
  }
}

//  Reset the stream, and save the slide XML 
// back to its part.
slideDoc.Save(slidePart.GetStream(
  FileMode.Create, FileAccess.Write));

//  Only modify the first slide that matches the specified title.
break;

The final code block creates a relationship that points to the new image part, and updates the relationship in the slide's XML to use the new relationship. It also deletes the old relationship, relying on PowerPoint to delete the orphaned image part, if necessary, the next time the presentation is loaded and resaved:

//  Create a relationship to the new part.
PackageRelationship newRelation = 
  slidePart.CreateRelationship(
  newImageUri, TargetMode.Internal, 
  imageRelationshipType);
string newRelId = newRelation.Id;

//  Update the relationship in the slide XML.
string searchString = string.Format(
  "//p:pic//a:blip[@r:embed='{0}']", oldRelID);
XmlNode relNode = 
  slideDoc.SelectSingleNode(searchString, nsManager);
if (relNode != null)
{
  relNode.Attributes["r:embed"].Value = newRelId;
}
// Delete the old relationship
// PowerPoint will clean up the orphaned 
// image part on the next save.
slidePart.DeleteRelationship(oldRelID);

//  Only update the first image you run across.
break;

To find the data that you need, it is important to understand the file structure of a simple PowerPoint document. To do that, create a PowerPoint document with several slides and provide a title for each slide. My test document, Test.pptx, contains several slides, including one named Image Slide, as shown in Figure 1.

Figure 1. The sample document contains two slides, each with a unique title

Image slide from test.pptx

To investigate the contents of the document, follow these steps:

  1. In Windows Explorer, rename the document Test.pptx.zip.

  2. Open the ZIP file, using either Window Explorer, or some ZIP-management application.

  3. View the _rels\.rels file, shown in Figure 2. This document contains information about the relationships between the parts in the document. Note the value for the presentation.xml part, as highlighted in the figure below (Figure 2)—this information allows you to find specific parts.

    Figure 2. Use relationships between document parts to find specific parts

    XML code snippet
  4. Open ppt\presentation.xml, shown in Figure 3. The highlighted element, p:sldIdLst, contains one reference for each slide in the deck. The snippet you investigate retrieves each of these slide references in turn, to retrieve the slide title.

    Figure 3. Use the r:id attribute to find each slide

    XML code snippet
  5. Open ppt\_rels\presentation.xml.rels, as shown in Figure 4. This document contains information about the relations between the document part and all the subsidiary parts, and the code snippet uses this information to find each of the slides, so that it can retrieve the title from the slide and compare it to the title you supply. Note that the slide whose relationship ID is rId3 refers to slides/slide2.xml.

    Figure 4. Each slide relationship appears in the presentation.xml.rels file

    XML code snippet presentation.xml
  6. Open ppt\slides\slide2.xml, as shown in Figure 5. This XML content contains the slide title. The code snippet uses XML-searching techniques to find this particular element within the XML content, and attempts to match the name it finds against the name you specified.

    Figure 5. The slide title appears within the XML content for the slide

    XML code snippet
  7. In the same file, find the p:pic element, which contains information about the picture on the slide. In Figure 6, the highlighted XML contains a relationship ID, rId2, that points to the picture file.

    Figure 6. Each picture includes a relationship ID so that you can work with the image

    XML code snippet
  8. Open ppt\slides\_rels\slide2.xml.rels, as shown in Figure 7. The highlighted element, corresponding to relationship rId2, refers to the existing image. Note that the link refers to ../media/image1.jpg.

    Figure 7. Each slide includes a relationship file that contains information about referenced parts, including images

    XML code snippet
  9. Browse to the ppt\media folder, and verify that you find an image file corresponding to the image on your existing slide. The PPTReplaceImageOnSlide snippet inserts a new image file here, and updates the reference to point to this new image instead of the old image.

  10. Close the tool you are using to view the presentation, and rename the file with a .PPTX extension.

Show:
© 2014 Microsoft