zoukankan      html  css  js  c++  java
  • java 使用 poi 更新 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   pptslides     幻灯片

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

     

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

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

     

    3.5   pptcharts 图表数据

    •   此目录记录以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

    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   pptembeddings 嵌入的文档

     

    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.    流程及源码

    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.    运行示例

     

     

  • 相关阅读:
    Python3 tkinter基础 Canvas create_text 在画布上添加文字
    js中如何返回一个存放对象的数组?
    vs2015 出现Lc.exe 已退出,代码为-1的问题,如何解决
    微信PC客户端无法发送图片,怎么解决?
    vs2015 编译时项目出现NuGet程序包还原失败,找不到xxx.xxx.xxx版本的程序包,怎么解决这个问题?
    sql server 根据身份证号计算出生日期和年龄的存储过程
    sql server中截取字符串的常用函数
    sql server 中进行除法运算时,如何得到结果是小数形式呢?
    sql中,如何获取一个数的整数部分和余数部分
    sql 中,如何获取两个日期之前月数、周数、天数
  • 原文地址:https://www.cnblogs.com/zhongchengyi/p/12101960.html
Copyright © 2011-2022 走看看