add fill template

This commit is contained in:
Looly 2024-08-25 12:17:27 +08:00
parent 1228c09a10
commit d3752b7648
8 changed files with 233 additions and 72 deletions

View File

@ -93,6 +93,17 @@ public class DynaBean implements Cloneable, Serializable {
}
}
/**
* 获得path表达式对应的值
*
* @param expression path表达式
* @param <T> 属性值类型
* @return
*/
public <T> T getProperty(final String expression) {
return BeanUtil.getProperty(bean, expression);
}
/**
* 获得字段对应值
*
@ -161,6 +172,18 @@ public class DynaBean implements Cloneable, Serializable {
}
}
/**
* 设置属性值
*
* @param expression path表达式
* @param value
* @return this
*/
public DynaBean setProperty(final String expression, final Object value) {
BeanUtil.setProperty(bean, expression, value);
return this;
}
/**
* 设置字段值
*

View File

@ -346,11 +346,7 @@ public abstract class StrTemplate {
// 根据 占位符 返回 需要序列化的值
final Object value = valueSupplier.apply(segment);
if (value != null) {
if (value instanceof String) {
return (String) value;
} else {
return StrUtil.utf8Str(value);
}
return StrUtil.utf8Str(value);
} else {
// 处理null值
return formatNullValue(segment);

View File

@ -17,19 +17,16 @@
package org.dromara.hutool.core.text.placeholder.template;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanDesc;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.func.LambdaUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.text.StrPool;
import org.dromara.hutool.core.text.placeholder.StrTemplate;
import org.dromara.hutool.core.text.placeholder.segment.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.*;
@ -345,15 +342,6 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
}
if (beanOrMap instanceof Map) {
return format((Map<String, ?>) beanOrMap);
} else if (BeanUtil.isReadableBean(beanOrMap.getClass())) {
final BeanDesc beanDesc = BeanUtil.getBeanDesc(beanOrMap.getClass());
return format(fieldName -> {
final Method getterMethod = beanDesc.getGetter(fieldName);
if (getterMethod == null) {
return null;
}
return LambdaUtil.buildGetter(getterMethod).apply(beanOrMap);
});
}
return format(fieldName -> BeanUtil.getProperty(beanOrMap, fieldName));
}
@ -374,14 +362,14 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
/**
* 使用 占位变量名称 valueSupplier 中查询值来 替换 占位符
*
* @param valueSupplier 根据 占位变量名称 返回
* @param valueProvider 根据 占位变量名称 返回
* @return 格式化字符串
*/
public String format(final Function<String, ?> valueSupplier) {
if (valueSupplier == null) {
public String format(final Function<String, ?> valueProvider) {
if (valueProvider == null) {
return getTemplate();
}
return formatBySegment(segment -> valueSupplier.apply(segment.getPlaceholder()));
return formatBySegment(segment -> valueProvider.apply(segment.getPlaceholder()));
}
/**

View File

@ -25,7 +25,6 @@ import org.apache.poi.ss.util.cellwalk.CellWalk;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.text.StrUtil;
/**
* {@link Sheet}相关工具类
@ -40,18 +39,27 @@ public class SheetUtil {
* 如果sheet表在Workbook中已经存在则获取之否则创建之
*
* @param book 工作簿{@link Workbook}
* @param sheetName 工作表名
* @param sheetName 工作表名{@code null}表示默认
* @return 工作表{@link Sheet}
* @since 4.0.2
*/
public static Sheet getOrCreateSheet(final Workbook book, String sheetName) {
public static Sheet getOrCreateSheet(final Workbook book, final String sheetName) {
if (null == book) {
return null;
}
sheetName = StrUtil.isBlank(sheetName) ? "sheet1" : sheetName;
Sheet sheet = book.getSheet(sheetName);
if (null == sheet) {
sheet = book.createSheet(sheetName);
Sheet sheet;
if (null == sheetName) {
sheet = book.getSheetAt(0);
if (null == sheet) {
// 工作簿中无sheet创建默认
sheet = book.createSheet();
}
} else {
sheet = book.getSheet(sheetName);
if (null == sheet) {
sheet = book.createSheet(sheetName);
}
}
return sheet;
}

View File

@ -41,6 +41,11 @@ public class ExcelWriteConfig extends ExcelConfig {
* 是否只保留别名对应的字段
*/
protected boolean onlyAlias;
/**
* 是否强制插入行<br>
* 如果为{@code true}则写入行以下的已存在行下移{@code false}则利用填充已有行不存在再创建行
*/
protected boolean insertRow;
/**
* 标题顺序比较器
*/
@ -76,6 +81,17 @@ public class ExcelWriteConfig extends ExcelConfig {
return this;
}
/**
* 设置是否插入行如果为true则写入行以下的已存在行下移false则利用填充已有行不存在时创建行
*
* @param insertRow 是否插入行
* @return this
*/
public ExcelWriteConfig setInsertRow(final boolean insertRow) {
this.insertRow = insertRow;
return this;
}
/**
* 获取单例的别名比较器比较器的顺序为别名加入的顺序
*

View File

@ -847,12 +847,15 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
public ExcelWriter writeHeadRow(final Iterable<?> rowData) {
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
this.headLocationCache = new SafeConcurrentHashMap<>();
final Row row = this.sheet.createRow(this.currentRow.getAndIncrement());
final int rowNum = this.currentRow.getAndIncrement();
final Row row = this.config.insertRow ? this.sheet.createRow(rowNum) : RowUtil.getOrCreateRow(this.sheet, rowNum);
final CellEditor cellEditor = this.config.getCellEditor();
int i = 0;
Cell cell;
for (final Object value : rowData) {
cell = row.createCell(i);
cell = CellUtil.getOrCreateCell(row, i);
CellUtil.setCellValue(cell, value, this.styleSet, true, cellEditor);
this.headLocationCache.put(StrUtil.toString(value), i);
i++;
@ -945,6 +948,17 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
return writeRow(rowMap, isWriteKeyAsHead);
}
/**
* 填充非列表模板变量一次性变量
*
* @param rowMap 行数据
* @return this
*/
public ExcelWriter fillOnce(final Map<?, ?> rowMap) {
rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, false));
return this;
}
/**
* 将一个Map写入到ExcelisWriteKeyAsHead为true写出两行Map的keys做为一行values做为第二行否则只写出一行values<br>
* 如果rowMap为空包括null则写出空行
@ -961,11 +975,17 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
return passCurrentRow();
}
// 模板写出
if (null != this.templateContext) {
fillRow(rowMap, this.config.insertRow);
return this;
}
final Table<?, ?, ?> aliasTable = this.config.aliasTable(rowMap);
if (isWriteKeyAsHead) {
// 写出标题行并记录标题别名和列号的关系
writeHeadRow(aliasTable.columnKeys());
// 记录原数据key对应列号
// 记录原数据key和别名对应列号
int i = 0;
for (final Object key : aliasTable.rowKeySet()) {
this.headLocationCache.putIfAbsent(StrUtil.toString(key), i);
@ -1005,21 +1025,10 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
*/
public ExcelWriter writeRow(final Iterable<?> rowData) {
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
RowUtil.writeRow(this.sheet.createRow(this.currentRow.getAndIncrement()), rowData, this.styleSet, false, this.config.getCellEditor());
return this;
}
// endregion
// region ----- fill
/**
* 填充模板行
*
* @param rowMap 行数据
* @return this
*/
public ExcelWriter fillRow(final Map<?, ?> rowMap) {
rowMap.forEach((key, value) -> this.templateContext.fillAndPointToNext(StrUtil.toStringOrNull(key), rowMap));
final int rowNum = this.currentRow.getAndIncrement();
final Row row = this.config.insertRow ? this.sheet.createRow(rowNum) : RowUtil.getOrCreateRow(this.sheet, rowNum);
RowUtil.writeRow(row, rowData, this.styleSet, false, this.config.getCellEditor());
return this;
}
// endregion
@ -1374,4 +1383,31 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
// 清空对象
this.styleSet = null;
}
/**
* 填充模板行用于列表填充
*
* @param rowMap 行数据
* @param insertRow 是否插入行如果为{@code true}则已有行下移否则利用已有行
*/
private void fillRow(final Map<?, ?> rowMap, final boolean insertRow) {
if(insertRow){
// 当前填充行的模板行以下全部下移
final int bottomRowIndex = this.templateContext.getBottomRowIndex(rowMap);
if(bottomRowIndex < 0){
// 无可填充行
return;
}
if(bottomRowIndex != 0){
final int lastRowNum = this.sheet.getLastRowNum();
if(bottomRowIndex <= lastRowNum){
// 填充行底部需有数据无数据跳过
// 虚拟行的行号就是需要填充的行这行的已有数据整体下移
this.sheet.shiftRows(bottomRowIndex, this.sheet.getLastRowNum(), 1);
}
}
}
rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, true));
}
}

View File

@ -22,6 +22,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.regex.ReUtil;
import org.dromara.hutool.core.text.StrPool;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.poi.excel.SheetUtil;
import org.dromara.hutool.poi.excel.cell.CellUtil;
@ -40,6 +41,9 @@ import java.util.regex.Pattern;
*/
public class TemplateContext {
private static final String VAR_PREFIX = StrPool.DELIM_START;
private static final String VAR_SUFFIX = StrPool.DELIM_END;
/**
* 变量正则
* <ol>
@ -75,14 +79,43 @@ public class TemplateContext {
}
/**
* 填充变量名name指向的单元格并将变量指向下一列
* 获取当前替换的数据行对应变量的底部索引<br>
* 此方法用户获取填充行以便下移填充行后的行<br>
* <ul>
* <li>如果为实体单元格直接填充无需下移返回0</li>
* <li>如果为{@link VirtualCell}返回最底部虚拟单元格各行号</li>
* </ul>
*
* @param name 变量名
* @param rowData 一行数据的键值对
* @param rowData 填充数据
* @return 最大行索引-1表示无数据填充0表示无需下移
*/
public int getBottomRowIndex(final Map<?, ?> rowData) {
int bottomRowIndex = -1;
Cell cell;
for (final Object key : rowData.keySet()) {
cell = this.varMap.get(StrUtil.toStringOrNull(key));
if (null != cell) {
if(cell instanceof VirtualCell){
bottomRowIndex = Math.max(bottomRowIndex, cell.getRowIndex());
} else{
// 实体单元格直接填充无需下移
bottomRowIndex = 0;
}
}
}
return bottomRowIndex;
}
/**
* 填充变量名name指向的单元格
*
* @param name 变量名
* @param rowData 一行数据的键值对
* @param isListVar 是否为列表填充列表填充会自动指向下一列否则填充结束后删除变量
* @since 6.0.0
*/
public void fillAndPointToNext(final String name, final Map<?, ?> rowData) {
Cell cell = varMap.get(name);
public void fill(final String name, final Map<?, ?> rowData, final boolean isListVar) {
final Cell cell = varMap.get(name);
if (null == cell) {
// 没有对应变量占位
return;
@ -90,27 +123,49 @@ public class TemplateContext {
final String templateStr = cell.getStringCellValue();
// 指向下一列的单元格
final Cell next = new VirtualCell(cell, cell.getColumnIndex(), cell.getRowIndex() + 1);
next.setCellValue(templateStr);
varMap.put(name, next);
if (isListVar) {
// 指向下一列的单元格
final Cell next = new VirtualCell(cell, cell.getColumnIndex(), cell.getRowIndex() + 1);
next.setCellValue(templateStr);
varMap.put(name, next);
} else {
// 非列表一次性填充即变量填充后和此单元格去掉关联
varMap.remove(name);
}
if(cell instanceof VirtualCell){
fill(cell, name, templateStr, rowData);
}
/**
* 填充数据
*
* @param cell 单元格非模板中变量所在单元格则为{@link VirtualCell}
* @param name 变量名
* @param templateStr 模板字符串
* @param rowData 填充的数据
*/
private void fill(Cell cell, final String name, final String templateStr, final Map<?, ?> rowData) {
if (cell instanceof VirtualCell) {
// 虚拟单元格转换为实际单元格
final Cell newCell = CellUtil.getCell(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex(), true);
final Cell newCell;
newCell = CellUtil.getCell(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex(), true);
Assert.notNull(newCell, "Can not get or create cell at {},{}", cell.getColumnIndex(), cell.getRowIndex());
newCell.setCellStyle(cell.getCellStyle());
cell = newCell;
}
final Object cellValue;
// 模板替换
if(StrUtil.equals(name, StrUtil.unWrap(templateStr, "{", "}"))){
if (StrUtil.equals(name, StrUtil.unWrap(templateStr, VAR_PREFIX, VAR_SUFFIX))) {
// 一个单元格只有一个变量
CellUtil.setCellValue(cell, rowData.get(name));
cellValue = rowData.get(name);
} else {
// 模板中存在多个变量或模板填充
CellUtil.setCellValue(cell, StrUtil.format(templateStr, rowData));
// 模板中存在多个变量或模板填充直接赋值为String
cellValue = StrUtil.format(templateStr, rowData);
}
CellUtil.setCellValue(cell, cellValue);
}
/**
@ -134,7 +189,7 @@ public class TemplateContext {
}
// 替换转义的变量
final String str = ReUtil.replaceAll(cellValue, ESCAPE_VAR_PATTERN, (matcher) -> "{" + matcher.group(1) + "}");
final String str = ReUtil.replaceAll(cellValue, ESCAPE_VAR_PATTERN, (matcher) -> VAR_PREFIX + matcher.group(1) + VAR_SUFFIX);
if (!StrUtil.equals(cellValue, str)) {
cell.setCellValue(str);
}

View File

@ -5,30 +5,69 @@ import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.poi.excel.ExcelUtil;
import org.junit.jupiter.api.Test;
import java.util.Map;
public class TemplateWriterTest {
@Test
void insertTest() {
final ExcelWriter writer = ExcelUtil.getWriter("d:/test/template.xlsx");
writer.getConfig().setInsertRow(true);
writer.setCurrentRow(3);
writer.getSheet().shiftRows(4, 4, 10);
writer.flush(FileUtil.file("d:/test/templateInsertResult.xlsx"), true);
writer.close();
}
@Test
void writeRowTest() {
final ExcelWriter writer = ExcelUtil.getWriter("d:/test/template.xlsx");
writer.getConfig().setInsertRow(true);
// 单个替换的变量
writer.fillRow(MapUtil
writer.fillOnce(MapUtil
.builder("date", (Object)"2024-01-01")
.build());
// 列表替换
for (int i = 0; i < 10; i++) {
writer.fillRow(MapUtil
.builder("user.name", (Object)"张三")
.put("user.age", 18)
.put("year", 2024)
.put("month", 8)
.put("day", 24)
.put("day", 24)
.put("user.area123", "某某市")
.put("invalid", "不替换")
.build());
writer.writeRow(createRow(), false);
}
writer.flush(FileUtil.file("d:/test/templateResult.xlsx"), true);
writer.close();
}
@Test
void writeRowWithFooterTest() {
final ExcelWriter writer = ExcelUtil.getWriter("d:/test/templateWithFooter.xlsx");
writer.getConfig().setInsertRow(true);
// 单个替换的变量
writer.fillOnce(MapUtil
.builder("date", (Object)"2024-01-01")
.build());
// 列表替换
for (int i = 0; i < 10; i++) {
writer.writeRow(createRow(), false);
}
writer.flush(FileUtil.file("d:/test/templateWithFooterResult.xlsx"), true);
writer.close();
}
private static Map<String, Object> createRow(){
return MapUtil
.builder("user.name", (Object)"张三")
.put("user.age", 18)
.put("year", 2024)
.put("month", 8)
.put("day", 24)
.put("day", 24)
.put("user.area123", "某某市")
.put("invalid", "不替换")
.build();
}
}