java 使用 apoi 更新 ppt 中图表的数据
本文源码: 1. https://github.com/zhongchengyi/zhongcy.demos/tree/master/apoi-ppt-chart
2. 在第5节也有核心源码
1. apoi简介
Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
其中:
HSSF - 提供读写Microsoft Excel格式档案的功能。
XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
HWPF - 提供读写Microsoft Word格式档案的功能。
HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF - 提供读写Microsoft Visio格式档案的功能。
这里主要用到 HSLF
2. POI PPT特点
- 比较原始,与 XSSF 不同,没有对ppt做太好的封装,基本全是操作xml的方法。
- 关于poi ppt的文档比较少
- 关于open-xml的文档也比较少
- 为数不多的可以操作ppt的库
3. PPT文档结构简介
由于文档稀少,推荐自己创建简单的PPT,了解里面xml的结构,再根据其结构,通过代码读取,修改。
如:我自己创建了一个简单的ppt,只有一页,里面两个图表,我想找到图表数据所在的位置。
3.1 新建1.pptx内容如下

3.2 将1.pptx修改为1.zip
3.3 用解压工具对1.zip解压


3.4 ppt\slides 幻灯片
- 里面是幻灯片的xml,每一个文件代表一页幻灯片
- 一般是按照 slide1.xml , slide2.xml 命名的,后面的数字是页号
- 每个xml都是压缩结构的文档(即内容只有两行)
使用idea打开slide1.xml,格式化后,如图:

slide.xml 是记录幻灯片的结构:其中 Shape会记录里面的文本,批注,图表,备注都是记录rid, 这些信息都是记录在p:spTree节点下。
3.5 ppt\charts 图表数据
- 此目录记录以chartxx.xml图表信息
- 每个图表一个文件
- 所有幻灯片的图表都在这个目录,没有子目录了。

打开 chart1.xml

再打开1.pptx,找到第一张图表关联的数据,下图标注了系列具体的位置,其中,ser2代表A列和C列(c:cat部分与第一个c:ser共用)

3.5.1 c:ser / c:cat

- c:f 图表与excel 的关联关系,Sheet1!$A$2:$A$4 代表是sheet1的A列2行,到A列4行
- c:strCache 图表的缓存数据,是一个数组,c:ptCount是数组的长度,c:pt是数组里面的数据(如果更新图表时数据行与ppt原图表的长度不一样,需要更新 c:f, c:ptCount, c:pt)
3.5.2 c:ser / c:num

- 结构上与 c:cat 是一样的。
- c:numRef代表excel中的这一列是数字类型,
- c:strRef代表excel中的这一列是字符类型。
- 需要注意的是:c:cat和c:val下都有可能是c:numRef 或 c:strRef(我的源码这里没有判断)
3.5.3 相关接口
3.5.3.1 获取幻灯片的Chart
- XSLFSlide.getRelationParts();
- 遍历上面的数组
- 检查XSLFSlide.getRelationParts().get(n).getDocumentPart()的类型 instanceof XSLFChart
3.5.3.2 Chart关联的excel
- 读取:XSSFWookbook workbook = XSLFChart.getWorkBook()
- 修改:使用XSSFWookbook, XSSFSheet的相关接口
- 保存:步骤1返回的workbook.write(chart.getPackagepart().getOutputStream())
3.5.3.3 chart的缓存数据
- 通过 3.5.3.1 找到XSLFChart
- 找到绘图区域(xml中c:plotArea):XSLFChart.getCTChart().getPlotArea()
- 根据类型找到图表实例(可能是:CTPieChart, CTBarChart等):XSLFChart.getCTChart().getPlotArea().getXXXChartList()不为空的。
- 每个Chart实例都是同样的结构,以CTPieChart为例:CTPieChart.getCat获取c:cat, CTPieChart.getVal获取c:val
3.6 ppt\embeddings 嵌入的文档

4. 准备
- 使用IDEA新建一个java 控制台程序
- 新建一个 pom.xml 文件
- 在 pom.xml 中增加 apache poi 的依赖
- 使用 maven 安装依赖
4.1 poi的依赖如下
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.1</version>
</dependency>
安装完成后,在idea的 libraies 里会增加以下:

5.
流程及源码
- 获取 SlideShow
- 遍历 XSLFSlide
- 遍历 XSLFSlide的依赖部分
- 找到依赖部分为图表 (XSLFChart)的
- 根据图表标题、类型找到对应图表
- 更新图表关联的excel
- 更新图表的界面缓存数据
- 更新图表与关联excel的关系
- 保存新文件
代码如下:调用 run 方法
package zhongcy.demos; import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.sl.usermodel.SlideShow;
import org.apache.poi.sl.usermodel.SlideShowFactory;
import org.apache.poi.xslf.usermodel.XSLFChart;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.chart.*; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern; public class PPTDemo { public void run() {
try {
SlideShow slideShow = SlideShowFactory.create(new File("./res/1.pptx")); for (Object o : slideShow.getSlides()) {
XSLFSlide slider = (XSLFSlide) o; // 第一页
if (slider.getSlideNumber() == 1) {
for (POIXMLDocumentPart.RelationPart part : slider.getRelationParts()) {
POIXMLDocumentPart documentPart = part.getDocumentPart();
// 是图表
if (documentPart instanceof XSLFChart) {
XSLFChart chart = (XSLFChart) documentPart; // 查看里面的图表数据,才能知道是什么图表
CTPlotArea plot = chart.getCTChart().getPlotArea(); // 测试数据
List<SeriesData> seriesDatas = Arrays.asList(
new SeriesData("", Arrays.asList(
new NameDouble("行1", Math.random() * 100),
new NameDouble("行2", Math.random() * 100),
new NameDouble("行3", Math.random() * 100),
new NameDouble("行4", Math.random() * 100),
new NameDouble("行5", Math.random() * 100)
)),
new SeriesData("", Arrays.asList(
new NameDouble("行1", Math.random() * 100),
new NameDouble("行2", Math.random() * 100),
new NameDouble("行3", Math.random() * 100),
new NameDouble("行4", Math.random() * 100),
new NameDouble("行5", Math.random() * 100)
))
);
XSSFWorkbook workbook = chart.getWorkbook();
XSSFSheet sheet = workbook.getSheetAt(0); // 柱状图
if (!plot.getBarChartList().isEmpty()) {
CTBarChart barChart = plot.getBarChartArray(0);
updateChartExcelV(seriesDatas, workbook, sheet);
workbook.write(chart.getPackagePart().getOutputStream()); int i = 0;
for (CTBarSer ser : barChart.getSerList()) {
updateChartCatAndNum(seriesDatas.get(i), ser.getTx(), ser.getCat(), ser.getVal());
++i;
}
} // 饼图
else if (!plot.getPieChartList().isEmpty()) {
// 示例饼图只有一列数据
updateChartExcelV(Arrays.asList(seriesDatas.get(0)), workbook, sheet);
workbook.write(chart.getPackagePart().getOutputStream()); CTPieChart pieChart = plot.getPieChartArray(0);
int i = 0;
for (CTPieSer ser : pieChart.getSerList()) {
updateChartCatAndNum(seriesDatas.get(i), ser.getTx(), ser.getCat(), ser.getVal());
++i;
}
}
}
}
} } try {
try (FileOutputStream out = new FileOutputStream("./res/o1.pptx")) {
slideShow.write(out);
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} } catch (IOException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
}
} /**
* 更新图表的关联 excel, 值是纵向的
*
* @param param
* @param workbook
* @param sheet
*/
protected void updateChartExcelV(List<SeriesData> seriesDatas, XSSFWorkbook workbook, XSSFSheet sheet) {
XSSFRow title = sheet.getRow(0);
for (int i = 0; i < seriesDatas.size(); i++) {
SeriesData data = seriesDatas.get(i);
if (data.name != null && !data.name.isEmpty()) {
// 系列名称,不能修改,修改后无法打开 excel
// title.getCell(i + 1).setCellValue(data.name);
}
int size = data.value.size();
for (int j = 0; j < size; j++) {
XSSFRow row = sheet.getRow(j + 1);
if (row == null) {
row = sheet.createRow(j + 1);
}
NameDouble cellValu = data.value.get(j);
XSSFCell cell = row.getCell(0);
if (cell == null) {
cell = row.createCell(0);
}
cell.setCellValue(cellValu.name); cell = row.getCell(i + 1);
if (cell == null) {
cell = row.createCell(i + 1);
}
cell.setCellValue(cellValu.value);
}
int lastRowNum = sheet.getLastRowNum();
if (lastRowNum > size) {
for (int idx = lastRowNum; idx > size; idx--) {
sheet.removeRow(sheet.getRow(idx));
}
}
}
} /**
* 更新 chart 的缓存数据
*
* @param data 数据
* @param serTitle 系列的标题缓存
* @param catDataSource 条目的数据缓存
* @param numDataSource 数据的缓存
*/
protected void updateChartCatAndNum(SeriesData data, CTSerTx serTitle, CTAxDataSource catDataSource,
CTNumDataSource numDataSource) { // 更新系列标题
// serTitle.getStrRef().setF(serTitle.getStrRef().getF()); //
// serTitle.getStrRef().getStrCache().getPtArray(0).setV(data.name); // TODO cat 也可能是 numRef
long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal();
long ptNumCnt = numDataSource.getNumRef().getNumCache().getPtCount().getVal();
int dataSize = data.value.size();
for (int i = 0; i < dataSize; i++) {
NameDouble cellValu = data.value.get(i);
CTStrVal cat = ptCatCnt > i ? catDataSource.getStrRef().getStrCache().getPtArray(i)
: catDataSource.getStrRef().getStrCache().addNewPt();
cat.setIdx(i);
cat.setV(cellValu.name); CTNumVal val = ptNumCnt > i ? numDataSource.getNumRef().getNumCache().getPtArray(i)
: numDataSource.getNumRef().getNumCache().addNewPt();
val.setIdx(i);
val.setV(String.format("%.2f", cellValu.value)); } // 更新对应 excel 的range
catDataSource.getStrRef().setF(
replaceRowEnd(catDataSource.getStrRef().getF(),
ptCatCnt,
dataSize));
numDataSource.getNumRef().setF(
replaceRowEnd(numDataSource.getNumRef().getF(),
ptNumCnt,
dataSize)); // 删除多的
if (ptNumCnt > dataSize) {
for (int idx = dataSize; idx < ptNumCnt; idx++) {
catDataSource.getStrRef().getStrCache().removePt(dataSize);
numDataSource.getNumRef().getNumCache().removePt(dataSize);
}
}
// 更新个数
catDataSource.getStrRef().getStrCache().getPtCount().setVal(dataSize);
numDataSource.getNumRef().getNumCache().getPtCount().setVal(dataSize);
} /**
* 替换 形如: Sheet1!$A$2:$A$4 的字符
*
* @param range
* @return
*/
public static String replaceRowEnd(String range, long oldSize, long newSize) {
Pattern pattern = Pattern.compile("(:\\$[A-Z]+\\$)(\\d+)");
Matcher matcher = pattern.matcher(range);
if (matcher.find()) {
long old = Long.parseLong(matcher.group(2));
return range.replaceAll("(:\\$[A-Z]+\\$)(\\d+)", "$1" + Long.toString(old - oldSize + newSize));
}
return range;
} /**
* 一个系列的数据
*/
public static class SeriesData { /**
* value 系列的名字
*/
public String name; public List<NameDouble> value; public SeriesData(java.util.List<NameDouble> value) {
this.value = value;
} public SeriesData(String name, List<NameDouble> value) {
this.name = name;
this.value = value;
} public SeriesData() {
}
} /**
*
*/
public class NameDouble { public String name; /**
*/
public double value; public NameDouble(String name, double value) {
this.name = name;
this.value = value;
} @SuppressWarnings("unused")
public NameDouble() {
} }
}
6. 运行示例

java 使用 apoi 更新 ppt 中图表的数据的更多相关文章
- SQL批量更新数据库中所有用户数据表中字段类型为tinyint为int
--SQL批量更新数据库中所有用户数据表中字段类型为tinyint为int --关键说明:--1.从系统表syscolumns中的查询所有xtype='48'的记录得到类型为[tinyint]的字段- ...
- MySQL_(Java)使用JDBC向数据库中修改(update)数据
MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC向数据库中插入(insert)数据 传送门 MySQL_(Java)使用JDBC向数据库中删除(d ...
- MySQL_(Java)使用JDBC向数据库中删除(delete)数据
MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC向数据库中插入(insert)数据 传送门 MySQL_(Java)使用JDBC向数据库中删除(d ...
- MySQL_(Java)使用JDBC向数据库中插入(insert)数据
MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC向数据库中插入(insert)数据 传送门 MySQL_(Java)使用JDBC向数据库中删除(d ...
- Java读取excel指定sheet中的各行数据,存入二维数组,包括首行,并打印
1. 读取 //读取excel指定sheet中的各行数据,存入二维数组,包括首行 public static String[][] getSheetData(XSSFSheet sheet) thro ...
- Java实现 LeetCode 442 数组中重复的数据
442. 数组中重复的数据 给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次. 找到所有出现两次的元素. 你可以不用到任何额外空间并在O( ...
- Java程序向MySql数据库中插入的中文数据变成了问号
找到mysql的安装目录,修改my.ini文件 (1)如何找到my.ini文件 如果my.ini文件不在MySQL的安装目录下,可能放在隐藏目录,要先去找到ProgramData,(这里要先打开显示隐 ...
- Java 在PPT中绘制图形
Microsoft PowerPoint可支持在幻灯片中插入各种类型的图形并且可设置图形填充.线条颜色.图形大小.位置等.下面将通过Java编程来演示在PPT中绘制图形的方法. 工具:Free Spi ...
- Java 在PPT中创建SmartArt图形、读取SmartArt图形中的文本
一.概述及环境准备 SmartArt 图形通过将文字.图形从多种不同布局.组合来表现内容和观点的逻辑关系,能够快速.有效地传达设计者的意图和信息.这种图文表达的视觉表示形式常用于PPT,Word,Ex ...
随机推荐
- 18.libgdx制作预览图,背景移动循环,改变地图颜色
经过构思,游戏将分为两部分, 1,预览图,只负责展示世界形势 2,根据预览图生成的战役项 现在要记录的是我制作预览图的部分 1.预览图只有实际地图的1/4,首先生成地图(建议不要缩放以前地图,由于误差 ...
- python print函数
- day5_python之环境变量设置
1.设置环境变量 import os,sys print(os.path.abspath(__file__)) #当前py文件的绝对路径 print(os.path.dirname(os.path.a ...
- Java排序算法总结
1.冒泡排序 冒泡排序是排序算法中最基本的一种排序方法,该方法逐次比较两个相邻数据的大小并交换位置来完成对数据排序,每次比较的结果都找出了这次比较中数据的最大项,因为是逐次比较,所以效率是O(N^2) ...
- Scrapy五大核心组件简介
五大核心组件 scrapy框架主要由五大组件组成,他们分别是调度器(Scheduler),下载器(Downloader),爬虫(Spider),和实体管道(Item Pipeline),Scrapy引 ...
- oracle避免在索引列上使用计算
WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描. 举例: 低效: SELECT … FROM DEPT WHERE SAL * 12 > 25000; 高效: SE ...
- Ant Design的Form组件中FormItem名称相同引起的问题
1.问题描述 在使用Antd组件Form表单的过程中,会出现FormItem同名的情况,此时要特别注意同名引起的表单行为异常问题,主要表现在以下方面: (1)同名表单项的值共享,并且其中一个的值改变, ...
- Python--day24--继承面试题
输出:(打印的是Dog.func而不是Animal.func) __init__方法如果本生的类具有的话,父类的__init__方法就不在调用,没有才调用父类的__init__方法 派生属性: 如果既 ...
- poj 2996
提示:很烦很简单的国际象棋棋盘模拟,输入比较麻烦而已 输出时: 1.不论黑白,KQRBN P均是依次输出,强制大写,但不输出“P”,只输出其坐标 2.对白棋的位置,小行优先大行输出(行的数字越小则优先 ...
- H3C 基本ACL部署位置示例