mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add SheetDataWriter and SheetTemplateWriter
This commit is contained in:
parent
e67f37fc2f
commit
c2e0da7182
@ -88,6 +88,16 @@ public class BeanMap implements Map<String, Object> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Path表达式对应的值
|
||||
*
|
||||
* @param expression Path表达式
|
||||
* @return 值
|
||||
*/
|
||||
public Object getProperty(final String expression) {
|
||||
return BeanUtil.getProperty(bean, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(final String key, final Object value) {
|
||||
final PropDesc propDesc = this.propDescMap.get(key);
|
||||
@ -99,6 +109,16 @@ public class BeanMap implements Map<String, Object> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Path表达式对应的值
|
||||
*
|
||||
* @param expression Path表达式
|
||||
* @param value 新值
|
||||
*/
|
||||
public void putProperty(final String expression, final Object value) {
|
||||
BeanUtil.setProperty(bean, expression, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(final Object key) {
|
||||
throw new UnsupportedOperationException("Can not remove field for Bean!");
|
||||
|
@ -19,6 +19,7 @@ package org.dromara.hutool.http.meta;
|
||||
import org.dromara.hutool.core.collection.CollUtil;
|
||||
import org.dromara.hutool.core.map.CaseInsensitiveMap;
|
||||
import org.dromara.hutool.core.net.url.UrlDecoder;
|
||||
import org.dromara.hutool.core.net.url.UrlEncoder;
|
||||
import org.dromara.hutool.core.regex.ReUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
||||
@ -55,6 +56,22 @@ public class HttpHeaderUtil {
|
||||
return headersIgnoreCase.get(name.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Content-Disposition头,用于下载文件<br>
|
||||
* 格式为:
|
||||
* <pre>{@code
|
||||
* attachment;filename="example.txt";filename*=UTF-8''example.txt
|
||||
* }</pre>
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @param charset 编码
|
||||
* @return Content-Disposition头
|
||||
*/
|
||||
public static String createAttachmentDisposition(final String fileName, final Charset charset) {
|
||||
final String encodeText = UrlEncoder.encodeAll(fileName, charset);
|
||||
return StrUtil.format("attachment;filename=\"{}\";filename*={}''{}", encodeText, charset.name(), encodeText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Content-Disposition头中获取文件名。<br>
|
||||
* 参考标准:https://datatracker.ietf.org/doc/html/rfc6266#section-4.1<br>
|
||||
|
@ -16,52 +16,38 @@
|
||||
|
||||
package org.dromara.hutool.http.server.servlet;
|
||||
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.bean.copier.CopyOptions;
|
||||
import org.dromara.hutool.core.bean.copier.ValueProvider;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.collection.iter.ArrayIter;
|
||||
import org.dromara.hutool.core.exception.HutoolException;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.map.CaseInsensitiveMap;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.net.NetUtil;
|
||||
import org.dromara.hutool.core.net.url.UrlEncoder;
|
||||
import org.dromara.hutool.http.multipart.MultipartFormData;
|
||||
import org.dromara.hutool.http.multipart.UploadSetting;
|
||||
import org.dromara.hutool.core.reflect.ConstructorUtil;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.array.ArrayUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
|
||||
import org.dromara.hutool.http.meta.HeaderName;
|
||||
import org.dromara.hutool.http.meta.HttpHeaderUtil;
|
||||
import org.dromara.hutool.http.meta.Method;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.dromara.hutool.http.multipart.MultipartFormData;
|
||||
import org.dromara.hutool.http.multipart.UploadSetting;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Servlet相关工具类封装
|
||||
@ -610,10 +596,8 @@ public class ServletUtil {
|
||||
*/
|
||||
public static void write(final HttpServletResponse response, final InputStream in, final String contentType, final String fileName) {
|
||||
final String charset = ObjUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.NAME_UTF_8);
|
||||
final String encodeText = UrlEncoder.encodeAll(fileName, CharsetUtil.charset(charset));
|
||||
response.setHeader("Content-Disposition",
|
||||
StrUtil.format("attachment;filename=\"{}\";filename*={}''{}", encodeText, charset, encodeText));
|
||||
response.setContentType(contentType);
|
||||
response.setHeader(HeaderName.CONTENT_DISPOSITION.getValue(), HttpHeaderUtil.createAttachmentDisposition(fileName, CharsetUtil.charset(charset)));
|
||||
write(response, in);
|
||||
}
|
||||
|
||||
|
@ -22,18 +22,13 @@ import org.apache.poi.ss.util.CellReference;
|
||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.dromara.hutool.core.data.id.IdUtil;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.lang.Assert;
|
||||
import org.dromara.hutool.core.net.url.UrlEncoder;
|
||||
import org.dromara.hutool.core.text.StrUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.poi.excel.cell.CellUtil;
|
||||
import org.dromara.hutool.poi.excel.style.StyleUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -110,16 +105,7 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
return this.workbook;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字体
|
||||
*
|
||||
* @return 字体
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public Font createFont() {
|
||||
return getWorkbook().createFont();
|
||||
}
|
||||
|
||||
// region ----- sheet ops
|
||||
/**
|
||||
* 返回工作簿表格数
|
||||
*
|
||||
@ -246,7 +232,9 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ----- cell ops
|
||||
/**
|
||||
* 获取指定坐标单元格,单元格不存在时返回{@code null}
|
||||
*
|
||||
@ -320,8 +308,9 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
public Cell getCell(final int x, final int y, final boolean isCreateIfNotExist) {
|
||||
return CellUtil.getCell(this.sheet, x, y, isCreateIfNotExist);
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
||||
// region ----- row ops
|
||||
/**
|
||||
* 获取或者创建行
|
||||
*
|
||||
@ -333,6 +322,36 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
return RowUtil.getOrCreateRow(this.sheet, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总行数,计算方法为:
|
||||
*
|
||||
* <pre>
|
||||
* 最后一行序号 + 1
|
||||
* </pre>
|
||||
*
|
||||
* @return 行数
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return this.sheet.getLastRowNum() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有记录的行数,计算方法为:
|
||||
*
|
||||
* <pre>
|
||||
* 最后一行序号 - 第一行序号 + 1
|
||||
* </pre>
|
||||
*
|
||||
* @return 行数
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public int getPhysicalRowCount() {
|
||||
return this.sheet.getPhysicalNumberOfRows();
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ----- style ops
|
||||
/**
|
||||
* 为指定单元格获取或者创建样式,返回样式后可以设置样式内容
|
||||
*
|
||||
@ -448,6 +467,18 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
return columnStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字体
|
||||
*
|
||||
* @return 字体
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public Font createFont() {
|
||||
return getWorkbook().createFont();
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ----- hyperlink ops
|
||||
/**
|
||||
* 创建 {@link Hyperlink},默认内容(标签为链接地址本身)
|
||||
*
|
||||
@ -475,34 +506,7 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
hyperlink.setLabel(label);
|
||||
return hyperlink;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总行数,计算方法为:
|
||||
*
|
||||
* <pre>
|
||||
* 最后一行序号 + 1
|
||||
* </pre>
|
||||
*
|
||||
* @return 行数
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return this.sheet.getLastRowNum() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有记录的行数,计算方法为:
|
||||
*
|
||||
* <pre>
|
||||
* 最后一行序号 - 第一行序号 + 1
|
||||
* </pre>
|
||||
*
|
||||
* @return 行数
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public int getPhysicalRowCount() {
|
||||
return this.sheet.getPhysicalNumberOfRows();
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 获取第一行总列数,计算方法为:
|
||||
@ -560,31 +564,6 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
return isXlsx() ? ExcelUtil.XLSX_CONTENT_TYPE : ExcelUtil.XLS_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Content-Disposition头对应的值,可以通过调用以下方法快速设置下载Excel的头信息:
|
||||
*
|
||||
* <pre>
|
||||
* response.setHeader("Content-Disposition", excelWriter.getDisposition("test.xlsx", CharsetUtil.CHARSET_UTF_8));
|
||||
* </pre>
|
||||
*
|
||||
* @param fileName 文件名,如果文件名没有扩展名,会自动按照生成Excel类型补齐扩展名,如果提供空,使用随机UUID
|
||||
* @param charset 编码,null则使用默认UTF-8编码
|
||||
* @return Content-Disposition值
|
||||
*/
|
||||
public String getDisposition(String fileName, Charset charset) {
|
||||
if (null == charset) {
|
||||
charset = CharsetUtil.UTF_8;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
// 未提供文件名使用随机UUID作为文件名
|
||||
fileName = IdUtil.fastSimpleUUID();
|
||||
}
|
||||
|
||||
fileName = StrUtil.addSuffixIfNot(UrlEncoder.encodeAll(fileName, charset), isXlsx() ? ".xlsx" : ".xls");
|
||||
return StrUtil.format("attachment; filename=\"{}\"", fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭工作簿<br>
|
||||
* 如果用户设定了目标文件,先写出目标文件后给关闭工作簿
|
||||
@ -596,4 +575,11 @@ public class ExcelBase<T extends ExcelBase<T, C>, C extends ExcelConfig> impleme
|
||||
this.workbook = null;
|
||||
this.isClosed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验Excel是否已经关闭
|
||||
*/
|
||||
protected void checkClosed() {
|
||||
Assert.isFalse(this.isClosed, "Excel has been closed!");
|
||||
}
|
||||
}
|
||||
|
@ -50,11 +50,7 @@ public class SheetUtil {
|
||||
|
||||
Sheet sheet;
|
||||
if (null == sheetName) {
|
||||
sheet = book.getSheetAt(0);
|
||||
if (null == sheet) {
|
||||
// 工作簿中无sheet,创建默认
|
||||
sheet = book.createSheet();
|
||||
}
|
||||
sheet = getOrCreateSheet(book, 0);
|
||||
} else {
|
||||
sheet = book.getSheet(sheetName);
|
||||
if (null == sheet) {
|
||||
|
@ -233,7 +233,7 @@ public class ExcelReader extends ExcelBase<ExcelReader, ExcelReadConfig> {
|
||||
* @since 5.3.8
|
||||
*/
|
||||
public void read(final int startRowIndex, final int endRowIndex, final SerBiConsumer<Cell, Object> cellHandler) {
|
||||
checkNotClosed();
|
||||
checkClosed();
|
||||
|
||||
final WalkSheetReader reader = new WalkSheetReader(startRowIndex, endRowIndex, cellHandler);
|
||||
reader.setExcelConfig(this.config);
|
||||
@ -315,7 +315,7 @@ public class ExcelReader extends ExcelBase<ExcelReader, ExcelReadConfig> {
|
||||
* @since 5.4.4
|
||||
*/
|
||||
public <T> T read(final SheetReader<T> sheetReader) {
|
||||
checkNotClosed();
|
||||
checkClosed();
|
||||
return Assert.notNull(sheetReader).read(this.sheet);
|
||||
}
|
||||
|
||||
@ -395,13 +395,6 @@ public class ExcelReader extends ExcelBase<ExcelReader, ExcelReadConfig> {
|
||||
return RowUtil.readRow(row, this.config.getCellEditor());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否未关闭状态
|
||||
*/
|
||||
private void checkNotClosed() {
|
||||
Assert.isFalse(this.isClosed, "ExcelReader has been closed!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Sheet,如果不存在则关闭{@link Workbook}并抛出异常,解决当sheet不存在时,文件依旧被占用问题<br>
|
||||
* 见:Issue#I8ZIQC
|
||||
|
@ -16,21 +16,16 @@
|
||||
|
||||
package org.dromara.hutool.poi.excel.writer;
|
||||
|
||||
import org.apache.poi.common.usermodel.Hyperlink;
|
||||
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.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
import org.dromara.hutool.core.io.IORuntimeException;
|
||||
import org.dromara.hutool.core.io.IoUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
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.text.StrUtil;
|
||||
import org.dromara.hutool.poi.POIException;
|
||||
import org.dromara.hutool.poi.excel.*;
|
||||
import org.dromara.hutool.poi.excel.cell.CellRangeUtil;
|
||||
import org.dromara.hutool.poi.excel.cell.CellUtil;
|
||||
@ -41,8 +36,10 @@ import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Excel 写入器<br>
|
||||
@ -62,18 +59,8 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* 样式集,定义不同类型数据样式
|
||||
*/
|
||||
private StyleSet styleSet;
|
||||
/**
|
||||
* 标题项对应列号缓存,每次写标题更新此缓存
|
||||
*/
|
||||
private Map<String, Integer> headLocationCache;
|
||||
/**
|
||||
* 当前行,用于标记初始可写数据的行和部分写完后当前的行
|
||||
*/
|
||||
private final AtomicInteger currentRow;
|
||||
/**
|
||||
* 模板上下文,存储模板中变量及其位置信息
|
||||
*/
|
||||
private TemplateContext templateContext;
|
||||
private SheetDataWriter sheetDataWriter;
|
||||
private SheetTemplateWriter sheetTemplateWriter;
|
||||
|
||||
// region ----- Constructors
|
||||
|
||||
@ -155,7 +142,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
} else {
|
||||
// 如果是已经存在的文件,则作为模板加载,此时不能写出到模板文件
|
||||
// 初始化模板
|
||||
this.templateContext = new TemplateContext(this.sheet);
|
||||
this.sheetTemplateWriter = new SheetTemplateWriter(this.sheet, this.config);
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +169,6 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
public ExcelWriter(final Sheet sheet) {
|
||||
super(new ExcelWriteConfig(), sheet);
|
||||
this.styleSet = new DefaultStyleSet(workbook);
|
||||
this.currentRow = new AtomicInteger(0);
|
||||
}
|
||||
// endregion
|
||||
|
||||
@ -192,20 +178,6 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
return super.setConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExcelWriter setSheet(final int sheetIndex) {
|
||||
super.setSheet(sheetIndex);
|
||||
// 切换到新sheet需要重置开始行
|
||||
return reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExcelWriter setSheet(final String sheetName) {
|
||||
super.setSheet(sheetName);
|
||||
// 切换到新sheet需要重置开始行
|
||||
return reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置Writer,包括:
|
||||
*
|
||||
@ -217,8 +189,47 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter reset() {
|
||||
this.headLocationCache.clear();
|
||||
return resetRow();
|
||||
this.sheetDataWriter = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭工作簿<br>
|
||||
* 如果用户设定了目标文件,先写出目标文件后给关闭工作簿
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
public void close() {
|
||||
if (null != this.targetFile) {
|
||||
flush();
|
||||
}
|
||||
closeWithoutFlush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭工作簿但是不写出
|
||||
*/
|
||||
protected void closeWithoutFlush() {
|
||||
super.close();
|
||||
this.reset();
|
||||
|
||||
// 清空样式
|
||||
this.styleSet = null;
|
||||
}
|
||||
|
||||
// region ----- sheet ops
|
||||
@Override
|
||||
public ExcelWriter setSheet(final int sheetIndex) {
|
||||
super.setSheet(sheetIndex);
|
||||
// 切换到新sheet需要重置开始行
|
||||
return reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExcelWriter setSheet(final String sheetName) {
|
||||
super.setSheet(sheetName);
|
||||
// 切换到新sheet需要重置开始行
|
||||
return reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,6 +255,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
this.workbook.setSheetName(sheet, sheetName);
|
||||
return this;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 设置所有列为自动宽度,不考虑合并单元格<br>
|
||||
@ -303,6 +315,9 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
*/
|
||||
public ExcelWriter setStyleSet(final StyleSet styleSet) {
|
||||
this.styleSet = styleSet;
|
||||
if (null != this.sheetDataWriter) {
|
||||
this.sheetDataWriter.setStyleSet(styleSet);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -329,7 +344,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return 当前行
|
||||
*/
|
||||
public int getCurrentRow() {
|
||||
return this.currentRow.get();
|
||||
return null == this.sheetDataWriter ? 0 : this.sheetDataWriter.getCurrentRow();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,7 +354,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter setCurrentRow(final int rowIndex) {
|
||||
this.currentRow.set(rowIndex);
|
||||
getSheetDataWriter().setCurrentRow(rowIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -359,18 +374,18 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter passCurrentRow() {
|
||||
this.currentRow.incrementAndGet();
|
||||
getSheetDataWriter().passAndGet();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过指定行数
|
||||
*
|
||||
* @param rows 跳过的行数
|
||||
* @param rowNum 跳过的行数
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter passRows(final int rows) {
|
||||
this.currentRow.addAndGet(rows);
|
||||
public ExcelWriter passRows(final int rowNum) {
|
||||
getSheetDataWriter().passRowsAndGet(rowNum);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -380,7 +395,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter resetRow() {
|
||||
this.currentRow.set(0);
|
||||
getSheetDataWriter().resetRow();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -584,14 +599,14 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public ExcelWriter merge(final int lastColumn, final Object content, final boolean isSetHeaderStyle) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
checkClosed();
|
||||
|
||||
final int rowIndex = this.currentRow.get();
|
||||
final int rowIndex = getCurrentRow();
|
||||
merge(CellRangeUtil.ofSingleRow(rowIndex, lastColumn), content, isSetHeaderStyle);
|
||||
|
||||
// 设置内容后跳到下一行
|
||||
if (null != content) {
|
||||
this.currentRow.incrementAndGet();
|
||||
this.passCurrentRow();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -606,7 +621,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @since 4.0.10
|
||||
*/
|
||||
public ExcelWriter merge(final CellRangeAddress cellRangeAddress, final Object content, final boolean isSetHeaderStyle) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
checkClosed();
|
||||
|
||||
CellStyle style = null;
|
||||
if (null != this.styleSet) {
|
||||
@ -627,7 +642,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @since 5.6.5
|
||||
*/
|
||||
public ExcelWriter merge(final CellRangeAddress cellRangeAddress, final Object content, final CellStyle cellStyle) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
checkClosed();
|
||||
|
||||
CellUtil.mergingCells(this.getSheet(), cellRangeAddress, cellStyle);
|
||||
|
||||
@ -684,7 +699,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public ExcelWriter write(final Iterable<?> data, final boolean isWriteKeyAsHead) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
checkClosed();
|
||||
boolean isFirst = true;
|
||||
for (final Object object : data) {
|
||||
writeRow(object, isFirst && isWriteKeyAsHead);
|
||||
@ -712,7 +727,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked", "resource"})
|
||||
public ExcelWriter write(final Iterable<?> data, final Comparator<String> comparator) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
checkClosed();
|
||||
boolean isFirstRow = true;
|
||||
Map<?, ?> map;
|
||||
for (final Object obj : data) {
|
||||
@ -845,21 +860,8 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter writeHeadRow(final Iterable<?> rowData) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
this.headLocationCache = new SafeConcurrentHashMap<>();
|
||||
|
||||
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 = CellUtil.getOrCreateCell(row, i);
|
||||
CellUtil.setCellValue(cell, value, this.styleSet, true, cellEditor);
|
||||
this.headLocationCache.put(StrUtil.toString(value), i);
|
||||
i++;
|
||||
}
|
||||
checkClosed();
|
||||
getSheetDataWriter().writeHeadRow(rowData);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -877,12 +879,15 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public ExcelWriter writeSecHeadRow(final Iterable<?> rowData) {
|
||||
final Row row = RowUtil.getOrCreateRow(this.sheet, this.currentRow.getAndIncrement());
|
||||
checkClosed();
|
||||
final Row row = getOrCreateRow(getCurrentRow());
|
||||
passCurrentRow();
|
||||
|
||||
final Iterator<?> iterator = rowData.iterator();
|
||||
//如果获取的row存在单元格,则执行复杂表头逻辑,否则直接调用writeHeadRow(Iterable<?> rowData)
|
||||
if (row.getLastCellNum() != 0) {
|
||||
final CellEditor cellEditor = this.config.getCellEditor();
|
||||
for (int i = 0; i < this.workbook.getSpreadsheetVersion().getMaxColumns(); i++) {
|
||||
for (int i = 0; ; i++) {
|
||||
Cell cell = row.getCell(i);
|
||||
if (cell != null) {
|
||||
continue;
|
||||
@ -916,46 +921,29 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @see #writeRow(Map, boolean)
|
||||
* @since 4.1.5
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public ExcelWriter writeRow(final Object rowBean, final boolean isWriteKeyAsHead) {
|
||||
final ExcelWriteConfig config = this.config;
|
||||
checkClosed();
|
||||
|
||||
final Map rowMap;
|
||||
if (rowBean instanceof Map) {
|
||||
if (MapUtil.isNotEmpty(config.getHeaderAlias())) {
|
||||
rowMap = MapUtil.newTreeMap((Map) rowBean, config.getCachedAliasComparator());
|
||||
} else {
|
||||
rowMap = (Map) rowBean;
|
||||
}
|
||||
} else if (rowBean instanceof Iterable) {
|
||||
// issue#2398@Github
|
||||
// MapWrapper由于实现了Iterable接口,应该优先按照Map处理
|
||||
return writeRow((Iterable<?>) rowBean);
|
||||
} else if (rowBean instanceof Hyperlink) {
|
||||
// Hyperlink当成一个值
|
||||
return writeRow(ListUtil.of(rowBean), isWriteKeyAsHead);
|
||||
} else if (BeanUtil.isReadableBean(rowBean.getClass())) {
|
||||
if (MapUtil.isEmpty(config.getHeaderAlias())) {
|
||||
rowMap = BeanUtil.beanToMap(rowBean, new LinkedHashMap<>(), false, false);
|
||||
} else {
|
||||
// 别名存在情况下按照别名的添加顺序排序Bean数据
|
||||
rowMap = BeanUtil.beanToMap(rowBean, new TreeMap<>(config.getCachedAliasComparator()), false, false);
|
||||
}
|
||||
} else {
|
||||
// 其它转为字符串默认输出
|
||||
return writeRow(ListUtil.of(rowBean), isWriteKeyAsHead);
|
||||
// 模板写出
|
||||
if (null != this.sheetTemplateWriter) {
|
||||
this.sheetTemplateWriter.fillRow(rowBean);
|
||||
return this;
|
||||
}
|
||||
return writeRow(rowMap, isWriteKeyAsHead);
|
||||
|
||||
getSheetDataWriter().writeRow(rowBean, isWriteKeyAsHead);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充非列表模板变量(一次性变量)
|
||||
*
|
||||
* @param rowMap 行数据
|
||||
* @param rowMap 行数据
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter fillOnce(final Map<?, ?> rowMap) {
|
||||
rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, false));
|
||||
checkClosed();
|
||||
Assert.notNull(this.sheetTemplateWriter, () -> new POIException("No template for this writer!"));
|
||||
this.sheetTemplateWriter.fillOnce(rowMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -967,51 +955,16 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @param isWriteKeyAsHead 为true写出两行,Map的keys做为一行,values做为第二行,否则只写出一行values
|
||||
* @return this
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public ExcelWriter writeRow(final Map<?, ?> rowMap, final boolean isWriteKeyAsHead) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
if (MapUtil.isEmpty(rowMap)) {
|
||||
// 如果写出数据为null或空,跳过当前行
|
||||
return passCurrentRow();
|
||||
}
|
||||
checkClosed();
|
||||
|
||||
// 模板写出
|
||||
if (null != this.templateContext) {
|
||||
fillRow(rowMap, this.config.insertRow);
|
||||
if (null != this.sheetTemplateWriter) {
|
||||
this.sheetTemplateWriter.fillRow(rowMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
final Table<?, ?, ?> aliasTable = this.config.aliasTable(rowMap);
|
||||
if (isWriteKeyAsHead) {
|
||||
// 写出标题行,并记录标题别名和列号的关系
|
||||
writeHeadRow(aliasTable.columnKeys());
|
||||
// 记录原数据key和别名对应列号
|
||||
int i = 0;
|
||||
for (final Object key : aliasTable.rowKeySet()) {
|
||||
this.headLocationCache.putIfAbsent(StrUtil.toString(key), i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已经写出标题行,根据标题行找对应的值写入
|
||||
if (MapUtil.isNotEmpty(this.headLocationCache)) {
|
||||
final Row row = RowUtil.getOrCreateRow(this.sheet, this.currentRow.getAndIncrement());
|
||||
final CellEditor cellEditor = this.config.getCellEditor();
|
||||
Integer location;
|
||||
for (final Table.Cell<?, ?, ?> cell : aliasTable) {
|
||||
// 首先查找原名对应的列号
|
||||
location = this.headLocationCache.get(StrUtil.toString(cell.getRowKey()));
|
||||
if (null == location) {
|
||||
// 未找到,则查找别名对应的列号
|
||||
location = this.headLocationCache.get(StrUtil.toString(cell.getColumnKey()));
|
||||
}
|
||||
if (null != location) {
|
||||
CellUtil.setCellValue(CellUtil.getOrCreateCell(row, location), cell.getValue(), this.styleSet, false, cellEditor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeRow(aliasTable.values());
|
||||
}
|
||||
getSheetDataWriter().writeRow(rowMap, isWriteKeyAsHead);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1024,11 +977,8 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @return this
|
||||
*/
|
||||
public ExcelWriter writeRow(final Iterable<?> rowData) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
|
||||
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());
|
||||
checkClosed();
|
||||
getSheetDataWriter().writeRow(rowData);
|
||||
return this;
|
||||
}
|
||||
// endregion
|
||||
@ -1098,8 +1048,8 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
public ExcelWriter writeCol(final Object headerVal, final int colIndex, final Iterable<?> colData, final boolean isResetRowIndex) {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
int currentRowIndex = currentRow.get();
|
||||
checkClosed();
|
||||
int currentRowIndex = getCurrentRow();
|
||||
if (null != headerVal) {
|
||||
writeCellValue(colIndex, currentRowIndex, headerVal, true);
|
||||
currentRowIndex++;
|
||||
@ -1109,7 +1059,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
currentRowIndex++;
|
||||
}
|
||||
if (!isResetRowIndex) {
|
||||
currentRow.set(currentRowIndex);
|
||||
setCurrentRow(currentRowIndex);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -1344,7 +1294,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
* @since 4.4.1
|
||||
*/
|
||||
public ExcelWriter flush(final OutputStream out, final boolean isCloseOut) throws IORuntimeException {
|
||||
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
|
||||
checkClosed();
|
||||
|
||||
try {
|
||||
this.workbook.write(out);
|
||||
@ -1361,53 +1311,14 @@ public class ExcelWriter extends ExcelBase<ExcelWriter, ExcelWriteConfig> {
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 关闭工作簿<br>
|
||||
* 如果用户设定了目标文件,先写出目标文件后给关闭工作簿
|
||||
*/
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
public void close() {
|
||||
if (null != this.targetFile) {
|
||||
flush();
|
||||
}
|
||||
closeWithoutFlush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭工作簿但是不写出
|
||||
*/
|
||||
protected void closeWithoutFlush() {
|
||||
super.close();
|
||||
this.currentRow.set(0);
|
||||
|
||||
// 清空对象
|
||||
this.styleSet = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充模板行,用于列表填充
|
||||
* 获取SheetDataWriter,没有则创建
|
||||
*
|
||||
* @param rowMap 行数据
|
||||
* @param insertRow 是否插入行,如果为{@code true},则已有行下移,否则利用已有行
|
||||
* @return SheetDataWriter
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
private SheetDataWriter getSheetDataWriter() {
|
||||
if (null == this.sheetDataWriter) {
|
||||
this.sheetDataWriter = new SheetDataWriter(this.sheet, this.config, this.styleSet);
|
||||
}
|
||||
|
||||
rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, true));
|
||||
return this.sheetDataWriter;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dromara.hutool.poi.excel.writer;
|
||||
|
||||
import org.apache.poi.common.usermodel.Hyperlink;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.dromara.hutool.core.bean.BeanUtil;
|
||||
import org.dromara.hutool.core.collection.ListUtil;
|
||||
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.text.StrUtil;
|
||||
import org.dromara.hutool.poi.excel.RowUtil;
|
||||
import org.dromara.hutool.poi.excel.cell.CellUtil;
|
||||
import org.dromara.hutool.poi.excel.cell.editors.CellEditor;
|
||||
import org.dromara.hutool.poi.excel.style.StyleSet;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Sheet数据写出器<br>
|
||||
* 此对象只封装将数据写出到Sheet中,并不刷新到文件
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class SheetDataWriter {
|
||||
|
||||
private final Sheet sheet;
|
||||
private final ExcelWriteConfig config;
|
||||
private StyleSet styleSet;
|
||||
|
||||
/**
|
||||
* 标题项对应列号缓存,每次写标题更新此缓存<br>
|
||||
* 此缓存用于用户多次write时,寻找标题位置
|
||||
*/
|
||||
private Map<String, Integer> headLocationCache;
|
||||
/**
|
||||
* 当前行,用于标记初始可写数据的行和部分写完后当前的行
|
||||
*/
|
||||
private final AtomicInteger currentRow;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param sheet {@link Sheet}
|
||||
* @param config Excel配置
|
||||
* @param styleSet 样式表
|
||||
*/
|
||||
public SheetDataWriter(final Sheet sheet, final ExcelWriteConfig config, final StyleSet styleSet) {
|
||||
this.sheet = sheet;
|
||||
this.config = config;
|
||||
this.styleSet = styleSet;
|
||||
this.currentRow = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置样式表
|
||||
* @param styleSet 样式表
|
||||
* @return this
|
||||
*/
|
||||
public SheetDataWriter setStyleSet(final StyleSet styleSet) {
|
||||
this.styleSet = styleSet;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出一行,根据rowBean数据类型不同,写出情况如下:
|
||||
*
|
||||
* <pre>
|
||||
* 1、如果为Iterable,直接写出一行
|
||||
* 2、如果为Map,isWriteKeyAsHead为true写出两行,Map的keys做为一行,values做为第二行,否则只写出一行values
|
||||
* 3、如果为Bean,转为Map写出,isWriteKeyAsHead为true写出两行,Map的keys做为一行,values做为第二行,否则只写出一行values
|
||||
* </pre>
|
||||
*
|
||||
* @param rowBean 写出的Bean
|
||||
* @param isWriteKeyAsHead 为true写出两行,Map的keys做为一行,values做为第二行,否则只写出一行values
|
||||
* @return this
|
||||
* @see #writeRow(Iterable)
|
||||
* @see #writeRow(Map, boolean)
|
||||
* @since 4.1.5
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public SheetDataWriter writeRow(final Object rowBean, final boolean isWriteKeyAsHead) {
|
||||
final ExcelWriteConfig config = this.config;
|
||||
|
||||
final Map rowMap;
|
||||
if (rowBean instanceof Map) {
|
||||
if (MapUtil.isNotEmpty(config.getHeaderAlias())) {
|
||||
rowMap = MapUtil.newTreeMap((Map) rowBean, config.getCachedAliasComparator());
|
||||
} else {
|
||||
rowMap = (Map) rowBean;
|
||||
}
|
||||
} else if (rowBean instanceof Iterable) {
|
||||
// issue#2398@Github
|
||||
// MapWrapper由于实现了Iterable接口,应该优先按照Map处理
|
||||
return writeRow((Iterable<?>) rowBean);
|
||||
} else if (rowBean instanceof Hyperlink) {
|
||||
// Hyperlink当成一个值
|
||||
return writeRow(ListUtil.of(rowBean), isWriteKeyAsHead);
|
||||
} else if (BeanUtil.isReadableBean(rowBean.getClass())) {
|
||||
if (MapUtil.isEmpty(config.getHeaderAlias())) {
|
||||
rowMap = BeanUtil.beanToMap(rowBean, new LinkedHashMap<>(), false, false);
|
||||
} else {
|
||||
// 别名存在情况下按照别名的添加顺序排序Bean数据
|
||||
rowMap = BeanUtil.beanToMap(rowBean, new TreeMap<>(config.getCachedAliasComparator()), false, false);
|
||||
}
|
||||
} else {
|
||||
// 其它转为字符串默认输出
|
||||
return writeRow(ListUtil.of(rowBean), isWriteKeyAsHead);
|
||||
}
|
||||
return writeRow(rowMap, isWriteKeyAsHead);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个Map写入到Excel,isWriteKeyAsHead为true写出两行,Map的keys做为一行,values做为第二行,否则只写出一行values<br>
|
||||
* 如果rowMap为空(包括null),则写出空行
|
||||
*
|
||||
* @param rowMap 写出的Map,为空(包括null),则写出空行
|
||||
* @param isWriteKeyAsHead 为true写出两行,Map的keys做为一行,values做为第二行,否则只写出一行values
|
||||
* @return this
|
||||
*/
|
||||
public SheetDataWriter writeRow(final Map<?, ?> rowMap, final boolean isWriteKeyAsHead) {
|
||||
if (MapUtil.isEmpty(rowMap)) {
|
||||
// 如果写出数据为null或空,跳过当前行
|
||||
passAndGet();
|
||||
return this;
|
||||
}
|
||||
|
||||
final Table<?, ?, ?> aliasTable = this.config.aliasTable(rowMap);
|
||||
if (isWriteKeyAsHead) {
|
||||
// 写出标题行,并记录标题别名和列号的关系
|
||||
writeHeadRow(aliasTable.columnKeys());
|
||||
// 记录原数据key和别名对应列号
|
||||
int i = 0;
|
||||
for (final Object key : aliasTable.rowKeySet()) {
|
||||
this.headLocationCache.putIfAbsent(StrUtil.toString(key), i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已经写出标题行,根据标题行找对应的值写入
|
||||
if (MapUtil.isNotEmpty(this.headLocationCache)) {
|
||||
final Row row = RowUtil.getOrCreateRow(this.sheet, this.currentRow.getAndIncrement());
|
||||
final CellEditor cellEditor = this.config.getCellEditor();
|
||||
Integer columnIndex;
|
||||
for (final Table.Cell<?, ?, ?> cell : aliasTable) {
|
||||
columnIndex = getColumnIndex(cell);
|
||||
if (null != columnIndex) {
|
||||
CellUtil.setCellValue(CellUtil.getOrCreateCell(row, columnIndex), cell.getValue(), this.styleSet, false, cellEditor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeRow(aliasTable.values());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出一行标题数据<br>
|
||||
* 本方法只是将数据写入Workbook中的Sheet,并不写出到文件<br>
|
||||
* 写出的起始行为当前行号,可使用{@link #getCurrentRow()}方法调用,根据写出的的行数,当前行号自动+1
|
||||
*
|
||||
* @param rowData 一行的数据
|
||||
* @return this
|
||||
*/
|
||||
public SheetDataWriter writeHeadRow(final Iterable<?> rowData) {
|
||||
this.headLocationCache = new SafeConcurrentHashMap<>();
|
||||
|
||||
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 = CellUtil.getOrCreateCell(row, i);
|
||||
CellUtil.setCellValue(cell, value, this.styleSet, true, cellEditor);
|
||||
this.headLocationCache.put(StrUtil.toString(value), i);
|
||||
i++;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出一行数据<br>
|
||||
* 本方法只是将数据写入Workbook中的Sheet,并不写出到文件<br>
|
||||
* 写出的起始行为当前行号,可使用{@link #getCurrentRow()}方法调用,根据写出的的行数,当前行号自动+1
|
||||
*
|
||||
* @param rowData 一行的数据
|
||||
* @return this
|
||||
*/
|
||||
public SheetDataWriter writeRow(final Iterable<?> rowData) {
|
||||
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;
|
||||
}
|
||||
|
||||
// region ----- currentRow ops
|
||||
/**
|
||||
* 获得当前行
|
||||
*
|
||||
* @return 当前行
|
||||
*/
|
||||
public int getCurrentRow() {
|
||||
return this.currentRow.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前所在行
|
||||
*
|
||||
* @param rowIndex 行号
|
||||
* @return this
|
||||
*/
|
||||
public SheetDataWriter setCurrentRow(final int rowIndex) {
|
||||
this.currentRow.set(rowIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过当前行,并获取下一行的行号
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public int passAndGet() {
|
||||
return this.currentRow.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过指定行数,并获取当前行号
|
||||
*
|
||||
* @param rowNum 跳过的行数
|
||||
* @return this
|
||||
*/
|
||||
public int passRowsAndGet(final int rowNum) {
|
||||
return this.currentRow.addAndGet(rowNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置当前行为0
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public SheetDataWriter resetRow() {
|
||||
this.currentRow.set(0);
|
||||
return this;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 查找标题或标题别名对应的列号
|
||||
*
|
||||
* @param cell 别名表,rowKey:原名,columnKey:别名
|
||||
* @return 列号,如果未找到返回null
|
||||
*/
|
||||
private Integer getColumnIndex(final Table.Cell<?, ?, ?> cell) {
|
||||
// 首先查找原名对应的列号
|
||||
Integer location = this.headLocationCache.get(StrUtil.toString(cell.getRowKey()));
|
||||
if (null == location) {
|
||||
// 未找到,则查找别名对应的列号
|
||||
location = this.headLocationCache.get(StrUtil.toString(cell.getColumnKey()));
|
||||
}
|
||||
return location;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Hutool Team and hutool.cn
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 模板Excel写入器<br>
|
||||
* 解析已有模板,并填充模板中的变量为数据
|
||||
*
|
||||
* @author Looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class SheetTemplateWriter {
|
||||
|
||||
private final Sheet sheet;
|
||||
private final ExcelWriteConfig config;
|
||||
/**
|
||||
* 模板上下文,存储模板中变量及其位置信息
|
||||
*/
|
||||
private final TemplateContext templateContext;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param sheet {@link Sheet}
|
||||
* @param config Excel写配置
|
||||
*/
|
||||
public SheetTemplateWriter(final Sheet sheet, final ExcelWriteConfig config) {
|
||||
this.sheet = sheet;
|
||||
this.config = config;
|
||||
this.templateContext = new TemplateContext(sheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充非列表模板变量(一次性变量)
|
||||
*
|
||||
* @param rowMap 行数据
|
||||
* @return this
|
||||
*/
|
||||
public SheetTemplateWriter fillOnce(final Map<?, ?> rowMap) {
|
||||
rowMap.forEach((key, value) -> this.templateContext.fill(StrUtil.toStringOrNull(key), rowMap, false));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充模板行,用于列表填充
|
||||
*
|
||||
* @param rowBean 行的Bean数据
|
||||
* @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);
|
||||
if (bottomRowIndex < 0) {
|
||||
// 无可填充行
|
||||
return this;
|
||||
}
|
||||
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));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
@ -23,14 +23,14 @@ import org.dromara.hutool.core.date.DateUtil;
|
||||
import org.dromara.hutool.core.io.file.FileUtil;
|
||||
import org.dromara.hutool.core.lang.Console;
|
||||
import org.dromara.hutool.core.map.MapUtil;
|
||||
import org.dromara.hutool.core.util.CharsetUtil;
|
||||
import org.dromara.hutool.core.util.ObjUtil;
|
||||
import org.dromara.hutool.poi.excel.*;
|
||||
import org.dromara.hutool.poi.excel.ExcelUtil;
|
||||
import org.dromara.hutool.poi.excel.OrderExcel;
|
||||
import org.dromara.hutool.poi.excel.TestBean;
|
||||
import org.dromara.hutool.poi.excel.cell.setters.EscapeStrCellSetter;
|
||||
import org.dromara.hutool.poi.excel.reader.ExcelReader;
|
||||
import org.dromara.hutool.poi.excel.style.DefaultStyleSet;
|
||||
import org.dromara.hutool.poi.excel.style.StyleUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -873,13 +873,6 @@ public class ExcelWriteTest {
|
||||
writer.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDispositionTest() {
|
||||
final ExcelWriter writer = ExcelUtil.getWriter(true);
|
||||
final String disposition = writer.getDisposition("测试A12.xlsx", CharsetUtil.UTF_8);
|
||||
Assertions.assertEquals("attachment; filename=\"%E6%B5%8B%E8%AF%95A12.xlsx\"", disposition);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void autoSizeColumnTest() {
|
||||
|
@ -1,6 +1,5 @@
|
||||
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;
|
||||
@ -29,7 +28,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();
|
||||
}
|
||||
|
||||
@ -51,7 +50,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();
|
||||
}
|
||||
|
||||
@ -73,7 +72,7 @@ public class TemplateWriterTest {
|
||||
writer.writeRow(createRow(), false);
|
||||
}
|
||||
|
||||
writer.flush(FileUtil.file(targetDir + "templateWithFooterResult.xlsx"), true);
|
||||
//writer.flush(FileUtil.file(targetDir + "templateWithFooterNoneOneLineResult.xlsx"), true);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user