Processing Power Point templates using OpenXml

  Processing Power-Point templates in automated processes is very common requirement. In such a scenarios we usually need to replace template placeholders with the dynamic content retrieved from the database or other data sources. 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.

http://www.dotnet-geek.co.uk/wp-content/uploads/2012/08/powerpoint-templates-258x300.png

To initiate processing we will use following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   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);

We will then have to parse the document to identify our placeholders and fill it with text or change images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 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);
            }
        }
    }
}

When replacing images, we simply need to find placeholders (blips) and replace embed ids with new image relation ids

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 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
                }
            }
            index += 1;
        }
    }
}

When replacing text placeholder we need to find all paragraphs in the slide and replace it with the our text if the placeholder id matches

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 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
        }
    }
}

The original article with the project files can be found here http://www.dotnet-geek.co.uk/index.php/openxml-power-point-templates-processing/

http://c.statcounter.com/9052428/0/942dbb33/1/