This article and the source codes attached should be used as reference only.Please thoroughly test your implementation before making changes to production environment
Checkout our NEW Video Channel you can like and subscribe too!

Introduction

Generating PPT from Java using Apache POI is cumbersome .On top of that creating an PPT from the scratch is pain and the end result will not be the same as intended.This is because apache POI libs have limitations and cannot reproduce content 1-1 to modern ppt templates.

Luckily, there is a way to this.All you need to know is few concepts around how exactly any office document is created and little bit of marshalling/un-marshalling of xml using POI.The advantage of this is you don’t have to create the entire PPT again which time consuming and not the actual goal.

The actual goal is to add dynamics to the existing ppt using that as the base(template).

Source Code

The PPT template

Our template is fun fruit chart for xyz person. His/Her consumption of fruits per day displayed in a pir chart. Note the place holder texts here and the pie chart.We are going to replace this actual data in out output ppt.

ppt_template_02052020.PNG

Secret Inside office doc

Any office doc is a collection of media and bunch of xmls.So any document you are viewing is nothing but a representation of this xmls and media! If we get a good grip of how to navigate and correlate this xml tags then we have the power to do any modifications we want.

Rename a ppt to zip.Unzip it.You will find below folder structure

root_02052020.PNG

our interest is on the ppt folder, hop into that

ppt-folder-structure.png

Marshalling and Un-marshalling

Simply to put we need to read the xml into java object (un-marshalling) and then stream it back as xml(marshalling)

you can read more about jaxb here

Concept

So by now I think you have guessed how we are going to go about here, read the xmls from the template ppt using POI,modify the objects live in the input stream and then marshall it back to output stream and save the output ppt with a name and date.

xml post mortem

Lets tear apart the xmls and see the places we need to updated dynamically Got to slides folder, you will find the list of slides there.You can use my example slide to experiment

/ppt/slides/slide1.xml

slide-xml-02052020.png

any slide you will find this xml elements

cSld
  spTree
    p:sp //@XmlRootElement(name="sp")
      p:txBody //getTxBody()
        a:p // CTTextParagraph

of course there are lot of other stuffs too, which we don’t bother now. All we need to do is replace the placeholder

<a:t>User_Name</a:t>

Note on Slide Id

Each slide have a Slide Id, useful when you have specific slide to edit or delete.Look into this file to find the correct Id against the Slide.Note it is not necessary the Slide Id will be ascending/descending order.

/ppt/_rels/presentation.xml.rels

Chart Object

Chart Object are little different.You will find it under

/ppt/charts/chart1.xml

the data inside the chart is referred from the embedded excel

/ppt/embeddings/Microsoft_Excel_Worksheet.xlsx

the chart object reference this embedded workbook for data, so we are going first update the excel and recreate this chart object and marshall it back to the slide

chart-xml-02052020.png

Lets look into the code

You can download the source code from here

Load all the pointers, the template ppt, output ppt, embedded excel,any logo image

    // Inputs
            String inputfilepath = "test2.pptx";
            String chartPartName = "/ppt/charts/chart1.xml";
            String xlsPartName = "/ppt/embeddings/Microsoft_Excel_Worksheet.xlsx";
            String outputfilepath = "C:/ppt" + "/OUT_EditEmbeddedCharts-" + System.currentTimeMillis() + ".pptx";
            String pic_location = "C:/ppt/lnl_logo.png";
    

Load the data, I have hard-coded it here for demo.Probably your datasource will be existing application/db

 //data source
        String Fruit_1 = "Mango";
        String Fruit_2 = "Orange";
        String Fruit_3 = "Guava";
        String User_Name = "Learn Now Lab";
        String pid = "1234";
        
 //embedded excel
        Map<String, Integer> topModulesMap = new HashMap<>();
        topModulesMap.put("Mango", 1);
        topModulesMap.put("Orange", 2);
        topModulesMap.put("Guava", 3);

Lets take care of the place holder text first.We can traverse to dept of textbody as describe in the above xmls.From there load the child txBody as string buffer replace the place holder and then marshall it back.Finally put it back to the originals slide object at the same index.

 //All shape classes to edit all texts in the template
        ClassFinder dmlShapeFinder = new ClassFinder(Shape.class);
        new TraversalUtil(slide.getJaxbElement().getCSld().getSpTree().getSpOrGrpSpOrGraphicFrame(), dmlShapeFinder);
        if (dmlShapeFinder.results.isEmpty()) return;

        for (int i = 0; i < dmlShapeFinder.results.size(); i++) {
            Shape index_shp = (Shape) dmlShapeFinder.results.get(i);

            //*******************  slot starts***********************

            String buff = XmlUtils.marshaltoString(index_shp.getTxBody().getP().get(0), true, true, Context.jcPML,
                    "http://schemas.openxmlformats.org/presentationml/2006/main", "txBody", CTTextParagraph.class);

            if (buff.indexOf(FRUIT_1) != -1) buff = buff.replaceFirst(FRUIT_1, Fruit_1);
            if (buff.indexOf(FRUIT_2) != -1) buff = buff.replaceFirst(FRUIT_2, Fruit_2);
            if (buff.indexOf(FRUIT_3) != -1) buff = buff.replaceFirst(FRUIT_3, Fruit_3);
            if (buff.indexOf(PID) != -1) buff = buff.replaceFirst(PID, pid);
            if (buff.indexOf(USER_NAME) != -1) buff = buff.replaceFirst(USER_NAME, User_Name);

            CTTextParagraph testtt = new CTTextParagraph();
            try {
                testtt = (CTTextParagraph) XmlUtils.unmarshalString(buff, Context.jcPML, CTTextParagraph.class);
            } catch (JAXBException e) {
                e.printStackTrace();
            }
            index_shp.getTxBody().getP().set(0, testtt);

            // ********************** slot ends*************************

        }

Now chart

Here we need to load the charObject and replace the piechart object in the chart xml picture above.

Note: we can also open the embedded excel from the powepoint too to have look at the excel structure. Right CLick any Object graph in ppt and then click edit data.

 //******************* Chart  starts***********************

        Chart chart = (Chart) ppt.getParts().get(new PartName(chartPartName));
        List<Object> objects = chart.getJaxbElement().getChart().getPlotArea().getAreaChartOrArea3DChartOrLineChart();

        for (Object object : objects) {

            String ptCount = "<c:ptCount val=\"" + topModulesMap.size() + "\"/>";
            String strRef = "";
            String numRef = "";
            Iterator<String> iterator = topModulesMap.keySet().iterator();
            int counter = 0;
            while (iterator.hasNext()) {
                String key = iterator.next().toString();
                Integer value = topModulesMap.get(key);
                strRef = strRef + "<c:pt idx=\"" + counter + "\">" + "<c:v>" + key + "</c:v>" + "</c:pt>";
                numRef = numRef + "<c:pt idx=\"" + counter + "\">" + "<c:v>" + value + "</c:v>" + "</c:pt>";
                counter += 1;
            }


            String addval = "<p:txBody xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\" xmlns:ns6=\"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:c=\"http://schemas.openxmlformats.org/drawingml/2006/chart\" xmlns:ns12=\"http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:ns11=\"http://schemas.openxmlformats.org/drawingml/2006/compatibility\" xmlns:xdr=\"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\" xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
                    + " <c:idx val=\"0\"/>"
                    + " <c:order val=\"0\"/> "
                    + "<c:tx> "
                    + "<c:strRef> <c:f>Sheet1!$B$1</c:f> <c:strCache> <c:ptCount val=\"1\"/> <c:pt idx=\"0\"> <c:v>EBS Purchasing History</c:v> </c:pt> </c:strCache> </c:strRef> "
                    + "</c:tx> "
                    + "<c:cat> "
                    + "<c:strRef> <c:f>Sheet1!$A$2:$A$" + (topModulesMap.size() + 1) + "</c:f> <c:strCache>" + ptCount + strRef + "</c:strCache> </c:strRef>"
                    + "</c:cat> "
                    + "<c:val>"
                    + " <c:numRef> <c:f>Sheet1!$B$2:$B$" + (topModulesMap.size() + 1) + "</c:f> <c:numCache> <c:formatCode>General</c:formatCode>" + ptCount + numRef + "</c:numCache></c:numRef>"
                    + "</c:val> "
                    + "<c:extLst> <c:ext uri=\"{C3380CC4-5D6E-409C-BE32-E72D297353CC}\"> <c16:uniqueId val=\"{00000000-310B-4ADF-8D12-0B51F5325448}\" xmlns:c16r2=\"http://schemas.microsoft.com/office/drawing/2015/06/chart\" xmlns:c16=\"http://schemas.microsoft.com/office/drawing/2014/chart\"/> </c:ext> </c:extLst> "
                    + "</p:txBody>";


            CTPieSer testtt123 = new CTPieSer();

            try {
                testtt123 = (CTPieSer) XmlUtils.unmarshalString(addval, Context.jcPML, CTPieSer.class);
            } catch (JAXBException e) {
                e.printStackTrace();
            }

            ((CTPieChart) object).getSer().set(0, testtt123);
        }

        /*
         * Get the spreadsheet and find the cell values that need to be updated
         */

        EmbeddedPackagePart epp = (EmbeddedPackagePart) ppt.getParts().get(new PartName(xlsPartName));

        if (epp == null) {
            throw new Docx4JException("Could find EmbeddedPackagePart: " + xlsPartName);
        }

        InputStream is = BufferUtil.newInputStream(epp.getBuffer());
        SpreadsheetMLPackage spreadSheet = (SpreadsheetMLPackage) SpreadsheetMLPackage.load(is);
        Map<PartName, Part> partsMap = spreadSheet.getParts().getParts();
        Iterator<Entry<PartName, Part>> it = partsMap.entrySet().iterator();

        while (it.hasNext()) {
            Entry<PartName, Part> pairs = it.next();

            if (partsMap.get(pairs.getKey()) instanceof WorksheetPart) {

                WorksheetPart wsp = (WorksheetPart) partsMap.get(pairs.getKey());
                List<Row> rows = wsp.getJaxbElement().getSheetData().getRow();
                Iterator<String> iterator = topModulesMap.keySet().iterator();

                int counter = 1;
                while (iterator.hasNext()) {
                    String key = iterator.next().toString();
                    Integer value = topModulesMap.get(key);
                    rows.get(counter).getC().get(0).setT(STCellType.STR);
                    rows.get(counter).getC().get(0).setV(key);
                    rows.get(counter).getC().get(1).setT(STCellType.N);
                    rows.get(counter).getC().get(1).setV(value.toString());

                    counter += 1;
                }
            }
        }

        //******************* Chart  ends***********************
        
  

Additional Image

We may have a logo or image that we need add Dynamically.For example for different Client you may have the same template format but logo of the Client changes.

    //Add the picture
        File file = new File(pic_location);
        BinaryPartAbstractImage imagePart;
        try {
            imagePart = BinaryPartAbstractImage.createImagePart(ppt, slide, file);
            slide.getJaxbElement().getCSld().getSpTree().getSpOrGrpSpOrGraphicFrame().add(createPicture(imagePart.getSourceRelationship().getId()));
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

we store the picture xml in the same way and dynamically add the picture into it

    private static String SAMPLE_PICTURE =
            "<p:pic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\"> "
                    + "<p:nvPicPr>"
                    + "<p:cNvPr id=\"${id1}\" name=\"${name}\" descr=\"${descr}\"/>"
                    + "<p:cNvPicPr>"
                    + "<a:picLocks noChangeAspect=\"1\"/>"
                    + "</p:cNvPicPr>"
                    + "<p:nvPr/>"
                    + "</p:nvPicPr>"
                    + "<p:blipFill>"
                    + "<a:blip r:embed=\"${rEmbedId}\" cstate=\"print\"/>"
                    + "<a:stretch>"
                    + "<a:fillRect/>"
                    + "</a:stretch>"
                    + "</p:blipFill>"
                    + "<p:spPr>"
                    + "<a:xfrm>"
                    + "<a:off x=\"${offx}\" y=\"${offy}\"/>"
                    + "<a:ext cx=\"${extcx}\" cy=\"${extcy}\"/>"
                    + "</a:xfrm>"
                    + "<a:prstGeom prst=\"rect\">"
                    + "<a:avLst/>"
                    + "</a:prstGeom>"
                    + "</p:spPr>"
                    + "</p:pic>";

Final Output

We get a generate ppt like this.

ppt-output-02052020.png

Note: Its take 3-4 odd secs to generate 1 slide.It should be fine if your application offload the report generation process as a batch job.

Conclusion

At an initial look this might sound like a lot of tweaking.But once you get hold of this few components.You can replicate to almost all powerpoint objects.Post me here if you face any issue or have questions.Thanks for reading

    Content