add TemplateContext

This commit is contained in:
Looly 2024-08-12 00:22:02 +08:00
parent 7a1983235f
commit 31b7583369
9 changed files with 313 additions and 68 deletions

View File

@ -81,27 +81,6 @@ public class ExcelImgUtil {
}
}
/**
* 写出图片本方法只是将数据写入Workbook中的Sheet并不写出到文件<br>
* 添加图片到当前sheet中
*
* @param sheet {@link Sheet}
* @param pictureData 数据bytes
* @param imgType 图片类型对应poi中Workbook类中的图片类型2-7变量
* @param clientAnchor 图片的位置和大小信息
* @author vhukze
* @since 6.0.0
*/
public static void writeImg(final Sheet sheet, final byte[] pictureData,
final ExcelImgType imgType, final SimpleClientAnchor clientAnchor) {
final Drawing<?> patriarch = sheet.createDrawingPatriarch();
final Workbook workbook = sheet.getWorkbook();
final ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
clientAnchor.copyTo(anchor);
patriarch.createPicture(anchor, workbook.addPicture(pictureData, imgType.getValue()));
}
// -------------------------------------------------------------------------------------------------------------- Private method start
/**

View File

@ -12,11 +12,15 @@
package org.dromara.hutool.poi.excel;
import org.apache.poi.ss.usermodel.IgnoredErrorType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.cellwalk.CellHandler;
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;
/**
@ -82,6 +86,18 @@ public class SheetUtil {
return null == sheet || (sheet.getLastRowNum() == 0 && sheet.getPhysicalNumberOfRows() == 0);
}
/**
* 遍历Sheet中的所有单元格
*
* @param sheet {@link Sheet}
* @param cellHandler 单元格处理器
*/
public static void walk(final Sheet sheet, final CellHandler cellHandler) {
walk(sheet,
new CellRangeAddress(0, sheet.getLastRowNum(), 0, sheet.getLastRowNum()),
cellHandler);
}
/**
* 遍历Sheet中的指定区域单元格
*
@ -93,4 +109,28 @@ public class SheetUtil {
final CellWalk cellWalk = new CellWalk(sheet, range);
cellWalk.traverse(cellHandler);
}
/**
* 设置忽略错误即Excel中的绿色警告小标只支持XSSFSheet和SXSSFSheet<br>
* https://stackoverflow.com/questions/23488221/how-to-remove-warning-in-excel-using-apache-poi-in-java
*
* @param sheet {@link Sheet}
* @param cellRangeAddress 指定单元格范围
* @param ignoredErrorTypes 忽略的错误类型列表
* @throws UnsupportedOperationException 如果sheet不是XSSFSheet
* @since 5.8.28
*/
public static void addIgnoredErrors(final Sheet sheet, final CellRangeAddress cellRangeAddress, final IgnoredErrorType... ignoredErrorTypes) throws UnsupportedOperationException {
if (sheet instanceof XSSFSheet) {
((XSSFSheet) sheet).addIgnoredErrors(cellRangeAddress, ignoredErrorTypes);
} else if (sheet instanceof SXSSFSheet) {
// SXSSFSheet并未提供忽略错误方法获得其内部_sh字段设置
final XSSFSheet xssfSheet = (XSSFSheet) FieldUtil.getFieldValue(sheet, "_sh");
if (null != xssfSheet) {
xssfSheet.addIgnoredErrors(cellRangeAddress, ignoredErrorTypes);
}
} else {
throw new UnsupportedOperationException("Only XSSFSheet supports addIgnoredErrors");
}
}
}

View File

@ -17,6 +17,7 @@ import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.poi.excel.ExcelImgType;
import org.dromara.hutool.poi.excel.ExcelImgUtil;
import org.dromara.hutool.poi.excel.SimpleClientAnchor;
import org.dromara.hutool.poi.excel.writer.ExcelDrawingUtil;
import java.io.File;
@ -69,7 +70,7 @@ public class ImgCellSetter implements CellSetter {
final int columnIndex = cell.getColumnIndex();
final int rowIndex = cell.getRowIndex();
ExcelImgUtil.writeImg(sheet, this.pictureData, this.imgType,
ExcelDrawingUtil.drawingImg(sheet, this.pictureData, this.imgType,
new SimpleClientAnchor(columnIndex, rowIndex, columnIndex + 1, rowIndex + 1));
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.poi.excel.writer;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
/**
* Excel数据验证相关工具
*
* @author Looly
* @since 6.0.0
*/
public class DataValidationUtil {
/**
* 增加下拉列表
*
* @param sheet {@link Sheet}
* @param regions {@link CellRangeAddressList} 指定下拉列表所占的单元格范围
* @param selectList 下拉列表内容
* @since 4.6.2
*/
public static void addSelect(final Sheet sheet, final CellRangeAddressList regions, final String... selectList) {
final DataValidationHelper validationHelper = sheet.getDataValidationHelper();
final DataValidationConstraint constraint = validationHelper.createExplicitListConstraint(selectList);
//设置下拉框数据
final DataValidation dataValidation = validationHelper.createValidation(constraint, regions);
//处理Excel兼容性问题
if (dataValidation instanceof XSSFDataValidation) {
dataValidation.setSuppressDropDownArrow(true);
dataValidation.setShowErrorBox(true);
} else {
dataValidation.setSuppressDropDownArrow(false);
}
sheet.addValidationData(dataValidation);
}
}

View File

@ -15,25 +15,45 @@ package org.dromara.hutool.poi.excel.writer;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPatriarch;
import org.apache.poi.hssf.usermodel.HSSFSimpleShape;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSimpleShape;
import org.dromara.hutool.poi.excel.ExcelImgType;
import org.dromara.hutool.poi.excel.SimpleClientAnchor;
import org.dromara.hutool.poi.excel.style.ShapeConfig;
import java.awt.Color;
/**
* 简单形状工具类<br>
* Excel绘制工具类<br>
* 用于辅助写出指定的图形
*
* @author Looly
* @since 6.0.0
*/
public class SimpleShapeUtil {
public class ExcelDrawingUtil {
/**
* 写出图片本方法只是将数据写入Workbook中的Sheet并不写出到文件<br>
* 添加图片到当前sheet中
*
* @param sheet {@link Sheet}
* @param pictureData 数据bytes
* @param imgType 图片类型对应poi中Workbook类中的图片类型2-7变量
* @param clientAnchor 图片的位置和大小信息
* @author vhukze
* @since 6.0.0
*/
public static void drawingImg(final Sheet sheet, final byte[] pictureData,
final ExcelImgType imgType, final SimpleClientAnchor clientAnchor) {
final Drawing<?> patriarch = sheet.createDrawingPatriarch();
final Workbook workbook = sheet.getWorkbook();
final ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
clientAnchor.copyTo(anchor);
patriarch.createPicture(anchor, workbook.addPicture(pictureData, imgType.getValue()));
}
/**
* 绘制简单形状
@ -43,7 +63,7 @@ public class SimpleShapeUtil {
* @param shapeConfig 形状配置包括形状类型线条样式线条宽度线条颜色填充颜色等
* @since 6.0.0
*/
public static void writeSimpleShape(final Sheet sheet, final SimpleClientAnchor clientAnchor, ShapeConfig shapeConfig) {
public static void drawingSimpleShape(final Sheet sheet, final SimpleClientAnchor clientAnchor, ShapeConfig shapeConfig) {
final Drawing<?> patriarch = sheet.createDrawingPatriarch();
final ClientAnchor anchor = sheet.getWorkbook().getCreationHelper().createClientAnchor();
clientAnchor.copyTo(anchor);
@ -72,4 +92,25 @@ public class SimpleShapeUtil {
throw new UnsupportedOperationException("Unsupported patriarch type: " + patriarch.getClass().getName());
}
}
/**
* 添加批注
*
* @param cell {@link Cell}
* @param clientAnchor 绘制区域信息
* @param content 内容
*/
public static void drawingCellComment(final Cell cell, final SimpleClientAnchor clientAnchor, final String content){
final Sheet sheet = cell.getSheet();
final Drawing<?> patriarch = sheet.createDrawingPatriarch();
final Workbook workbook = sheet.getWorkbook();
final ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
clientAnchor.copyTo(anchor);
final RichTextString richTextString = workbook.getCreationHelper().createRichTextString(content);
final Comment cellComment = patriarch.createCellComment(anchor);
cellComment.setString(richTextString);
cell.setCellComment(cellComment);
}
}

View File

@ -17,9 +17,6 @@ import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.io.IORuntimeException;
@ -29,7 +26,6 @@ import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.map.concurrent.SafeConcurrentHashMap;
import org.dromara.hutool.core.map.multi.Table;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.poi.excel.*;
import org.dromara.hutool.poi.excel.cell.CellRangeUtil;
@ -67,11 +63,15 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
*/
private Map<String, Integer> headLocationCache;
/**
* 当前行
* 当前行用于标记初始可写数据的行和部分写完后当前的行
*/
private final AtomicInteger currentRow;
/**
* 模板上下文存储模板中变量及其位置信息
*/
private TemplateContext templateContext;
// region Constructors
// region ----- Constructors
/**
* 构造默认生成xlsx格式的Excel文件<br>
@ -148,8 +148,11 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
if (!FileUtil.exists(targetFile)) {
this.targetFile = targetFile;
} else{
// 如果是已经存在的文件则作为模板加载此时不能写出到模板文件
// 初始化模板
this.templateContext = new TemplateContext(this.sheet);
}
// 如果是已经存在的文件则作为模板加载此时不能写出到模板文件
}
/**
@ -389,9 +392,6 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
return this;
}
//region header alias
//endregion
/**
* 设置窗口冻结之前冻结的窗口会被覆盖如果rowSplit为0表示取消冻结
*
@ -502,19 +502,8 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
* @since 5.8.28
*/
public ExcelWriter addIgnoredErrors(final CellRangeAddress cellRangeAddress, final IgnoredErrorType... ignoredErrorTypes) throws UnsupportedOperationException {
final Sheet sheet = this.sheet;
if (sheet instanceof XSSFSheet) {
((XSSFSheet) sheet).addIgnoredErrors(cellRangeAddress, ignoredErrorTypes);
return this;
} else if (sheet instanceof SXSSFSheet) {
// SXSSFSheet并未提供忽略错误方法获得其内部_sh字段设置
final XSSFSheet xssfSheet = (XSSFSheet) FieldUtil.getFieldValue(sheet, "_sh");
if (null != xssfSheet) {
xssfSheet.addIgnoredErrors(cellRangeAddress, ignoredErrorTypes);
}
}
throw new UnsupportedOperationException("Only XSSFSheet supports addIgnoredErrors");
SheetUtil.addIgnoredErrors(this.sheet, cellRangeAddress, ignoredErrorTypes);
return this;
}
/**
@ -539,21 +528,8 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
* @since 4.6.2
*/
public ExcelWriter addSelect(final CellRangeAddressList regions, final String... selectList) {
final DataValidationHelper validationHelper = this.sheet.getDataValidationHelper();
final DataValidationConstraint constraint = validationHelper.createExplicitListConstraint(selectList);
//设置下拉框数据
final DataValidation dataValidation = validationHelper.createValidation(constraint, regions);
//处理Excel兼容性问题
if (dataValidation instanceof XSSFDataValidation) {
dataValidation.setSuppressDropDownArrow(true);
dataValidation.setShowErrorBox(true);
} else {
dataValidation.setSuppressDropDownArrow(false);
}
return addValidationData(dataValidation);
DataValidationUtil.addSelect(this.sheet, regions, selectList);
return this;
}
/**
@ -568,6 +544,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
return this;
}
// region ----- merge
/**
* 合并当前行的单元格
*
@ -656,7 +633,9 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
}
return this;
}
// endregion
// region ----- write
/**
* 写出数据本方法只是将数据写入Workbook中的Sheet并不写出到文件<br>
* 写出的起始行为当前行号可使用{@link #getCurrentRow()}方法调用根据写出的的行数当前行号自动增加
@ -744,6 +723,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
}
return this;
}
// endregion
// region ----- writeImg
@ -805,7 +785,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
* @since 6.0.0
*/
public ExcelWriter writeImg(final byte[] pictureData, final ExcelImgType imgType, final SimpleClientAnchor clientAnchor) {
ExcelImgUtil.writeImg(this.sheet, pictureData, imgType, clientAnchor);
ExcelDrawingUtil.drawingImg(this.sheet, pictureData, imgType, clientAnchor);
return this;
}
@ -843,7 +823,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
* @since 6.0.0
*/
public ExcelWriter writeSimpleShape(final SimpleClientAnchor clientAnchor, final ShapeConfig shapeConfig) {
SimpleShapeUtil.writeSimpleShape(this.sheet, clientAnchor, shapeConfig);
ExcelDrawingUtil.drawingSimpleShape(this.sheet, clientAnchor, shapeConfig);
return this;
}
// endregion

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
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.collection.CollUtil;
import org.dromara.hutool.core.regex.ReUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.poi.excel.SheetUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 模板上下文记录了模板中变量所在的Cell
*
* @author Looly
* @since 6.0.0
*/
public class TemplateContext {
/**
* 变量正则
* <ol>
* <li>变量名只能包含字母数字下划线$符号.符号不能以数字开头</li>
* <li>变量以 { 开始 } 结束</li>
* <li>\{表示转义非变量符号</li>
* <li>.开头的变量表示列表.出现在中间表示表达式子对象</li>
* </ol>
*/
private static final Pattern VAR_PATTERN = Pattern.compile("(?<!\\\\)\\{([.$_a-zA-Z]+\\d*[.$_a-zA-Z]*)}");
private static final Pattern ESCAPE_VAR_PATTERN = Pattern.compile("\\\\\\{([.$_a-zA-Z]+\\d*[.$_a-zA-Z]*)\\\\}");
// 存储变量对应单元格的映射
private final Map<String, Cell> varMap = new HashMap<>();
/**
* 构造
*
* @param templateSheet 模板sheet
*/
public TemplateContext(final Sheet templateSheet) {
init(templateSheet);
}
/**
* 获取变量对应的单元格列表变量以.开头
*
* @param varName 变量名
* @return 单元格
*/
public Cell getCell(final String varName) {
return varMap.get(varName);
}
/**
* 初始化提取变量及位置并将转义的变量回填
*
* @param templateSheet 模板sheet
*/
private void init(final Sheet templateSheet) {
SheetUtil.walk(templateSheet, (cell, ctx) -> {
if (CellType.STRING == cell.getCellType()) {
// 只读取字符串类型的单元格
final String cellValue = cell.getStringCellValue();
// 字符串中可能有多个变量
final List<String> vars = ReUtil.findAllGroup1(VAR_PATTERN, cellValue);
if (CollUtil.isNotEmpty(vars)) {
// 模板变量
for (final String var : vars) {
varMap.put(var, cell);
}
}
// 替换转义的变量
final String str = ReUtil.replaceAll(cellValue, ESCAPE_VAR_PATTERN, (matcher) -> "{" + matcher.group(1) + "}");
if (!StrUtil.equals(cellValue, str)) {
cell.setCellValue(str);
}
}
});
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.poi.excel.writer;
import org.dromara.hutool.poi.excel.ExcelUtil;
import org.junit.jupiter.api.Test;
public class TemplateContextTest {
@Test
void readTemplate() {
final ExcelWriter writer = ExcelUtil.getWriter("d:/test/template.xlsx");
final TemplateContext templateContext = new TemplateContext(writer.getSheet());
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.poi.excel.writer;
import org.dromara.hutool.poi.excel.ExcelUtil;
import org.dromara.hutool.poi.excel.SimpleClientAnchor;
import org.junit.jupiter.api.Test;
public class WriteCommentTest {
@Test
void writeCommentTest() {
final ExcelWriter writer = ExcelUtil.getWriter("d:/test/comments.xlsx");
final SimpleClientAnchor clientAnchor = new SimpleClientAnchor(0, 0, 1, 1);
ExcelDrawingUtil.drawingCellComment(writer.getOrCreateCell(5, 6), clientAnchor, "测试内容");
writer.close();
}
}