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.
**Applies to:**2007 Microsoft Office system, Microsoft Office PowerPoint 2007, Visual Studio 2005
Ken Getz, MCW Technologies, LLC
March 2007
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
To investigate the contents of the document, follow these steps:
In Windows Explorer, rename the document Test.pptx.zip.
Open the ZIP file, using either Window Explorer, or some ZIP-management application.
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
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
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
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
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
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
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.
Close the tool you are using to view the presentation, and rename the file with a .PPTX extension.