OpenXml Power Point templates processing
Handling Power Point templates is quite similar to handling Words templates as they both are based on OpenXML standard (see my previous article to learn how to process Word templates). In this article I will show you how to replace Power Point template with text and images using C# based application.
After creating new project we need to add two main references: DocumentFormat.OpenXml from DocumentFormat.OpenXml.dll and WindowsBase (.Net reference). Next, we need to prepare template with placeholders that our application will replace. For texts we will insert placeholder values in the the unique format that we can find parsing the document e.g. [#Paragraph1#].
For the images we need to insert image placeholders (other images) and set their names to match our parameters (e.g. myPicture1). This names needs to be provided in the parameter objects so we can find the placeholders by image name when parsing document.
The next step is to create parameters structure that we will use when processing the document. You may want to set your own parameters depending on your project requirements.
public class PowerPointParameter { public string Name { get; set; } public string Text { get; set; } public FileInfo Image { get; set; } }
We will use them as follows when initiating the objects
var templ = new PowerPointTemplate(); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#Paragraph1#]", Text = "Slide 1" }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#Paragraph2#]", Text = "Slide 2" }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#List1(string[])#]", Text = "test1 \n test 2 \n test3 \n test 2" }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#List2(string[])#]", Text = "test1 \n test 2 \n test3 \n test 2" }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "1", Image = new FileInfo(GetRootPath() + @"\Images\1.jpg") }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "2", Image = new FileInfo(GetRootPath() + @"\Images\2.jpg") }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "3", Image = new FileInfo(GetRootPath() + @"\Images\3.jpg") }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "4", Image = new FileInfo(GetRootPath() + @"\Images\4.jpg") }); templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "5", Image = new FileInfo(GetRootPath() + @"\Images\5.jpg") }); var templatePath = GetRootPath() + @"\Templates\Template.pptx"; var outputPath = GetRootPath() + @"\Output\Document.pptx"; templ.ParseTemplate(templatePath, outputPath);
Having done that we can create our main function that parses the template and fills our placeholders with texts and images. We may also want to delete an slide from presentation, we can do that by getting the slideID from the SlideIdList property and use RemoveChild function. Please see the inline comments within the function below.
public void ParseTemplate(string templatePath, string templateOutputPath) { using (var templateFile = File.Open(templatePath, FileMode.Open, FileAccess.Read)) //read our template { using (var stream = new MemoryStream()) { templateFile.CopyTo(stream); //copy template using (var presentationDocument = PresentationDocument.Open(stream, true)) //open presentation document { // Get the presentation part from the presentation document. var presentationPart = presentationDocument.PresentationPart; // Get the presentation from the presentation part. var presentation = presentationPart.Presentation; var slideList = new List<SlidePart>(); //get available slide list foreach (SlideId slideID in presentation.SlideIdList) { var slide = (SlidePart)presentationPart.GetPartById(slideID.RelationshipId); slideList.Add(slide); SlideDictionary.Add(slide, slideID);//add to dictionary to be used when needed } //loop all slides and replace images and texts foreach (var slide in slideList) { ReplaceImages(presentationDocument, slide); //replace images by name var paragraphs = slide.Slide.Descendants<Paragraph>().ToList(); //get all paragraphs in the slide foreach (var paragraph in paragraphs) { ReplaceText(paragraph); //replace text by placeholder name } } var slideCount = presentation.SlideIdList.ToList().Count; //count slides DeleteSlide(presentation, slideList[slideCount - 1]); //delete last slide presentation.Save(); //save document changes we've made } stream.Seek(0, SeekOrigin.Begin);//scroll to stream start point //save output file using (var fileStream = File.Create(templateOutputPath)) { stream.CopyTo(fileStream); } } } }
Function that replaces the images. It gets all Blip objects from the slide and changes it’s embed ID that points to the image.
Similar to Word templates, we can give our own styles and transformation to the image template and it will be preserved and applied to the new image 🙂
When replacing the images we search for all image parts in a slide and then check it’s names using NonVisualDrawingProperties of the Picture elements. Please see the inline comments.
void ReplaceImages(PresentationDocument presentationDocument, SlidePart slidePart) { // get all images in the slide var imagesToReplace = slidePart.Slide.Descendants<Blip>().ToList(); if (imagesToReplace.Any()) { var index = 0;//image index within the slide //find all image names in the slide var slidePartImageNames = slidePart.Slide.Descendants<DocumentFormat.OpenXml.Presentation.Picture>() .Where(a => a.NonVisualPictureProperties.NonVisualDrawingProperties.Name.HasValue) .Select(a => a.NonVisualPictureProperties.NonVisualDrawingProperties.Name.Value).Distinct().ToList(); //check all images in the slide and replace them if it matches our parameter foreach (var imagePlaceHolder in slidePartImageNames) { //check if we have image parameter that matches slide part image foreach (var param in PowerPointParameters) { //replace it if found by image name if (param.Image != null && param.Name.ToLower() == imagePlaceHolder.ToLower()) { var imagePart = slidePart.AddImagePart(ImagePartType.Jpeg); //add image to document using (FileStream imgStream = new FileStream(param.Image.FullName, FileMode.Open)) { imagePart.FeedData(imgStream); //feed it with data } var relID = slidePart.GetIdOfPart(imagePart); // get relationship ID imagesToReplace.Skip(index).First().Embed = relID; //assign new relID, skip if this is another image in one slide part //to change picture size dynamically you can use this code //int width = 150; int height = 100; //int imageWidthEMU = (int)Math.Round((decimal)width * 9525); //int imageHeightEMU = (int)Math.Round((decimal)height * 9525); //var picture = (DocumentFormat.OpenXml.Presentation.Picture)imagesToReplace.Skip(index).First().Parent.Parent; //picture.ShapeProperties.Transform2D.Extents.Cx = imageWidthEMU; //picture.ShapeProperties.Transform2D.Extents.Cy = imageHeightEMU; /////// } } index += 1; } } }
When replacing the texts we check if paragraph contains the text that matches our parameter. If yes, then we check if to include one or multiple lines of text.
Next we create new parameter by copying the old parameter’s OuterXML (this preserves the styles). We also need to replace text that is stored in our parameter.
void ReplaceText(Paragraph paragraph) { var parent = paragraph.Parent; //get parent element - to be used when removing placeholder var dataParam = new PowerPointParameter(); if (ContainsParam(paragraph, ref dataParam)) //check if paragraph is on our parameter list { //insert text list if (dataParam.Name.Contains("string[]")) //check if param is a list { var arrayText = dataParam.Text.Split(Environment.NewLine.ToCharArray()); //in our case we split it into lines if (arrayText is IEnumerable) //enumerate if we can { foreach (var itemData in arrayText) { Paragraph bullet = CloneParaGraphWithStyles(paragraph, dataParam.Name, itemData);// create new param - preserve styles parent.InsertBefore(bullet, paragraph); //insert new element } } paragraph.Remove();//delete placeholder } else { //insert text line var param = CloneParaGraphWithStyles(paragraph, dataParam.Name, dataParam.Text); // create new param - preserve styles parent.InsertBefore(param, paragraph);//insert new element paragraph.Remove();//delete placeholder } } }
Creating the new paragraph object preserving the styles
public static Paragraph CloneParaGraphWithStyles(Paragraph sourceParagraph, string paramKey, string text) { var xmlSource = sourceParagraph.OuterXml; xmlSource = xmlSource.Replace(paramKey.Trim(), text.Trim()); return new Paragraph(xmlSource); }
Please note that when replacing images, the image placeholder names must be found in the document. Images that don’t match the parameter name, wont be replaced.
Having done that we can finally test our application. I have included complete working application for your tests.