How to duplicate a shape apache POI?

932 views Asked by At

I need to duplicate the same shape in a slide using Apache POI (XSLF) ppt.

I can do the something like this below code ?

static void cloneShape(XMLSlideShow slideShow, int slideNumber, String textBoxId) {
    Optional<XSLFShape> textBoxopt = getShapesByName(slideShow, slideNumber, textBoxId).stream().findFirst();
    XSLFAutoShape shapeToBeCloned = (XSLFAutoShape) textBoxopt.get();
    XSLFShapeContainer slide = slideShow.getSlides().get(slideNumber);
    XSLFAutoShape shape1 = slide.createAutoShape(***shapeToBeCloned***);
2

There are 2 answers

0
Axel Richter On

There is not any clone method for XSLFShapes. And even if it would, then there is not any method to add a cloned XSLFShape to the XSLFSheet (slide). There is XSSFSheet.addShape(XSLFShape shape) but this does nothing but throwing UnsupportedOperationException. I love the sense of humor of the apache poi developers.

So if one want copy a shape of a slide, then one only is able using the underlying objects. The class org.apache.xmlbeans.XmlObject provides a copy method which makes a deep copy of the XML. Then that copy needs to be added into the shape tree of the slide. Then the shape tree of the slide needs to be new initialized. After that the high level object of the shape can be got from XSSFSheet.getShapes(). Unfortunately most of the needed methods are not public. So reflection needs to be used.

Following code shows one way to do this. It simply clones all shapes except group shapes and graphical object frame shapes in each slide of the given PPTIn.pptx.

import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.apache.poi.sl.usermodel.*;
import org.apache.poi.xslf.usermodel.*;
import java.util.List;
import java.util.ArrayList;

public class PowerPointCloneShape {
    
 static List<XSLFShape> getShapesByName(XMLSlideShow slideShow, String shapeName) {
  List<XSLFShape> shapes = new ArrayList<XSLFShape>();
  for (XSLFSlide slide : slideShow.getSlides()) {
   for (XSLFShape shape : slide.getShapes()) {
    //System.out.println(shape.getShapeName());
    if (shapeName.equals(shape.getShapeName())) {
     shapes.add(shape);
    }
   }
  }
  return shapes;
 }
 
 static List<XSLFShape> getShapes(XMLSlideShow slideShow) {
  List<XSLFShape> shapes = new ArrayList<XSLFShape>();
  for (XSLFSlide slide : slideShow.getSlides()) {
   for (XSLFShape shape : slide.getShapes()) {
    shapes.add(shape);
   }
  }
  return shapes;
 }
 
 // method to new initialize drawing and shapes in sheet from updated shape tree
 static void initDrawingAndShapes(XSLFSheet sheet) throws Exception {
  java.lang.reflect.Field _drawing  = XSLFSheet.class.getDeclaredField("_drawing");
  _drawing.setAccessible(true);
  _drawing.set(sheet, null);
  java.lang.reflect.Field _shapes = XSLFSheet.class.getDeclaredField("_shapes");
  _shapes.setAccessible(true);
  _shapes.set(sheet, null);
  java.lang.reflect.Method initDrawingAndShapes = XSLFSheet.class.getDeclaredMethod("initDrawingAndShapes");
  initDrawingAndShapes.setAccessible(true);
  initDrawingAndShapes.invoke(sheet); 
 }
 
 // method to allocate the next shape ID in sheet
 static int allocateShapeId(XSLFSheet sheet) throws Exception {
  java.lang.reflect.Method allocateShapeId = XSLFSheet.class.getDeclaredMethod("allocateShapeId");
  allocateShapeId.setAccessible(true);
  int nextId = (int)allocateShapeId.invoke(sheet); 
  return nextId;
 }
 
 // method to get the shape tree of sheet
 static org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape getSpTree(XSLFSheet sheet) throws Exception {
  java.lang.reflect.Field _spTree = XSLFSheet.class.getDeclaredField("_spTree");
  _spTree.setAccessible(true);
  org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = (org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape)_spTree.get(sheet);
  return spTree;     
 }
 
 // method to clone a shape contained in a sheet
 static XSLFShape cloneShape(XSLFShape shape) throws Exception {
  // first clone low level XML   
  org.apache.xmlbeans.XmlObject xmlObject = shape.getXmlObject();
  org.apache.xmlbeans.XmlObject xmlClone =  xmlObject.copy();
  //System.out.println(xmlClone.getClass().getName());
  // then create high level clone shapes
  if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTShape) { // simple shape
   org.openxmlformats.schemas.presentationml.x2006.main.CTShape ctShapeClone = (org.openxmlformats.schemas.presentationml.x2006.main.CTShape)xmlClone;
   // get sheet
   XSLFSheet sheet = shape.getSheet();
   // set new ID
   int nextId = allocateShapeId(sheet);
   ctShapeClone.getNvSpPr().getCNvPr().setId(nextId);
   // add into the shape tree of sheet
   org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = getSpTree(sheet);
   spTree.addNewSp();
   spTree.setSpArray​(spTree.sizeOfSpArray()-1, ctShapeClone);
   // new initialize drawing and shapes in sheet
   initDrawingAndShapes(sheet);
   // get clone
   XSLFShape clone = sheet.getShapes().get(sheet.getShapes().size()-1);
   return clone;
  } else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTPicture) { // picture shape
   org.openxmlformats.schemas.presentationml.x2006.main.CTPicture ctPictureClone = (org.openxmlformats.schemas.presentationml.x2006.main.CTPicture)xmlClone;   
 
   XSLFSheet sheet = shape.getSheet();

   int nextId = allocateShapeId(sheet);
   ctPictureClone.getNvPicPr().getCNvPr().setId(nextId);
   
   org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = getSpTree(sheet);
   spTree.addNewPic();
   spTree.setPicArray​(spTree.sizeOfPicArray()-1, ctPictureClone);

   initDrawingAndShapes(sheet);
   
   XSLFShape clone = sheet.getShapes().get(sheet.getShapes().size()-1);
   return clone;
  } else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTConnector) { // connector shape
   org.openxmlformats.schemas.presentationml.x2006.main.CTConnector ctConnectorClone = (org.openxmlformats.schemas.presentationml.x2006.main.CTConnector)xmlClone;   

   XSLFSheet sheet = shape.getSheet();

   int nextId = allocateShapeId(sheet);
   ctConnectorClone.getNvCxnSpPr().getCNvPr().setId(nextId);
   
   org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape spTree = getSpTree(sheet);
   spTree.addNewCxnSp();
   spTree.setCxnSpArray​(spTree.sizeOfCxnSpArray()-1, ctConnectorClone);

   initDrawingAndShapes(sheet);
   
   XSLFShape clone = sheet.getShapes().get(sheet.getShapes().size()-1);
   // connector has connecting points which also simple are cloned but would must be new adjusted
   return clone;
  } else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape) { // group shape
   // cloning is not that simple
  } else if (xmlClone instanceof org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame) { // graphical object frame shape (table, chart, diagram, ...)
   // cloning is not that simple
  }
  return null;
 }

 public static void main(String args[]) throws Exception {

  XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("./PPTIn.pptx"));
  //List<XSLFShape> shapes = getShapesByName(slideShow, "Textbox 1");
  List<XSLFShape> shapes = getShapes(slideShow);
  System.out.println(shapes);
  //if (shapes.size() > 0 ) {
  //XSLFShape shape = shapes.get(0);  
  for (XSLFShape shape : shapes) {
   System.out.println("source: " + shape);
   XSLFShape clone = cloneShape(shape);
   System.out.println("clone: " + clone);
   if (clone instanceof PlaceableShape) {
    if (!clone.isPlaceholder() || clone.getPlaceholder() == Placeholder.CONTENT) { // do not change anchor of placeholders except content
     PlaceableShape placeableShape = (PlaceableShape)clone;   
     java.awt.geom.Rectangle2D anchor = shape.getAnchor();
     placeableShape.setAnchor(new java.awt.geom.Rectangle2D.Double(anchor.getX()+100, anchor.getY()+100, anchor.getWidth(), anchor.getHeight()));
     //System.out.println(clone.getAnchor());
    }
   }
   if (clone instanceof XSLFTextShape) {
    XSLFTextShape textShape = (XSLFTextShape)clone;
    if (textShape.getTextParagraphs().size() > 0 && textShape.getTextParagraphs().get(0).getTextRuns().size() > 0) {
     textShape.getTextParagraphs().get(0).getTextRuns().get(0).setText("new text");
    } else {
     textShape.setText("new text");     
    }
    //System.out.println(textShape.getText());
   }
  }

  FileOutputStream out = new FileOutputStream("./PPTOut.pptx");
  slideShow.write(out);
  out.close();
  slideShow.close();
 }
}
0
Артём Ощепков On

If after cloning you got warning shape id X has been already used, try clear shapeIds before calling initDrawingAndShapes:

static void clearShapeIds(XSLFSheet sheet) {
        Field _shapeIds = XSLFSheet.class.getDeclaredField("shapeIds");
        _shapeIds.setAccessible(true);
        ((SparseBitSet) _shapeIds.get(sheet)).clear();
}