使用POI向Excel模板动态添加内容

一、功能的需求以及实现的效果

下载链接:https://download.csdn.net/download/weixin_38500202/12275618

1、首先先交代一下需求,需根据Excel模板中特定的值,动态的添加内容。图片如下: 

使用POI向Excel模板动态添加内容_第1张图片

2、如上图,${cls.f10} 标识表示需获取对象中的字段,${cls.templateData.outSth()} 需获取对象中的方法(此处使用Java的反射原理)。

 

使用POI向Excel模板动态添加内容_第2张图片

3、 改模板还支持列表型标识,如${cls.templatedata[#].type},模板如下

使用POI向Excel模板动态添加内容_第3张图片

 4、列表型标识生成的结果如下

使用POI向Excel模板动态添加内容_第4张图片

 二、源码实现

1、引入poi(可以maven也可以直接下jar包)

    --引入poi的版本如下

    
    
      org.apache.poi
      poi-ooxml
      4.0.1
    

2、实现思路,主要分以下三步

  • 循环整个Excel,试着去寻找标识。如为非列表标识,则直接置换;如为列表标识,则记录至listRecord
  • 循环listRecord,并保持单元格的样式
  • 动态插入数据即可

3、源码实现(主要代码如下)

package com.ssm.POITemplate;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author create by bin
 * @Description Excel模板引擎
 * @Date 2020-3-25 16:48
 * @Version 1.0
 */
public class POITemplateEngine {
    /**
     * @Description: 根据模板生成excel文件
     * @param data 数据源
     * @param inputStream 输入流
     * @param outputStream 输出流
     * @return: boolean
     * @author: edit by bin
     * @date: 2020-3-25 11:02
     */
    public static boolean process(Object data, InputStream inputStream, OutputStream outputStream) {
        //定义工作簿
        XSSFWorkbook wb = null;
        try {
            //OPCPackage pkg = OPCPackage.open(templatePath);
            //XSSFWorkbook只对于xlsx的excel支持
            wb = new XSSFWorkbook(inputStream);
            Iterator iterable = wb.sheetIterator();
            while (iterable.hasNext()) {
                processSheet(data, iterable.next());
            }
            wb.write(outputStream);
            wb.close();
        } catch (POITemplateEngineException | IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * @Description: 处理excel中的sheet
     * @param data 数据源(只能是map类型的,如需调用实体类的某个方法,将实体类put进map中即可)
     * @param sheet excel中的表单
     * @return:
     * @author: edit by bin
     * @date: 2020-3-25 11:00
     */
    private static void processSheet(Object data, Sheet sheet) {
        Map> listRecord = new LinkedHashMap<>();
        int lastRowNum = sheet.getLastRowNum();
        //第一步:遍历excel中所有有数据的地方,并试着去寻找字段标记
        for (int i = lastRowNum; i >= 0; i--) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }
            int lastCellNum = row.getLastCellNum();
            for (int j = 0; j < lastCellNum; j++) {
                Cell cell = row.getCell(j);
                if (cell == null) {
                    continue;
                }
                try {
                    // //如果不为String类型,则跳过
                    if (cell.getCellType() != CellType.STRING)
                        continue;
                    String cellValue = cell.getStringCellValue();
                    if (cellValue.matches(".*\\$\\{[\\w.()]+}.*")) {
                        //非列表形置换标识,如${cls.type}
                        fillCell(cell, cellValue, data);
                    } else if (cellValue.matches(".*\\$\\{[\\w.]+\\[#][\\w.]+}.*")) {
                        //列表形置换标志,如${cls.students[#].name}
                        //将单元格存入listRecord中
                        Map rowRecord = listRecord.computeIfAbsent(i, k -> new HashMap<>());
                        rowRecord.put(j, cell);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new POITemplateEngineException("设置非列表置换标识出错,msg:"+e.getMessage());
                }
            }
        }

        //第二步:根据listRecord对excel进行动态插入行数,并记录下每个单元格的样式和单元格的置换标记
        Map listInData = new HashMap<>();
        Map listCellStyle = new HashMap<>();
        Map listCellPath = new HashMap<>();
        listRecord.forEach((rowNum, colMap) -> {
            Pattern p = Pattern.compile("\\$\\{[\\w.\\[#\\]]+}");
            Set listPath = new HashSet<>();
            colMap.forEach((colNum, cell) -> {
                String cellValue = cell.getStringCellValue();
                Matcher m = p.matcher(cellValue);
                if (m.find()) {
                    String reg = m.group();
                    String regPre = reg.substring(2, reg.indexOf("["));
                    String regSuf = reg.substring(reg.lastIndexOf("].") + 2, reg.length() - 1);
                    listPath.add(regPre);
                    listCellStyle.put(String.format("%s.%s", regPre, regSuf), cell.getCellStyle());
                    listCellPath.put(cell, String.format("%s#%s", regPre, regSuf));
                }
            });
            int maxRow = 0;
            for (String s : listPath) {
                Object list = getAttributeByPath(data, s);
                if (list == null) {
                    list = new ArrayList<>();
                }
                if (list instanceof List) {
                    int len = ((List) list).size();
                    maxRow = Math.max(len, maxRow);
                    listInData.put(s, ((List) list));
                } else {
                    throw new POITemplateEngineException(String.format("%s 不是一个列表路径,此相对应的类为: %s", s, list.getClass().getSimpleName()));
                }
            }
            if (maxRow > 1) {
                int endRow = sheet.getLastRowNum();
                sheet.shiftRows(rowNum + 1, endRow + 1, maxRow - 1);
            }
        });


        //第三步、根据listRecord填充表格中的内容即可
        listRecord.forEach((rowNum, colMap) -> colMap.forEach((colNum, cell) -> {
            String path = listCellPath.get(cell);
            String[] pathData = path.split("#");
            List list = listInData.get(pathData[0]);
            int baseRowIndex = cell.getRowIndex();
            int colIndex = cell.getColumnIndex();
            CellStyle style = listCellStyle.get(String.format("%s.%s", pathData[0], pathData[1]));
            for (int i = 0; i < list.size(); i++) {
                int rowIndex = baseRowIndex + i;
                Row row = sheet.getRow(rowIndex);
                if (row == null) {
                    row = sheet.createRow(rowIndex);
                }
                Cell cellToFill = row.getCell(colIndex);
                if (cellToFill == null) {
                    cellToFill = row.createCell(colIndex);
                }
                cellToFill.setCellStyle(style);
                setCellValue(cellToFill, getAttribute(list.get(i), pathData[1]));
            }
        }));
    }

    /**
     * @Description: 填充单元格(非列表形数据,形如${cls.type})
     * @param cell 单元格
     * @param expression 置换标记
     * @param data  数据源
     * @return:
     * @author: edit by bin
     * @date: 2020-3-25 10:54
     */
    private static void fillCell(Cell cell, String expression, Object data) {

        Pattern p = Pattern.compile("\\$\\{[\\w.\\[\\]()]+}");  //正则表达式,形如${cls.type}
        Matcher m = p.matcher(expression);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            //如果表达式满足正则表达式的话
            String exp = m.group();
            String path = exp.substring(2, exp.length() - 1);
            Object value = getAttributeByPath(data, path);
            m.appendReplacement(sb, value == null ? "" : value.toString());
        }
        setCellValue(cell, sb.toString());
    }

    /**
     * @Description: 设置单元格的值
     * @param cell cell对象
     * @param value 值
     * @return:
     * @author: edit by bin
     * @date: 2020-3-25 10:40
     */
    private static void setCellValue(Cell cell, Object value) {
        if (value == null) {
            cell.setCellValue("");
        } else if (value instanceof Date) {
            cell.setCellValue((Date) value);
        } else if (value instanceof Integer) {
            cell.setCellValue((Integer) value);
        } else if (value instanceof Long) {
            cell.setCellValue((Long) value);
        } else if (value instanceof Double) {
            cell.setCellValue((Double) value);
        } else if (value instanceof Float) {
            cell.setCellValue((Float) value);
        } else if (value instanceof Character) {
            cell.setCellValue((Character) value);
        } else if (value instanceof BigDecimal) {
            cell.setCellValue(((BigDecimal) value).doubleValue());
        } else {
            cell.setCellValue(value.toString());
        }
    }


    /**
     * @Description: 根据路径得到属性值
     * @param obj 数据源
     * @param path  属性路径,如(cls.type,cls.students.size())
     * @return: {@link Object}
     * @author: edit by bin
     * @date: 2020-3-25 10:37
     */
    private static Object getAttributeByPath(Object obj, String path) {

        String[] paths = path.split("\\.");
        Object o = obj;
        for (String s : paths) {
            o = getAttribute(o, s);
        }
        return o;
    }

    /**
     * @Description: 得到对象中的指定的属性内容
     * @param obj   可以为map或者实体类(如为实体类,需给出getter)
     * @param member    key值或方法名(方法名需加()作为标识)
     * @return: {@link Object}
     * @author: edit by bin
     * @date: 2020-3-25 10:35
     */
    private static Object getAttribute(Object obj, String member) {
        if (obj == null) {
            return null;
        }
        boolean isMethod = member.endsWith("()");
        if (!isMethod && obj instanceof Map) {
            //如果不是方法,则取map中的键值对
            return ((Map) obj).get(member);
        }
        try {
            Class cls = obj.getClass();
            if (isMethod) {
                //如果是方法,则进行反射
                Method method = cls.getDeclaredMethod(member.substring(0, member.length() - 2));
                return method.invoke(obj);
            } else {
                //获得某个类的所有声明的字段,即包括public、private和protected
                Field field = cls.getDeclaredField(member);
                field.setAccessible(true);  //此处是为了取得private修饰的字段
                return field.get(obj);
            }
        } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new POITemplateEngineException("方法反射出错或者类中没有此字段,msg:"+e.getLocalizedMessage());
        }
    }
}

3、如有更好的实现方式,还请批评指正。该工具免费下载,已放在CSDN。请给我一个赞吧!~~

你可能感兴趣的:(#,Java,poi,列表,excel,java)