需求:根据给定的ppt模板,由后端整合数据,输出一份新的ppt。其实就是将ppt模板中的占位符替换成需要的数据,例如图片、文本等,然后再调整下样式,合并每一页成一份完整的ppt即可。
使用依赖以及相关包
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-slides</artifactId>
<version>21.11</version> <!-- 版本号根据实际情况调整 -->
</dependency>
准备工作:一些ppt,将其中需要替换的数据设置成,插入文本框,根据你的喜好设置占位符,我的是:#{},如下
插入文本框是因为这个比较好处理,当然也可以用其他形状,到时候遍历每一页的ppt,找到符合的形状,替换即可。
以下是关键源码
两个实体类,一个是文本对象,一个是图片对象,包含了各自的属性,用来调整样式
@Data
public class PptStrRequest {
/**
* 文本
*/
private String strValue;
/**
* 字号
*/
private Float strFont;
/**
* 颜色
*/
private Color strColor;
public PptStrRequest(String strValue, Float strFont, Color strColor) {
this.strValue = strValue;
this.strFont = strFont;
this.strColor = strColor;
}
}
@Data
public class PptImgRequest {
/**
* 图片路径
*/
private String imgUrl;
/**
* 矩形的左上角 x 坐标
*/
private Float imgLeftX;
/**
* 矩形的左上角 y 坐标
*/
private Float imgLeftY;
/**
* 矩形的宽度
*/
private Float imgW;
/**
* 矩形的高度。
*/
private Float imgH;
public PptImgRequest(String imgUrl, Float imgLeftX, Float imgLeftY, Float imgW, Float imgH) {
this.imgUrl = imgUrl;
this.imgLeftX = imgLeftX;
this.imgLeftY = imgLeftY;
this.imgW = imgW;
this.imgH = imgH;
}
}
params 用来存储数据,在PPT进行数据替换时使用,可以调用自己需要的接口,
Color类构造对象直接穿RGB值,当然还有其他方法,按自己需要使用
HashMap<String, Object> params = new HashMap<>();
params.put("1_str1", new PptStrRequest(formatter.format(LocalDateTime.now()), 15f, new Color(68, 114, 196)));
Map<String, String> page2 = pptDataService.getRainfallForecast();
params.put("2_str1", new PptStrRequest(page2.get("2_str1"), 20f, new Color(68, 114, 196)));
params.put("2_str2", new PptStrRequest(page2.get("2_str2"), 15f, new Color(0, 176, 240)));
params.put("2_img1", new PptImgRequest(page2.get("2_img1"), 300f, 100f, 420f, 360f));
params.put("2_img2", new PptImgRequest("/pic/2_img2.png", 520f, 440f, 170f, 13f));
包装好数据后,就进行PPT操作了,总的做法是:先加载整个PPT文件,然后遍历每一页,每一页再遍历所有形状,找到自己需要替换数据的形状,替换成相应的文本或者图片,在调整样式,例如相对位置、大小、颜色等
我的是每一个PPT文件就设置成一页,后期随意页码自由组合成一份新的PPT文件。
上源码
// 多个PPT模板
List<String> ppts = Arrays.asList("upload/ppt0","upload/ppt1","upload/ppt2","upload/ppt3");
for (int i = 0; i < ppts.size(); i++) {
String outputPath = pptPath + "/out/ppt" + i + ".pptx";
if (ppts.get(i).contains("ppt0")) {
continue;
}
Presentation presentation = new Presentation(ppts.get(i));
// 遍历每一页
for (int j = 0; j < presentation.getSlides().size(); j++) {
// 替换占位符
try {
replacePlaceholders(presentation, params);
} catch (IOException e) {
e.printStackTrace();
}
presentation.save(outputPath, com.aspose.slides.SaveFormat.Pptx);
}
}
// 占位符替换
private void replacePlaceholders(Presentation presentation, HashMap<String, Object> params) throws IOException {
for (ISlide slide : presentation.getSlides()) {
for (int i = 0; i < slide.getShapes().size(); i++) {
IShape shape = slide.getShapes().get_Item(i);
// 处理文本框
if (shape instanceof AutoShape) {
AutoShape autoShape = (AutoShape) shape;
// 替换文本
if (autoShape.getTextFrame() != null) {
String replaceHolder = autoShape.getTextFrame().getText();
if (replaceHolder.startsWith("#{") && replaceHolder.endsWith("}")) {
Iterator var6 = params.entrySet().iterator();
while (var6.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry) var6.next();
if (StringUtils.isEmpty(entry.getKey())) {
continue;
}
String key = entry.getKey();
String subKey = "#{" + key + "}";
Object obj = params.get(key);
if (subKey.contains("str") && replaceHolder.equals(subKey)) {
PptStrRequest entity = (PptStrRequest) obj;
String tx = entity.getStrValue();
if (StringUtils.isEmpty(tx)) {
tx = "无";
}
autoShape.getTextFrame().setText((tx));
// 设置文本字体、颜色、字号
IPortionFormat portionFormat = autoShape.getTextFrame().getParagraphs().get_Item(0).getPortions().get_Item(0).getPortionFormat();
portionFormat.setFontHeight(entity.getStrFont());
// portionFormat.getFillFormat().setFillType(FillType.Solid);
portionFormat.getFillFormat().getSolidFillColor().setColor(entity.getStrColor());
} else if (subKey.contains("img") && replaceHolder.equals(subKey)) {
PptImgRequest entity = (PptImgRequest) obj;
((AutoShape) shape).getTextFrame().setText(""); // 清空文本内容
String url = entity.getImgUrl();
if (StringUtils.isEmpty(url) || !new File(url).exists()) {
url = pptPath + "/pic/bg.png";
}
IPPImage image = presentation.getImages().addImage(new FileInputStream(url));
// 设置宽高以及位置
slide.getShapes().addPictureFrame(ShapeType.Rectangle, entity.getImgLeftX(),
entity.getImgLeftY(), entity.getImgW(), entity.getImgH(), image);
shape.getFillFormat().setFillType(com.aspose.slides.FillType.Picture);
}
}
}
}
}
}
}
}
处理完每个PPT文件后,进行合并成一份
public void mergePPTs(String filePath) throws IOException {
// 模板中的第一份PPT是一个空白的,将输出的PPT合并到这个空白的PPT
List<String> modelPpt = Arrays.asList("","","");
// 替换完厚的PPT
List<String> outPpt = Arrays.asList("","","");
// 空白ppt模板
if (CollectionUtil.isEmpty(modelPpt)) {
throw new BindException("ppt模板为空!");
}
Presentation pres1 = new Presentation(modelPpt.get(0));
try {
for (int i = 0; i < outPpt.size(); i++) {
Presentation temp = new Presentation(outPpt.get(i));
try {
for (ISlide slide : temp.getSlides()) {
// 此处易报错,采用这种方式不会
pres1.getSlides().addClone(slide);
}
} finally {
if (temp != null) {
temp.dispose();
}
}
}
pres1.save(filePath, SaveFormat.Pptx);
} finally {
if (pres1 != null) {
pres1.dispose();
}
}
}
有问题的合并方式如下
// 合并不完全,样式错乱
for (String pptFile : pptFiles) {
Presentation ppt = new Presentation(pptFile);
mergedPPT.getSlides().insertClone(mergedPPT.getSlides().size(), ppt.getSlides().get_Item(0));
ppt.dispose();
}
mergedPPT.save(outputFileName, SaveFormat.Pptx);
mergedPPT.dispose();
// 报错 importContent 方法会爆 Currently only SolidPaint is supported!
public static void mergedPresentation1() throws IOException {
List<String> pptFiles = Arrays.asList("template/ppt/out/ppt1.pptx", "template/ppt/out/ppt2.pptx");
XMLSlideShow mergedPPT = new XMLSlideShow();
for (String pptFile : pptFiles) {
XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(pptFile));
for (XSLFSlide slide : ppt.getSlides()) {
XSLFSlide newSlide = mergedPPT.createSlide();
for (XSLFShape shape : slide.getShapes()) {
XSLFFill fill = shape.getFill();
if (!(fill instanceof XSLFSolidPaint)) {
// 如果填充不是SolidPaint,则替换为SolidPaint
shape.setFill(new XSLFSolidPaint());
}
newSlide.importContent(shape);
}
}
ppt.close();
}
FileOutputStream out = new FileOutputStream("merged.pptx");
mergedPPT.write(out);
out.close();
mergedPPT.close();
}
aspose-slides 开发文档地址:
Aspose.Slides for Java|Aspose.Slides Documentation
还是看人家的,网上其余的一大堆都不能使用,会出各种奇怪的问题
生成PPT文档有些耗时,可以加个异步处理,先返回给前端数据
AsynCaller.Run(() -> {
// PPT生成
});
------------------------------------------------------割了---------------------------------------------------------------------今天稍微做了下优化,被吐槽说之前那样太麻烦了,还要一个个调,确实是哦。
1.不再使用实体类控制文本样式,在PPT模板上设置好,直接替换文本,更加便捷
2.不再使用文本框替换图片的方式,直接在模板上设置一个默认的底图,调好位置和宽高,在替换的时候,先获取底图的x、y、w、h四个参数,然后直接隐藏底图,把新的图片插入并且沿用原来的样式。
主要是 replacePlaceholders方法做了调整
private void replacePlaceholders(Presentation presentation, HashMap<String, String> params) throws IOException {
for (ISlide slide : presentation.getSlides()) {
for (int i = 0; i < slide.getShapes().size(); i++) {
IShape shape = slide.getShapes().get_Item(i);
if (shape instanceof AutoShape) {
AutoShape autoShape = (AutoShape) shape;
// 替换文本
if (autoShape.getTextFrame() != null) {
String replaceHolder = autoShape.getTextFrame().getText();
if (replaceHolder.startsWith("#{") && replaceHolder.endsWith("}")) {
Iterator var6 = params.entrySet().iterator();
while (var6.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry) var6.next();
if (StringUtils.isEmpty(entry.getKey())) {
continue;
}
String key = entry.getKey();
String subKey = "#{" + key + "}";
if (subKey.contains("str") && replaceHolder.equals(subKey)) {
String tx = params.get(key);
if (StringUtils.isEmpty(tx)) {
tx = "无";
}
autoShape.getTextFrame().setText((tx));
}
}
}
}
} else if (shape instanceof IPictureFrame) {
// 图片形状处理
Iterator var6 = params.entrySet().iterator();
while (var6.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry) var6.next();
// 在PPT模板中设置好需要替换图片的名字,入参时就用这个名字作为key
// params.put("我是老王", page2.get("2_img1"));
if (StringUtils.isEmpty(entry.getKey())
|| !shape.getName().equals(entry.getKey())) {
continue;
}
String url = params.get(entry.getKey());
if (StringUtils.isEmpty(url) || !new File(url).exists()) {
url = pptPath + "/pic/bg.png";
}
// 将原来的形状隐藏
shape.setHidden(true);
// 获取新的图片
IPPImage image = presentation.getImages().addImage(new FileInputStream(url));
IShapeCollection collection = slide.getShapes();
// 沿用原先的布局
collection.addPictureFrame(ShapeType.Rectangle, shape.getX(), shape.getY(), shape.getWidth(), shape.getHeight(), image);
}
}
}
}
}
shape的处理类型有这些,按需处理
if (shape instanceof IAutoShape) {
// 处理自动形状
IAutoShape autoShape = (IAutoShape) shape;
System.out.println("ppt元素类型"+"IAutoShape");
// 进行自动形状的操作
} else if (shape instanceof IGroupShape) {
// 处理组合形状
IGroupShape groupShape = (IGroupShape) shape;
System.out.println("ppt元素类型"+"IGroupShape");
// 进行组合形状的操作
} else if (shape instanceof IPictureFrame) {
// 处理图片框
IPictureFrame pictureFrame = (IPictureFrame) shape;
System.out.println("ppt元素类型"+"IPictureFrame");
// 进行图片框的操作
} else if (shape instanceof ITable) {
// 处理表格
ITable table = (ITable) shape;
System.out.println("ppt元素类型"+"ITable");
// 进行表格的操作
} else if (shape instanceof ITextFrame) {
// 处理文本框
ITextFrame textFrame = (ITextFrame) shape;
System.out.println("ppt元素类型"+"ITextFrame");
// 进行文本框的操作
} else {
// 其他类型的形状,根据实际情况进行处理
System.out.println("ppt元素类型"+"其他");
}