diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java index 9f05be414..5b6f41161 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java @@ -195,6 +195,15 @@ public class BeanUtil { if (null == bean || StrUtil.isBlank(expression)) { return null; } + + // 先尝试直接获取属性 + if (bean instanceof Map) { + final Map map = (Map) bean; + if(map.containsKey(expression)){ + return (T) map.get(expression); + } + } + return (T) BeanPath.of(expression).getValue(bean); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java index f91831d78..97bb1242a 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java @@ -164,6 +164,9 @@ public class BeanPath implements Iterator { */ public Object getValue(final Object bean) { final Object value = this.node.getValue(bean); + if(null == value){ + return null; + } if (!hasNext()) { return value; } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java index 574c049ef..b697b2817 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java @@ -52,6 +52,9 @@ public class NameNode implements Node { @Override public Object getValue(final Object bean) { + if(null == bean){ + return null; + } if ("$".equals(name)) { return bean; } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java index 71e0d7339..31b17caf9 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java @@ -43,10 +43,7 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.text.MessageFormat; import java.text.Normalizer; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -2485,6 +2482,46 @@ public class CharSequenceUtil extends StrValidator { public static String indexedFormat(final CharSequence pattern, final Object... arguments) { return MessageFormat.format(pattern.toString(), arguments); } + + /** + * 格式化文本,使用 {varName} 占位
+ * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue + * + * @param template 文本模板,被替换的部分用 {key} 表示 + * @param map 参数值对 + * @return 格式化后的文本 + */ + public static String formatByMap(final CharSequence template, final Map map) { + return formatByMap(template, map, true); + } + + /** + * 格式化文本,使用 {varName} 占位
+ * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue + * + * @param template 文本模板,被替换的部分用 {key} 表示 + * @param map 参数值对 + * @param ignoreNull 是否忽略 {@code null} 值,忽略则 {@code null} 值对应的变量不被替换,否则替换为"" + * @return 格式化后的文本 + * @since 5.4.3 + */ + public static String formatByMap(final CharSequence template, final Map map, final boolean ignoreNull) { + return StrFormatter.formatByBean(template, map, ignoreNull); + } + + /** + * 格式化文本,使用 {varName} 占位
+ * bean = User:{a: "aValue", b: "bValue"} format("{a} and {b}", bean) ---=》 aValue and bValue + * + * @param template 文本模板,被替换的部分用 {key} 表示 + * @param bean 参数Bean + * @param ignoreNull 是否忽略 {@code null} 值,忽略则 {@code null} 值对应的变量不被替换,否则替换为"" + * @return 格式化后的文本 + * @since 5.4.3 + */ + public static String formatByBean(final CharSequence template, final Object bean, final boolean ignoreNull) { + return StrFormatter.formatByBean(template, bean, ignoreNull); + } // endregion // region ----- wrap diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java index 003cad0f7..3b07ecc7b 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/StrUtil.java @@ -18,7 +18,6 @@ package org.dromara.hutool.core.text; import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.func.FunctionPool; -import org.dromara.hutool.core.text.placeholder.StrFormatter; import org.dromara.hutool.core.text.split.SplitUtil; import org.dromara.hutool.core.util.CharsetUtil; @@ -26,7 +25,6 @@ import java.io.StringReader; import java.io.StringWriter; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.util.Map; /** * 字符串工具类
@@ -36,10 +34,10 @@ import java.util.Map; * 字符串分割split参考:{@link SplitUtil}   
* 多字符串判空hasBlank参考:{@link ArrayUtil} *

- * @see SplitUtil#split(CharSequence, CharSequence) 对字符串分割 - * @see ArrayUtil#hasBlank(CharSequence...) 对多个字符串判空 * * @author Looly + * @see SplitUtil#split(CharSequence, CharSequence) 对字符串分割 + * @see ArrayUtil#hasBlank(CharSequence...) 对多个字符串判空 */ public class StrUtil extends CharSequenceUtil implements StrPool { @@ -130,6 +128,7 @@ public class StrUtil extends CharSequenceUtil implements StrPool { } // region ----- str + /** * 将对象转为字符串
* @@ -164,9 +163,9 @@ public class StrUtil extends CharSequenceUtil implements StrPool { if (obj instanceof String) { return (String) obj; - }else if(obj instanceof char[]){ + } else if (obj instanceof char[]) { return new String((char[]) obj); - }else if (obj instanceof byte[]) { + } else if (obj instanceof byte[]) { return str((byte[]) obj, charset); } else if (obj instanceof Byte[]) { return str((Byte[]) obj, charset); @@ -240,7 +239,7 @@ public class StrUtil extends CharSequenceUtil implements StrPool { * @param value char[]值,注意这个数组不可修改!! * @return String */ - public static String strFast(final char[] value){ + public static String strFast(final char[] value) { return FunctionPool.createString(value); } // endregion @@ -372,30 +371,4 @@ public class StrUtil extends CharSequenceUtil implements StrPool { public static String similar(final String str1, final String str2, final int scale) { return TextSimilarity.similar(str1, str2, scale); } - - /** - * 格式化文本,使用 {varName} 占位
- * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue - * - * @param template 文本模板,被替换的部分用 {key} 表示 - * @param map 参数值对 - * @return 格式化后的文本 - */ - public static String format(final CharSequence template, final Map map) { - return format(template, map, true); - } - - /** - * 格式化文本,使用 {varName} 占位
- * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue - * - * @param template 文本模板,被替换的部分用 {key} 表示 - * @param map 参数值对 - * @param ignoreNull 是否忽略 {@code null} 值,忽略则 {@code null} 值对应的变量不被替换,否则替换为"" - * @return 格式化后的文本 - * @since 5.4.3 - */ - public static String format(final CharSequence template, final Map map, final boolean ignoreNull) { - return StrFormatter.format(template, map, ignoreNull); - } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrFormatter.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrFormatter.java index 39206acb8..bf3c9ec7f 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrFormatter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrFormatter.java @@ -18,6 +18,7 @@ package org.dromara.hutool.core.text.placeholder; import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.lang.mutable.MutableEntry; +import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.map.reference.WeakConcurrentMap; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.placeholder.template.NamedPlaceholderStrTemplate; @@ -70,27 +71,31 @@ public class StrFormatter { return strPattern; } return ((SinglePlaceholderStrTemplate) CACHE.computeIfAbsent(MutableEntry.of(strPattern, placeHolder), k -> - StrTemplate.of(strPattern).placeholder(placeHolder).build())) - .format(argArray); + StrTemplate.of(strPattern).placeholder(placeHolder).build())) + .format(argArray); } /** * 格式化文本,使用 {varName} 占位
- * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue + * bean = User:{a: "aValue", b: "bValue"} format("{a} and {b}", bean) ---=》 aValue and bValue * * @param template 文本模板,被替换的部分用 {key} 表示 - * @param map 参数值对 + * @param bean 参数Bean * @param ignoreNull 是否忽略 {@code null} 值,忽略则 {@code null} 值对应的变量不被替换,否则替换为"" * @return 格式化后的文本 - * @since 5.7.10 + * @since 6.0.0 */ - public static String format(final CharSequence template, final Map map, final boolean ignoreNull) { + public static String formatByBean(final CharSequence template, final Object bean, final boolean ignoreNull) { if (null == template) { return null; } - if (null == map || map.isEmpty()) { - return template.toString(); + + if (bean instanceof Map) { + if (MapUtil.isEmpty((Map) bean)) { + return template.toString(); + } } + // Bean的空检查需要反射,性能很差,此处不检查 return ((NamedPlaceholderStrTemplate) CACHE.computeIfAbsent(MutableEntry.of(template, ignoreNull), k -> { final NamedPlaceholderStrTemplate.Builder builder = StrTemplate.ofNamed(template.toString()); @@ -100,7 +105,7 @@ public class StrFormatter { builder.addFeatures(StrTemplate.Feature.FORMAT_NULL_VALUE_TO_EMPTY); } return builder.build(); - })).format(map); + })).format(bean); } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java index faf6d12ea..c5d0a0347 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java @@ -22,6 +22,7 @@ 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.lang.Assert; +import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.math.NumberUtil; import org.dromara.hutool.core.text.StrPool; import org.dromara.hutool.core.text.placeholder.StrTemplate; @@ -353,7 +354,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate { * @return 格式化字符串 */ public String format(final Map map) { - if (map == null) { + if (MapUtil.isEmpty(map)) { return getTemplate(); } return format(map::get, map::containsKey); diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/text/StrFormatterTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/text/StrFormatterTest.java index deef09683..411e99d08 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/text/StrFormatterTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/text/StrFormatterTest.java @@ -12,6 +12,8 @@ package org.dromara.hutool.core.text; +import lombok.AllArgsConstructor; +import lombok.Data; import org.dromara.hutool.core.text.placeholder.StrFormatter; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -62,4 +64,20 @@ public class StrFormatterTest { final String result3 = StrFormatter.formatWith("this is \\\\$$$ for $$$", "$$$", "a", "b"); Assertions.assertEquals("this is \\a for b", result3); } + + @Test + void formatByBeanTest() { + final User user = new User("张三", 18, true); + final String s = StrFormatter.formatByBean("User name: {name}, age: {age}, gender: {gender}", user, true); + + Assertions.assertEquals("User name: 张三, age: 18, gender: true", s); + } + + @Data + @AllArgsConstructor + private static class User{ + private String name; + private int age; + private boolean gender; + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/text/StrUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/text/StrUtilTest.java index 9a325b23c..5e90acb07 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/text/StrUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/text/StrUtilTest.java @@ -89,10 +89,10 @@ public class StrUtilTest { @Test public void formatTest() { final String template = "你好,我是{name},我的电话是:{phone}"; - final String result = StrUtil.format(template, Dict.of().set("name", "张三").set("phone", "13888881111")); + final String result = StrUtil.formatByMap(template, Dict.of().set("name", "张三").set("phone", "13888881111")); Assertions.assertEquals("你好,我是张三,我的电话是:13888881111", result); - final String result2 = StrUtil.format(template, Dict.of().set("name", "张三").set("phone", null)); + final String result2 = StrUtil.formatByMap(template, Dict.of().set("name", "张三").set("phone", null)); Assertions.assertEquals("你好,我是张三,我的电话是:{phone}", result2); } diff --git a/hutool-log/src/main/java/org/dromara/hutool/log/engine/console/ConsoleLog.java b/hutool-log/src/main/java/org/dromara/hutool/log/engine/console/ConsoleLog.java index 5aaa76d16..a3a394961 100644 --- a/hutool-log/src/main/java/org/dromara/hutool/log/engine/console/ConsoleLog.java +++ b/hutool-log/src/main/java/org/dromara/hutool/log/engine/console/ConsoleLog.java @@ -143,7 +143,7 @@ public class ConsoleLog extends AbstractLog { .set("name", this.name) .set("msg", StrUtil.format(format, arguments)); - final String logMsg = StrUtil.format(logFormat, dict); + final String logMsg = StrUtil.formatByMap(logFormat, dict); //WARN以上级别打印至System.err if (level.ordinal() >= Level.WARN.ordinal()) { diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java index 0d5c570c1..fac6d74dc 100644 --- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java +++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java @@ -41,6 +41,7 @@ public class VirtualCell extends CellBase { private CellType cellType; private Object value; private CellStyle style; + private Comment comment; /** * 构造 @@ -57,7 +58,20 @@ public class VirtualCell extends CellBase { this.comment = cell.getCellComment(); } - private Comment comment; + /** + * 构造 + * + * @param cell 参照单元格 + * @param x 新的列号,从0开始 + * @param y 新的行号,从0开始 + * @param value 新值 + */ + public VirtualCell(final Cell cell, final int x, final int y, final Object value) { + this(cell.getRow(), x, y); + this.style = cell.getCellStyle(); + this.comment = cell.getCellComment(); + CellUtil.setCellValue(this, value); + } /** * 构造 @@ -91,31 +105,37 @@ public class VirtualCell extends CellBase { @Override protected void setCellValueImpl(final double value) { + this.cellType = CellType.NUMERIC; this.value = value; } @Override protected void setCellValueImpl(final Date value) { + this.cellType = CellType.NUMERIC; this.value = value; } @Override protected void setCellValueImpl(final LocalDateTime value) { + this.cellType = CellType.NUMERIC; this.value = value; } @Override protected void setCellValueImpl(final Calendar value) { + this.cellType = CellType.NUMERIC; this.value = value; } @Override protected void setCellValueImpl(final String value) { + this.cellType = CellType.STRING; this.value = value; } @Override protected void setCellValueImpl(final RichTextString value) { + this.cellType = CellType.STRING; this.value = value; } @@ -192,11 +212,13 @@ public class VirtualCell extends CellBase { @Override public void setCellValue(final boolean value) { + this.cellType = CellType.BOOLEAN; this.value = value; } @Override public void setCellErrorValue(final byte value) { + this.cellType = CellType.ERROR; this.value = value; } diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/SheetTemplateWriter.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/SheetTemplateWriter.java index ad9059957..f020dfcd5 100644 --- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/SheetTemplateWriter.java +++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/SheetTemplateWriter.java @@ -18,7 +18,6 @@ package org.dromara.hutool.poi.excel.writer; import org.apache.poi.ss.usermodel.Sheet; import org.dromara.hutool.core.map.BeanMap; -import org.dromara.hutool.core.text.StrUtil; import java.util.Map; @@ -57,31 +56,20 @@ public class SheetTemplateWriter { * @return this */ public SheetTemplateWriter fillOnce(final Map rowMap) { - rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, false)); + this.templateContext.fill(rowMap, false); return this; } /** * 填充模板行,用于列表填充 * - * @param rowBean 行的Bean数据 + * @param rowBean 行的Bean或Map数据 * @return this */ public SheetTemplateWriter fillRow(final Object rowBean) { - // TODO 支持Bean的级联属性获取 - return fillRow(new BeanMap(rowBean)); - } - - /** - * 填充模板行,用于列表填充 - * - * @param rowMap 行数据 - * @return this - */ - public SheetTemplateWriter fillRow(final Map rowMap) { if (this.config.insertRow) { // 当前填充行的模板行以下全部下移 - final int bottomRowIndex = this.templateContext.getBottomRowIndex(rowMap); + final int bottomRowIndex = this.templateContext.getBottomRowIndex(rowBean); if (bottomRowIndex < 0) { // 无可填充行 return this; @@ -96,7 +84,7 @@ public class SheetTemplateWriter { } } - rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, true)); + this.templateContext.fill(rowBean, true); return this; } diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java index b85d4c0c7..daa5bc11e 100644 --- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java +++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java @@ -19,11 +19,14 @@ package org.dromara.hutool.poi.excel.writer; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Sheet; +import org.dromara.hutool.core.bean.BeanUtil; import org.dromara.hutool.core.collection.CollUtil; import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.core.map.MapUtil; 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.core.util.ObjUtil; import org.dromara.hutool.poi.excel.SheetUtil; import org.dromara.hutool.poi.excel.cell.CellUtil; import org.dromara.hutool.poi.excel.cell.VirtualCell; @@ -31,6 +34,7 @@ import org.dromara.hutool.poi.excel.cell.VirtualCell; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; /** @@ -86,54 +90,71 @@ public class TemplateContext { *
  • 如果为{@link VirtualCell},返回最底部虚拟单元格各行号
  • * * - * @param rowData 填充数据 + * @param rowDataBean 填充数据 * @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{ + public int getBottomRowIndex(final Object rowDataBean) { + final AtomicInteger bottomRowIndex = new AtomicInteger(-1); + this.varMap.forEach((name, cell) -> { + if(null != BeanUtil.getProperty(rowDataBean, name)){ + if (cell instanceof VirtualCell) { + bottomRowIndex.set(Math.max(bottomRowIndex.get(), cell.getRowIndex())); + } else if(bottomRowIndex.get() < 0){ // 实体单元格,直接填充,无需下移 - bottomRowIndex = 0; + bottomRowIndex.set(0); } } - } - return bottomRowIndex; + }); + return bottomRowIndex.get(); } /** * 填充变量名name指向的单元格 * - * @param name 变量名 - * @param rowData 一行数据的键值对 - * @param isListVar 是否为列表填充,列表填充会自动指向下一列,否则填充结束后删除变量 + * @param rowDataBean 一行数据的键值对 + * @param isListVar 是否为列表填充,列表填充会自动指向下一列,否则填充结束后删除变量 * @since 6.0.0 */ - public void fill(final String name, final Map rowData, final boolean isListVar) { - final Cell cell = varMap.get(name); - if (null == cell) { - // 没有对应变量占位 - return; + public void fill(final Object rowDataBean, final boolean isListVar) { + final Map varMap = this.varMap; + varMap.forEach((name, cell) -> { + if (null == cell) { + return; + } + + final String templateStr = cell.getStringCellValue(); + // 填充单元格 + if (fill(cell, name, rowDataBean)) { + // 指向下一个单元格 + putNext(name, cell, templateStr, isListVar); + } + }); + + if (!isListVar) { + // 清理已经匹配完毕的变量 + MapUtil.removeNullValue(varMap); } + } - final String templateStr = cell.getStringCellValue(); - + /** + * 将变量指向下一行的单元格
    + * 如果为列表,则指向下一行的虚拟单元格(不创建单元格) + * 如果非列表,则清空此变量 + * + * @param name 变量名 + * @param currentCell 当前单元格 + * @param templateStr 模板字符串 + * @param isListVar 是否为列表填充 + */ + private void putNext(final String name, final Cell currentCell, final String templateStr, final boolean isListVar) { if (isListVar) { // 指向下一列的单元格 - final Cell next = new VirtualCell(cell, cell.getColumnIndex(), cell.getRowIndex() + 1); - next.setCellValue(templateStr); + final Cell next = new VirtualCell(currentCell, currentCell.getColumnIndex(), currentCell.getRowIndex() + 1, templateStr); varMap.put(name, next); } else { // 非列表,一次性填充,即变量填充后,和此单元格去掉关联 - varMap.remove(name); + varMap.put(name, null); } - - fill(cell, name, templateStr, rowData); } /** @@ -141,10 +162,11 @@ public class TemplateContext { * * @param cell 单元格,非模板中变量所在单元格则为{@link VirtualCell} * @param name 变量名 - * @param templateStr 模板字符串 - * @param rowData 填充的数据 + * @param rowDataBean 填充的数据,可以为Map或Bean + * @return 是否填充成功,{@code false}表示无数据 */ - private void fill(Cell cell, final String name, final String templateStr, final Map rowData) { + private boolean fill(Cell cell, final String name, final Object rowDataBean) { + final String templateStr = cell.getStringCellValue(); if (cell instanceof VirtualCell) { // 虚拟单元格,转换为实际单元格 final Cell newCell; @@ -159,13 +181,23 @@ public class TemplateContext { final Object cellValue; // 模板替换 if (StrUtil.equals(name, StrUtil.unWrap(templateStr, VAR_PREFIX, VAR_SUFFIX))) { - // 一个单元格只有一个变量 - cellValue = rowData.get(name); + // 一个单元格只有一个变量,支持多级表达式 + cellValue = BeanUtil.getProperty(rowDataBean, name); + if (null == cellValue) { + // 对应表达式无提供的值,跳过填充 + return false; + } } else { // 模板中存在多个变量或模板填充,直接赋值为String - cellValue = StrUtil.format(templateStr, rowData); + // 没有找到值的变量保留原样 + cellValue = StrUtil.formatByBean(templateStr, rowDataBean, false); + if (ObjUtil.equals(cellValue, templateStr)) { + // 模板无修改,说明没有变量替换,跳过填充 + return false; + } } CellUtil.setCellValue(cell, cellValue); + return true; } /** diff --git a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java index 3d4a0f2c0..a95c32c2a 100644 --- a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java +++ b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java @@ -1,5 +1,6 @@ package org.dromara.hutool.poi.excel.writer; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.poi.excel.ExcelUtil; import org.junit.jupiter.api.Test; @@ -28,7 +29,7 @@ public class TemplateWriterTest { writer.writeRow(createRow(), false); } - //writer.flush(FileUtil.file(targetDir + "templateResult.xlsx"), true); + writer.flush(FileUtil.file(targetDir + "templateResult.xlsx"), true); writer.close(); } @@ -50,7 +51,7 @@ public class TemplateWriterTest { writer.writeRow(createRow(), false); } - //writer.flush(FileUtil.file(targetDir + "templateWithFooterResult.xlsx"), true); + writer.flush(FileUtil.file(targetDir + "templateWithFooterResult.xlsx"), true); writer.close(); }