一、功能的需求以及实现的效果
下载链接:https://download.csdn.net/download/weixin_38500202/12275618
1、首先先交代一下需求,需根据Excel模板中特定的值,动态的添加内容。图片如下:
2、如上图,${cls.f10} 标识表示需获取对象中的字段,${cls.templateData.outSth()} 需获取对象中的方法(此处使用Java的反射原理)。
3、 改模板还支持列表型标识,如${cls.templatedata[#].type},模板如下
4、列表型标识生成的结果如下
二、源码实现
1、引入poi(可以maven也可以直接下jar包)
--引入poi的版本如下
org.apache.poi
poi-ooxml
4.0.1
2、实现思路,主要分以下三步
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。请给我一个赞吧!~~