mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
add fill template
This commit is contained in:
parent
1228c09a10
commit
d3752b7648
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段值
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例的别名比较器,比较器的顺序为别名加入的顺序
|
||||
*
|
||||
|
@ -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写入到Excel,isWriteKeyAsHead为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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user