Posts

Adding links and styled text to Power-Point presentation

Formatting paragraphs in OpenXML documents is playing crucial part in proper implementation as the very common requirement is to change PowerPoint files programmatically during the business workflow processes.
I have recently received lot of queries about how to insert formatted text and links into the presentation. Hence, in this article I will show you how to do it.

In the past I have written article about auto generating Power-Point documents from the template file, in this part I will extend the functionality I have implemented before.

power-point-links

Let’s start with defining placeholder paragraphs we will use to insert our formatted texts and links:

 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 test2 n test3 n test4" });
 templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#List2(string[])#]", Text = "test1 n test2 n test3 n test4" });

 templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#Link1(#link#)#]", Text = "My link - to Microsoft; https://microsoft.com" });
 templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#Link2(#link#)#]", Text = "My link - to Google; https://google.com" });

As you can see, we will handle text lists and single links defined in our template. Of course the names and params will depend on your requirements.

I will skip describing how the process works as this can be read in my previous article. I will go straight to the styled paragraph creation.

Function below returns new paragraph with the styles we have defined in parameters. The RunProperties class is created, then we are assigning the styles – please note that the color needs to be converted to HEX before adding it to run properties. Next we need to define the text-box object containing paragraph’s text. At the end we simply need to append run properties and text to the run and finally append run to the paragraph object.

If you need multiple colors within one paragraph, simply create multiple texts, runs and run properties and configure it separately before assigning it to the paragraph object.

 public Paragraph CreateStyledParagraph(string text, System.Drawing.Color color, bool isBold, bool isItalic, int fontSize = 2000)
 {
    var runProperties = new RunProperties(); //set basic styles for paragraph
    runProperties.Bold = isBold;
    runProperties.Italic = isItalic;
    runProperties.FontSize = fontSize;
    runProperties.Dirty = false;

    var hexColor = color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2");//convert color to hex
    var solidFill = new SolidFill();
    var rgbColorModelHex = new RgbColorModelHex() { Val = hexColor };

    solidFill.Append(rgbColorModelHex);
    runProperties.Append(solidFill);

    //use this to assign the font family
    runProperties.Append(new LatinFont() { Typeface = "Arial Black" });

    var textBody = new Drawing.Text();
    textBody.Text = text; //assign text

    var run = new Drawing.Run();
    var newParagraph = new Paragraph();

    run.Append(runProperties);//append styles
    run.Append(textBody);//append text
    newParagraph.Append(run);//append run to paragraph

    return newParagraph;
}

Creating links is a little bit different. In order to do it we first need to create HyperlinkRelationship in the slide, the HyperlinkOnClick class contained within the run will then map to it on user click event. Please note that link color is not supported.

 public Paragraph CreateStyledLinkParagraph(SlidePart slidePart, string url, string text, bool isBold, bool isItalic, int fontSize = 2000)
 {
    //note: HyperlinkOnClick does not support link color

    var relationshipId = "rIdlink" + Guid.NewGuid().ToString();//create unique id
    slidePart.AddHyperlinkRelationship(new System.Uri(url, System.UriKind.Absolute), true, relationshipId);//assign hyperlink to the current slide we process

    var runProperties = new RunProperties(
         new LatinFont() { Typeface = "Bodoni MT Black"},
         new HyperlinkOnClick() { Id = relationshipId }); //set basic styles and assign relationshipId
    runProperties.Bold = isBold;
    runProperties.Italic = isItalic;
    runProperties.FontSize = fontSize;
    runProperties.Dirty = false;

    var textBody = new Drawing.Text();
    textBody.Text = text; //assign text

    var run = new Drawing.Run();
    var newParagraph = new Paragraph();

    run.Append(runProperties);//append styles
    run.Append(textBody);//append text
    newParagraph.Append(run);//append run to paragraph

    return newParagraph;
 }

The final replacement is being done in ReplaceText function. See in-line comments:

 void ReplaceText(Paragraph paragraph, SlidePart currentSlide)
 {
    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)
                {
                    //var newParagraph = CloneParaGraphWithStyles(paragraph, dataParam.Name, itemData);// create new param - preserve styles
                    var newParagraph = CreateStyledParagraph(itemData.Trim(), System.Drawing.Color.Green, false, true, 2000);

                    parent.InsertBefore(newParagraph, paragraph); //insert new element
                }
            }
            paragraph.Remove();//delete placeholder
        }
        else if (dataParam.Name.Contains("#link#")) //check if param is a link
        {
            var linkText = dataParam.Text.Split(';')[0].Trim();
            var linkUrl = dataParam.Text.Split(';')[1].Trim();

            var newParagraph = CreateStyledLinkParagraph(currentSlide, linkUrl, linkText, false, true, 1500);
            parent.InsertBefore(newParagraph, 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
        }
    }
 }

I have included fully working solution below so you can easily convert it to your needs. I hope this information was useful for you 🙂

PowerPoint-styled-TextLinks

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

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.

powerpoint-templates

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.

PowerPointTemplates

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

OpenXML Word templates processing

Office Open XML it’s the Microsoft’s zipped-xml based standard for processing Office documents. It allows to create, amend and process MS office files using e.g. .Net platform. In this article I will show you how to replace Word 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. [#Project-Name#].

For the images we need to insert image placeholders (other images) and just remember their original names (e.g. myPicture2.jpg). This names needs to be provided in the parameter objects so we can find the placeholders by image name when parsing document.

word-template-300x184

The next step is to create parameters structure that we will use when processing the document. You may want to create your own parameters depending or your requirements.

  public class WordParameter
    {
        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 WordTemplate();
    //add parameters
    templ.WordParameters.Add(new WordParameter() { Name = "[#Project-Name#]", Text = "Test project 123" });
    templ.WordParameters.Add(new WordParameter() { Name = "[#Features (string[])#]", Text = "Lorem Ipsum is simply dummy text of the printing \n Lorem Ipsum is simply dummy text of the printing \n Lorem Ipsum is simply dummy text of the printing..." });

    //original image names to be replaced with the new ones
    templ.WordParameters.Add(new WordParameter() { Name = "1.jpg", Image = new FileInfo(WordTemplate.GetRootPath() + @"\Images\1.jpg") });
    templ.WordParameters.Add(new WordParameter() { Name = "2.jpg", Image = new FileInfo(WordTemplate.GetRootPath() + @"\Images\2.jpg") });
    templ.WordParameters.Add(new WordParameter() { Name = "3.jpg", Image = new FileInfo(WordTemplate.GetRootPath() + @"\Images\3.jpg") });
    templ.WordParameters.Add(new WordParameter() { Name = "4.jpg", Image = new FileInfo(WordTemplate.GetRootPath() + @"\Images\4.jpg") });
    templ.WordParameters.Add(new WordParameter() { Name = "5.jpg", Image = new FileInfo(WordTemplate.GetRootPath() + @"\Images\5.jpg") });

    templ.ParseTemplate(); //create document from template

Having done that we can create our main function that parses the template and fills our placeholders with texts and images. Please see the inline comments within the function below.

 public void ParseTemplate()
 {
    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 wordDoc = WordprocessingDocument.Open(stream, true)) //open word document
            {
                foreach (var paragraph in wordDoc.MainDocumentPart.Document.Descendants<Paragraph>().ToList()) //loop through all paragraphs 
                {
                    ReplaceImages(wordDoc, paragraph); //replace images

                    ReplaceText(paragraph); //replace text
                }

                wordDoc.MainDocumentPart.Document.Save(); //save document changes we've made
            }
            stream.Seek(0, SeekOrigin.Begin);//scroll to stream start point

            //save file or overwrite it
            var outPath = WordTemplate.GetRootPath() + @"\Output\DocumentOutput.docx";

            using (var fileStream = File.Create(outPath))
            {
                stream.CopyTo(fileStream);
            }
        }
    }
 }

Function that replaces the images. It gets all Blip objects from the paragraph and changes it’s embed ID that points to the image.

The cool thing about it is fact that you can give your own styles and transformation to the image template and it will be preserved and applied to the new image 🙂

Please see the inline comments.

 void ReplaceImages(WordprocessingDocument wordDoc, Paragraph paragraph)
 {
    // get all images in paragraph
    var imagesToReplace = paragraph.Descendants<A.Blip>().ToList();

    if (imagesToReplace.Any())
    {
        var index = 0;//image index within paragraph

        //find all original image names in paragraph
        var paragraphImageNames = paragraph.Descendants<DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties>().ToList();

        //check all images in the paragraph and replace them if it matches our parameter
        foreach (var imagePlaceHolder in paragraphImageNames)
        {
            //check if we have image parameter that matches paragraph image
            foreach (var param in WordParameters)
            {
                //replace it if found by original image name
                if (param.Image != null && param.Image.Name.ToLower() == imagePlaceHolder.Name.Value.ToLower())
                {
                    var imagePart = wordDoc.MainDocumentPart.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 = wordDoc.MainDocumentPart.GetIdOfPart(imagePart); // get relationship ID

                    imagesToReplace.Skip(index).First().Embed = relID; //assign new relID, skip if this is another image in one paragraph
                }
            }
            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 WordParameter();

    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.
WordTemplates

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...