根據Excel模板生成表格。很早之前寫的代碼,之前使用jxl
後來升級到了poi
,一直在用。後來發現阿里開源的easyexcel
也有相同的功能,並且優化了xlsx
的內存問題。如有相同需求推薦使用easyexcel
。
- 支持表頭、表尾數據填充
- 支持列表數據行遍歷
- 支持多列表
- 支持單元格格式
- 支持合併單元格
public class ExcelUtils {
private static final String REG = "\\{([a-zA-Z_]+)\\}";// 匹配"{exp}"
private static final String REG_LIST = "\\{(\\.[a-zA-Z_]+)\\}";// 匹配"{.exp}"
private static final Pattern PATTERN = Pattern.compile(REG);
private static final Pattern PATTERN_LIST = Pattern.compile(REG_LIST);
/**
* 根據模板生成Excel文件
*
* @param templateFile 模版文件
* @param context 表頭或表尾數據集合
* @param list 列表
* @return
*/
public static byte[] writeExcel(File templateFile, Map<String, Object> context, List<Map<String, Object>> list) {
try (Workbook workbook = WorkbookFactory.create(templateFile)) {
Sheet sheet = workbook.getSheetAt(0);// 獲取配置文件sheet 頁
int listStartRowNum = -1;
for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row != null) {
for (int j = 0; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
if (cell != null && cell.getCellType() == CellType.STRING) {
String cellValue = cell.getStringCellValue();
// 獲取到列表數據所在行
if (listStartRowNum == -1 && cellValue.matches(REG_LIST)) {
listStartRowNum = i;
}
Object newValue = cellValue;
Matcher matcher = PATTERN.matcher(cellValue);
while (matcher.find()) {
String replaceExp = matcher.group();// 匹配到的表達式
String key = matcher.group(1);// 獲取key
Object replaceValue = context.get(key);
if (replaceValue == null) {
replaceValue = "";
}
if (replaceExp.equals(cellValue)) {// 單元格是一個表達式
newValue = replaceValue;
} else {// 以字符串替換
newValue = ((String) newValue).replace(replaceExp, replaceValue.toString());
}
}
setCellValue(cell, newValue);
}
}
}
}
if (-1 != listStartRowNum) {// 如果不爲 -1 說明有需要循環的列表表達式
Row listStartRow = sheet.getRow(listStartRowNum);
if (CollectionUtils.isEmpty(list)) {// 列表數據爲空,清空列表表達式行
for (int i = 0; i < listStartRow.getLastCellNum(); i++) {
Cell cell = listStartRow.getCell(i);
if (cell != null) {
cell.setCellValue("");
}
}
} else {
if (listStartRowNum + 1 <= sheet.getLastRowNum()) {
sheet.shiftRows(listStartRowNum + 1, sheet.getLastRowNum(), list.size(), true, false);// 列表數據行後面行下移,留出數據填充區域
}
for (int i = 0; i < list.size(); i++) {// 循環列表數據 生成行
Map<String, Object> map = list.get(i);// 一行數據
int newRowNum = listStartRowNum + i + 1;// 保留表達式行
Row newRow = sheet.createRow(newRowNum);// 創建新行
for (int j = 0; j < listStartRow.getLastCellNum(); j++) {// 循環遍歷單元格
Cell cell = listStartRow.getCell(j);// 列表數據行
// 填充數據
if (cell != null) {
Cell newCell = newRow.createCell(j);
newCell.setCellStyle(cell.getCellStyle());// 設置單元格格式
if (cell.getCellType() == CellType.STRING
&& cell.getStringCellValue().matches(REG_LIST)) {// 單元格是一個表達式
String cellExp = cell.getStringCellValue();
Matcher matcher = PATTERN_LIST.matcher(cellExp);
matcher.find();
String key = matcher.group(1).substring(1);// 獲取key
Object newValue = map.get(key);
if (newValue == null) {
newValue = "";
}
setCellValue(newCell, newValue);
} else {// 不是表達式複製單元格數據
CellType cellType = cell.getCellType();
if (cellType == CellType.NUMERIC) {
newCell.setCellValue(cell.getNumericCellValue());
} else if (cellType == CellType.BOOLEAN) {
newCell.setCellValue(cell.getBooleanCellValue());
} else if (cellType == CellType.STRING) {
newCell.setCellValue(cell.getStringCellValue());
} else if (cellType == CellType.FORMULA) {
// 處理公式,待實現
} else {
newCell.setCellValue(cell.getStringCellValue());
}
}
}
}
}
sheet.removeRow(listStartRow);// 刪除list表達式行
sheet.shiftRows(listStartRowNum + 1, sheet.getLastRowNum(), -1, true, false);// 數據區域上移一行,覆蓋表達式行
// 合併單元格處理
for (int i = 0; i < listStartRow.getLastCellNum(); i++) {
CellRangeAddress mergedRangeAddress = getMergedRangeAddress(sheet, listStartRowNum, i);
if (mergedRangeAddress != null) {// 合併的單元格
i = mergedRangeAddress.getLastColumn();
for (int j = 1; j < list.size(); j++) {
int newRowNum = listStartRowNum + j;
sheet.addMergedRegionUnsafe(new CellRangeAddress(newRowNum, newRowNum,
mergedRangeAddress.getFirstColumn(), mergedRangeAddress.getLastColumn()));
}
}
}
}
}
// 公式生效
sheet.setForceFormulaRecalculation(true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
workbook.write(out);
return out.toByteArray();
} catch (Exception e) {
throw new RuntimeException("生成excel失敗!", e);
}
}
private static void setCellValue(Cell cell, Object value) {
if (value instanceof Number) {// 如果是數字類型的設置爲數值
cell.setCellValue(Double.parseDouble(value.toString()));
} else if (value instanceof Date) {// 如果爲時間類型的設置爲時間
cell.setCellValue((Date) value);
} else if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Boolean) {
cell.setCellValue((Boolean) value);
} else {
cell.setCellValue(value.toString());
}
}
/**
* 獲取指定行/列的合併單元格區域
*
* @param sheet
* @param row
* @param column
* @return CellRangeAddress 不是合併單元格返回null
*/
private static CellRangeAddress getMergedRangeAddress(Sheet sheet, int row, int column) {
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
for (CellRangeAddress cellAddresses : mergedRegions) {
if (row >= cellAddresses.getFirstRow() && row <= cellAddresses.getLastRow()
&& column >= cellAddresses.getFirstColumn() && column <= cellAddresses.getLastColumn()) {
return cellAddresses;
}
}
return null;
}
/**
* 多個列表支持,按順序寫入excel。 列表數據數量需等於列表表達式數量,不然多餘的表達式不會被清空。多餘的列表數據不會被寫入
*
* @param templateFile
* @param context
* @param dataLists
* @return
*/
public static byte[] writeMultiList(File templateFile, Map<String, Object> context,
List<Map<String, Object>>[] dataLists) {
try {
File temp = templateFile;
for (int i = 0; i < dataLists.length; i++) {
byte[] tempBytes = writeExcel(temp, context, dataLists[i]);
temp = File.createTempFile("multi_excel", ".excel");
FileUtils.writeByteArrayToFile(temp, tempBytes);
}
return FileUtils.readFileToByteArray(temp);
} catch (Exception e) {
throw new RuntimeException("生成Excel失敗!", e);
}
}
}