diff --git a/CHANGELOG.md b/CHANGELOG.md
index 00729e860..58d538385 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,20 +5,23 @@
# 5.5.0 (2020-11-14)
+### 大版本特性
+* 【extra 】 增加jakarta.validation-api封装:ValidationUtil(pr#207@Gitee)
+* 【extra 】 增加表达式引擎封装:ExpressionUtil(pr#1203@Github)
+* 【extra 】 新增基于Apache-FtpServer封装:SimpleFtpServer
+* 【extra 】 新增基于Commons-Compress封装:CompressUtil
+
### 新特性
* 【core 】 NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee)
* 【core 】 增加ArrayUtil.isSub、indexOfSub、lastIndexOfSub方法(issue#I23O1K@Gitee)
-* 【extra 】 增加ValidationUtil(pr#207@Gitee)
* 【core 】 反射调用支持传递参数的值为null(pr#1205@Github)
* 【core 】 HexUtil增加format方法(issue#I245NF@Gitee)
* 【poi 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee)
* 【core 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee)
-* 【extra 】 增加表达式引擎封装(ExpressionUtil)(pr#1203@Github)
* 【core 】 增加enum转数字支持(issue#I24QZY@Gitee)
* 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee)
* 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee)
-* 【extra 】 新增SimpleFtpServer
-* 【extra 】 新增CompressUtil压缩封装
+* 【poi 】 ExcelReader的read方法读取空单元格增加CellEditor处理(issue#1213@Github)
### Bug修复
* 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github)
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java
index 3479a5d8a..8cbdb7eb2 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java
@@ -1,5 +1,6 @@
package cn.hutool.core.util;
+import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Dict;
import org.junit.Assert;
import org.junit.Test;
@@ -433,6 +434,14 @@ public class StrUtilTest {
Assert.assertEquals(0, results2.length);
}
+ @Test
+ public void subBetweenAllTest3() {
+ String src1 = "'abc'and'123'";
+
+ final String[] strings = StrUtil.subBetweenAll(src1, "'", "'");
+ Console.log(strings);
+ }
+
@Test
public void briefTest() {
String str = RandomUtil.randomString(1000);
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java
index 07db936c7..fdd0cda3c 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java
@@ -1,5 +1,7 @@
package cn.hutool.extra.compress;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.compress.archiver.Archiver;
import cn.hutool.extra.compress.archiver.SevenZArchiver;
import cn.hutool.extra.compress.archiver.StreamArchiver;
@@ -8,6 +10,10 @@ import cn.hutool.extra.compress.extractor.SenvenZExtractor;
import cn.hutool.extra.compress.extractor.StreamExtractor;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.StreamingNotSupportedException;
+import org.apache.commons.compress.compressors.CompressorException;
+import org.apache.commons.compress.compressors.CompressorInputStream;
+import org.apache.commons.compress.compressors.CompressorOutputStream;
+import org.apache.commons.compress.compressors.CompressorStreamFactory;
import java.io.File;
import java.io.InputStream;
@@ -23,6 +29,62 @@ import java.nio.charset.Charset;
*/
public class CompressUtil {
+ /**
+ * 获取压缩输出流,用于压缩指定内容,支持的格式例如:
+ *
+ * - {@value CompressorStreamFactory#GZIP}
+ * - {@value CompressorStreamFactory#BZIP2}
+ * - {@value CompressorStreamFactory#XZ}
+ * - {@value CompressorStreamFactory#PACK200}
+ * - {@value CompressorStreamFactory#SNAPPY_FRAMED}
+ * - {@value CompressorStreamFactory#LZ4_BLOCK}
+ * - {@value CompressorStreamFactory#LZ4_FRAMED}
+ * - {@value CompressorStreamFactory#ZSTANDARD}
+ * - {@value CompressorStreamFactory#DEFLATE}
+ *
+ *
+ * @param compressorName 压缩名称,见:{@link CompressorStreamFactory}
+ * @param out 输出流,可以输出到内存、网络或文件
+ * @return {@link CompressorOutputStream}
+ */
+ public CompressorOutputStream getOut(String compressorName, OutputStream out) {
+ try {
+ return new CompressorStreamFactory().createCompressorOutputStream(compressorName, out);
+ } catch (CompressorException e) {
+ throw new CompressException(e);
+ }
+ }
+
+ /**
+ * 获取压缩输入流,用于解压缩指定内容,支持的格式例如:
+ *
+ * - {@value CompressorStreamFactory#GZIP}
+ * - {@value CompressorStreamFactory#BZIP2}
+ * - {@value CompressorStreamFactory#XZ}
+ * - {@value CompressorStreamFactory#PACK200}
+ * - {@value CompressorStreamFactory#SNAPPY_FRAMED}
+ * - {@value CompressorStreamFactory#LZ4_BLOCK}
+ * - {@value CompressorStreamFactory#LZ4_FRAMED}
+ * - {@value CompressorStreamFactory#ZSTANDARD}
+ * - {@value CompressorStreamFactory#DEFLATE}
+ *
+ *
+ * @param compressorName 压缩名称,见:{@link CompressorStreamFactory},null表示自动检测
+ * @param in 输出流,可以输出到内存、网络或文件
+ * @return {@link CompressorOutputStream}
+ */
+ public CompressorInputStream getIn(String compressorName, InputStream in) {
+ in = IoUtil.toMarkSupportStream(in);
+ try {
+ if(StrUtil.isBlank(compressorName)){
+ compressorName = CompressorStreamFactory.detect(in);
+ }
+ return new CompressorStreamFactory().createCompressorInputStream(compressorName, in);
+ } catch (CompressorException e) {
+ throw new CompressException(e);
+ }
+ }
+
/**
* 创建归档器,支持:
*
@@ -108,11 +170,11 @@ public class CompressUtil {
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) {
return new SenvenZExtractor(file);
}
- try{
+ try {
return new StreamExtractor(charset, archiverName, file);
- } catch (CompressException e){
+ } catch (CompressException e) {
final Throwable cause = e.getCause();
- if(cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")){
+ if (cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")) {
return new SenvenZExtractor(file);
}
throw e;
@@ -158,11 +220,11 @@ public class CompressUtil {
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) {
return new SenvenZExtractor(in);
}
- try{
+ try {
return new StreamExtractor(charset, archiverName, in);
- } catch (CompressException e){
+ } catch (CompressException e) {
final Throwable cause = e.getCause();
- if(cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")){
+ if (cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")) {
return new SenvenZExtractor(in);
}
throw e;
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java
index 9f371a9c6..4a926765b 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java
@@ -66,6 +66,15 @@ public class SevenZArchiver implements Archiver {
}
}
+ /**
+ * 获取{@link SevenZOutputFile}以便自定义相关设置
+ *
+ * @return {@link SevenZOutputFile}
+ */
+ public SevenZOutputFile getSevenZOutputFile() {
+ return this.sevenZOutputFile;
+ }
+
@Override
public SevenZArchiver add(File file, String path, Filter filter) {
try {
@@ -93,9 +102,9 @@ public class SevenZArchiver implements Archiver {
} catch (Exception ignore) {
//ignore
}
- if(null != out && this.channel instanceof SeekableInMemoryByteChannel){
+ if (null != out && this.channel instanceof SeekableInMemoryByteChannel) {
try {
- out.write(((SeekableInMemoryByteChannel)this.channel).array());
+ out.write(((SeekableInMemoryByteChannel) this.channel).array());
} catch (IOException e) {
throw new IORuntimeException(e);
}
diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java
index c7a9f0d94..d2711a065 100644
--- a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java
+++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java
@@ -73,7 +73,7 @@ public class RowUtil {
Object cellValue;
boolean isAllNull = true;
for (int i = startCellNumInclude; i < size; i++) {
- cellValue = CellUtil.getCellValue(row.getCell(i), cellEditor);
+ cellValue = CellUtil.getCellValue(CellUtil.getCell(row, i), cellEditor);
isAllNull &= StrUtil.isEmptyIfStr(cellValue);
cellValues.add(cellValue);
}
diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java
index 0d24577d7..2b71e08aa 100644
--- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java
+++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java
@@ -74,10 +74,7 @@ public class CellUtil {
* @return 值,类型可能为:Date、Double、Boolean、String
*/
public static Object getCellValue(Cell cell, CellEditor cellEditor) {
- if (null == cell) {
- return null;
- }
- return getCellValue(cell, cell.getCellTypeEnum(), cellEditor);
+ return getCellValue(cell, null, cellEditor);
}
/**
@@ -105,6 +102,9 @@ public class CellUtil {
if (null == cell) {
return null;
}
+ if(cell instanceof NullCell){
+ return null == cellEditor ? null : cellEditor.edit(cell, null);
+ }
if (null == cellType) {
cellType = cell.getCellTypeEnum();
}
@@ -235,7 +235,23 @@ public class CellUtil {
}
/**
- * 获取已有行或创建新行
+ *获取单元格,如果单元格不存在,返回{@link NullCell}
+ *
+ * @param row Excel表的行
+ * @param cellIndex 列号
+ * @return {@link Row}
+ * @since 5.5.0
+ */
+ public static Cell getCell(Row row, int cellIndex) {
+ Cell cell = row.getCell(cellIndex);
+ if (null == cell) {
+ return new NullCell(row, cellIndex);
+ }
+ return cell;
+ }
+
+ /**
+ * 获取已有单元格或创建新单元格
*
* @param row Excel表的行
* @param cellIndex 列号
diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java
new file mode 100644
index 000000000..ac750b061
--- /dev/null
+++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java
@@ -0,0 +1,240 @@
+package cn.hutool.poi.excel.cell;
+
+import org.apache.poi.ss.formula.FormulaParseException;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Comment;
+import org.apache.poi.ss.usermodel.Hyperlink;
+import org.apache.poi.ss.usermodel.RichTextString;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.time.LocalDateTime;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 当单元格不存在时使用此对象表示,得到的值都为null,此对象只用于标注单元格所在位置信息。
+ *
+ * @author looly
+ * @since 5.5.0
+ */
+public class NullCell implements Cell {
+
+ private final Row row;
+ private final int columnIndex;
+
+ /**
+ * 构造
+ *
+ * @param row 行
+ * @param columnIndex 列号,从0开始
+ */
+ public NullCell(Row row, int columnIndex) {
+ this.row = row;
+ this.columnIndex = columnIndex;
+ }
+
+ @Override
+ public int getColumnIndex() {
+ return this.columnIndex;
+ }
+
+ @Override
+ public int getRowIndex() {
+ return getRow().getRowNum();
+ }
+
+ @Override
+ public Sheet getSheet() {
+ return getRow().getSheet();
+ }
+
+ @Override
+ public Row getRow() {
+ return this.row;
+ }
+
+ @Override
+ public void setCellType(CellType cellType) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setBlank() {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public CellType getCellType() {
+ return null;
+ }
+
+ @Override
+ public CellType getCellTypeEnum() {
+ return null;
+ }
+
+ @Override
+ public CellType getCachedFormulaResultType() {
+ return null;
+ }
+
+ @Override
+ public CellType getCachedFormulaResultTypeEnum() {
+ return null;
+ }
+
+ @Override
+ public void setCellValue(double value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellValue(Date value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellValue(LocalDateTime value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellValue(Calendar value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellValue(RichTextString value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellValue(String value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellFormula(String formula) throws FormulaParseException, IllegalStateException {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void removeFormula() throws IllegalStateException {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public String getCellFormula() {
+ return null;
+ }
+
+ @Override
+ public double getNumericCellValue() {
+ throw new UnsupportedOperationException("Cell value is null!");
+ }
+
+ @Override
+ public Date getDateCellValue() {
+ return null;
+ }
+
+ @Override
+ public LocalDateTime getLocalDateTimeCellValue() {
+ return null;
+ }
+
+ @Override
+ public RichTextString getRichStringCellValue() {
+ return null;
+ }
+
+ @Override
+ public String getStringCellValue() {
+ return null;
+ }
+
+ @Override
+ public void setCellValue(boolean value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void setCellErrorValue(byte value) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public boolean getBooleanCellValue() {
+ throw new UnsupportedOperationException("Cell value is null!");
+ }
+
+ @Override
+ public byte getErrorCellValue() {
+ throw new UnsupportedOperationException("Cell value is null!");
+ }
+
+ @Override
+ public void setCellStyle(CellStyle style) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public CellStyle getCellStyle() {
+ return null;
+ }
+
+ @Override
+ public void setAsActiveCell() {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public CellAddress getAddress() {
+ return null;
+ }
+
+ @Override
+ public void setCellComment(Comment comment) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public Comment getCellComment() {
+ return null;
+ }
+
+ @Override
+ public void removeCellComment() {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public Hyperlink getHyperlink() {
+ return null;
+ }
+
+ @Override
+ public void setHyperlink(Hyperlink link) {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public void removeHyperlink() {
+ throw new UnsupportedOperationException("Can not set any thing to null cell!");
+ }
+
+ @Override
+ public CellRangeAddress getArrayFormulaRange() {
+ return null;
+ }
+
+ @Override
+ public boolean isPartOfArrayFormulaGroup() {
+ throw new UnsupportedOperationException("Cell value is null!");
+ }
+}
diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java
index 6137f47de..459348ec1 100644
--- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java
+++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java
@@ -3,6 +3,7 @@ package cn.hutool.poi.excel.test;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import lombok.Data;
@@ -201,4 +202,20 @@ public class ExcelReadTest {
Console.log(list);
}
}
+
+ @Test
+ public void nullValueEditTest(){
+ final ExcelReader reader = ExcelUtil.getReader("null_cell_test.xlsx");
+ reader.setCellEditor((cell, value)-> ObjectUtil.defaultIfNull(value, "#"));
+ final List> read = reader.read();
+
+ // 对于任意一个单元格有值的情况下,之前的单元格值按照null处理
+ Assert.assertEquals(1, read.get(1).size());
+ Assert.assertEquals(2, read.get(2).size());
+ Assert.assertEquals(3, read.get(3).size());
+
+ Assert.assertEquals("#", read.get(2).get(0));
+ Assert.assertEquals("#", read.get(3).get(0));
+ Assert.assertEquals("#", read.get(3).get(1));
+ }
}
diff --git a/hutool-poi/src/test/resources/null_cell_test.xlsx b/hutool-poi/src/test/resources/null_cell_test.xlsx
new file mode 100644
index 000000000..9329db568
Binary files /dev/null and b/hutool-poi/src/test/resources/null_cell_test.xlsx differ