POI实现根据PPTX模板渲染PPT

目录

1、前言

2、了解pptx文件结构

3、POI组件

3.1、引入依赖

3.2、常见的类

3.3、实现原理

3.4、关键代码片段

3.4.1、获取ppt实例

3.4.2、获取每页幻灯片

3.4.3、循环遍历幻灯片处理

3.4.3.1、文本

3.4.3.2、饼图

3.4.3.3、柱状图

3.4.3.4、表格

3.4.3.5、本地文件连接

3.4.3.6、入口主类


1、前言

项目中有时候需要实现导出ppt格式报告,生成ppt文件的方式有很多,常见的有poi,aspose,pptx4j。

**Apache POI,**适合需要处理PPT基础功能的情况,免费开源。

**Aspose.Slides,**适合企业级应用,功能强大但收费。

**Docx4j + pptx4j,**较低层次的PPT操作工具,适合需要与docx4j一同使用的项目。

现在基本项目中都依赖了poi,因此这里首选poi来实现。基本的实现包括:文字占位替换,表格生成,报表生成(包括饼图,柱状图),超文本连接替换。

2、了解pptx文件结构

常见的pptx文件,实际上是基于XML的压缩文件。我们将.pptx文件的后缀改成.zip。即可直接解压缩出来内部的文件内容。通常包括以下几个主要部分:

  1. [Content_Types].xml:描述PPTX文件的内容类型,用于指定各个组件的格式(如幻灯片、文本、图像等)。

  2. docProps:包含文件属性,分为两部分:

    • core.xml:存储核心属性,如标题、作者、主题、创建日期等。

    • app.xml:存储应用属性,如幻灯片数量、主题、文档内容等。

  3. ppt文件夹 :PPTX的主要内容,包括以下子文件夹和文件:

    • slides:包含每张幻灯片的内容(如文本、图像、动画等),每张幻灯片都对应一个XML文件。
    • slides/_rels:每张幻灯片的关系文件,描述幻灯片内容中图像、视频、音频等的关联关系。
    • media:存储幻灯片中包含的媒体文件(如图像、视频和音频文件)。
    • theme:定义幻灯片的主题样式,包含配色方案、字体等。
    • charts:存储PPT中的图表数据。
    • tables:存储PPT中的表格信息。
    • notesSlides:包含每张幻灯片的演讲者备注内容。
    • embeddings:PPT报表关联的Excel文件。
  4. _rels文件夹:该文件夹用于管理文件之间的关系,通常包含一个**.rels**文件,描述各组件之间的关联性,比如幻灯片、媒体、样式等的链接关系。

由于我们这次需要渲染多种报表,报表的生成本质是依赖于Excel文本的数据填充,以及公式的计算和渲染。因此我们将会重点关注ppt\charts图表数据和ppt\embeddings的Excel文件。

3、POI组件

3.1、引入依赖

XML 复制代码
<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.3.0</version>
    </dependency>

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.28</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.32</version>
    </dependency>
</dependencies>

3.2、常见的类

|------------------|------------------------------------------------------|
| 术语 | 解释 |
| XMLSlideShow | PPT演示文稿,通常指一份.pptx文件。代码中会优先获取该ppt实例,根据该实例获取ppt内具体元素。 |
| XSLFSlide | 幻灯片,指ppt内的每一页。通过该对象可以获取每一页幻灯片内的所有元素。 |
| XSLFShape | 幻灯片内的所有形状,比如图形元素等。 |
| XSLFTextShape | XSLFShape的子类,指文本框元素 |
| XSLFTable | XSLFShape的子类,指表格元素 |
| XSLFGraphicFrame | XSLFShape的子类,指表格元素 |
| XSLFChart | 报表元素 |
| CTPieChart | 饼图元素 |
| CTBarChart | 柱状图元素 |

3.3、实现原理

简单介绍下POI渲染PPT的原理:

  1. 读取pptx模板,new XMLSlideShow(inputStream)得到ppt实例;
  2. 通过getSlides()方法获取该ppt的所有幻灯片集合;
  3. 循环遍历所有的slides,通过getShapes()获取XSLFShape,每个幻灯片上的形状;
  4. 结合形状的类型,或报表的标题,以及该幻灯片的页码,可以确定我们需要渲染的某一个报表图形;
  5. 将shape转成对应图形元素,如果是文字类型,直接设置对应文本内容即可;
  6. 如果是报表类型,根据对应的报表类型转换后,渲染对应的Ser,Cat,Val等属性;本质其实是ppt关联了一份内置的excel,刷新excel索引渲染出报表;如:
  1. 具体的cat和val的属性节点,每份ppt解压出来后,每个报表都会对应一份chartxx.xml,打开这份xml即为这个报表对应的节点信息。如:
  1. 最后渲染报表索引。

3.4、关键代码片段

3.4.1、获取ppt实例

java 复制代码
public class PowerPointUtil {

    public static XMLSlideShow getXmlSlideShow(FileInputStream inputStream) {
        try {
            return new XMLSlideShow(inputStream);
        } catch (IOException e) {
            System.err.println("初始化ppt实例错误");
        }
        return null;
    }

}

3.4.2、获取每页幻灯片

java 复制代码
FileInputStream inputStream = new FileInputStream("模板.0.pptx");
XMLSlideShow ppt = PowerPointUtil.getXmlSlideShow(inputStream);

// 获取幻灯片 XSLFSlide
List<XSLFSlide> slides = ppt.getSlides();

3.4.3、循环遍历幻灯片处理

3.4.3.1、文本

如果是文本,直接将ppt需要渲染的文字替换为关键字符,如PA_DEVICE、PA_SUPPLIER等。

java 复制代码
if (shape instanceof XSLFTextShape) {
    XSLFTextShapeImpl.generalXSLFText(ppt, DataParam.getTextDataMap());
}

public class XSLFTextShapeImpl {
    public static void generalXSLFText(XMLSlideShow ppt, Map<String, String> dataParam){
        // 获取幻灯片 XSLFSlide
        List<XSLFSlide> slides = ppt.getSlides();

        if(CollUtil.isEmpty(slides)){
            return ;
        }

        slides.forEach(slide -> {
            List<XSLFShape> shapes = slide.getShapes();
            if(CollUtil.isEmpty(shapes)){
                return ;
            }

            shapes.stream().filter(shape -> shape instanceof XSLFTextShape).forEach(shape -> {
                XSLFTextShape textShape = (XSLFTextShape) shape;
                for (XSLFTextParagraph textParagraph : textShape.getTextParagraphs()) {
                    for (XSLFTextRun textRun : textParagraph.getTextRuns()) {
                        final String[] text = {textRun.getRawText()};
                        dataParam.forEach((key, value) -> text[0] = text[0].replace(key, value));
                        textRun.setText(text[0]);
                    }
                }
            });
        });
    }

}

其中dataParam数据为:

java 复制代码
/**
 * 文字占位
 * @return
 */
public static Map<String, String> getTextDataMap(){
    // 文本数据映射表
    Map<String, String> textDataMap = new HashMap<>();

    textDataMap.put("PA_TITLE", "测试报告");
    String formatted = DateUtil.format(DateUtil.date(), "yyyy年MM月dd日");
    textDataMap.put("PA_CREATE_TIME", formatted);

    textDataMap.put("PA_SUPPLIER_C", "10");
    textDataMap.put("PA_DEVICE_C", "50");
    textDataMap.put("PA_DEVICE_PER", "80%");
    return textDataMap;
}
3.4.3.2、饼图
java 复制代码
/**
 *
 * @param chart
 * @param is3DPie
 * @param dataParam
 * @throws IOException
 * @throws InvalidFormatException
 */
public static void generalXSLFPieChart(XSLFChart chart, boolean is3DPie, List<DataParam.NamedValue> dataParam) throws IOException, InvalidFormatException {
    if(CollUtil.isEmpty(dataParam)){
        return ;
    }
    XSSFWorkbook workbook = chart.getWorkbook();
    XSSFSheet sheetAt = workbook.getSheetAt(0);

    for (int i = 0; i < dataParam.size(); i++) {
        int j = i + 1;
        sheetAt.createRow(j);
        sheetAt.getRow(j).createCell(0).setCellValue(dataParam.get(i).getName());
        sheetAt.getRow(j).createCell(1).setCellValue(dataParam.get(i).getValue());
    }
    workbook.write(chart.getPackagePart().getOutputStream());

    // 刷新图表缓存
    CTPlotArea plotArea = chart.getCTChart().getPlotArea();

    // 是3D饼图还是扁平饼图
    List<CTPieSer> serList = is3DPie ? plotArea.getPie3DChartArray(0).getSerList() : plotArea.getPieChartArray(0).getSerList();
    for (CTPieSer ser : serList) {
        // 更新excel范围range
        CTNumDataSource numDataSource = ser.getVal();
        CTAxDataSource catDataSource = ser.getCat();

        // TODO cat 也可能是 numRef
        long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal();
        long ptNumCnt = numDataSource.getNumRef().getNumCache().getPtCount().getVal();

        for (int i = 0; i < dataParam.size(); i++) {
            DataParam.NamedValue cellValue = dataParam.get(i);
            CTStrVal cat = ptCatCnt > i ? catDataSource.getStrRef().getStrCache().getPtArray(i)
                    : catDataSource.getStrRef().getStrCache().addNewPt();
            cat.setIdx(i);
            cat.setV(cellValue.getName());

            CTNumVal val = ptNumCnt > i ? numDataSource.getNumRef().getNumCache().getPtArray(i)
                    : numDataSource.getNumRef().getNumCache().addNewPt();
            val.setIdx(i);
            val.setV(String.format("%.2f", Double.parseDouble(cellValue.getValue())));

        }

        catDataSource.getStrRef().setF(
                replaceRowEnd(catDataSource.getStrRef().getF(),
                        ptCatCnt,
                        dataParam.size()));
        numDataSource.getNumRef().setF(
                replaceRowEnd(numDataSource.getNumRef().getF(),
                        ptNumCnt,
                        dataParam.size()));
        // 更新个数
        catDataSource.getStrRef().getStrCache().getPtCount().setVal(dataParam.size());
        numDataSource.getNumRef().getNumCache().getPtCount().setVal(dataParam.size());
    }
}
3.4.3.3、柱状图
java 复制代码
public class XSLFBarChartShapeImpl extends AbstractXSLFChartShape {

    private static final String[] COL_TITLE_F = {"B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};

    /**
     * 2列数据柱状图
     * @param chart
     * @param dataParam
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static void generalXSLFBarChart(XSLFChart chart, List<DataParam.NamedValue> dataParam, boolean isSort) throws IOException, InvalidFormatException {
        XSSFWorkbook workbook = chart.getWorkbook();
        XSSFSheet sheetAt = workbook.getSheetAt(0);
        if(isSort){
            dataParam = dataParam.stream().sorted(Comparator.comparing(DataParam.NamedValue::getValue)).collect(Collectors.toCollection(LinkedList::new));
        }

        for (int i = 0; i < dataParam.size(); i++) {
            int j = i + 1;
            sheetAt.createRow(j);
            sheetAt.getRow(j).createCell(0).setCellValue(dataParam.get(i).getName());
            sheetAt.getRow(j).createCell(1).setCellValue(dataParam.get(i).getValue());
        }
        workbook.write(chart.getPackagePart().getOutputStream());

        // 刷新图表缓存
        CTPlotArea plotArea = chart.getCTChart().getPlotArea();
        CTBarChart ctChart = plotArea.getBarChartArray(0);
        for (CTBarSer ser : ctChart.getSerList()) {
            // 更新excel范围range
            CTNumDataSource numDataSource = ser.getVal();
            CTAxDataSource catDataSource = ser.getCat();

            // TODO cat 也可能是 numRef
            long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal();
            long ptNumCnt = numDataSource.getNumRef().getNumCache().getPtCount().getVal();

            for (int i = 0; i < dataParam.size(); i++) {
                DataParam.NamedValue cellValue = dataParam.get(i);
                CTStrVal cat = ptCatCnt > i ? catDataSource.getStrRef().getStrCache().getPtArray(i)
                        : catDataSource.getStrRef().getStrCache().addNewPt();
                cat.setIdx(i);
                cat.setV(cellValue.getName());

                CTNumVal val = ptNumCnt > i ? numDataSource.getNumRef().getNumCache().getPtArray(i)
                        : numDataSource.getNumRef().getNumCache().addNewPt();
                val.setIdx(i);
                val.setV(String.valueOf(cellValue.getValue()));

            }

            catDataSource.getStrRef().setF(
                    replaceRowEnd(catDataSource.getStrRef().getF(),
                            ptCatCnt,
                            dataParam.size()));
            numDataSource.getNumRef().setF(
                    replaceRowEnd(numDataSource.getNumRef().getF(),
                            ptNumCnt,
                            dataParam.size()));

            // 更新个数
            catDataSource.getStrRef().getStrCache().getPtCount().setVal(dataParam.size());
            numDataSource.getNumRef().getNumCache().getPtCount().setVal(dataParam.size());
        }
    }


    /**
     * 多数据维度柱状图
     * @param chart
     * @param dataParamList
     * @param isSort
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static void generalXSLFBarChart2(XSLFChart chart, List<DataParam.CategoryNamedValue> dataParamList, boolean isSort) throws IOException, InvalidFormatException {

        if(CollUtil.isEmpty(dataParamList)){
            return ;
        }

        // 组装成Map<colCellKey, Map<rowTitleKey, value>>
        LinkedHashMap<String, LinkedHashMap<String, String>> dataParam = new LinkedHashMap<>();
        if(isSort){
            dataParamList = dataParamList.stream().sorted(Comparator.comparing(DataParam.CategoryNamedValue::getValue)).collect(Collectors.toList());
        }

        // 获取category名称集合
        List<String> rowTitleList = dataParamList.stream().map(DataParam.CategoryNamedValue::getCategory).distinct().collect(Collectors.toList());
        // 获取设备型号
        List<String> colTitleList = dataParamList.stream().map(DataParam.CategoryNamedValue::getName).distinct().collect(Collectors.toList());

        if(CollUtil.isEmpty(rowTitleList)){
            return ;
        }

        XSSFWorkbook workbook = chart.getWorkbook();
        XSSFSheet sheetAt = workbook.getSheetAt(0);

        AtomicInteger cellIndex = new AtomicInteger(1);
        XSSFRow row0 = sheetAt.createRow(0);
        row0.createCell(0).setCellValue("");
        rowTitleList.forEach(rowTitle -> row0.createCell(cellIndex.getAndIncrement()).setCellValue(rowTitle));

        int i = 0;
        for (DataParam.CategoryNamedValue categoryNamedValue : dataParamList) {
            // entry是一行的数据
            int j = ++i;
            XSSFRow currentRow = sheetAt.createRow(j);
            currentRow.createCell(0).setCellValue(categoryNamedValue.getName());
            // 找到这个category所在的cell
            currentRow.createCell(rowTitleList.indexOf(categoryNamedValue.getCategory()) + 1)
                    .setCellValue(categoryNamedValue.getValue());

        }

        workbook.write(chart.getPackagePart().getOutputStream());

        // 刷新图表缓存
        CTPlotArea plotArea = chart.getCTChart().getPlotArea();
        CTBarChart ctChart = plotArea.getBarChartArray(0);
        for (int j = 0; j < ctChart.getSerList().size(); j++) {
            ctChart.removeSer(j);
        }

        for (int j = 0; j < rowTitleList.size(); j++) {
            CTBarSer ser = ctChart.addNewSer();
            // 设置系列ID (索引),为新系列分配一个唯一的 id
            CTUnsignedInt idx = ser.addNewIdx();
            idx.setVal(ctChart.sizeOfSerArray()); // 使用系列数量作为索引

            CTStrRef ctStrRef = ser.addNewTx().addNewStrRef();
            ctStrRef.setF("Sheet1!$" + COL_TITLE_F[j] + "$1");
            CTStrVal ctStrVal = ctStrRef.addNewStrCache().addNewPt();
            ctStrVal.setIdx(j);
            ctStrVal.setV(rowTitleList.get(j));
        }

        //
        for (int j = 0; j < ctChart.getSerList().size(); j++) {
            CTBarSer ctBarSer = ctChart.getSerList().get(j);
            // cat
            CTAxDataSource catDataSource = ctBarSer.addNewCat();
            CTStrRef catRef = catDataSource.addNewStrRef();
            catRef.setF("Sheet1!$A$2:$A$" + (dataParamList.size() + 1));
            CTStrData catStrCache = catRef.addNewStrCache();

            CTNumDataSource valDataSource = ctBarSer.addNewVal();
            CTNumRef numRef = valDataSource.addNewNumRef();
            CTNumData numCache = numRef.addNewNumCache();
            numRef.setF("Sheet1!$" + COL_TITLE_F[j] + "$2:$" + COL_TITLE_F[j] + "$" + (colTitleList.size() + 1));

            for (int k = 0; k < colTitleList.size(); k++) {
                CTStrVal catStrVal = catStrCache.addNewPt();
                catStrVal.setIdx(k);
                catStrVal.setV(colTitleList.get(k));
            }

            // val
            for (int k = 0; k < dataParamList.size(); k++) {
                DataParam.CategoryNamedValue categoryNamedValue = dataParamList.get(k);
                // 同一个系列,同一个cat下
                if (categoryNamedValue.getCategory().equalsIgnoreCase(ctBarSer.getTx().getStrRef().getStrCache().getPtArray(0).getV())) {
                    int ptIdx = colTitleList.indexOf(categoryNamedValue.getName());
                    if (ptIdx != -1) {
                        CTNumVal numVal = numCache.addNewPt();
                        numVal.setIdx(ptIdx);  // 只有一个点,表示数量
                        numVal.setV(categoryNamedValue.getValue());
                    }
                }
            }
        }
    }

    /**
     * 竖向柱状图,这里是固定列只有数量
     * @param chart
     * @param dataParam
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static void generalXSLFVerticalBarChart(XSLFChart chart, List<DataParam.CategoryNamedValue> dataParamList) throws IOException, InvalidFormatException {

        if(CollUtil.isEmpty(dataParamList)){
            return ;
        }

        // 获取category名称集合
        List<String> rowTitleList = dataParamList.stream().map(DataParam.CategoryNamedValue::getCategory).distinct().collect(Collectors.toList());
        // 获取设备型号
        List<String> colTitleList = dataParamList.stream().map(DataParam.CategoryNamedValue::getName).distinct().collect(Collectors.toList());

        if(CollUtil.isEmpty(rowTitleList)){
            return ;
        }

        XSSFWorkbook workbook = chart.getWorkbook();
        XSSFSheet sheetAt = workbook.getSheetAt(0);

        AtomicInteger cellIndex = new AtomicInteger(1);
        XSSFRow row0 = sheetAt.createRow(0);
        row0.createCell(0).setCellValue("");
        rowTitleList.forEach(rowTitle -> row0.createCell(cellIndex.getAndIncrement()).setCellValue(rowTitle));

        int i = 0;
        for (String colCellValue : colTitleList) {
            // entry是一行的数据
            int j = ++i;
            XSSFRow currentRow = sheetAt.createRow(j);
            currentRow.createCell(0).setCellValue(colCellValue);

            dataParamList.stream().filter(data -> data.getName().equalsIgnoreCase(colCellValue)).forEach(data -> {
                // 找到这个category所在的cell
                int catCellIndex = rowTitleList.indexOf(data.getCategory());
                currentRow.createCell(catCellIndex + 1)
                        .setCellValue(data.getValue());

            });
        }

        workbook.write(chart.getPackagePart().getOutputStream());

        // 刷新图表缓存
        CTPlotArea plotArea = chart.getCTChart().getPlotArea();
        CTBarChart ctChart = plotArea.getBarChartArray(0);
        for (int j = 0; j < ctChart.getSerList().size(); j++) {
            ctChart.removeSer(j);
            ctChart.removeSer(j);
        }

        for (int j = 0; j < rowTitleList.size(); j++) {
            CTBarSer ser = ctChart.addNewSer();
            // 设置系列ID (索引),为新系列分配一个唯一的 id
            CTUnsignedInt idx = ser.addNewIdx();
            idx.setVal(ctChart.sizeOfSerArray()); // 使用系列数量作为索引

            CTStrRef ctStrRef = ser.addNewTx().addNewStrRef();
            ctStrRef.setF("Sheet1!$" + COL_TITLE_F[j] + "$1");
            CTStrVal ctStrVal = ctStrRef.addNewStrCache().addNewPt();
            ctStrVal.setIdx(j);
            ctStrVal.setV(rowTitleList.get(j));
        }

        //
        for (int j = 0; j < ctChart.getSerList().size(); j++) {
            CTBarSer ctBarSer = ctChart.getSerList().get(j);
            // cat
            CTAxDataSource catDataSource = ctBarSer.addNewCat();
            CTStrRef catRef = catDataSource.addNewStrRef();
            catRef.setF("Sheet1!$A$2:$A$" + (dataParamList.size() + 1));
            CTStrData catStrCache = catRef.addNewStrCache();

            CTNumDataSource valDataSource = ctBarSer.addNewVal();
            CTNumRef numRef = valDataSource.addNewNumRef();
            CTNumData numCache = numRef.addNewNumCache();
            numRef.setF("Sheet1!$" + COL_TITLE_F[j] + "$2:$" + COL_TITLE_F[j] + "$" + (colTitleList.size() + 1));

            for (int k = 0; k < colTitleList.size(); k++) {
                CTStrVal catStrVal = catStrCache.addNewPt();
                catStrVal.setIdx(k);
                catStrVal.setV(colTitleList.get(k));
            }

            // val
            for (int k = 0; k < dataParamList.size(); k++) {
                DataParam.CategoryNamedValue categoryNamedValue = dataParamList.get(k);
                // 同一个系列,同一个cat下
                if (categoryNamedValue.getCategory().equalsIgnoreCase(ctBarSer.getTx().getStrRef().getStrCache().getPtArray(0).getV())) {
                    int ptIdx = colTitleList.indexOf(categoryNamedValue.getName());
                    if (ptIdx != -1) {
                        CTNumVal numVal = numCache.addNewPt();
                        numVal.setIdx(ptIdx);  // 只有一个点,表示数量
                        numVal.setV(categoryNamedValue.getValue());
                    }
                }
            }
        }
    }


    private static void sortValueAsc(List<Map<String, String>> listOfMaps){

        // 根据值排序
        listOfMaps.sort((mapA, mapB) -> {
            // 获取mapA中的第一个值
            String valueA = mapA.entrySet().iterator().next().getValue();
            // 获取mapB中的第一个值
            String valueB = mapB.entrySet().iterator().next().getValue();
            // 比较值进行排序
            return valueA.compareTo(valueB);
        });
    }
}
3.4.3.4、表格
java 复制代码
public class XSLFTableShapeImpl {


    /**
     *
     * @param shape
     * @param dataMapList
     */
    public static void generalXSLFTable(XSLFTable shape, LinkedList<String> titleList, List<Map<String, String>> dataMapList, boolean isMerge, int mergeCol){

        if(CollUtil.isEmpty(dataMapList) || CollUtil.isEmpty(titleList)){
            return ;
        }

        if(isMerge && mergeCol >= titleList.size()){
            throw new IllegalArgumentException("表格头字段列数小于合并列数,请检查");
        }

        // 按同一列分组
        Map<String, List<Map<String, String>>> mergeListMap = new HashMap<>();
        mergeListMap.put("", dataMapList);
        if(isMerge){
            mergeListMap = dataMapList.stream().collect(Collectors.groupingBy(map -> map.get(titleList.get(mergeCol))));
        }

        // 填充数据到Excel表中,并处理单元格合并
        int rowNum = 1;
        for (Map.Entry<String, List<Map<String, String>>> entry : mergeListMap.entrySet()) {
            List<Map<String, String>> values = entry.getValue();
            int startRow = rowNum;

            // 填充每个供应商的型号和数量
            for (Map<String, String> dataMap : values) {
                rowNum++;
                XSLFTableRow row = shape.addRow();
                for (String title : titleList) {
                    XSLFTableCell cell = row.addCell();
                    setTableCellStyle(dataMap.get(title), cell);
                }

                // 合并供应商列的单元格
                if (isMerge && values.size() > 1) {
                    shape.mergeCells(startRow, rowNum - 1, mergeCol, mergeCol);
                }
            }
        }
    }


    /**
     *
     * @param text
     * @param cell
     */
    private static void setTableCellStyle(String text, XSLFTableCell cell){
        XSLFTextParagraph p = cell.addNewTextParagraph();
        p.setTextAlign(TextParagraph.TextAlign.CENTER);
        XSLFTextRun r = p.addNewTextRun();
        r.setText(text);
        r.setFontSize(12.0);  //

        // 设置单元格边框
        cell.setBorderColor(TableCell.BorderEdge.bottom, Color.LIGHT_GRAY);
        cell.setBorderColor(TableCell.BorderEdge.top, Color.LIGHT_GRAY);
        cell.setBorderColor(TableCell.BorderEdge.left, Color.LIGHT_GRAY);
        cell.setBorderColor(TableCell.BorderEdge.right, Color.LIGHT_GRAY);

        cell.setBorderWidth(TableCell.BorderEdge.bottom, 1.0);
        cell.setBorderWidth(TableCell.BorderEdge.top, 1.0);
        cell.setBorderWidth(TableCell.BorderEdge.left, 1.0);
        cell.setBorderWidth(TableCell.BorderEdge.right, 1.0);

        // 设置背景颜色
        cell.setFillColor(Color.WHITE);
    }

}
3.4.3.5、本地文件连接
java 复制代码
public class XSLFHyperlinkShapeImpl {

    public static void generalHyperLink(XSLFTextShape shape, String text, Path localFilePath) throws URISyntaxException {
        // 清除旧的文本内容
        shape.clearText();

        // 创建新的超链接文本
        XSLFTextRun textRun = shape.addNewTextParagraph().addNewTextRun();
        textRun.setText(text);
        textRun.setFontSize(12.0);

        // 创建文件链接
        URI fileUri = new URI("file:///" + localFilePath.toString().replace("\\", "/")); // 确保路径格式正确
        XSLFHyperlink hyperlink = textRun.createHyperlink();
        hyperlink.setAddress(fileUri.toString());
    }

}
3.4.3.6、入口主类
java 复制代码
public class PowerPointMainDemo {


    /**
     * 为了保持ppt模板报表以及其他图形的样式,这里采用的是直接替换原有excel关联数据,而不是重新生成。
     * 因此需要保证每个报表关联的excel至少有一条数据,来保证所获取的CTSer是有值的。
     * 表格除外,表格采用的是直接追加的形式,所以表格的模板上个除了标题,不能有其他行数据。
     * 这里表格暂时只支持单列的合并
     * @param args
     * @throws IOException
     * @throws InvalidFormatException
     * @throws URISyntaxException
     */
    public static void main(String[] args) throws IOException, InvalidFormatException, URISyntaxException {
        // 读取PPT模板
        FileInputStream inputStream = new FileInputStream("模板.0.pptx");
        XMLSlideShow ppt = PowerPointUtil.getXmlSlideShow(inputStream);

        // 获取幻灯片 XSLFSlide
        List<XSLFSlide> slides = ppt.getSlides();
        for(XSLFSlide slide : slides){
            for (XSLFShape shape : slide.getShapes()) {
                // 处理文本。默认这里每一页的key都不一样,所以不需要根据页码来判定
                if (shape instanceof XSLFTextShape) {
                    XSLFTextShapeImpl.generalXSLFText(ppt, DataParam.getTextDataMap());
                }

                // 处理报表
                if(shape instanceof XSLFGraphicFrame) {
                    XSLFGraphicFrame graphicFrame = (XSLFGraphicFrame) shape;
                    if (graphicFrame.hasChart()) {
                        XSLFChart chart = graphicFrame.getChart();
                        String text = chart.getTitleShape().getText();
                        // 处理第6页的饼图
                        if(slide.getSlideNumber() == 6 && text.equalsIgnoreCase("各厂商设备型号占比")){
                            XSLFPieChartShapeImpl.generalXSLFPieChart(chart, false, DataParam.getSupplierPercentList());
                        }
                        // 处理第6页的竖向柱状图
                        if(slide.getSlideNumber() == 6 && text.equalsIgnoreCase("各厂商设备型号分布")){
                            XSLFBarChartShapeImpl.generalXSLFBarChart(chart, DataParam.getSupplierPercentList(), true);
                        }
                        // 处理第8页的横向柱状图
                        if(slide.getSlideNumber() == 8 && text.equalsIgnoreCase("设备型号分布Top10")){
                            XSLFBarChartShapeImpl.generalXSLFBarChart2(chart, DataParam.getSupplierModelCountList3(),  true);
                        }
                        // 处理第8页的横向柱状图
                        if(slide.getSlideNumber() == 14 && text.equalsIgnoreCase("设备持续运行时间(Top50)")){
                            XSLFBarChartShapeImpl.generalXSLFBarChart2(chart, DataParam.getSupplierModelCountList3(),  true);
                        }
                        // 处理第9页的3D饼图
                        if(slide.getSlideNumber() == 9 && text.equalsIgnoreCase("全网设备生命周期分布")){
                            XSLFPieChartShapeImpl.generalXSLFPieChart(chart, true, DataParam.getDeviceMaintainPercent());
                        }
                        // 处理第9页的3D饼图
                        if(slide.getSlideNumber() == 10 && text.equalsIgnoreCase("各厂商设备维保信息统计")){
                            XSLFBarChartShapeImpl.generalXSLFVerticalBarChart(chart, DataParam.getDeviceLifecycleList());
                        }
                    }

                }

                // 处理表格
                if(shape instanceof  XSLFTable && slide.getSlideNumber() == 8) {
                    XSLFTable table = (XSLFTable) shape;
                    LinkedList<String> titleList = new LinkedList<>();
                    titleList.add("设备厂商");
                    titleList.add("设备型号");
                    titleList.add("数量");
                    XSLFTableShapeImpl.generalXSLFTable(table, titleList, DataParam.getSupplierModelCountListMap(), true, 0);
                }

                // 处理表格
                if(shape instanceof  XSLFTable && slide.getSlideNumber() == 9) {
                    XSLFTable table = (XSLFTable) shape;
                    LinkedList<String> titleList = new LinkedList<>();
                    titleList.add("厂商");
                    titleList.add("设备型号");
                    titleList.add("数量");
                    titleList.add("EOM时间");
                    titleList.add("EOS时间");
                    XSLFTableShapeImpl.generalXSLFTable(table, titleList, DataParam.getSupplierModelCountList2(), false, 0);
                }

                // 处理文件连接
                if(slide.getSlideNumber() == 6) {
                    if(shape instanceof XSLFTextShape){
                        XSLFTextShape textShape = (XSLFTextShape) shape;
                        List<XSLFTextParagraph> paragraphs = textShape.getTextParagraphs();
                        for (XSLFTextParagraph paragraph : paragraphs) {
                            if(StrUtil.isNotBlank(paragraph.getText()) && paragraph.getText().equalsIgnoreCase("设备数据表总览.xlsx")){
                                XSLFHyperlinkShapeImpl.generalHyperLink(textShape, "设备数据表总览.xlsx", Paths.get(System.getProperty("user.dir"), "设备数据.xlsx"));
                            }
                        }
                    }
                }
            }
        }

        // 输出新的PPT文件
        FileOutputStream outputStream = new FileOutputStream("报告模板v1-" + DateUtil.format(DateUtil.date(), "yyyyMMdd") + ".pptx");
        ppt.write(outputStream);

        outputStream.close();
        ppt.close();

    }
}
相关推荐
一棵开花的树,枝芽无限靠近你15 小时前
【PPTist】开源PPT编辑器初体验
编辑器·powerpoint
MindMaster User3 天前
如何在MindMaster思维导图中制作PPT课件?
powerpoint·思维导图·优惠券·mindmaster
鹧鸪云光伏与储能软件开发3 天前
光伏电站的方案PPT总结
分布式·powerpoint·光伏发电·光伏·光伏计算
doll ~CJ4 天前
一种基于PowerPoint和Photoshop的.gif动图制作方法
powerpoint·photoshop·gif动图
小奥超人7 天前
PPT技巧:如何合并PPT文件?
windows·经验分享·microsoft·powerpoint·办公技巧
@PHARAOH7 天前
HOW - PPT 制作系列(一)
powerpoint
匆匆整棹还8 天前
已有账号,重装系统激活office后发现没有ppt,word,excel等
word·powerpoint·excel
子非吾喵10 天前
在Element Ui中支持从系统粘贴版中获取图片和PDF,Docx,Doc,PPT等文档
ui·pdf·powerpoint
灵境引路人13 天前
【AIGC探索】AI实现PPT生产全流程
人工智能·aigc·powerpoint