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、引入依赖

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

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、获取每页幻灯片

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

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数据为:

/**
 * 文字占位
 * @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、饼图
/**
 *
 * @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、柱状图
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、表格
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、本地文件连接
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、入口主类
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();

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/917461.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高级数据结构——hash表与布隆过滤器

文章目录 hash表与布隆过滤器1. hash函数2. 选择hash函数3. 散列冲突3.1 负载因子3.2 冲突解决3. STL中的散列表 4. 布隆过滤器4.1 背景1. 应用场景2. 常见的处理场景&#xff1a; 4.2 布隆过滤器构成4.3 原理4.4 应用分析4.5 要点 5. 分布式一致性hash5.1 缓存失效问题 6. 大数…

小程序19-微信小程序的样式和组件介绍

在小程序中不能使用 HTML 标签&#xff0c;也就没有 DOM 和 BOM&#xff0c;CSS 也仅支持部分选择器 小程序提供了 WXML 进行页面结构的编写&#xff0c;WXSS 进行页面的样式编写 WXML 提供了 view、text、image、navigator等标签构建页面结构&#xff0c;小程序中标签称为组件…

[Linux]多线程详解

多线程 1.线程的概念和理解1.1线程的优点1.2线程的缺点1.3线程的设计1.4线程 VS 进程 2.线程控制2.1线程等待2.2 线程终止2.3 线程分离 3.线程互斥3.1背景3.2抢票代码演示3.3保护公共资源&#xff08;加锁&#xff09;3.3.1创建锁/销毁锁3.3.2申请锁/尝试申请锁/解锁 3.4解决抢…

CSP-J 2024题解

省流&#xff1a;300->260 乐。 Poker: 我考场上寻思着会不会有人写成了 joker.in joker.out&#xff0c;结果真的有 joker Sol EZ problem&#xff0c;拿 set 搞一下就行了&#xff08;虽然我赛事没想到&#xff0c;用了 map&#xff09; Code #include <bits/std…

【miniMax开放平台-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被机器执行自动化程序攻击&#xff0c;存在如下风险&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露&#xff0c;不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 &#xff0c;造成用户无法登陆、注册&#xff0c;大量收到垃圾短信的…

python机器人Agent编程——多Agent框架的底层逻辑(上)

目录 一、前言二、两个核心概念2.1 Routines&#xff08;1&#xff09;清晰的Prompt&#xff08;2&#xff09;工具调用json schema自动生成&#xff08;3&#xff09;解析模型的toolcall指令&#xff08;4&#xff09;单Agent的循环决策与输出 PS.扩展阅读ps1.六自由度机器人相…

达梦 DG

监视器 switchover 关于达梦DG switchover的细节&#xff0c;以下是一些关键步骤和注意事项&#xff1a; • 切换前检查确认&#xff1a; • 确认数据库版本和DG架构&#xff0c;包括IP信息及切换角色前后的情况。 • 检查DG切换方式&#xff0c;是switch over还是fail ove…

blind-watermark - 水印绑定

文章目录 一、关于 blind-watermark安装 二、bash 中使用三、Python 调用1、基本使用2、attacks on Watermarked Image3、embed images4、embed array of bits 四、并发五、相关 Project 一、关于 blind-watermark Blind watermark 基于 DWT-DCT-SVD. github : https://githu…

从零开始的c++之旅——二叉搜索树

1、二叉搜索树概念 1. ⼆叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树&#xff0c;它或者是⼀棵空树&#xff0c;或者是具有以下性质的⼆叉树: • 若它的左⼦树不为空&#xff0c;则左⼦树上所有结点的值都⼩于等于根结点的值 • 若它的右⼦树不为空&#xff0c;则右⼦树上所有结…

类与对象;

目录 一、认识类&#xff1b; 1、类的引入&#xff1b; 2、类的定义&#xff1b; 类的两种定义方式&#xff1a; 3、类的访问限定符及封装&#xff1b; 4、类的作用域&#xff1b; 5、类的实例化&#xff1b; 6、类对象模型&#xff1b; 计算类对象的大小&#xff1b; …

ceph的集群管理

0 环境说明 ip地址主机名额外硬盘是否加入ceph集群10.0.0.141ceph141sdb 300G&#xff0c;sdc 500G是10.0.0.142ceph142sdb 300G&#xff0c;sdc 500G, sdd 1000G否10.0.0.143ceph143sdb 300G&#xff0c;sdc 500G否 在上一篇文章中&#xff0c;已经成功地初始化了一个ceph管…

企业无线解决方案

前言 无线广域网 无线广域网WWAN&#xff08;Wireless Wide Area Network&#xff09;目前已经成为了全球通信系统的核心组成部分&#xff0c;我们所熟悉的2G网络、3G网络和4G网络&#xff08;LTE&#xff09;等等都是WWAN的典型代表。通过WWAN&#xff0c;用户几乎可以在任何…

Springboot集成ElasticSearch实现minio文件内容全文检索

一、docker安装Elasticsearch &#xff08;1&#xff09;springboot和Elasticsearch的版本对应关系如下&#xff0c;请看版本对应&#xff1a; 注意安装对应版本&#xff0c;否则可能会出现一些未知的错误。 &#xff08;2&#xff09;拉取镜像 docker pull elasticsearch:7…

前后端请求响应

引入 在之前的例子中&#xff0c;我们编写了一个简单的web类&#xff0c;我们运行启动类&#xff0c;启动内嵌的tomcat后就可以在浏览器通过特定的路径访问tomcat中的应用程序。 但之前编写的程序仅仅是个简单的java类&#xff0c;其并未实现某个接口或继承某个类&…

VS2022编译32位OpenCV

使用环境 Visual Studio 2022 OpenCV: 4.7.0 cmake: 3.30.2一、使用CMake工具生成vs2022的openCV工程解决方案 打开cmake&#xff0c;选择opencv的源代码目录&#xff0c;创建一个文件夹&#xff0c;作为VS工程文件的生成目录 点击configure构建项目&#xff0c;弹出构建设置…

电子应用设计方案-12:智能窗帘系统方案设计

一、系统概述 本设计方案旨在打造便捷、高效的全自动智能窗帘系统。 二、硬件选择 1. 电机&#xff1a;选用低噪音、扭矩合适的智能电机&#xff0c;根据窗帘尺寸和重量确定电机功率&#xff0c;确保能平稳拉动窗帘。 2. 轨道&#xff1a;选择坚固、顺滑的铝合金轨道&…

MySQL系统优化

文章目录 MySQL系统优化第一章&#xff1a;引言第二章&#xff1a;MySQL服务架构优化1. 读写分离2. 水平分区与垂直分区3. 缓存策略 第三章&#xff1a;MySQL配置优化1. 内存分配优化Buffer Pool 的优化查询缓存与表缓存Key Buffer 2. 连接优化最大连接数会话超时连接池 3. 日志…

LLM - 计算 多模态大语言模型 的参数量(Qwen2-VL、Llama-3.1) 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/143749468 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 影响 (…

光耦选型指南

一、指南说明 针对光偶选型和实际使用过程中出现因光偶特性变化引起的产品失效问题&#xff0c;提供指导。光耦属于易失效器件&#xff0c;选型和使用过程中要特别的小心。目前发现&#xff0c;因光偶的选型&#xff0c;光偶工作电流&#xff0c;CTR参数选型不合适&#xff0c…

git创建远程仓库,以gitee码云为例GitHub同理

git远程Remote服务端仓库构建的视频教程在这 Git建立服务端Remote远程仓库&#xff0c;gitee码云例&#xff0c;Github_哔哩哔哩_bilibili 1、登gitee码云/Github 登录 - Gitee.com https://github.com/ &#xff08;没账号的注册一下就行&#xff09; 点击如下图位置的创…