Office Open XML Formats: Replacing PowerPoint 2007 Slide Images

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

Code 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")
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 Sub PPTReplaceImageOnSlide( _
 ByVal fileName As String, ByVal slideTitle As String, _
 ByVal imagePath As String)

  ' 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 doesn't have an image on it, do nothing.

  Const documentRelationshipType As String = _
   "http://schemas.openxmlformats.org/officeDocument/" & _
   "2006/relationships/officeDocument"
  Const slideRelationshipType As String = _
   "http://schemas.openxmlformats.org/officeDocument/" & _
   "2006/relationships/slide"
  Const imageRelationshipType As String = _
   "http://schemas.openxmlformats.org/officeDocument/" & _
   "2006/relationships/image"
  Const imageContentType As String = "image/jpeg"

  Const presentationmlNamespace As String = _
   "http://schemas.openxmlformats.org/presentationml/2006/main"
  Const drawingmlNamespace As String = _
   "http://schemas.openxmlformats.org/drawingml/2006/main"
  Const relationshipNamespace As String = _
   "http://schemas.openxmlformats.org/officeDocument/" & _
   "2006/relationships"

  Dim documentPart As PackagePart = Nothing
  Using pptPackage As Package = Package.Open( _
   fileName, FileMode.Open, FileAccess.ReadWrite)
    ' Get the main document part (presentation.xml).
    For Each documentRelationship As PackageRelationship _
     In pptPackage.GetRelationshipsByType( _
     documentRelationshipType)
      Dim documentUri As Uri = _
       PackUriHelper.ResolvePartUri( _
       New Uri("/", UriKind.Relative), _
       documentRelationship.TargetUri)
      documentPart = pptPackage.GetPart(documentUri)
      ' There is only one document.
      Exit For
    Next

    ' Next code block goes here.

  End Using
End Sub
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.
Dim nt As New NameTable()
Dim nsManager As New XmlNamespaceManager(nt)
nsManager.AddNamespace("p", presentationmlNamespace)
nsManager.AddNamespace("a", drawingmlNamespace)
nsManager.AddNamespace("r", relationshipNamespace)

' Next code block goes here.
//  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:

Dim slidePart As PackagePart = Nothing
Dim slideUri As Uri = Nothing

' Select each slide document part (slides/slideX.xml)
' via relationship with document part.
For Each slidePartRelation As PackageRelationship In _
documentPart.GetRelationshipsByType(slideRelationshipType)
  slideUri = PackUriHelper.ResolvePartUri( _
   documentPart.Uri, slidePartRelation.TargetUri)
  slidePart = pptPackage.GetPart(slideUri)

  ' Get the slide part from the package.
  Dim slideDoc As XmlDocument = New XmlDocument(nt)
  slideDoc.Load(slidePart.GetStream())

  ' Locate the slide title using XPath.
  Dim titleNode As XmlNode = slideDoc.SelectSingleNode( _
   "//p:sp//p:ph[@type='title' or @type='ctrTitle']", nsManager)
  If titleNode IsNot Nothing Then
    Dim titleText As String = _
     titleNode.ParentNode.ParentNode.ParentNode.InnerText

    ' Perform a case-insensitive comparison.
    If String.Compare(titleText, slideTitle, True) = 0 Then
      ' Found a match. Modify this slide.

      ' Next code block goes here.

    End If
  End If
Next
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.

Dim imagePart As PackagePart = Nothing

' Look through the slide's relationships to see if 
' there's an image in there:
For Each imagePartRelation As PackageRelationship _
In slidePart.GetRelationshipsByType(imageRelationshipType)
  Dim imageUri As Uri = PackUriHelper.ResolvePartUri( _
   slidePart.Uri, imagePartRelation.TargetUri)
  imagePart = pptPackage.GetPart(imageUri)
  If imagePart IsNot Nothing Then
    ' You've got an image!
    Dim oldRelID As String = imagePartRelation.Id

    ' Create a part for the new image
    ' (Be warned: If the package already contains 
    ' a part with this name (unlikely), you wouldn't be able
    ' to create the new part):
    Dim imageFile As String = Path.GetFileName(imagePath)
    Dim newImageUri As Uri = _
     New Uri("/ppt/media/" & imageFile, UriKind.Relative)
    Dim newImagePart As PackagePart = _
     pptPackage.CreatePart(newImageUri, imageContentType)

    ' Next code block goes here.

    ' Only update the first image you run across:
    Exit For
  End If
Next

' 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.
Exit For
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:
Dim newRelation As PackageRelationship = _
 slidePart.CreateRelationship( _
 newImageUri, TargetMode.Internal, imageRelationshipType)
Dim newRelId As String = newRelation.Id

' Update the relationship in the slide XML:
Dim searchString As String = _
 String.Format("//p:pic//a:blip[@r:embed='{0}']", oldRelID)
Dim relNode As XmlNode = _
 slideDoc.SelectSingleNode(searchString, nsManager)
If relNode IsNot Nothing Then
  relNode.Attributes("r:embed").Value = newRelId
End If

' 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.
Exit For
//  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;

Read It

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.

Explore It