我需要使用 Apache POI (XSLF) ppt 在幻灯片中复制相同的形状。
我可以做下面这样的代码吗?
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***);
XSLFShape
s 没有任何克隆方法。即使可以,也没有任何方法可以将克隆的 XSLFShape
添加到 XSLFSheet
(幻灯片)。有 XSSFSheet.addShape(XSLFShape shape) 但这除了抛出 UnsupportedOperationException
之外什么也没做。我喜欢apache poi
开发人员的幽默感。
因此,如果想要复制幻灯片的形状,则只能使用底层对象。类
org.apache.xmlbeans.XmlObject
提供了 copy
方法,可以对 XML 进行深层复制。然后需要将该副本添加到幻灯片的形状树中。然后需要重新初始化幻灯片的形状树。之后可以从XSSFSheet.getShapes()
获得形状的高级对象。不幸的是,大多数所需的方法不是公开的。所以需要用到反射。
以下代码显示了执行此操作的一种方法。它只是克隆给定
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();
}
}
如果克隆后您收到警告
shape id X has been already used
,
在调用之前尝试清除 shapeIds initDrawingAndShapes
:
static void clearShapeIds(XSLFSheet sheet) {
Field _shapeIds = XSLFSheet.class.getDeclaredField("shapeIds");
_shapeIds.setAccessible(true);
((SparseBitSet) _shapeIds.get(sheet)).clear();
}