From 50134d88220c53c4d60fbe736b0228965fd9a048 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 26 Sep 2020 02:22:34 +0800 Subject: [PATCH] add reader --- CHANGELOG.md | 1 + .../java/cn/hutool/cron/demo/CronTest.java | 24 ++- .../hutool/poi/excel/ExcelExtractorUtil.java | 45 +++++ .../java/cn/hutool/poi/excel/ExcelReader.java | 165 +++++------------- .../poi/excel/reader/AbstractSheetReader.java | 140 +++++++++++++++ .../poi/excel/reader/BeanSheetReader.java | 87 +++++++++ .../poi/excel/reader/ListSheetReader.java | 50 ++++++ .../poi/excel/reader/MapSheetReader.java | 63 +++++++ .../hutool/poi/excel/reader/SheetReader.java | 20 +++ .../hutool/poi/excel/reader/package-info.java | 7 + .../excel/sax/handler/AbstractRowHandler.java | 59 +++++++ .../poi/excel/sax/handler/BeanRowHandler.java | 51 ++++++ .../poi/excel/sax/handler/MapRowHandler.java | 49 ++++++ 13 files changed, 639 insertions(+), 122 deletions(-) create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelExtractorUtil.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/AbstractSheetReader.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/BeanSheetReader.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ListSheetReader.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/MapSheetReader.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/SheetReader.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/reader/package-info.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3899a6d9b..f8f831059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * 【http 】 完善StrUtil的注释(pr#186@Gitee) * 【aop 】 去除调试日志(issue#1116@Github) * 【core 】 增加'反转义(pr#1121@Github) +* 【poi 】 增加SheetReader和XXXRowHandler(issue#I1WHJP@Gitee) ### Bug修复 * 【crypto 】 修复SM2验签后无法解密问题(issue#I1W0VP@Gitee) diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java index 2f166c2c9..569e54cd4 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java @@ -1,12 +1,13 @@ package cn.hutool.cron.demo; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.lang.Console; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.cron.CronUtil; +import cn.hutool.cron.TaskExecutor; +import cn.hutool.cron.listener.TaskListener; import cn.hutool.cron.task.Task; +import org.junit.Ignore; +import org.junit.Test; /** * 定时任务样例 @@ -38,6 +39,23 @@ public class CronTest { @Test @Ignore public void cronTest2() { + CronUtil.getScheduler().addListener(new TaskListener() { + @Override + public void onStart(TaskExecutor executor) { + Console.log("Listen task start!"); + } + + @Override + public void onSucceeded(TaskExecutor executor) { + + } + + @Override + public void onFailed(TaskExecutor executor, Throwable exception) { + + } + }); + // 支持秒级别定时任务 CronUtil.setMatchSecond(true); CronUtil.start(); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelExtractorUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelExtractorUtil.java new file mode 100644 index 000000000..db7022dff --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelExtractorUtil.java @@ -0,0 +1,45 @@ +package cn.hutool.poi.excel; + +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.extractor.ExcelExtractor; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.extractor.XSSFExcelExtractor; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +/** + * {@link ExcelExtractor}工具封装 + * + * @author looly + * @since 5.4.4 + */ +public class ExcelExtractorUtil { + /** + * 获取 {@link ExcelExtractor} 对象 + * + * @return {@link ExcelExtractor} + */ + public static ExcelExtractor getExtractor(Workbook wb) { + ExcelExtractor extractor; + if (wb instanceof HSSFWorkbook) { + extractor = new org.apache.poi.hssf.extractor.ExcelExtractor((HSSFWorkbook) wb); + } else { + extractor = new XSSFExcelExtractor((XSSFWorkbook) wb); + } + return extractor; + } + + /** + * 读取为文本格式
+ * 使用{@link ExcelExtractor} 提取Excel内容 + * + * @param wb {@link Workbook} + * @param withSheetName 是否附带sheet名 + * @return Excel文本 + * @since 4.1.0 + */ + public static String readAsText(Workbook wb, boolean withSheetName) { + final ExcelExtractor extractor = getExtractor(wb); + extractor.setIncludeSheetNames(withSheetName); + return extractor.getText(); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 308de73b7..a8dd2c4e6 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -1,28 +1,22 @@ package cn.hutool.poi.excel; -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.CellEditor; import cn.hutool.poi.excel.cell.CellHandler; import cn.hutool.poi.excel.cell.CellUtil; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import cn.hutool.poi.excel.reader.BeanSheetReader; +import cn.hutool.poi.excel.reader.ListSheetReader; +import cn.hutool.poi.excel.reader.MapSheetReader; +import cn.hutool.poi.excel.reader.SheetReader; import org.apache.poi.ss.extractor.ExcelExtractor; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.extractor.XSSFExcelExtractor; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -256,37 +250,31 @@ public class ExcelReader extends ExcelBase { } /** - * 读取工作簿中指定的Sheet + * 读取工作簿中指定的Sheet,此方法会把第一行作为标题行,替换标题别名 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) * @return 行的集合,一行使用List表示 */ - @SuppressWarnings({"rawtypes", "unchecked"}) public List> read(int startRowIndex, int endRowIndex) { - checkNotClosed(); - List> resultList = new ArrayList<>(); + return read(startRowIndex, endRowIndex, true); + } - startRowIndex = Math.max(startRowIndex, this.sheet.getFirstRowNum());// 读取起始行(包含) - endRowIndex = Math.min(endRowIndex, this.sheet.getLastRowNum());// 读取结束行(包含) - boolean isFirstLine = true; - List rowList; - for (int i = startRowIndex; i <= endRowIndex; i++) { - rowList = readRow(i); - if (CollUtil.isNotEmpty(rowList) || false == ignoreEmptyRow) { - if (null == rowList) { - rowList = new ArrayList<>(0); - } - if (isFirstLine) { - isFirstLine = false; - if (MapUtil.isNotEmpty(this.headerAlias)) { - rowList = aliasHeader(rowList); - } - } - resultList.add(rowList); - } - } - return resultList; + /** + * 读取工作簿中指定的Sheet + * + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + * @param aliasFirstLine 是否首行作为标题行转换别名 + * @return 行的集合,一行使用List表示 + * @since 5.4.4 + */ + public List> read(int startRowIndex, int endRowIndex, boolean aliasFirstLine) { + final ListSheetReader reader = new ListSheetReader(startRowIndex, endRowIndex, aliasFirstLine); + reader.setCellEditor(this.cellEditor); + reader.setIgnoreEmptyRow(this.ignoreEmptyRow); + reader.setHeaderAlias(headerAlias); + return read(reader); } /** @@ -348,33 +336,11 @@ public class ExcelReader extends ExcelBase { * @return Map的列表 */ public List> read(int headerRowIndex, int startRowIndex, int endRowIndex) { - checkNotClosed(); - // 边界判断 - final int firstRowNum = sheet.getFirstRowNum(); - final int lastRowNum = sheet.getLastRowNum(); - if (headerRowIndex < firstRowNum) { - throw new IndexOutOfBoundsException(StrUtil.format("Header row index {} is lower than first row index {}.", headerRowIndex, firstRowNum)); - } else if (headerRowIndex > lastRowNum) { - throw new IndexOutOfBoundsException(StrUtil.format("Header row index {} is greater than last row index {}.", headerRowIndex, firstRowNum)); - } - startRowIndex = Math.max(startRowIndex, firstRowNum);// 读取起始行(包含) - endRowIndex = Math.min(endRowIndex, lastRowNum);// 读取结束行(包含) - - // 读取header - List headerList = readRow(sheet.getRow(headerRowIndex)); - - final List> result = new ArrayList<>(endRowIndex - startRowIndex + 1); - List rowList; - for (int i = startRowIndex; i <= endRowIndex; i++) { - if (i != headerRowIndex) { - // 跳过标题行 - rowList = readRow(sheet.getRow(i)); - if (CollUtil.isNotEmpty(rowList) || false == ignoreEmptyRow) { - result.add(IterUtil.toMap(aliasHeader(headerList), rowList, true)); - } - } - } - return result; + final MapSheetReader reader = new MapSheetReader(headerRowIndex, startRowIndex, endRowIndex); + reader.setCellEditor(this.cellEditor); + reader.setIgnoreEmptyRow(this.ignoreEmptyRow); + reader.setHeaderAlias(headerAlias); + return read(reader); } /** @@ -412,19 +378,25 @@ public class ExcelReader extends ExcelBase { * @param beanType 每行对应Bean的类型 * @return Map的列表 */ - @SuppressWarnings("unchecked") public List read(int headerRowIndex, int startRowIndex, int endRowIndex, Class beanType) { - checkNotClosed(); - final List> mapList = read(headerRowIndex, startRowIndex, endRowIndex); - if (Map.class.isAssignableFrom(beanType)) { - return (List) mapList; - } + final BeanSheetReader reader = new BeanSheetReader<>(headerRowIndex, startRowIndex, endRowIndex, beanType); + reader.setCellEditor(this.cellEditor); + reader.setIgnoreEmptyRow(this.ignoreEmptyRow); + reader.setHeaderAlias(headerAlias); + return read(reader); + } - final List beanList = new ArrayList<>(mapList.size()); - for (Map map : mapList) { - beanList.add(BeanUtil.toBean(map, beanType)); - } - return beanList; + /** + * 读取数据为指定类型 + * + * @param 读取数据类型 + * @param sheetReader {@link SheetReader}实现 + * @return 数据读取结果 + * @since 5.4.4 + */ + public T read(SheetReader sheetReader){ + checkNotClosed(); + return Assert.notNull(sheetReader).read(this.sheet); } /** @@ -436,9 +408,7 @@ public class ExcelReader extends ExcelBase { * @since 4.1.0 */ public String readAsText(boolean withSheetName) { - final ExcelExtractor extractor = getExtractor(); - extractor.setIncludeSheetNames(withSheetName); - return extractor.getText(); + return ExcelExtractorUtil.readAsText(this.workbook, withSheetName); } /** @@ -448,14 +418,7 @@ public class ExcelReader extends ExcelBase { * @since 4.1.0 */ public ExcelExtractor getExtractor() { - ExcelExtractor extractor; - Workbook wb = this.workbook; - if (wb instanceof HSSFWorkbook) { - extractor = new org.apache.poi.hssf.extractor.ExcelExtractor((HSSFWorkbook) wb); - } else { - extractor = new XSSFExcelExtractor((XSSFWorkbook) wb); - } - return extractor; + return ExcelExtractorUtil.getExtractor(this.workbook); } /** @@ -504,42 +467,6 @@ public class ExcelReader extends ExcelBase { return RowUtil.readRow(row, this.cellEditor); } - /** - * 转换标题别名,如果没有别名则使用原标题,当标题为空时,列号对应的字母便是header - * - * @param headerList 原标题列表 - * @return 转换别名列表 - */ - private List aliasHeader(List headerList) { - if(CollUtil.isEmpty(headerList)){ - return new ArrayList<>(0); - } - - final int size = headerList.size(); - final ArrayList result = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - result.add(aliasHeader(headerList.get(i), i)); - } - return result; - } - - /** - * 转换标题别名,如果没有别名则使用原标题,当标题为空时,列号对应的字母便是header - * - * @param headerObj 原标题 - * @param index 标题所在列号,当标题为空时,列号对应的字母便是header - * @return 转换别名列表 - * @since 4.3.2 - */ - private String aliasHeader(Object headerObj, int index) { - if (null == headerObj) { - return ExcelUtil.indexToColName(index); - } - - final String header = headerObj.toString(); - return ObjectUtil.defaultIfNull(this.headerAlias.get(header), header); - } - /** * 检查是否未关闭状态 */ diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/AbstractSheetReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/AbstractSheetReader.java new file mode 100644 index 000000000..88f177c14 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/AbstractSheetReader.java @@ -0,0 +1,140 @@ +package cn.hutool.poi.excel.reader; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.poi.excel.ExcelUtil; +import cn.hutool.poi.excel.RowUtil; +import cn.hutool.poi.excel.cell.CellEditor; +import org.apache.poi.ss.usermodel.Sheet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 抽象{@link Sheet}数据读取实现 + * + * @param 读取类型 + * @author looly + * @since 5.4.4 + */ +public abstract class AbstractSheetReader implements SheetReader { + + /** + * 读取起始行(包含,从0开始计数) + */ + protected final int startRowIndex; + /** + * 读取结束行(包含,从0开始计数) + */ + protected final int endRowIndex; + /** + * 是否忽略空行 + */ + protected boolean ignoreEmptyRow = true; + /** + * 单元格值处理接口 + */ + protected CellEditor cellEditor; + /** + * 标题别名 + */ + private Map headerAlias = new HashMap<>(); + + /** + * 构造 + * + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + */ + public AbstractSheetReader(int startRowIndex, int endRowIndex) { + this.startRowIndex = startRowIndex; + this.endRowIndex = endRowIndex; + } + + /** + * 设置单元格值处理逻辑
+ * 当Excel中的值并不能满足我们的读取要求时,通过传入一个编辑接口,可以对单元格值自定义,例如对数字和日期类型值转换为字符串等 + * + * @param cellEditor 单元格值处理接口 + */ + public void setCellEditor(CellEditor cellEditor) { + this.cellEditor = cellEditor; + } + + /** + * 设置是否忽略空行 + * + * @param ignoreEmptyRow 是否忽略空行 + */ + public void setIgnoreEmptyRow(boolean ignoreEmptyRow) { + this.ignoreEmptyRow = ignoreEmptyRow; + } + + /** + * 设置标题行的别名Map + * + * @param headerAlias 别名Map + */ + public void setHeaderAlias(Map headerAlias) { + this.headerAlias = headerAlias; + } + + /** + * 增加标题别名 + * + * @param header 标题 + * @param alias 别名 + */ + public void addHeaderAlias(String header, String alias) { + this.headerAlias.put(header, alias); + } + + /** + * 转换标题别名,如果没有别名则使用原标题,当标题为空时,列号对应的字母便是header + * + * @param headerList 原标题列表 + * @return 转换别名列表 + */ + protected List aliasHeader(List headerList) { + if (CollUtil.isEmpty(headerList)) { + return new ArrayList<>(0); + } + + final int size = headerList.size(); + final ArrayList result = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + result.add(aliasHeader(headerList.get(i), i)); + } + return result; + } + + /** + * 转换标题别名,如果没有别名则使用原标题,当标题为空时,列号对应的字母便是header + * + * @param headerObj 原标题 + * @param index 标题所在列号,当标题为空时,列号对应的字母便是header + * @return 转换别名列表 + * @since 4.3.2 + */ + protected String aliasHeader(Object headerObj, int index) { + if (null == headerObj) { + return ExcelUtil.indexToColName(index); + } + + final String header = headerObj.toString(); + return ObjectUtil.defaultIfNull(this.headerAlias.get(header), header); + } + + /** + * 读取某一行数据 + * + * @param rowIndex 行号,从0开始 + * @return 一行数据 + * @since 4.0.3 + */ + protected List readRow(Sheet sheet, int rowIndex) { + return RowUtil.readRow(sheet.getRow(rowIndex), this.cellEditor); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/BeanSheetReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/BeanSheetReader.java new file mode 100644 index 000000000..94327ace4 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/BeanSheetReader.java @@ -0,0 +1,87 @@ +package cn.hutool.poi.excel.reader; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.poi.excel.cell.CellEditor; +import org.apache.poi.ss.usermodel.Sheet; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 读取{@link Sheet}为bean的List列表形式 + * + * @author looly + * @since 5.4.4 + */ +public class BeanSheetReader implements SheetReader> { + + private final Class beanClass; + private final MapSheetReader mapSheetReader; + + /** + * 构造 + * + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略 + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + * @param beanClass 每行对应Bean的类型 + */ + public BeanSheetReader(int headerRowIndex, int startRowIndex, int endRowIndex, Class beanClass) { + mapSheetReader = new MapSheetReader(headerRowIndex, startRowIndex, endRowIndex); + this.beanClass = beanClass; + } + + @Override + @SuppressWarnings("unchecked") + public List read(Sheet sheet) { + final List> mapList = mapSheetReader.read(sheet); + if (Map.class.isAssignableFrom(this.beanClass)) { + return (List) mapList; + } + + final List beanList = new ArrayList<>(mapList.size()); + for (Map map : mapList) { + beanList.add(BeanUtil.toBean(map, this.beanClass)); + } + return beanList; + } + + /** + * 设置单元格值处理逻辑
+ * 当Excel中的值并不能满足我们的读取要求时,通过传入一个编辑接口,可以对单元格值自定义,例如对数字和日期类型值转换为字符串等 + * + * @param cellEditor 单元格值处理接口 + */ + public void setCellEditor(CellEditor cellEditor) { + this.mapSheetReader.setCellEditor(cellEditor); + } + + /** + * 设置是否忽略空行 + * + * @param ignoreEmptyRow 是否忽略空行 + */ + public void setIgnoreEmptyRow(boolean ignoreEmptyRow) { + this.mapSheetReader.setIgnoreEmptyRow(ignoreEmptyRow); + } + + /** + * 设置标题行的别名Map + * + * @param headerAlias 别名Map + */ + public void setHeaderAlias(Map headerAlias) { + this.mapSheetReader.setHeaderAlias(headerAlias); + } + + /** + * 增加标题别名 + * + * @param header 标题 + * @param alias 别名 + */ + public void addHeaderAlias(String header, String alias) { + this.mapSheetReader.addHeaderAlias(header, alias); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ListSheetReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ListSheetReader.java new file mode 100644 index 000000000..df15aaff8 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/ListSheetReader.java @@ -0,0 +1,50 @@ +package cn.hutool.poi.excel.reader; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import org.apache.poi.ss.usermodel.Sheet; + +import java.util.ArrayList; +import java.util.List; + +/** + * 读取{@link Sheet}为List列表形式 + * + * @author looly + * @since 5.4.4 + */ +public class ListSheetReader extends AbstractSheetReader>> { + + private final boolean aliasFirstLine; + + /** + * 构造 + * + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + */ + public ListSheetReader(int startRowIndex, int endRowIndex, boolean aliasFirstLine) { + super(startRowIndex, endRowIndex); + this.aliasFirstLine = aliasFirstLine; + } + + @Override + public List> read(Sheet sheet) { + final List> resultList = new ArrayList<>(); + + int startRowIndex = Math.max(this.startRowIndex, sheet.getFirstRowNum());// 读取起始行(包含) + int endRowIndex = Math.min(this.endRowIndex, sheet.getLastRowNum());// 读取结束行(包含) + List rowList; + for (int i = startRowIndex; i <= endRowIndex; i++) { + rowList = readRow(sheet, i); + if (CollUtil.isNotEmpty(rowList) || false == ignoreEmptyRow) { + if (aliasFirstLine && i == startRowIndex) { + // 第一行作为标题行,替换别名 + rowList = Convert.toList(Object.class, aliasHeader(rowList)); + } + resultList.add(rowList); + } + } + return resultList; + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/MapSheetReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/MapSheetReader.java new file mode 100644 index 000000000..91f915bc5 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/MapSheetReader.java @@ -0,0 +1,63 @@ +package cn.hutool.poi.excel.reader; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.poi.ss.usermodel.Sheet; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 读取{@link Sheet}为Map的List列表形式 + * + * @author looly + * @since 5.4.4 + */ +public class MapSheetReader extends AbstractSheetReader>> { + + private final int headerRowIndex; + + /** + * 构造 + * + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略 + * @param startRowIndex 起始行(包含,从0开始计数) + * @param endRowIndex 结束行(包含,从0开始计数) + */ + public MapSheetReader(int headerRowIndex, int startRowIndex, int endRowIndex) { + super(startRowIndex, endRowIndex); + this.headerRowIndex = headerRowIndex; + } + + @Override + public List> read(Sheet sheet) { + // 边界判断 + final int firstRowNum = sheet.getFirstRowNum(); + final int lastRowNum = sheet.getLastRowNum(); + if (headerRowIndex < firstRowNum) { + throw new IndexOutOfBoundsException(StrUtil.format("Header row index {} is lower than first row index {}.", headerRowIndex, firstRowNum)); + } else if (headerRowIndex > lastRowNum) { + throw new IndexOutOfBoundsException(StrUtil.format("Header row index {} is greater than last row index {}.", headerRowIndex, firstRowNum)); + } + final int startRowIndex = Math.max(this.startRowIndex, firstRowNum);// 读取起始行(包含) + final int endRowIndex = Math.min(this.endRowIndex, lastRowNum);// 读取结束行(包含) + + // 读取header + List headerList = aliasHeader(readRow(sheet, headerRowIndex)); + + final List> result = new ArrayList<>(endRowIndex - startRowIndex + 1); + List rowList; + for (int i = startRowIndex; i <= endRowIndex; i++) { + // 跳过标题行 + if (i != headerRowIndex) { + rowList = readRow(sheet, i); + if (CollUtil.isNotEmpty(rowList) || false == ignoreEmptyRow) { + result.add(IterUtil.toMap(headerList, rowList, true)); + } + } + } + return result; + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/SheetReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/SheetReader.java new file mode 100644 index 000000000..2e20dff1d --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/SheetReader.java @@ -0,0 +1,20 @@ +package cn.hutool.poi.excel.reader; + +import org.apache.poi.ss.usermodel.Sheet; + +/** + * Excel {@link Sheet}读取接口,通过实现此接口,将{@link Sheet}中的数据读取为不同类型。 + * + * @param 读取的数据类型 + */ +@FunctionalInterface +public interface SheetReader { + + /** + * 读取数据 + * + * @param sheet {@link Sheet} + * @return 读取结果 + */ + T read(Sheet sheet); +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/package-info.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/package-info.java new file mode 100644 index 000000000..8d343fd12 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/reader/package-info.java @@ -0,0 +1,7 @@ +/** + * 数据读取接口及实现,此包中定义了SheetReader,通过实现此接口,实现sheet中的数据读取为不同类型。 + * + * @author looly + * + */ +package cn.hutool.poi.excel.reader; \ No newline at end of file diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java new file mode 100644 index 000000000..540bb84f4 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/AbstractRowHandler.java @@ -0,0 +1,59 @@ +package cn.hutool.poi.excel.sax.handler; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.func.Func1; + +import java.util.List; + +/** + * 抽象行数据处理器,通过实现{@link #handle(int, long, List)} 处理原始数据
+ * 并调用{@link #handleData(int, long, Object)}处理经过转换后的数据。 + * + * @param 转换后的数据类型 + * @author looly + * @since 5.4.4 + */ +public abstract class AbstractRowHandler implements RowHandler { + + /** + * 读取起始行(包含,从0开始计数) + */ + protected final int startRowIndex; + /** + * 读取结束行(包含,从0开始计数) + */ + protected final int endRowIndex; + /** + * 行数据转换函数 + */ + protected Func1, T> convertFunc; + + /** + * 构造 + * + * @param startRowIndex 读取起始行(包含,从0开始计数) + * @param endRowIndex 读取结束行(包含,从0开始计数) + */ + public AbstractRowHandler(int startRowIndex, int endRowIndex) { + this.startRowIndex = startRowIndex; + this.endRowIndex = endRowIndex; + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + Assert.notNull(convertFunc); + if (rowIndex < this.startRowIndex || rowIndex > this.endRowIndex) { + return; + } + handleData(sheetIndex, rowIndex, convertFunc.callWithRuntimeException(rowList)); + } + + /** + * 处理转换后的数据 + * + * @param sheetIndex 当前Sheet序号 + * @param rowIndex 当前行号,从0开始计数 + * @param data 行数据 + */ + public abstract void handleData(int sheetIndex, long rowIndex, T data); +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java new file mode 100644 index 000000000..ab618e565 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/BeanRowHandler.java @@ -0,0 +1,51 @@ +package cn.hutool.poi.excel.sax.handler; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; + +import java.util.List; + +/** + * Bean形式的行处理器
+ * 将一行数据转换为Map,key为指定行,value为当前行对应位置的值 + * + * @author looly + * @since 5.4.4 + */ +public abstract class BeanRowHandler extends AbstractRowHandler{ + + /** + * 标题所在行(从0开始计数) + */ + private final int headerRowIndex; + /** + * 标题行 + */ + List headerList; + + /** + * 构造 + * + * @param headerRowIndex 标题所在行(从0开始计数) + * @param startRowIndex 读取起始行(包含,从0开始计数) + * @param endRowIndex 读取结束行(包含,从0开始计数) + */ + public BeanRowHandler(int headerRowIndex, int startRowIndex, int endRowIndex, Class clazz){ + super(startRowIndex, endRowIndex); + Assert.isTrue(headerRowIndex <= startRowIndex, "Header row must before the start row!"); + this.headerRowIndex = headerRowIndex; + this.convertFunc = (rowList)-> BeanUtil.toBean(IterUtil.toMap(headerList, rowList), clazz); + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + if (rowIndex == this.headerRowIndex) { + this.headerList = ListUtil.unmodifiable(Convert.toList(String.class, rowList)); + return; + } + super.handle(sheetIndex, rowIndex, rowList); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java new file mode 100644 index 000000000..1771da41f --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/MapRowHandler.java @@ -0,0 +1,49 @@ +package cn.hutool.poi.excel.sax.handler; + +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.convert.Convert; + +import java.util.List; +import java.util.Map; + +/** + * Map形式的行处理器
+ * 将一行数据转换为Map,key为指定行,value为当前行对应位置的值 + * + * @author looly + * @since 5.4.4 + */ +public abstract class MapRowHandler extends AbstractRowHandler> { + + /** + * 标题所在行(从0开始计数) + */ + private final int headerRowIndex; + /** + * 标题行 + */ + List headerList; + + /** + * 构造 + * + * @param headerRowIndex 标题所在行(从0开始计数) + * @param startRowIndex 读取起始行(包含,从0开始计数) + * @param endRowIndex 读取结束行(包含,从0开始计数) + */ + public MapRowHandler(int headerRowIndex, int startRowIndex, int endRowIndex){ + super(startRowIndex, endRowIndex); + this.headerRowIndex = headerRowIndex; + this.convertFunc = (rowList)-> IterUtil.toMap(headerList, rowList); + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + if (rowIndex == this.headerRowIndex) { + this.headerList = ListUtil.unmodifiable(Convert.toList(String.class, rowList)); + return; + } + super.handle(sheetIndex, rowIndex, rowList); + } +}