java 使用 apoi 更新 ppt 中图表的数据

时间:2023-03-09 04:57:19
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内容如下

java 使用 apoi 更新 ppt 中图表的数据

3.2   将1.pptx修改为1.zip

3.3   用解压工具对1.zip解压

java 使用 apoi 更新 ppt 中图表的数据

java 使用 apoi 更新 ppt 中图表的数据

3.4   ppt\slides     幻灯片

  • 里面是幻灯片的xml,每一个文件代表一页幻灯片
  • 一般是按照 slide1.xml , slide2.xml 命名的,后面的数字是页号
  • 每个xml都是压缩结构的文档(即内容只有两行)

使用idea打开slide1.xml,格式化后,如图:

java 使用 apoi 更新 ppt 中图表的数据

slide.xml 是记录幻灯片的结构:其中 Shape会记录里面的文本,批注,图表,备注都是记录rid, 这些信息都是记录在p:spTree节点下。

3.5   ppt\charts 图表数据

  • 此目录记录以chartxx.xml图表信息
  • 每个图表一个文件
  • 所有幻灯片的图表都在这个目录,没有子目录了。

java 使用 apoi 更新 ppt 中图表的数据

打开 chart1.xml

java 使用 apoi 更新 ppt 中图表的数据

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

java 使用 apoi 更新 ppt 中图表的数据

3.5.1   c:ser / c:cat

java 使用 apoi 更新 ppt 中图表的数据

  • 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

java 使用 apoi 更新 ppt 中图表的数据

  • 结构上与 c:cat 是一样的。
  • c:numRef代表excel中的这一列是数字类型,
  • c:strRef代表excel中的这一列是字符类型。
  • 需要注意的是:c:cat和c:val下都有可能是c:numRef 或 c:strRef(我的源码这里没有判断)

3.5.3   相关接口

3.5.3.1            获取幻灯片的Chart

  1. XSLFSlide.getRelationParts();
  2. 遍历上面的数组
  3. 检查XSLFSlide.getRelationParts().get(n).getDocumentPart()的类型 instanceof XSLFChart

3.5.3.2            Chart关联的excel

  1. 读取:XSSFWookbook workbook = XSLFChart.getWorkBook()
  2. 修改:使用XSSFWookbook, XSSFSheet的相关接口
  3. 保存:步骤1返回的workbook.write(chart.getPackagepart().getOutputStream())

3.5.3.3            chart的缓存数据

  1. 通过 3.5.3.1 找到XSLFChart
  2. 找到绘图区域(xml中c:plotArea):XSLFChart.getCTChart().getPlotArea()
  3. 根据类型找到图表实例(可能是:CTPieChart, CTBarChart等):XSLFChart.getCTChart().getPlotArea().getXXXChartList()不为空的。
  4. 每个Chart实例都是同样的结构,以CTPieChart为例:CTPieChart.getCat获取c:cat, CTPieChart.getVal获取c:val

3.6   ppt\embeddings 嵌入的文档

java 使用 apoi 更新 ppt 中图表的数据

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 里会增加以下:

java 使用 apoi 更新 ppt 中图表的数据

5.   
流程及源码

  1. 获取 SlideShow
  2. 遍历 XSLFSlide
  3. 遍历 XSLFSlide的依赖部分
  4. 找到依赖部分为图表 (XSLFChart)的
  5. 根据图表标题、类型找到对应图表
  6. 更新图表关联的excel
  7. 更新图表的界面缓存数据
  8. 更新图表与关联excel的关系
  9. 保存新文件

代码如下:调用 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 中图表的数据

相关文章