POI 填充Excel模板

一、使用场景

由于系统环境原因,无法使用EasyExcel的填充功能,只能使用POI处理Excel。业务给到一个需求,要求按照模板导出数据至Excel的指定数据页签,用户会在其他页签引用该页签的数据进行各种分析计算。如下所示,有”data“、”分析“两个页签,需要将基础数据载入第一个页签,并且刷新第二个页签的公式。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、实现

2.1、POI依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
    <scope>compile</scope>
</dependency>

2.2、FileUtils

实现文件工具类,主要是生成文件名。

import java.io.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class FileUtils {

    private static final String COPY_FILE_SUFFIX = "_副本";

    /**
     * 将输入流写入文件
     * @param in 输入流
     * @param path 输出文件的路径
     * @param fileName 输出文件的名称
     *
     * */
    public static void writeFile(InputStream in ,String path ,String fileName) throws FileNotFoundException {

        FileOutputStream outputStream = new FileOutputStream("");

    }


    /**
     * 给定一个目录和文件,若该目录存在名称相同的文件,则返回一个可用的副本文件名,否则直接返回输入的文件名。
     * @param path 文件的目录
     * @param fileName 文件名
     * @return 可用的文件名
     * */
    public static String getAvailableName(String path ,String fileName){

        File filePath = new File(path);

        String suffix = fileName.substring(fileName.lastIndexOf("."));
        File[] files = filePath.listFiles( f -> f.getName().endsWith(suffix) );
        if ( Objects.isNull(files) ) return fileName;

        Set<String> fileNameSet = new HashSet<>();
        for (File file: files ) {
            fileNameSet.add(file.getName().substring(0 , file.getName().lastIndexOf(".")));
        }

        boolean isExist = true;
        int count = 1;
        String tempFileName = fileName.substring(0,fileName.lastIndexOf("."));
        String tempFileName1 = tempFileName;
        while(isExist){

            if (fileNameSet.contains(tempFileName)){
                tempFileName = tempFileName1 + COPY_FILE_SUFFIX + count++;
            }else{
                isExist = false;
            }

        }

        return tempFileName + suffix;
    }


    /**
     * 给定一个文件的完整路径,若该文件已经存在,则返回一个可用的副本文件名,否则直接返回输入的文件名。
     * @param fullPath 文件的完整路径
     * @return 可用的文件名
     * */
    public static String getAvailableName( String fullPath ){
        File file = new File(fullPath);
        if ( !file.isFile() ) throw new RuntimeException( String.format("not a file [%s].",fullPath));

        return getAvailableName( file.getParent() ,file.getName() );
    }

    /**
     * 给定一个文件的完整路径,若该文件已经存在,则返回一个完整可用的副本文件名,否则直接返回输入的文件名。
     * @param fullPath 文件的完整路径
     * @return 完整可用的文件名
     * */
    public static String getAvailableFullName( String fullPath ){
        File file = new File(fullPath);
        if ( !file.isFile() ) throw new RuntimeException( String.format("not a file [%s].",fullPath));

        return file.getParent() + File.separator + getAvailableName(fullPath);
    }

}

2.3、ExcelFillUtils

实现Excel填充工具类,用来填充Excel模板,并生成填充好的副本文件。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

public class ExcelFillUtils {

//    public static final String FILL_EXPRESSION_REGEX = "\\{\\.\\w+\\}";
    private static final String FILL_EXPRESSION_REGEX = "\\{\\.[\\p{L}\\p{M}\\S]+\\}";

    /**
     * 给定模板,指定某个页签,将数据填充到模板中的指定页签,并在模板所在目录生成新的副本文件。
     * @param template 模板文件地址
     * @param sheetName 页签名称
     * @param data 待填充的数据,数据格式如下
     *                  [
     *                      {"colName1":v1 ,"colName2":v2...},
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      ,...
     *                  ]
     * @return 新生成的副本文件的地址
     *
     * */
    public static String fillOneSheet(String template ,String sheetName , List<Map<String,Object>> data) {
        return fillOneSheet(template ,FileUtils.getAvailableFullName(template) ,sheetName ,data);
    }

    /**
     * 给定模板,指定某个页签,将数据填充到模板中的指定页签,并将数据导入到指定文件上。
     * @param template 模板文件地址
     * @param outputFile 新生成的文件的地址
     * @param sheetName 页签名称
     * @param data 待填充的数据,数据格式如下
     *                  [
     *                      {"colName1":v1 ,"colName2":v2...},
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      ,...
     *                  ]
     * @return 新生成的副本文件的地址
     *
     * */
    public static String fillOneSheet(String template , String outputFile,String sheetName , List<Map<String,Object>> data){

        try (Workbook workbook = new XSSFWorkbook(new FileInputStream(template))) {

            fill(workbook,sheetName,data);//填充数据
            refreshFormula(workbook);//刷新公式

            try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
                workbook.write(outputStream);
            }catch (Exception e){
                throw new RuntimeException(e);
            }

        }catch (IOException e){
            throw new RuntimeException(e);
        }

        return outputFile;
    }

    /**
     * 给定模板,将数据填充到模板中的多个页签,并在模板所在目录生成新的副本文件。
     * @param template 模板文件地址
     * @param datas 待填充的数据集,数据格式如下
     *              {
     *                  "SheetName1":[
     *                      {"colName1":v1 ,"colName2":v2...},
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      ,...
     *                  ],
     *                  "SheetName2":[
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      ,...
     *                  ],
     *                  ...
     *              }
     *
     * */
    public static String fillMultipleSheet(String template ,Map<String,List<Map<String,Object>>> datas) {
        return fillMultipleSheet(template ,FileUtils.getAvailableFullName(template) ,datas);
    }

    /**
     * 给定模板,将数据填充到模板中的多个页签,并将数据导入到指定文件上。
     * @param template 模板文件地址
     * @param datas 待填充的数据集,数据格式如下
     *              {
     *                  "SheetName1":[
     *                      {"colName1":v1 ,"colName2":v2...},
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      ,...
     *                  ],
     *                  "SheetName2":[
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      {"colName1":v1 ,"colName2":v2...}
     *                      ,...
     *                  ],
     *                  ...
     *              }
     *
     * */
    public static String fillMultipleSheet(String template ,String outputFile ,Map<String,List<Map<String,Object>>> datas){

        try (Workbook workbook = new XSSFWorkbook(new FileInputStream(template))) {

            datas.forEach( (sheetName ,data)-> fill(workbook,sheetName,data) );//填充数据
            refreshFormula(workbook);//刷新公式

            try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
                workbook.write(outputStream);
            }catch (Exception e){
                throw new RuntimeException(e);
            }

        }catch (IOException e){
            throw new RuntimeException(e);
        }

        return outputFile;
    }

    private static Workbook fill(Workbook workbook ,String sheetName ,List<Map<String,Object>> data) {

        Sheet sheet = workbook.getSheet(sheetName);
        if (sheet == null) {
            throw new RuntimeException(String.format("sheet [%s] does not exist.",sheetName));
        }

        //找到所有的表达式单元格
        Map<String,Cell> expressionCellMap = new HashMap<>();
        for( int i = 0 ;i < sheet.getPhysicalNumberOfRows() ;i++){
            Row row = sheet.getRow(i);
            for( int j = 0 ;j< row.getPhysicalNumberOfCells() ;j++){
                Cell cell = row.getCell(j);
                if( !Objects.isNull(cell) && isFillExpression(cell.getStringCellValue()) ){//判断该单元格是否是填充公式
                    expressionCellMap.put(getColNameFromEx(cell.getStringCellValue()),cell);
                }
            }
        }

        //填充数据
        for(int i = 0 ;i< data.size() ;i++){
            Map<String,Object> dataRow = data.get(i);
            for (Map.Entry<String,Object> entry : dataRow.entrySet()){
                String colName = entry.getKey();
                Object value = entry.getValue();
                if(expressionCellMap.containsKey(colName)){

                    Cell cell = expressionCellMap.get(colName);//公式所在的单元格
                    int rowID = cell.getRowIndex() + i;
                    int colId = cell.getColumnIndex();

                    Row fillRow = sheet.getRow(rowID);
                    fillRow = Objects.isNull(fillRow) ? sheet.createRow(rowID) : fillRow;
                    Cell fillCell = fillRow.getCell(colId);
                    //创建的新单元格需要复制公式单元格的格式
                    fillCell = Objects.isNull(fillCell) ? CellUtil.createCell(fillRow,colId,"", cell.getCellStyle()) : fillCell;

                    if ( value instanceof String){
                        fillCell.setCellValue( String.valueOf(value) );
                    }else if( value instanceof Number ){
                        fillCell.setCellValue( ((Number)value).doubleValue() );
                    }else{
                        throw new RuntimeException(String.format("Unsupported data type [%s].",value.getClass().toString()));
                    }

                }
            }
        }

        return workbook;
    }

    private static void refreshFormula(Workbook workbook){
        FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
        evaluator.evaluateAll();
    }

    private static boolean isFillExpression(String ex){
        if (ex.isBlank()) return false;
        return Pattern.matches(FILL_EXPRESSION_REGEX ,ex);
    }

    private static String getColNameFromEx(String ex){
        if (!isFillExpression(ex)) throw new RuntimeException("Illegal expression " + ex );
        return ex.substring(2,ex.length() - 1);
    }

}

2.4、测试

@Test
public void fillOneTest_1(){
    // 定义要填充数据的Excel模板文件的路径
    String template = "C:\\Users\\Administrator\\Desktop\\test.xlsx";
    
    // 定义要填充数据的工作表名称
    String sheetName = "data";
    
    // 创建一个列表,用来存储要填充到Excel中的数据
    List<Map<String,Object>> list = new ArrayList<>();
    
    // 向列表中添加数据
    list.add( Map.of("name","zou" ,"age" ,18) );
    list.add( Map.of("name","li" ,"age" ,28) );
    list.add( Map.of("name","wang" ,"age" ,15) );
    list.add( Map.of("name","quan" ,"age" ,19) );
    list.add( Map.of("name","zhao" ,"age" ,98) );
    
    // 调用ExcelFillUtils类的fillOneSheet方法,实现对Excel模板的数据填充
    ExcelFillUtils.fillOneSheet(template ,sheetName ,list);
}

### 使用 Apache POI 填充 Excel 模板并导出文件 在 Java 中,Apache POI 是一种常用的库,用于处理 Microsoft Office 文件格式(如 Word 和 Excel)。以下是关于如何使用 Apache POI 填充 Excel 模板并将结果导出为文件的具体说明。 #### 1. 添加 Maven 依赖 为了使用 Apache POI,在项目的 `pom.xml` 文件中需添加以下依赖项: ```xml <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency> ``` 此版本号可以根据实际需求调整[^1]。 #### 2. 加载模板文件 通过加载现有的 Excel 模板文件,可以将其作为基础进行修改。下面是一个示例代码片段,展示如何读取模板文件: ```java import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class ExcelTemplateFiller { public static void main(String[] args) throws IOException { // 定义模板路径 String templatePath = "path/to/template.xlsx"; try (FileInputStream fis = new FileInputStream(templatePath); Workbook workbook = new XSSFWorkbook(fis)) { Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表 // 修改单元格内容 Row row = sheet.getRow(0); // 获取第1行 Cell cell = row.getCell(0); // 获取A列的单元格 if (cell == null) { cell = row.createCell(0); } cell.setCellValue("Filled Value"); // 设置新值 // 导出到目标文件 String outputPath = "path/to/output.xlsx"; try (FileOutputStream fos = new FileOutputStream(outputPath)) { workbook.write(fos); } } catch (IOException e) { System.out.println("Error occurred while processing the file."); throw e; } } } ``` 上述代码展示了如何打开一个 `.xlsx` 文件,并向其中写入新的数据[^2]。 #### 3. 动态设置模板路径 如果需要动态获取模板路径,可采用 Spring 的 `@Value` 注解来注入资源路径。例如: ```java import org.springframework.core.io.Resource; public class TemplateLoader { @Value("classpath:templates/excel/MyTemplate.xlsx") private Resource templateResource; public InputStream getTemplateInputStream() throws IOException { return templateResource.getInputStream(); } } ``` 这种方式特别适合在基于 Spring 的应用程序中使用[^3]。 #### 4. 处理大数据量场景 对于较大的数据集,推荐分批处理以减少内存占用。具体做法如下: - 将数据分割成若干个小批量; - 对每一批次单独生成一个新的工作簿或追加至现有工作簿中的不同工作表; - 或者将多个独立的工作簿压缩进 ZIP 归档文件中以便传输和存储。 当单个 Excel 文件内的记录数超过一定阈值时(通常建议不超过十万条),应考虑拆分为多份文档。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值