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   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

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

 

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM