mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
Merge remote-tracking branch 'upstream/v5-dev' into v5-dev
This commit is contained in:
commit
94cda2591a
20
CHANGELOG.md
20
CHANGELOG.md
@ -3,17 +3,35 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.3 (2020-12-06)
|
||||
# 5.5.3 (2020-12-11)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 IdcardUtil增加行政区划83(issue#1277@Github)
|
||||
* 【core 】 multipart中int改为long,解决大文件上传越界问题(issue#I27WZ3@Gitee)
|
||||
* 【core 】 ListUtil.page增加检查(pr#224@Gitee)
|
||||
* 【db 】 Db增加使用sql的page方法(issue#247@Gitee)
|
||||
* 【cache 】 CacheObj的isExpired()逻辑修改(issue#1295@Github)
|
||||
* 【json 】 JSONStrFormater改为JSONStrFormatter
|
||||
* 【dfa 】 增加FoundWord(pr#1290@Github)
|
||||
* 【core 】 增加Segment(pr#1290@Github)
|
||||
* 【core 】 增加CharSequenceUtil
|
||||
* 【poi 】 Excel07SaxReader拆分出SheetDataSaxHandler
|
||||
* 【core 】 CollUtil.addAll增加判空(pr#228@Gitee)
|
||||
* 【core 】 修正DateUtil.betweenXXX注释错误(issue#I28XGW@Gitee)
|
||||
* 【core 】 增加NioUtil
|
||||
* 【core 】 增加GanymedUtil
|
||||
|
||||
### Bug修复
|
||||
* 【cache 】 修复Cache中get重复misCount计数问题(issue#1281@Github)
|
||||
* 【poi 】 修复sax读取自定义格式单元格无法识别日期类型的问题(issue#1283@Github)
|
||||
* 【core 】 修复CollUtil.get越界问题(issue#1292@Github)
|
||||
* 【core 】 修复TemporalAccessorUtil无法格式化LocalDate带时间问题(issue#1289@Github)
|
||||
* 【json 】 修复自定义日期格式的LocalDateTime没有包装引号问题(issue#1289@Github)
|
||||
* 【cache 】 get中unlock改为unlockRead(issue#1294@Github)
|
||||
* 【db 】 修复表名包含点导致的问题(issue#1300@Github)
|
||||
* 【poi 】 修复xdr:row标签导致的问题(issue#1297@Github)
|
||||
* 【core 】 修复FileUtil.loopFiles使用FileFilter无效问题(issue#I28V48@Gitee)
|
||||
* 【extra 】 修复JschUtil.execByShell返回空的问题(issue#1067@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -174,7 +174,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
return co.get(isUpdateLastAccess);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock(stamp);
|
||||
lock.unlockRead(stamp);
|
||||
}
|
||||
|
||||
// 过期
|
||||
|
@ -44,9 +44,8 @@ public class CacheObj<K, V> implements Serializable{
|
||||
*/
|
||||
boolean isExpired() {
|
||||
if(this.ttl > 0) {
|
||||
final long expiredTime = this.lastAccess + this.ttl;
|
||||
// expiredTime > 0 杜绝Long类型溢出变负数问题,当当前时间超过过期时间,表示过期
|
||||
return expiredTime > 0 && expiredTime < System.currentTimeMillis();
|
||||
// 此处不考虑时间回拨
|
||||
return (System.currentTimeMillis() - this.lastAccess) > this.ttl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -5,6 +5,14 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>4.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
|
@ -1870,7 +1870,7 @@ public class CollUtil {
|
||||
/**
|
||||
* Iterator转换为Enumeration
|
||||
* <p>
|
||||
* Adapt the specified <code>Iterator</code> to the <code>Enumeration</code> interface.
|
||||
* Adapt the specified {@link Iterator} to the {@link Enumeration} interface.
|
||||
*
|
||||
* @param <E> 集合元素类型
|
||||
* @param iter {@link Iterator}
|
||||
@ -1883,7 +1883,7 @@ public class CollUtil {
|
||||
/**
|
||||
* Enumeration转换为Iterator
|
||||
* <p>
|
||||
* Adapt the specified <code>Enumeration</code> to the <code>Iterator</code> interface
|
||||
* Adapt the specified {@code Enumeration} to the {@code Iterator} interface
|
||||
*
|
||||
* @param <E> 集合元素类型
|
||||
* @param e {@link Enumeration}
|
||||
@ -2108,6 +2108,9 @@ public class CollUtil {
|
||||
* @return 原集合
|
||||
*/
|
||||
public static <T> Collection<T> addAll(Collection<T> collection, Iterable<T> iterable) {
|
||||
if (iterable == null) {
|
||||
return collection;
|
||||
}
|
||||
return addAll(collection, iterable.iterator());
|
||||
}
|
||||
|
||||
@ -2186,7 +2189,7 @@ public class CollUtil {
|
||||
}
|
||||
|
||||
// 检查越界
|
||||
if (index >= size) {
|
||||
if (index >= size || index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ public class DateBetween implements Serializable{
|
||||
|
||||
/**
|
||||
* 计算两个日期相差月数<br>
|
||||
* 在非重置情况下,如果起始日期的天小于结束日期的天,月数要少算1(不足1个月)
|
||||
* 在非重置情况下,如果起始日期的天大于结束日期的天,月数要少算1(不足1个月)
|
||||
*
|
||||
* @param isReset 是否重置时间为起始时间(重置天时分秒)
|
||||
* @return 相差月数
|
||||
@ -122,7 +122,7 @@ public class DateBetween implements Serializable{
|
||||
|
||||
/**
|
||||
* 计算两个日期相差年数<br>
|
||||
* 在非重置情况下,如果起始日期的月小于结束日期的月,年数要少算1(不足1年)
|
||||
* 在非重置情况下,如果起始日期的月大于结束日期的月,年数要少算1(不足1年)
|
||||
*
|
||||
* @param isReset 是否重置时间为起始时间(重置月天时分秒)
|
||||
* @return 相差年数
|
||||
|
@ -1339,8 +1339,8 @@ public class DateUtil extends CalendarUtil {
|
||||
* <pre>
|
||||
* 有时候我们计算相差天数的时候需要忽略时分秒。
|
||||
* 比如:2016-02-01 23:59:59和2016-02-02 00:00:00相差一秒
|
||||
* 如果isReset为<code>false</code>相差天数为0。
|
||||
* 如果isReset为<code>true</code>相差天数将被计算为1
|
||||
* 如果isReset为{@code false}相差天数为0。
|
||||
* 如果isReset为{@code true}相差天数将被计算为1
|
||||
* </pre>
|
||||
*
|
||||
* @param beginDate 起始日期
|
||||
@ -1375,7 +1375,7 @@ public class DateUtil extends CalendarUtil {
|
||||
|
||||
/**
|
||||
* 计算两个日期相差月数<br>
|
||||
* 在非重置情况下,如果起始日期的天小于结束日期的天,月数要少算1(不足1个月)
|
||||
* 在非重置情况下,如果起始日期的天大于结束日期的天,月数要少算1(不足1个月)
|
||||
*
|
||||
* @param beginDate 起始日期
|
||||
* @param endDate 结束日期
|
||||
@ -1389,7 +1389,7 @@ public class DateUtil extends CalendarUtil {
|
||||
|
||||
/**
|
||||
* 计算两个日期相差年数<br>
|
||||
* 在非重置情况下,如果起始日期的月小于结束日期的月,年数要少算1(不足1年)
|
||||
* 在非重置情况下,如果起始日期的月大于结束日期的月,年数要少算1(不足1年)
|
||||
*
|
||||
* @param beginDate 起始日期
|
||||
* @param endDate 结束日期
|
||||
|
@ -13,6 +13,7 @@ import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.TemporalField;
|
||||
import java.time.temporal.UnsupportedTemporalTypeException;
|
||||
|
||||
/**
|
||||
* {@link TemporalAccessor} 工具类封装
|
||||
@ -54,7 +55,19 @@ public class TemporalAccessorUtil extends TemporalUtil{
|
||||
formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return formatter.format(time);
|
||||
} catch (UnsupportedTemporalTypeException e){
|
||||
if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){
|
||||
// 用户传入LocalDate,但是要求格式化带有时间部分,转换为LocalDateTime重试
|
||||
return formatter.format(((LocalDate) time).atStartOfDay());
|
||||
}else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){
|
||||
// 用户传入LocalTime,但是要求格式化带有日期部分,转换为LocalDateTime重试
|
||||
return formatter.format(((LocalTime) time).atDate(LocalDate.now()));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,6 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.io.file.FileCopier;
|
||||
import cn.hutool.core.io.file.FileMode;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
@ -173,13 +172,7 @@ public class FileUtil extends PathUtil {
|
||||
* @return 文件列表
|
||||
*/
|
||||
public static List<File> loopFiles(File file, FileFilter fileFilter) {
|
||||
if (null == file || false == file.exists()) {
|
||||
return ListUtil.empty();
|
||||
}
|
||||
|
||||
final List<File> fileList = new ArrayList<>();
|
||||
walkFiles(file, fileList::add);
|
||||
return fileList;
|
||||
return loopFiles(file, -1, fileFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,7 +209,7 @@ public class FileUtil extends PathUtil {
|
||||
* @return 文件列表
|
||||
* @since 4.6.3
|
||||
*/
|
||||
public static List<File> loopFiles(File file, int maxDepth, final FileFilter fileFilter) {
|
||||
public static List<File> loopFiles(File file, int maxDepth, FileFilter fileFilter) {
|
||||
return loopFiles(file.toPath(), maxDepth, fileFilter);
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,8 @@ import java.io.PushbackReader;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
@ -48,25 +43,7 @@ import java.util.zip.Checksum;
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class IoUtil {
|
||||
|
||||
/**
|
||||
* 默认缓存大小 8192
|
||||
*/
|
||||
public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
|
||||
/**
|
||||
* 默认中等缓存大小 16384
|
||||
*/
|
||||
public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
|
||||
/**
|
||||
* 默认大缓存大小 32768
|
||||
*/
|
||||
public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
|
||||
|
||||
/**
|
||||
* 数据流末尾
|
||||
*/
|
||||
public static final int EOF = -1;
|
||||
public class IoUtil extends NioUtil{
|
||||
|
||||
// -------------------------------------------------------------------------------------- Copy start
|
||||
|
||||
@ -195,21 +172,6 @@ public class IoUtil {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
|
||||
* 本方法不会关闭流
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param out 输出流
|
||||
* @param bufferSize 缓存大小
|
||||
* @param streamProgress 进度条
|
||||
* @return 传输的byte数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
|
||||
return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件流,使用NIO
|
||||
*
|
||||
@ -227,79 +189,13 @@ public class IoUtil {
|
||||
try {
|
||||
inChannel = in.getChannel();
|
||||
outChannel = out.getChannel();
|
||||
return inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
return copy(inChannel, outChannel);
|
||||
} finally {
|
||||
close(outChannel);
|
||||
close(inChannel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭流
|
||||
*
|
||||
* @param in {@link ReadableByteChannel}
|
||||
* @param out {@link WritableByteChannel}
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
|
||||
return copy(in, out, DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭流
|
||||
*
|
||||
* @param in {@link ReadableByteChannel}
|
||||
* @param out {@link WritableByteChannel}
|
||||
* @param bufferSize 缓冲大小,如果小于等于0,使用默认
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
|
||||
return copy(in, out, bufferSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭流
|
||||
*
|
||||
* @param in {@link ReadableByteChannel}
|
||||
* @param out {@link WritableByteChannel}
|
||||
* @param bufferSize 缓冲大小,如果小于等于0,使用默认
|
||||
* @param streamProgress {@link StreamProgress}进度处理器
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
|
||||
Assert.notNull(in, "InputStream is null !");
|
||||
Assert.notNull(out, "OutputStream is null !");
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
|
||||
long size = 0;
|
||||
if (null != streamProgress) {
|
||||
streamProgress.start();
|
||||
}
|
||||
try {
|
||||
while (in.read(byteBuffer) != EOF) {
|
||||
byteBuffer.flip();// 写转读
|
||||
size += out.write(byteBuffer);
|
||||
byteBuffer.clear();
|
||||
if (null != streamProgress) {
|
||||
streamProgress.progress(size);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
if (null != streamProgress) {
|
||||
streamProgress.finish();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
// -------------------------------------------------------------------------------------- Copy end
|
||||
|
||||
// -------------------------------------------------------------------------------------- getReader and getWriter start
|
||||
@ -455,22 +351,7 @@ public class IoUtil {
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String read(InputStream in, Charset charset) throws IORuntimeException {
|
||||
FastByteArrayOutputStream out = read(in);
|
||||
return null == charset ? out.toString() : out.toString(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容,读取完毕后并不关闭流
|
||||
*
|
||||
* @param channel 可读通道,读取完毕后并不关闭通道
|
||||
* @param charset 字符集
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
|
||||
FastByteArrayOutputStream out = read(channel);
|
||||
return null == charset ? out.toString() : out.toString(charset);
|
||||
return StrUtil.str(readBytes(in), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -486,19 +367,6 @@ public class IoUtil {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容,读到输出流中
|
||||
*
|
||||
* @param channel 可读通道,读取完毕后并不关闭通道
|
||||
* @return 输出流
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
|
||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||
copy(channel, Channels.newChannel(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Reader中读取String,读取完毕后关闭Reader
|
||||
*
|
||||
@ -535,47 +403,6 @@ public class IoUtil {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从FileChannel中读取UTF-8编码内容
|
||||
*
|
||||
* @param fileChannel 文件管道
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String readUtf8(FileChannel fileChannel) throws IORuntimeException {
|
||||
return read(fileChannel, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从FileChannel中读取内容,读取完毕后并不关闭Channel
|
||||
*
|
||||
* @param fileChannel 文件管道
|
||||
* @param charsetName 字符集
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String read(FileChannel fileChannel, String charsetName) throws IORuntimeException {
|
||||
return read(fileChannel, CharsetUtil.charset(charsetName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从FileChannel中读取内容
|
||||
*
|
||||
* @param fileChannel 文件管道
|
||||
* @param charset 字符集
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String read(FileChannel fileChannel, Charset charset) throws IORuntimeException {
|
||||
MappedByteBuffer buffer;
|
||||
try {
|
||||
buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).load();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return StrUtil.str(buffer, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取bytes,读取完毕后关闭流
|
||||
*
|
||||
@ -597,12 +424,19 @@ public class IoUtil {
|
||||
* @since 5.0.4
|
||||
*/
|
||||
public static byte[] readBytes(InputStream in, boolean isCloseStream) throws IORuntimeException {
|
||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||
copy(in, out);
|
||||
if (isCloseStream) {
|
||||
close(in);
|
||||
final InputStream availableStream = toAvailableStream(in);
|
||||
try{
|
||||
final int available = availableStream.available();
|
||||
if(available > 0){
|
||||
byte[] result = new byte[available];
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
availableStream.read(result);
|
||||
return result;
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (IOException e){
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -966,6 +800,42 @@ public class IoUtil {
|
||||
return (in instanceof PushbackInputStream) ? (PushbackInputStream) in : new PushbackInputStream(in, pushBackSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定{@link InputStream} 转换为{@link InputStream#available()}方法可用的流。<br>
|
||||
* 在Socket通信流中,服务端未返回数据情况下{@link InputStream#available()}方法始终为{@code 0}<br>
|
||||
* 因此,在读取前需要调用{@link InputStream#read()}读取一个字节(未返回会阻塞),一旦读取到了,{@link InputStream#available()}方法就正常了。<br>
|
||||
* 此方法返回对象的规则为:
|
||||
*
|
||||
* <ul>
|
||||
* <li>FileInputStream 返回原对象,因为文件流的available方法本身可用</li>
|
||||
* <li>其它InputStream 返回PushbackInputStream</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param in 被转换的流
|
||||
* @return 转换后的流,可能为{@link PushbackInputStream}
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static InputStream toAvailableStream(InputStream in) {
|
||||
if(in instanceof FileInputStream){
|
||||
// FileInputStream本身支持available方法。
|
||||
return in;
|
||||
}
|
||||
|
||||
final PushbackInputStream pushbackInputStream = toPushbackStream(in, 1);
|
||||
try {
|
||||
final int available = pushbackInputStream.available();
|
||||
if (available <= 0) {
|
||||
//此操作会阻塞,直到有数据被读到
|
||||
int b = pushbackInputStream.read();
|
||||
pushbackInputStream.unread(b);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return pushbackInputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将byte[]写到流中
|
||||
*
|
||||
@ -1113,22 +983,6 @@ public class IoUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭<br>
|
||||
* 关闭失败不会抛出异常
|
||||
*
|
||||
* @param closeable 被关闭的对象
|
||||
*/
|
||||
public static void close(AutoCloseable closeable) {
|
||||
if (null != closeable) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
// 静默关闭
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试关闭指定对象<br>
|
||||
* 判断对象如果实现了{@link AutoCloseable},则调用之
|
||||
|
227
hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
Normal file
227
hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
Normal file
@ -0,0 +1,227 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* NIO相关工具封装,主要针对Channel读写、拷贝等封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public class NioUtil {
|
||||
|
||||
/**
|
||||
* 默认缓存大小 8192
|
||||
*/
|
||||
public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
|
||||
/**
|
||||
* 默认中等缓存大小 16384
|
||||
*/
|
||||
public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
|
||||
/**
|
||||
* 默认大缓存大小 32768
|
||||
*/
|
||||
public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
|
||||
|
||||
/**
|
||||
* 数据流末尾
|
||||
*/
|
||||
public static final int EOF = -1;
|
||||
|
||||
/**
|
||||
* 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
|
||||
* 本方法不会关闭流
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param out 输出流
|
||||
* @param bufferSize 缓存大小
|
||||
* @param streamProgress 进度条
|
||||
* @return 传输的byte数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
|
||||
return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件Channel,使用NIO,拷贝后不会关闭channel
|
||||
*
|
||||
* @param inChannel {@link FileChannel}
|
||||
* @param outChannel {@link FileChannel}
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static long copy(FileChannel inChannel, FileChannel outChannel) throws IORuntimeException {
|
||||
Assert.notNull(inChannel, "In channel is null!");
|
||||
Assert.notNull(outChannel, "Out channel is null!");
|
||||
|
||||
try {
|
||||
return inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭channel
|
||||
*
|
||||
* @param in {@link ReadableByteChannel}
|
||||
* @param out {@link WritableByteChannel}
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
|
||||
return copy(in, out, DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭channel
|
||||
*
|
||||
* @param in {@link ReadableByteChannel}
|
||||
* @param out {@link WritableByteChannel}
|
||||
* @param bufferSize 缓冲大小,如果小于等于0,使用默认
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
|
||||
return copy(in, out, bufferSize, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭channel
|
||||
*
|
||||
* @param in {@link ReadableByteChannel}
|
||||
* @param out {@link WritableByteChannel}
|
||||
* @param bufferSize 缓冲大小,如果小于等于0,使用默认
|
||||
* @param streamProgress {@link StreamProgress}进度处理器
|
||||
* @return 拷贝的字节数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
|
||||
Assert.notNull(in, "InputStream is null !");
|
||||
Assert.notNull(out, "OutputStream is null !");
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
|
||||
long size = 0;
|
||||
if (null != streamProgress) {
|
||||
streamProgress.start();
|
||||
}
|
||||
try {
|
||||
while (in.read(byteBuffer) != EOF) {
|
||||
byteBuffer.flip();// 写转读
|
||||
size += out.write(byteBuffer);
|
||||
byteBuffer.clear();
|
||||
if (null != streamProgress) {
|
||||
streamProgress.progress(size);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
if (null != streamProgress) {
|
||||
streamProgress.finish();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容,读取完毕后并不关闭流
|
||||
*
|
||||
* @param channel 可读通道,读取完毕后并不关闭通道
|
||||
* @param charset 字符集
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
|
||||
FastByteArrayOutputStream out = read(channel);
|
||||
return null == charset ? out.toString() : out.toString(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容,读到输出流中
|
||||
*
|
||||
* @param channel 可读通道,读取完毕后并不关闭通道
|
||||
* @return 输出流
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
|
||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
|
||||
copy(channel, Channels.newChannel(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从FileChannel中读取UTF-8编码内容
|
||||
*
|
||||
* @param fileChannel 文件管道
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String readUtf8(FileChannel fileChannel) throws IORuntimeException {
|
||||
return read(fileChannel, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从FileChannel中读取内容,读取完毕后并不关闭Channel
|
||||
*
|
||||
* @param fileChannel 文件管道
|
||||
* @param charsetName 字符集
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String read(FileChannel fileChannel, String charsetName) throws IORuntimeException {
|
||||
return read(fileChannel, CharsetUtil.charset(charsetName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从FileChannel中读取内容
|
||||
*
|
||||
* @param fileChannel 文件管道
|
||||
* @param charset 字符集
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String read(FileChannel fileChannel, Charset charset) throws IORuntimeException {
|
||||
MappedByteBuffer buffer;
|
||||
try {
|
||||
buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).load();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return StrUtil.str(buffer, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭<br>
|
||||
* 关闭失败不会抛出异常
|
||||
*
|
||||
* @param closeable 被关闭的对象
|
||||
*/
|
||||
public static void close(AutoCloseable closeable) {
|
||||
if (null != closeable) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception e) {
|
||||
// 静默关闭
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package cn.hutool.core.lang;
|
||||
|
||||
/**
|
||||
* 片段默认实现
|
||||
*
|
||||
* @param <T> 数字类型,用于表示位置index
|
||||
* @author looly
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public class DefaultSegment<T extends Number> implements Segment<T> {
|
||||
|
||||
protected T startIndex;
|
||||
protected T endIndex;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param startIndex 起始位置
|
||||
* @param endIndex 结束位置
|
||||
*/
|
||||
public DefaultSegment(T startIndex, T endIndex) {
|
||||
this.startIndex = startIndex;
|
||||
this.endIndex = endIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getStartIndex() {
|
||||
return this.startIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getEndIndex() {
|
||||
return this.endIndex;
|
||||
}
|
||||
}
|
41
hutool-core/src/main/java/cn/hutool/core/lang/Segment.java
Normal file
41
hutool-core/src/main/java/cn/hutool/core/lang/Segment.java
Normal file
@ -0,0 +1,41 @@
|
||||
package cn.hutool.core.lang;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* 片段表示,用于表示文本、集合等数据结构的一个区间。
|
||||
* @param <T> 数字类型,用于表示位置index
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public interface Segment<T extends Number> {
|
||||
|
||||
/**
|
||||
* 获取起始位置
|
||||
*
|
||||
* @return 起始位置
|
||||
*/
|
||||
T getStartIndex();
|
||||
|
||||
/**
|
||||
* 获取结束位置
|
||||
*
|
||||
* @return 结束位置
|
||||
*/
|
||||
T getEndIndex();
|
||||
|
||||
/**
|
||||
* 片段长度,默认计算方法为abs({@link #getEndIndex()} - {@link #getEndIndex()})
|
||||
*
|
||||
* @return 片段长度
|
||||
*/
|
||||
default T length(){
|
||||
final T start = Assert.notNull(getStartIndex(), "Start index must be not null!");
|
||||
final T end = Assert.notNull(getEndIndex(), "End index must be not null!");
|
||||
return Convert.convert((Type) start.getClass(), NumberUtil.sub(end, start).abs());
|
||||
}
|
||||
}
|
4231
hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
Normal file
4231
hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -239,7 +239,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
|
||||
*/
|
||||
public StrBuilder insert(int index, CharSequence csq) {
|
||||
if (null == csq) {
|
||||
csq = "null";
|
||||
csq = StrUtil.EMPTY;
|
||||
}
|
||||
int len = csq.length();
|
||||
moveDataAfterIndex(index, csq.length());
|
||||
@ -523,7 +523,8 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
|
||||
* @param minimumCapacity 最小容量
|
||||
*/
|
||||
private void ensureCapacity(int minimumCapacity) {
|
||||
if (minimumCapacity > value.length) {
|
||||
// overflow-conscious code
|
||||
if (minimumCapacity - value.length > 0) {
|
||||
expandCapacity(minimumCapacity);
|
||||
}
|
||||
}
|
||||
@ -535,8 +536,9 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
|
||||
* @param minimumCapacity 需要扩展的最小容量
|
||||
*/
|
||||
private void expandCapacity(int minimumCapacity) {
|
||||
int newCapacity = value.length * 2 + 2;
|
||||
if (newCapacity < minimumCapacity) {
|
||||
int newCapacity = (value.length << 1) + 2;
|
||||
// overflow-conscious code
|
||||
if (newCapacity - minimumCapacity < 0) {
|
||||
newCapacity = minimumCapacity;
|
||||
}
|
||||
if (newCapacity < 0) {
|
||||
|
@ -510,7 +510,7 @@ public class IdcardUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据身份编号获取户籍省份,只支持15或18位身份证号码
|
||||
* 根据身份编号获取市级编码,只支持15或18位身份证号码
|
||||
*
|
||||
* @param idcard 身份编码
|
||||
* @return 市级编码。
|
||||
@ -658,9 +658,9 @@ public class IdcardUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取省份代码
|
||||
* 获取市级编码
|
||||
*
|
||||
* @return 省份代码
|
||||
* @return 市级编码
|
||||
*/
|
||||
public String getCityCode() {
|
||||
return this.cityCode;
|
||||
|
@ -383,7 +383,7 @@ public class ObjectUtil {
|
||||
* 克隆对象<br>
|
||||
* 如果对象实现Cloneable接口,调用其clone方法<br>
|
||||
* 如果实现Serializable接口,执行深度克隆<br>
|
||||
* 否则返回<code>null</code>
|
||||
* 否则返回{@code null}
|
||||
*
|
||||
* @param <T> 对象类型
|
||||
* @param obj 被克隆对象
|
||||
@ -606,11 +606,24 @@ public class ObjectUtil {
|
||||
return ArrayUtil.emptyCount(objs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在{@code null}对象,通过{@link ObjectUtil#isNull(Object)} 判断元素
|
||||
*
|
||||
* @param objs 被检查对象
|
||||
* @return 是否存在
|
||||
* @since 5.5.3
|
||||
* @see ArrayUtil#hasNull(Object[])
|
||||
*/
|
||||
public static boolean hasNull(Object... objs) {
|
||||
return ArrayUtil.hasNull(objs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素
|
||||
*
|
||||
* @param objs 被检查对象
|
||||
* @return 是否存在
|
||||
* @see ArrayUtil#hasEmpty(Object...)
|
||||
*/
|
||||
public static boolean hasEmpty(Object... objs) {
|
||||
return ArrayUtil.hasEmpty(objs);
|
||||
|
@ -1,5 +1,8 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
import cn.hutool.core.lang.DefaultSegment;
|
||||
import cn.hutool.core.lang.Segment;
|
||||
|
||||
/**
|
||||
* 分页工具类
|
||||
*
|
||||
@ -135,6 +138,35 @@ public class PageUtil {
|
||||
return new int[]{start, getEndByStart(start, pageSize)};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将页数和每页条目数转换为开始位置和结束位置<br>
|
||||
* 此方法用于包括结束位置的分页方法<br>
|
||||
* 例如:
|
||||
*
|
||||
* <pre>
|
||||
* 页码:0,每页10 =》 [0, 10]
|
||||
* 页码:1,每页10 =》 [10, 20]
|
||||
* ……
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 当{@link #setFirstPageNo(int)}设置为1时:
|
||||
* <pre>
|
||||
* 页码:1,每页10 =》 [0, 10]
|
||||
* 页码:2,每页10 =》 [10, 20]
|
||||
* ……
|
||||
* </pre>
|
||||
*
|
||||
* @param pageNo 页码(从0计数)
|
||||
* @param pageSize 每页条目数
|
||||
* @return {@link Segment}
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static Segment<Integer> toSegment(int pageNo, int pageSize) {
|
||||
final int[] startEnd = transToStartEnd(pageNo, pageSize);
|
||||
return new DefaultSegment<>(startEnd[0], startEnd[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据总数计算总页数
|
||||
*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -65,6 +65,36 @@ import java.util.Map;
|
||||
*/
|
||||
public class XmlUtil {
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 空格转义 {@code " " -> " "}
|
||||
*/
|
||||
public static final String NBSP = " ";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML And 符转义 {@code "&" -> "&"}
|
||||
*/
|
||||
public static final String AMP = "&";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 双引号转义 {@code """ -> "\""}
|
||||
*/
|
||||
public static final String QUOTE = """;
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 单引号转义 {@code "&apos" -> "'"}
|
||||
*/
|
||||
public static final String APOS = "'";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 小于号转义 {@code "<" -> "<"}
|
||||
*/
|
||||
public static final String LT = "<";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 大于号转义 {@code ">" -> ">"}
|
||||
*/
|
||||
public static final String GT = ">";
|
||||
|
||||
/**
|
||||
* 在XML中无效的字符 正则
|
||||
*/
|
||||
|
@ -0,0 +1,23 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public class TemporalAccessorUtilTest {
|
||||
|
||||
@Test
|
||||
public void formatLocalDateTest(){
|
||||
final String format = TemporalAccessorUtil.format(LocalDate.of(2020, 12, 7), DatePattern.NORM_DATETIME_PATTERN);
|
||||
Assert.assertEquals("2020-12-07 00:00:00", format);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatLocalTimeTest(){
|
||||
final String today = TemporalAccessorUtil.format(LocalDate.now(), DatePattern.NORM_DATE_PATTERN);
|
||||
final String format = TemporalAccessorUtil.format(LocalTime.MIN, DatePattern.NORM_DATETIME_PATTERN);
|
||||
Assert.assertEquals(today + " 00:00:00", format);
|
||||
}
|
||||
}
|
@ -75,6 +75,12 @@ public class IdcardUtilTest {
|
||||
Assert.assertEquals(province2, "内蒙古");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCityCodeByIdCardTest() {
|
||||
String codeByIdCard = IdcardUtil.getCityCodeByIdCard(ID_18);
|
||||
Assert.assertEquals("32108", codeByIdCard);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getGenderByIdCardTest() {
|
||||
int gender = IdcardUtil.getGenderByIdCard(ID_18);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.db;
|
||||
|
||||
import cn.hutool.core.lang.Segment;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import cn.hutool.db.sql.Order;
|
||||
@ -12,7 +13,7 @@ import java.util.Arrays;
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class Page implements Serializable {
|
||||
public class Page implements Segment<Integer>, Serializable {
|
||||
private static final long serialVersionUID = 97792549823353462L;
|
||||
|
||||
public static final int DEFAULT_PAGE_SIZE = 20;
|
||||
@ -159,15 +160,27 @@ public class Page implements Serializable {
|
||||
|
||||
/**
|
||||
* @return 开始位置
|
||||
* @see #getStartIndex()
|
||||
*/
|
||||
public int getStartPosition() {
|
||||
return getStartIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getStartIndex() {
|
||||
return PageUtil.getStart(this.pageNumber, this.pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 结束位置
|
||||
* @see #getEndIndex()
|
||||
*/
|
||||
public int getEndPosition() {
|
||||
return getEndIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getEndIndex() {
|
||||
return PageUtil.getEnd(this.pageNumber, this.pageSize);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.db.sql;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
@ -98,7 +99,7 @@ public class Wrapper {
|
||||
|
||||
//对于Oracle这类数据库,表名中包含用户名需要单独拆分包装
|
||||
if(field.contains(StrUtil.DOT)){
|
||||
final Collection<String> target = CollectionUtil.filter(StrUtil.split(field, StrUtil.C_DOT), (Editor<String>) t -> StrUtil.format("{}{}{}", preWrapQuote, t, sufWrapQuote));
|
||||
final Collection<String> target = CollUtil.filter(StrUtil.split(field, StrUtil.C_DOT, 2), (Editor<String>) t -> StrUtil.format("{}{}{}", preWrapQuote, t, sufWrapQuote));
|
||||
return CollectionUtil.join(target, StrUtil.DOT);
|
||||
}
|
||||
|
||||
|
61
hutool-dfa/src/main/java/cn/hutool/dfa/FoundWord.java
Normal file
61
hutool-dfa/src/main/java/cn/hutool/dfa/FoundWord.java
Normal file
@ -0,0 +1,61 @@
|
||||
package cn.hutool.dfa;
|
||||
|
||||
import cn.hutool.core.lang.DefaultSegment;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 匹配到的单词,包含单词,text中匹配单词的内容,以及匹配内容在text中的下标,
|
||||
* 下标可以用来做单词的进一步处理,如果替换成**
|
||||
*
|
||||
* @author 肖海斌
|
||||
*/
|
||||
public class FoundWord extends DefaultSegment<Integer> {
|
||||
/**
|
||||
* 生效的单词,即单词树中的词
|
||||
*/
|
||||
private final String word;
|
||||
/**
|
||||
* 单词匹配到的内容,即文中的单词
|
||||
*/
|
||||
private final String foundWord;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param word 生效的单词,即单词树中的词
|
||||
* @param foundWord 单词匹配到的内容,即文中的单词
|
||||
* @param startIndex 起始位置(包含)
|
||||
* @param endIndex 结束位置(包含)
|
||||
*/
|
||||
public FoundWord(String word, String foundWord, int startIndex, int endIndex) {
|
||||
super(startIndex, endIndex);
|
||||
this.word = word;
|
||||
this.foundWord = foundWord;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生效的单词,即单词树中的词
|
||||
*
|
||||
* @return 生效的单词
|
||||
*/
|
||||
public String getWord() {
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单词匹配到的内容,即文中的单词
|
||||
* @return 单词匹配到的内容
|
||||
*/
|
||||
public String getFoundWord() {
|
||||
return foundWord;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的,只输出匹配到的关键字
|
||||
* @return 匹配到的关键字
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.foundWord;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cn.hutool.dfa;
|
||||
|
||||
/**
|
||||
* @author 肖海斌
|
||||
* 敏感词过滤处理器,默认按字符数替换成*
|
||||
*/
|
||||
public interface SensitiveProcessor {
|
||||
|
||||
/**
|
||||
* 敏感词过滤处理
|
||||
* @param foundWord 敏感词匹配到的内容
|
||||
* @return 敏感词过滤后的内容,默认按字符数替换成*
|
||||
*/
|
||||
default String process(FoundWord foundWord) {
|
||||
int length = foundWord.getFoundWord().length();
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append("*");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -1,17 +1,20 @@
|
||||
package cn.hutool.dfa;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 敏感词工具类
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public final class SensitiveUtil {
|
||||
|
||||
@ -22,11 +25,12 @@ public final class SensitiveUtil {
|
||||
* @return 是否已经被初始化
|
||||
*/
|
||||
public static boolean isInited() {
|
||||
return !sensitiveTree.isEmpty();
|
||||
return false == sensitiveTree.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化敏感词树
|
||||
*
|
||||
* @param isAsync 是否异步初始化
|
||||
* @param sensitiveWords 敏感词列表
|
||||
*/
|
||||
@ -43,6 +47,7 @@ public final class SensitiveUtil {
|
||||
|
||||
/**
|
||||
* 初始化敏感词树
|
||||
*
|
||||
* @param sensitiveWords 敏感词列表
|
||||
*/
|
||||
public static void init(Collection<String> sensitiveWords) {
|
||||
@ -53,6 +58,7 @@ public final class SensitiveUtil {
|
||||
|
||||
/**
|
||||
* 初始化敏感词树
|
||||
*
|
||||
* @param sensitiveWords 敏感词列表组成的字符串
|
||||
* @param isAsync 是否异步初始化
|
||||
* @param separator 分隔符
|
||||
@ -65,6 +71,7 @@ public final class SensitiveUtil {
|
||||
|
||||
/**
|
||||
* 初始化敏感词树,使用逗号分隔每个单词
|
||||
*
|
||||
* @param sensitiveWords 敏感词列表组成的字符串
|
||||
* @param isAsync 是否异步初始化
|
||||
*/
|
||||
@ -87,6 +94,7 @@ public final class SensitiveUtil {
|
||||
|
||||
/**
|
||||
* 是否包含敏感词
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 是否包含
|
||||
*/
|
||||
@ -96,6 +104,7 @@ public final class SensitiveUtil {
|
||||
|
||||
/**
|
||||
* 是否包含敏感词
|
||||
*
|
||||
* @param obj bean,会被转为JSON字符串
|
||||
* @return 是否包含
|
||||
*/
|
||||
@ -105,31 +114,88 @@ public final class SensitiveUtil {
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的第一个敏感词
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 敏感词
|
||||
* @deprecated 请使用 {@link #getFoundFirstSensitive(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getFindedFirstSensitive(String text) {
|
||||
return sensitiveTree.match(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的第一个敏感词
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 敏感词
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static FoundWord getFoundFirstSensitive(String text) {
|
||||
return sensitiveTree.matchWord(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的第一个敏感词
|
||||
*
|
||||
* @param obj bean,会被转为JSON字符串
|
||||
* @return 敏感词
|
||||
* @deprecated 请使用 {@link #getFoundFirstSensitive(Object)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getFindedFirstSensitive(Object obj) {
|
||||
return sensitiveTree.match(JSONUtil.toJsonStr(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词
|
||||
* @param text 文本
|
||||
* 查找敏感词,返回找到的第一个敏感词
|
||||
*
|
||||
* @param obj bean,会被转为JSON字符串
|
||||
* @return 敏感词
|
||||
*/
|
||||
public static FoundWord getFoundFirstSensitive(Object obj) {
|
||||
return sensitiveTree.matchWord(JSONUtil.toJsonStr(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 敏感词
|
||||
* @deprecated 请使用 {@link #getFoundAllSensitive(String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static List<String> getFindedAllSensitive(String text) {
|
||||
return sensitiveTree.matchAll(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词
|
||||
*
|
||||
* @param text 文本
|
||||
* @return 敏感词
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static List<FoundWord> getFoundAllSensitive(String text) {
|
||||
return sensitiveTree.matchAllWords(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词<br>
|
||||
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
|
||||
* 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab]
|
||||
*
|
||||
* @param text 文本
|
||||
* @param isDensityMatch 是否使用密集匹配原则
|
||||
* @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则
|
||||
* @return 敏感词
|
||||
* @deprecated 请使用 {@link #getFoundAllSensitive(String, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static List<String> getFindedAllSensitive(String text, boolean isDensityMatch, boolean isGreedMatch) {
|
||||
return sensitiveTree.matchAll(text, -1, isDensityMatch, isGreedMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词<br>
|
||||
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
|
||||
@ -140,19 +206,33 @@ public final class SensitiveUtil {
|
||||
* @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则
|
||||
* @return 敏感词
|
||||
*/
|
||||
public static List<String> getFindedAllSensitive(String text, boolean isDensityMatch, boolean isGreedMatch){
|
||||
return sensitiveTree.matchAll(text, -1, isDensityMatch, isGreedMatch);
|
||||
public static List<FoundWord> getFoundAllSensitive(String text, boolean isDensityMatch, boolean isGreedMatch) {
|
||||
return sensitiveTree.matchAllWords(text, -1, isDensityMatch, isGreedMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词
|
||||
*
|
||||
* @param bean 对象,会被转为JSON
|
||||
* @return 敏感词
|
||||
* @deprecated 请使用 {@link #getFoundAllSensitive(Object)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static List<String> getFindedAllSensitive(Object bean) {
|
||||
return sensitiveTree.matchAll(JSONUtil.toJsonStr(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词
|
||||
*
|
||||
* @param bean 对象,会被转为JSON
|
||||
* @return 敏感词
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static List<FoundWord> getFoundAllSensitive(Object bean) {
|
||||
return sensitiveTree.matchAllWords(JSONUtil.toJsonStr(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词<br>
|
||||
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
|
||||
@ -162,8 +242,77 @@ public final class SensitiveUtil {
|
||||
* @param isDensityMatch 是否使用密集匹配原则
|
||||
* @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则
|
||||
* @return 敏感词
|
||||
* @deprecated 请使用 {@link #getFoundAllSensitive(Object, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static List<String> getFindedAllSensitive(Object bean, boolean isDensityMatch, boolean isGreedMatch) {
|
||||
return getFindedAllSensitive(JSONUtil.toJsonStr(bean), isDensityMatch, isGreedMatch);
|
||||
return sensitiveTree.matchAll(JSONUtil.toJsonStr(bean), -1, isDensityMatch, isGreedMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找敏感词,返回找到的所有敏感词<br>
|
||||
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
|
||||
* 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab]
|
||||
*
|
||||
* @param bean 对象,会被转为JSON
|
||||
* @param isDensityMatch 是否使用密集匹配原则
|
||||
* @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则
|
||||
* @return 敏感词
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public static List<FoundWord> getFoundAllSensitive(Object bean, boolean isDensityMatch, boolean isGreedMatch) {
|
||||
return getFoundAllSensitive(JSONUtil.toJsonStr(bean), isDensityMatch, isGreedMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 敏感词过滤
|
||||
*
|
||||
* @param bean 对象,会被转为JSON
|
||||
* @param isGreedMatch 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab]
|
||||
* @param sensitiveProcessor 敏感词处理器,默认按匹配内容的字符数替换成*
|
||||
* @param <T> bean的class类型
|
||||
* @return 敏感词过滤处理后的bean对象
|
||||
*/
|
||||
public static <T> T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
|
||||
String jsonText = JSONUtil.toJsonStr(bean);
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<T> c = (Class<T>) bean.getClass();
|
||||
return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过滤文本中的敏感词,默认替换成*
|
||||
*
|
||||
* @param text 文本
|
||||
* @param isGreedMatch 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab]
|
||||
* @param sensitiveProcessor 敏感词处理器,默认按匹配内容的字符数替换成*
|
||||
* @return 敏感词过滤处理后的文本
|
||||
*/
|
||||
public static String sensitiveFilter(String text, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
//敏感词过滤场景下,不需要密集匹配
|
||||
List<FoundWord> foundWordList = getFoundAllSensitive(text, false, isGreedMatch);
|
||||
if (CollUtil.isEmpty(foundWordList)) {
|
||||
return text;
|
||||
}
|
||||
sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() {
|
||||
} : sensitiveProcessor;
|
||||
Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size());
|
||||
foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord));
|
||||
int length = text.length();
|
||||
StringBuilder textStringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
FoundWord fw = foundWordMap.get(i);
|
||||
if (fw != null) {
|
||||
textStringBuilder.append(sensitiveProcessor.process(fw));
|
||||
i = fw.getEndIndex();
|
||||
} else {
|
||||
textStringBuilder.append(text.charAt(i));
|
||||
}
|
||||
}
|
||||
return textStringBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.dfa;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
@ -31,7 +32,7 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
private static final long serialVersionUID = -4646423269465809276L;
|
||||
|
||||
/**
|
||||
* 敏感词字符末尾标识,用于标识单词末尾字符
|
||||
* 单词字符末尾标识,用于标识单词末尾字符
|
||||
*/
|
||||
private final Set<Character> endCharacterSet = new HashSet<>();
|
||||
/**
|
||||
@ -67,26 +68,30 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
* 增加一组单词
|
||||
*
|
||||
* @param words 单词集合
|
||||
* @return this
|
||||
*/
|
||||
public void addWords(Collection<String> words) {
|
||||
public WordTree addWords(Collection<String> words) {
|
||||
if (false == (words instanceof Set)) {
|
||||
words = new HashSet<>(words);
|
||||
}
|
||||
for (String word : words) {
|
||||
addWord(word);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一组单词
|
||||
*
|
||||
* @param words 单词数组
|
||||
* @return this
|
||||
*/
|
||||
public void addWords(String... words) {
|
||||
public WordTree addWords(String... words) {
|
||||
HashSet<String> wordsSet = CollectionUtil.newHashSet(words);
|
||||
for (String word : wordsSet) {
|
||||
addWord(word);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +99,7 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
*
|
||||
* @param word 单词
|
||||
*/
|
||||
public void addWord(String word) {
|
||||
public WordTree addWord(String word) {
|
||||
final Filter<Character> charFilter = this.charFilter;
|
||||
WordTree parent = null;
|
||||
WordTree current = this;
|
||||
@ -117,8 +122,8 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
if (null != parent) {
|
||||
parent.setEnd(currentChar);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------- match
|
||||
|
||||
/**
|
||||
@ -131,7 +136,7 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
if (null == text) {
|
||||
return false;
|
||||
}
|
||||
return null != match(text);
|
||||
return null != matchWord(text);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,14 +146,23 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
* @return 匹配到的关键字
|
||||
*/
|
||||
public String match(String text) {
|
||||
final FoundWord foundWord = matchWord(text);
|
||||
return null != foundWord ? foundWord.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得第一个匹配的关键字
|
||||
*
|
||||
* @param text 被检查的文本
|
||||
* @return 匹配到的关键字
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public FoundWord matchWord(String text) {
|
||||
if (null == text) {
|
||||
return null;
|
||||
}
|
||||
List<String> matchAll = matchAll(text, 1);
|
||||
if (CollectionUtil.isNotEmpty(matchAll)) {
|
||||
return matchAll.get(0);
|
||||
}
|
||||
return null;
|
||||
final List<FoundWord> matchAll = matchAllWords(text, 1);
|
||||
return CollUtil.get(matchAll, 0);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------- match all
|
||||
@ -163,6 +177,17 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
return matchAll(text, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出所有匹配的关键字
|
||||
*
|
||||
* @param text 被检查的文本
|
||||
* @return 匹配的词列表
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public List<FoundWord> matchAllWords(String text) {
|
||||
return matchAllWords(text, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出所有匹配的关键字
|
||||
*
|
||||
@ -174,6 +199,18 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
return matchAll(text, limit, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出所有匹配的关键字
|
||||
*
|
||||
* @param text 被检查的文本
|
||||
* @param limit 限制匹配个数
|
||||
* @return 匹配的词列表
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public List<FoundWord> matchAllWords(String text, int limit) {
|
||||
return matchAllWords(text, limit, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出所有匹配的关键字<br>
|
||||
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
|
||||
@ -186,19 +223,38 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
* @return 匹配的词列表
|
||||
*/
|
||||
public List<String> matchAll(String text, int limit, boolean isDensityMatch, boolean isGreedMatch) {
|
||||
final List<FoundWord> matchAllWords = matchAllWords(text, limit, isDensityMatch, isGreedMatch);
|
||||
return CollUtil.map(matchAllWords, FoundWord::toString, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 找出所有匹配的关键字<br>
|
||||
* 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]<br>
|
||||
* 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab]
|
||||
*
|
||||
* @param text 被检查的文本
|
||||
* @param limit 限制匹配个数
|
||||
* @param isDensityMatch 是否使用密集匹配原则
|
||||
* @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则
|
||||
* @return 匹配的词列表
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public List<FoundWord> matchAllWords(String text, int limit, boolean isDensityMatch, boolean isGreedMatch) {
|
||||
if (null == text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> foundWords = new ArrayList<>();
|
||||
List<FoundWord> foundWords = new ArrayList<>();
|
||||
WordTree current = this;
|
||||
int length = text.length();
|
||||
final Filter<Character> charFilter = this.charFilter;
|
||||
//存放查找到的字符缓存。完整出现一个词时加到findedWords中,否则清空
|
||||
final StrBuilder wordBuffer = StrUtil.strBuilder();
|
||||
final StrBuilder keyBuffer = StrUtil.strBuilder();
|
||||
char currentChar;
|
||||
for (int i = 0; i < length; i++) {
|
||||
wordBuffer.reset();
|
||||
keyBuffer.reset();
|
||||
for (int j = i; j < length; j++) {
|
||||
currentChar = text.charAt(j);
|
||||
// Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar);
|
||||
@ -216,9 +272,10 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
break;
|
||||
}
|
||||
wordBuffer.append(currentChar);
|
||||
keyBuffer.append(currentChar);
|
||||
if (current.isEnd(currentChar)) {
|
||||
//到达单词末尾,关键词成立,从此词的下一个位置开始查找
|
||||
foundWords.add(wordBuffer.toString());
|
||||
foundWords.add(new FoundWord(keyBuffer.toString(), wordBuffer.toString(), i, j));
|
||||
if (limit > 0 && foundWords.size() >= limit) {
|
||||
//超过匹配限制个数,直接返回
|
||||
return foundWords;
|
||||
@ -241,8 +298,6 @@ public class WordTree extends HashMap<Character, WordTree> {
|
||||
}
|
||||
return foundWords;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
|
@ -1,12 +1,10 @@
|
||||
package cn.hutool.dfa.test;
|
||||
|
||||
import java.util.List;
|
||||
package cn.hutool.dfa;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.dfa.WordTree;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DFA单元测试
|
||||
@ -29,7 +27,7 @@ public class DfaTest {
|
||||
// 匹配到【大】,就不再继续匹配了,因此【大土豆】不匹配
|
||||
// 匹配到【刚出锅】,就跳过这三个字了,因此【出锅】不匹配(由于刚首先被匹配,因此长的被匹配,最短匹配只针对第一个字相同选最短)
|
||||
List<String> matchAll = tree.matchAll(text, -1, false, false);
|
||||
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("大", "土^豆", "刚出锅"));
|
||||
Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "土^豆", "刚出锅"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,7 +43,7 @@ public class DfaTest {
|
||||
// 【大】被匹配,最短匹配原则【大土豆】被跳过,【土豆继续被匹配】
|
||||
// 【刚出锅】被匹配,由于不跳过已经匹配的词,【出锅】被匹配
|
||||
List<String> matchAll = tree.matchAll(text, -1, true, false);
|
||||
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("大", "土^豆", "刚出锅", "出锅"));
|
||||
Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "土^豆", "刚出锅", "出锅"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +59,7 @@ public class DfaTest {
|
||||
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配
|
||||
// 由于【大土豆】被匹配,【土豆】被跳过,由于【刚出锅】被匹配,【出锅】被跳过
|
||||
List<String> matchAll = tree.matchAll(text, -1, false, true);
|
||||
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("大", "大土^豆", "刚出锅"));
|
||||
Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "大土^豆", "刚出锅"));
|
||||
|
||||
}
|
||||
|
||||
@ -78,7 +76,7 @@ public class DfaTest {
|
||||
// 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配,由于不跳过已经匹配的关键词,土豆继续被匹配
|
||||
// 【刚出锅】被匹配,由于不跳过已经匹配的词,【出锅】被匹配
|
||||
List<String> matchAll = tree.matchAll(text, -1, true, true);
|
||||
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("大", "大土^豆", "土^豆", "刚出锅", "出锅"));
|
||||
Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "大土^豆", "土^豆", "刚出锅", "出锅"));
|
||||
|
||||
}
|
||||
|
||||
@ -91,7 +89,7 @@ public class DfaTest {
|
||||
tree.addWord("tio");
|
||||
|
||||
List<String> all = tree.matchAll("AAAAAAAt-ioBBBBBBB");
|
||||
Assert.assertEquals(all, CollectionUtil.newArrayList("t-io"));
|
||||
Assert.assertEquals(all, CollUtil.newArrayList("t-io"));
|
||||
}
|
||||
|
||||
@Test
|
@ -0,0 +1,48 @@
|
||||
package cn.hutool.dfa;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SensitiveUtilTest {
|
||||
|
||||
@Test
|
||||
public void testSensitiveFilter() {
|
||||
List<String> wordList = new ArrayList<>();
|
||||
wordList.add("大");
|
||||
wordList.add("大土豆");
|
||||
wordList.add("土豆");
|
||||
wordList.add("刚出锅");
|
||||
wordList.add("出锅");
|
||||
TestBean bean = new TestBean();
|
||||
bean.setStr("我有一颗$大土^豆,刚出锅的");
|
||||
bean.setNum(100);
|
||||
SensitiveUtil.init(wordList);
|
||||
bean = SensitiveUtil.sensitiveFilter(bean, true, null);
|
||||
Assert.assertEquals(bean.getStr(), "我有一颗$****,***的");
|
||||
}
|
||||
|
||||
public static class TestBean {
|
||||
private String str;
|
||||
private Integer num;
|
||||
|
||||
public String getStr() {
|
||||
return str;
|
||||
}
|
||||
|
||||
public void setStr(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public Integer getNum() {
|
||||
return num;
|
||||
}
|
||||
|
||||
public void setNum(Integer num) {
|
||||
this.num = num;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -130,6 +130,13 @@
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.ethz.ganymed</groupId>
|
||||
<artifactId>ganymed-ssh2</artifactId>
|
||||
<version>262</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- 二维码 -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Spring相关工具封装
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.extra.spring;
|
130
hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java
Normal file
130
hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java
Normal file
@ -0,0 +1,130 @@
|
||||
package cn.hutool.extra.ssh;
|
||||
|
||||
import ch.ethz.ssh2.Connection;
|
||||
import ch.ethz.ssh2.Session;
|
||||
import ch.ethz.ssh2.StreamGobbler;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Ganymed-SSH2封装,见:http://www.ganymed.ethz.ch/ssh2/
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public class GanymedUtil {
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*
|
||||
* @param sshHost 主机
|
||||
* @param sshPort 端口
|
||||
* @return {@link Connection}
|
||||
*/
|
||||
public static Connection connect(String sshHost, int sshPort) {
|
||||
Connection conn = new Connection(sshHost, sshPort);
|
||||
try {
|
||||
conn.connect();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开远程会话
|
||||
*
|
||||
* @param sshHost 主机
|
||||
* @param sshPort 端口
|
||||
* @param sshUser 用户名,如果为null,默认root
|
||||
* @param sshPass 密码
|
||||
* @return {@link Session}
|
||||
*/
|
||||
public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass) {
|
||||
// 默认root用户
|
||||
if (StrUtil.isEmpty(sshUser)) {
|
||||
sshUser = "root";
|
||||
}
|
||||
|
||||
final Connection connect = connect(sshHost, sshPort);
|
||||
try {
|
||||
connect.authenticateWithPassword(sshUser, sshPass);
|
||||
return connect.openSession();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Shell命令(使用EXEC方式)
|
||||
* <p>
|
||||
* 此方法单次发送一个命令到服务端,不读取环境变量,执行结束后自动关闭Session,不会产生阻塞。
|
||||
* </p>
|
||||
*
|
||||
* @param session Session会话
|
||||
* @param cmd 命令
|
||||
* @param charset 发送和读取内容的编码
|
||||
* @param errStream 错误信息输出到的位置
|
||||
*/
|
||||
public static String exec(Session session, String cmd, Charset charset, OutputStream errStream) {
|
||||
final String result;
|
||||
try {
|
||||
session.execCommand(cmd, charset.name());
|
||||
result = IoUtil.read(new StreamGobbler(session.getStdout()), charset);
|
||||
|
||||
// 错误输出
|
||||
IoUtil.copy(new StreamGobbler(session.getStdout()), errStream);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
close(session);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Shell命令
|
||||
* <p>
|
||||
* 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。
|
||||
* </p>
|
||||
*
|
||||
* @param session Session会话
|
||||
* @param cmd 命令
|
||||
* @param charset 发送和读取内容的编码
|
||||
* @param errStream 错误信息输出到的位置
|
||||
*/
|
||||
public static String execByShell(Session session, String cmd, Charset charset, OutputStream errStream) {
|
||||
final String result;
|
||||
try {
|
||||
session.requestDumbPTY();
|
||||
IoUtil.write(session.getStdin(), charset, true, cmd);
|
||||
|
||||
result = IoUtil.read(new StreamGobbler(session.getStdout()), charset);
|
||||
if(null != errStream){
|
||||
// 错误输出
|
||||
IoUtil.copy(new StreamGobbler(session.getStdout()), errStream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
close(session);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭会话
|
||||
*
|
||||
* @param session 会话通道
|
||||
*/
|
||||
public static void close(Session session) {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -417,7 +417,7 @@ public class JschUtil {
|
||||
* @param cmd 命令
|
||||
* @param charset 发送和读取内容的编码
|
||||
* @param errStream 错误信息输出到的位置
|
||||
* @return {@link ChannelExec}
|
||||
* @return 执行结果内容
|
||||
* @since 4.3.1
|
||||
*/
|
||||
public static String exec(Session session, String cmd, Charset charset, OutputStream errStream) {
|
||||
@ -432,7 +432,7 @@ public class JschUtil {
|
||||
try {
|
||||
channel.connect();
|
||||
in = channel.getInputStream();
|
||||
return IoUtil.read(in, CharsetUtil.CHARSET_UTF_8);
|
||||
return IoUtil.read(in, charset);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} catch (JSchException e) {
|
||||
@ -461,7 +461,6 @@ public class JschUtil {
|
||||
shell.setPty(true);
|
||||
OutputStream out = null;
|
||||
InputStream in = null;
|
||||
final StringBuilder result = StrUtil.builder();
|
||||
try {
|
||||
out = shell.getOutputStream();
|
||||
in = shell.getInputStream();
|
||||
@ -469,9 +468,7 @@ public class JschUtil {
|
||||
out.write(StrUtil.bytes(cmd, charset));
|
||||
out.flush();
|
||||
|
||||
while (in.available() > 0) {
|
||||
result.append(IoUtil.read(in, charset));
|
||||
}
|
||||
return IoUtil.read(in, charset);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
@ -479,7 +476,6 @@ public class JschUtil {
|
||||
IoUtil.close(in);
|
||||
close(shell);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,9 +10,9 @@ import org.springframework.test.context.junit4.SpringRunner;
|
||||
* @author sidian
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = EnableSprintUtilTest.class)
|
||||
@SpringBootTest(classes = EnableSpringUtilTest.class)
|
||||
@EnableSpringUtil
|
||||
public class EnableSprintUtilTest {
|
||||
public class EnableSpringUtilTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
@ -240,11 +240,14 @@ final class InternalJSONUtil {
|
||||
*/
|
||||
private static String formatDate(Object dateObj, String format) {
|
||||
if (StrUtil.isNotBlank(format)) {
|
||||
final String dateStr;
|
||||
if(dateObj instanceof TemporalAccessor){
|
||||
return TemporalAccessorUtil.format((TemporalAccessor) dateObj, format);
|
||||
dateStr = TemporalAccessorUtil.format((TemporalAccessor) dateObj, format);
|
||||
} else{
|
||||
dateStr = DateUtil.format(Convert.toDate(dateObj), format);
|
||||
}
|
||||
//用户定义了日期格式
|
||||
return JSONUtil.quote(DateUtil.format(Convert.toDate(dateObj), format));
|
||||
return JSONUtil.quote(dateStr);
|
||||
}
|
||||
|
||||
//默认使用时间戳
|
||||
|
@ -115,7 +115,8 @@ public class JSONConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置日期格式,null表示默认的时间戳
|
||||
* 设置日期格式,null表示默认的时间戳<br>
|
||||
* 此方法设置的日期格式仅对转换为JSON字符串有效,对解析JSON为bean无效。
|
||||
*
|
||||
* @param dateFormat 日期格式,null表示默认的时间戳
|
||||
* @return this
|
||||
|
@ -20,10 +20,10 @@ public interface JSONGetter<K> extends OptNullBasicTypeFromObjectGetter<K> {
|
||||
JSONConfig getConfig();
|
||||
|
||||
/**
|
||||
* key对应值是否为<code>null</code>或无此key
|
||||
* key对应值是否为{@code null}或无此key
|
||||
*
|
||||
* @param key 键
|
||||
* @return true 无此key或值为<code>null</code>或{@link JSONNull#NULL}返回<code>false</code>,其它返回<code>true</code>
|
||||
* @return true 无此key或值为{@code null}或{@link JSONNull#NULL}返回{@code false},其它返回{@code true}
|
||||
*/
|
||||
default boolean isNull(K key) {
|
||||
return JSONNull.NULL.equals(this.getObj(key));
|
||||
|
@ -261,7 +261,8 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置转为字符串时的日期格式,默认为时间戳(null值)
|
||||
* 设置转为字符串时的日期格式,默认为时间戳(null值)<br>
|
||||
* 此方法设置的日期格式仅对转换为JSON字符串有效,对解析JSON为bean无效。
|
||||
*
|
||||
* @param format 格式,null表示使用时间戳
|
||||
* @return this
|
||||
@ -341,7 +342,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 键值对到JSONObject中,在忽略null模式下,如果值为<code>null</code>,将此键移除
|
||||
* PUT 键值对到JSONObject中,在忽略null模式下,如果值为{@code null},将此键移除
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL.
|
||||
@ -356,7 +357,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置键值对到JSONObject中,在忽略null模式下,如果值为<code>null</code>,将此键移除
|
||||
* 设置键值对到JSONObject中,在忽略null模式下,如果值为{@code null},将此键移除
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL.
|
||||
@ -426,7 +427,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
* @param key 键
|
||||
* @param value 被积累的值
|
||||
* @return this.
|
||||
* @throws JSONException 如果给定键为<code>null</code>或者键对应的值存在且为非JSONArray
|
||||
* @throws JSONException 如果给定键为{@code null}或者键对应的值存在且为非JSONArray
|
||||
*/
|
||||
public JSONObject accumulate(String key, Object value) throws JSONException {
|
||||
InternalJSONUtil.testValidity(value);
|
||||
@ -447,7 +448,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return this.
|
||||
* @throws JSONException 如果给定键为<code>null</code>或者键对应的值存在且为非JSONArray
|
||||
* @throws JSONException 如果给定键为{@code null}或者键对应的值存在且为非JSONArray
|
||||
*/
|
||||
public JSONObject append(String key, Object value) throws JSONException {
|
||||
InternalJSONUtil.testValidity(value);
|
||||
@ -545,7 +546,7 @@ public class JSONObject implements JSON, JSONGetter<String>, Map<String, Object>
|
||||
|
||||
/**
|
||||
* 返回JSON字符串<br>
|
||||
* 如果解析错误,返回<code>null</code>
|
||||
* 如果解析错误,返回{@code null}
|
||||
*
|
||||
* @return JSON字符串
|
||||
*/
|
||||
|
@ -9,7 +9,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
* @author looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class JSONStrFormater {
|
||||
public class JSONStrFormatter {
|
||||
|
||||
/** 单位缩进字符串。*/
|
||||
private static final String SPACE = " ";
|
@ -694,12 +694,12 @@ public final class JSONUtil {
|
||||
* 在需要的时候包装对象<br>
|
||||
* 包装包括:
|
||||
* <ul>
|
||||
* <li><code>null</code> =》 <code>JSONNull.NULL</code></li>
|
||||
* <li>{@code null} =》 {@code JSONNull.NULL}</li>
|
||||
* <li>array or collection =》 JSONArray</li>
|
||||
* <li>map =》 JSONObject</li>
|
||||
* <li>standard property (Double, String, et al) =》 原对象</li>
|
||||
* <li>来自于java包 =》 字符串</li>
|
||||
* <li>其它 =》 尝试包装为JSONObject,否则返回<code>null</code></li>
|
||||
* <li>其它 =》 尝试包装为JSONObject,否则返回{@code null}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param object 被包装的对象
|
||||
@ -776,7 +776,7 @@ public final class JSONUtil {
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public static String formatJsonStr(String jsonStr) {
|
||||
return JSONStrFormater.format(jsonStr);
|
||||
return JSONStrFormatter.format(jsonStr);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,21 +13,21 @@ public class JSONStrFormaterTest {
|
||||
@Test
|
||||
public void formatTest() {
|
||||
String json = "{'age':23,'aihao':['pashan','movies'],'name':{'firstName':'zhang','lastName':'san','aihao':['pashan','movies','name':{'firstName':'zhang','lastName':'san','aihao':['pashan','movies']}]}}";
|
||||
String result = JSONStrFormater.format(json);
|
||||
String result = JSONStrFormatter.format(json);
|
||||
Assert.assertNotNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatTest2() {
|
||||
String json = "{\"abc\":{\"def\":\"\\\"[ghi]\"}}";
|
||||
String result = JSONStrFormater.format(json);
|
||||
String result = JSONStrFormatter.format(json);
|
||||
Assert.assertNotNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatTest3() {
|
||||
String json = "{\"id\":13,\"title\":\"《标题》\",\"subtitle\":\"副标题z'c'z'xv'c'xv\",\"user_id\":6,\"type\":0}";
|
||||
String result = JSONStrFormater.format(json);
|
||||
String result = JSONStrFormatter.format(json);
|
||||
Assert.assertNotNull(result);
|
||||
}
|
||||
}
|
||||
|
@ -2,30 +2,19 @@ package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.cell.FormulaCellValue;
|
||||
import cn.hutool.poi.excel.sax.handler.RowHandler;
|
||||
import cn.hutool.poi.exceptions.POIException;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||
import org.apache.poi.ss.usermodel.BuiltinFormats;
|
||||
import org.apache.poi.xssf.eventusermodel.XSSFReader;
|
||||
import org.apache.poi.xssf.model.SharedStringsTable;
|
||||
import org.apache.poi.xssf.model.StylesTable;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sax方式读取Excel文件<br>
|
||||
@ -34,50 +23,11 @@ import java.util.List;
|
||||
* @author Looly
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<Excel07SaxReader> {
|
||||
public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
|
||||
// sheet r:Id前缀
|
||||
private static final String RID_PREFIX = "rId";
|
||||
|
||||
// 单元格的格式表,对应style.xml
|
||||
private StylesTable stylesTable;
|
||||
// excel 2007 的共享字符串表,对应sharedString.xml
|
||||
private SharedStringsTable sharedStringsTable;
|
||||
// sheet的索引
|
||||
private int sheetIndex;
|
||||
|
||||
// 当前非空行
|
||||
private int index;
|
||||
// 当前列
|
||||
private int curCell;
|
||||
// 单元数据类型
|
||||
private CellDataType cellDataType;
|
||||
// 当前行号,从0开始
|
||||
private long rowNumber;
|
||||
// 当前列坐标, 如A1,B5
|
||||
private String curCoordinate;
|
||||
// 当前节点名称
|
||||
private ElementName curElementName;
|
||||
// 前一个列的坐标
|
||||
private String preCoordinate;
|
||||
// 行的最大列坐标
|
||||
private String maxCellCoordinate;
|
||||
// 单元格样式
|
||||
private XSSFCellStyle xssfCellStyle;
|
||||
// 单元格存储的格式化字符串,nmtFmt的formatCode属性的值
|
||||
private String numFmtString;
|
||||
|
||||
// 上一次的内容
|
||||
private final StrBuilder lastContent = StrUtil.strBuilder();
|
||||
// 上一次的内容
|
||||
private final StrBuilder lastFormula = StrUtil.strBuilder();
|
||||
// 存储每行的列元素
|
||||
private List<Object> rowCellList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 行处理器
|
||||
*/
|
||||
private RowHandler rowHandler;
|
||||
private final SheetDataSaxHandler handler;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@ -85,7 +35,7 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
|
||||
* @param rowHandler 行处理器
|
||||
*/
|
||||
public Excel07SaxReader(RowHandler rowHandler) {
|
||||
this.rowHandler = rowHandler;
|
||||
this.handler = new SheetDataSaxHandler(rowHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +45,7 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
|
||||
* @return this
|
||||
*/
|
||||
public Excel07SaxReader setRowHandler(RowHandler rowHandler) {
|
||||
this.rowHandler = rowHandler;
|
||||
this.handler.setRowHandler(rowHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -170,16 +120,16 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
|
||||
* @since 5.4.4
|
||||
*/
|
||||
public Excel07SaxReader read(XSSFReader xssfReader, String idOrRid) throws POIException {
|
||||
// 获取共享样式表
|
||||
// 获取共享样式表,样式非必须
|
||||
try {
|
||||
stylesTable = xssfReader.getStylesTable();
|
||||
} catch (Exception e) {
|
||||
this.handler.stylesTable = xssfReader.getStylesTable();
|
||||
} catch (IOException | InvalidFormatException ignore) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// 获取共享字符串表
|
||||
try {
|
||||
this.sharedStringsTable = xssfReader.getSharedStringsTable();
|
||||
this.handler.sharedStringsTable = xssfReader.getSharedStringsTable();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} catch (InvalidFormatException e) {
|
||||
@ -190,62 +140,6 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
|
||||
}
|
||||
// ------------------------------------------------------------------------------ Read end
|
||||
|
||||
/**
|
||||
* 读到一个xml开始标签时的回调处理方法
|
||||
*/
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
final ElementName name = ElementName.of(localName);
|
||||
this.curElementName = name;
|
||||
|
||||
if(null != name){
|
||||
switch (name){
|
||||
case row:
|
||||
// 行开始
|
||||
startRow(attributes);
|
||||
break;
|
||||
case c:
|
||||
// 单元格元素
|
||||
startCell(attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签结束的回调处理方法
|
||||
*/
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
this.curElementName = null;
|
||||
if (ElementName.c.match(localName)) { // 单元格结束
|
||||
endCell();
|
||||
} else if (ElementName.row.match(localName)) {// 行结束
|
||||
endRow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* s标签结束的回调处理方法
|
||||
*/
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
final ElementName elementName = this.curElementName;
|
||||
if(null != elementName){
|
||||
switch (elementName){
|
||||
case v:
|
||||
// 得到单元格内容的值
|
||||
lastContent.append(ch, start, length);
|
||||
break;
|
||||
case f:
|
||||
// 得到单元格内容的值
|
||||
lastFormula.append(ch, start, length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 其它标签忽略
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
@ -266,25 +160,25 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
|
||||
idOrRid = rid;
|
||||
}
|
||||
}
|
||||
this.sheetIndex = Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRid, RID_PREFIX));
|
||||
this.handler.sheetIndex = Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRid, RID_PREFIX));
|
||||
InputStream sheetInputStream = null;
|
||||
try {
|
||||
if (this.sheetIndex > -1) {
|
||||
if (this.handler.sheetIndex > -1) {
|
||||
// 根据 rId# 或 rSheet# 查找sheet
|
||||
sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.sheetIndex + 1));
|
||||
ExcelSaxUtil.readFrom(sheetInputStream, this);
|
||||
rowHandler.doAfterAllAnalysed();
|
||||
sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.handler.sheetIndex + 1));
|
||||
ExcelSaxUtil.readFrom(sheetInputStream, this.handler);
|
||||
this.handler.rowHandler.doAfterAllAnalysed();
|
||||
} else {
|
||||
this.sheetIndex = -1;
|
||||
this.handler.sheetIndex = -1;
|
||||
// 遍历所有sheet
|
||||
final Iterator<InputStream> sheetInputStreams = xssfReader.getSheetsData();
|
||||
while (sheetInputStreams.hasNext()) {
|
||||
// 重新读取一个sheet时行归零
|
||||
index = 0;
|
||||
this.sheetIndex++;
|
||||
this.handler.index = 0;
|
||||
this.handler.sheetIndex++;
|
||||
sheetInputStream = sheetInputStreams.next();
|
||||
ExcelSaxUtil.readFrom(sheetInputStream, this);
|
||||
rowHandler.doAfterAllAnalysed();
|
||||
ExcelSaxUtil.readFrom(sheetInputStream, this.handler);
|
||||
this.handler.rowHandler.doAfterAllAnalysed();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
@ -296,140 +190,5 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 行开始
|
||||
*
|
||||
* @param attributes 属性列表
|
||||
*/
|
||||
private void startRow(Attributes attributes) {
|
||||
this.rowNumber = Long.parseLong(AttributeName.r.getValue(attributes)) - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单元格开始
|
||||
*
|
||||
* @param attributes 属性列表
|
||||
*/
|
||||
private void startCell(Attributes attributes) {
|
||||
// 获取当前列坐标
|
||||
final String tempCurCoordinate = AttributeName.r.getValue(attributes);
|
||||
// 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64
|
||||
if (preCoordinate == null) {
|
||||
preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR);
|
||||
} else {
|
||||
// 存在,则前一列要设置为上一列的坐标
|
||||
preCoordinate = curCoordinate;
|
||||
}
|
||||
// 重置当前列
|
||||
curCoordinate = tempCurCoordinate;
|
||||
// 设置单元格类型
|
||||
setCellType(attributes);
|
||||
|
||||
// 清空之前的数据
|
||||
lastContent.reset();
|
||||
lastFormula.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个单元格结尾
|
||||
*/
|
||||
private void endCell() {
|
||||
// 补全单元格之间的空格
|
||||
fillBlankCell(preCoordinate, curCoordinate, false);
|
||||
|
||||
final String contentStr = StrUtil.trim(lastContent);
|
||||
Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString);
|
||||
if(false == this.lastFormula.isEmpty()){
|
||||
value = new FormulaCellValue(StrUtil.trim(lastFormula), value);
|
||||
}
|
||||
addCellValue(curCell++, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一行结尾
|
||||
*/
|
||||
private void endRow() {
|
||||
// 最大列坐标以第一个非空行的为准
|
||||
if (index == 0) {
|
||||
maxCellCoordinate = curCoordinate;
|
||||
}
|
||||
|
||||
// 补全一行尾部可能缺失的单元格
|
||||
if (maxCellCoordinate != null) {
|
||||
fillBlankCell(curCoordinate, maxCellCoordinate, true);
|
||||
}
|
||||
|
||||
rowHandler.handle(sheetIndex, rowNumber, rowCellList);
|
||||
|
||||
// 一行结束
|
||||
// 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理)
|
||||
rowCellList = new ArrayList<>(curCell + 1);
|
||||
// 行数增加
|
||||
index++;
|
||||
// 当前列置0
|
||||
curCell = 0;
|
||||
// 置空当前列坐标和前一列坐标
|
||||
curCoordinate = null;
|
||||
preCoordinate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一行中的指定列增加值
|
||||
*
|
||||
* @param index 位置
|
||||
* @param value 值
|
||||
*/
|
||||
private void addCellValue(int index, Object value) {
|
||||
this.rowCellList.add(index, value);
|
||||
this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充空白单元格,如果前一个单元格大于后一个,不需要填充<br>
|
||||
*
|
||||
* @param preCoordinate 前一个单元格坐标
|
||||
* @param curCoordinate 当前单元格坐标
|
||||
* @param isEnd 是否为最后一个单元格
|
||||
*/
|
||||
private void fillBlankCell(String preCoordinate, String curCoordinate, boolean isEnd) {
|
||||
if (false == curCoordinate.equals(preCoordinate)) {
|
||||
int len = ExcelSaxUtil.countNullCell(preCoordinate, curCoordinate);
|
||||
if (isEnd) {
|
||||
len++;
|
||||
}
|
||||
while (len-- > 0) {
|
||||
addCellValue(curCell++, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单元格的类型
|
||||
*
|
||||
* @param attributes 属性
|
||||
*/
|
||||
private void setCellType(Attributes attributes) {
|
||||
// numFmtString的值
|
||||
numFmtString = StrUtil.EMPTY;
|
||||
this.cellDataType = CellDataType.of(AttributeName.t.getValue(attributes));
|
||||
|
||||
// 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf
|
||||
if (null != this.stylesTable) {
|
||||
final String xfIndexStr = AttributeName.s.getValue(attributes);
|
||||
if (null != xfIndexStr) {
|
||||
this.xssfCellStyle = stylesTable.getStyleAt(Integer.parseInt(xfIndexStr));
|
||||
// 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引
|
||||
final int numFmtIndex = xssfCellStyle.getDataFormat();
|
||||
this.numFmtString = ObjectUtil.defaultIfNull(
|
||||
xssfCellStyle.getDataFormatString(),
|
||||
BuiltinFormats.getBuiltinFormat(numFmtIndex));
|
||||
if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) {
|
||||
cellDataType = CellDataType.DATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// --------------------------------------------------------------------------------------- Private method end
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.cell.FormulaCellValue;
|
||||
import cn.hutool.poi.excel.sax.handler.RowHandler;
|
||||
import org.apache.poi.ss.usermodel.BuiltinFormats;
|
||||
import org.apache.poi.xssf.model.SharedStringsTable;
|
||||
import org.apache.poi.xssf.model.StylesTable;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* sheetData标签内容读取处理器
|
||||
*
|
||||
* <pre>
|
||||
* <sheetData></sheetData>
|
||||
* </pre>
|
||||
* @since 5.5.3
|
||||
*/
|
||||
public class SheetDataSaxHandler extends DefaultHandler {
|
||||
|
||||
// 单元格的格式表,对应style.xml
|
||||
protected StylesTable stylesTable;
|
||||
// excel 2007 的共享字符串表,对应sharedString.xml
|
||||
protected SharedStringsTable sharedStringsTable;
|
||||
// sheet的索引
|
||||
protected int sheetIndex;
|
||||
|
||||
// 当前非空行
|
||||
protected int index;
|
||||
// 当前列
|
||||
private int curCell;
|
||||
// 单元数据类型
|
||||
private CellDataType cellDataType;
|
||||
// 当前行号,从0开始
|
||||
private long rowNumber;
|
||||
// 当前列坐标, 如A1,B5
|
||||
private String curCoordinate;
|
||||
// 当前节点名称
|
||||
private ElementName curElementName;
|
||||
// 前一个列的坐标
|
||||
private String preCoordinate;
|
||||
// 行的最大列坐标
|
||||
private String maxCellCoordinate;
|
||||
// 单元格样式
|
||||
private XSSFCellStyle xssfCellStyle;
|
||||
// 单元格存储的格式化字符串,nmtFmt的formatCode属性的值
|
||||
private String numFmtString;
|
||||
// 是否处于sheetData标签内,sax只解析此标签内的内容,其它标签忽略
|
||||
private boolean isInSheetData;
|
||||
|
||||
// 上一次的内容
|
||||
private final StrBuilder lastContent = StrUtil.strBuilder();
|
||||
// 上一次的内容
|
||||
private final StrBuilder lastFormula = StrUtil.strBuilder();
|
||||
// 存储每行的列元素
|
||||
private List<Object> rowCellList = new ArrayList<>();
|
||||
|
||||
public SheetDataSaxHandler(RowHandler rowHandler){
|
||||
this.rowHandler = rowHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 行处理器
|
||||
*/
|
||||
protected RowHandler rowHandler;
|
||||
|
||||
/**
|
||||
* 设置行处理器
|
||||
*
|
||||
* @param rowHandler 行处理器
|
||||
*/
|
||||
public void setRowHandler(RowHandler rowHandler) {
|
||||
this.rowHandler = rowHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读到一个xml开始标签时的回调处理方法
|
||||
*/
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
if ("sheetData".equals(qName)) {
|
||||
this.isInSheetData = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (false == this.isInSheetData) {
|
||||
// 非sheetData标签,忽略解析
|
||||
return;
|
||||
}
|
||||
|
||||
final ElementName name = ElementName.of(qName);
|
||||
this.curElementName = name;
|
||||
|
||||
if (null != name) {
|
||||
switch (name) {
|
||||
case row:
|
||||
// 行开始
|
||||
startRow(attributes);
|
||||
break;
|
||||
case c:
|
||||
// 单元格元素
|
||||
startCell(attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签结束的回调处理方法
|
||||
*/
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if ("sheetData".equals(qName)) {
|
||||
// sheetData结束,不再解析别的标签
|
||||
this.isInSheetData = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (false == this.isInSheetData) {
|
||||
// 非sheetData标签,忽略解析
|
||||
return;
|
||||
}
|
||||
|
||||
this.curElementName = null;
|
||||
if (ElementName.c.match(qName)) { // 单元格结束
|
||||
endCell();
|
||||
} else if (ElementName.row.match(qName)) {// 行结束
|
||||
endRow();
|
||||
}
|
||||
// 其它标签忽略
|
||||
}
|
||||
|
||||
/**
|
||||
* s标签结束的回调处理方法
|
||||
*/
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
if (false == this.isInSheetData) {
|
||||
// 非sheetData标签,忽略解析
|
||||
return;
|
||||
}
|
||||
|
||||
final ElementName elementName = this.curElementName;
|
||||
if (null != elementName) {
|
||||
switch (elementName) {
|
||||
case v:
|
||||
// 得到单元格内容的值
|
||||
lastContent.append(ch, start, length);
|
||||
break;
|
||||
case f:
|
||||
// 得到单元格内容的值
|
||||
lastFormula.append(ch, start, length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 其它标签忽略
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 行开始
|
||||
*
|
||||
* @param attributes 属性列表
|
||||
*/
|
||||
private void startRow(Attributes attributes) {
|
||||
final String rValue = AttributeName.r.getValue(attributes);
|
||||
if (null != rValue) {
|
||||
this.rowNumber = Long.parseLong(rValue) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单元格开始
|
||||
*
|
||||
* @param attributes 属性列表
|
||||
*/
|
||||
private void startCell(Attributes attributes) {
|
||||
// 获取当前列坐标
|
||||
final String tempCurCoordinate = AttributeName.r.getValue(attributes);
|
||||
// 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64
|
||||
if (preCoordinate == null) {
|
||||
preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR);
|
||||
} else {
|
||||
// 存在,则前一列要设置为上一列的坐标
|
||||
preCoordinate = curCoordinate;
|
||||
}
|
||||
// 重置当前列
|
||||
curCoordinate = tempCurCoordinate;
|
||||
// 设置单元格类型
|
||||
setCellType(attributes);
|
||||
|
||||
// 清空之前的数据
|
||||
lastContent.reset();
|
||||
lastFormula.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 一行结尾
|
||||
*/
|
||||
private void endRow() {
|
||||
// 最大列坐标以第一个非空行的为准
|
||||
if (index == 0) {
|
||||
maxCellCoordinate = curCoordinate;
|
||||
}
|
||||
|
||||
// 补全一行尾部可能缺失的单元格
|
||||
if (maxCellCoordinate != null) {
|
||||
fillBlankCell(curCoordinate, maxCellCoordinate, true);
|
||||
}
|
||||
|
||||
rowHandler.handle(sheetIndex, rowNumber, rowCellList);
|
||||
|
||||
// 一行结束
|
||||
// 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理)
|
||||
rowCellList = new ArrayList<>(curCell + 1);
|
||||
// 行数增加
|
||||
index++;
|
||||
// 当前列置0
|
||||
curCell = 0;
|
||||
// 置空当前列坐标和前一列坐标
|
||||
curCoordinate = null;
|
||||
preCoordinate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个单元格结尾
|
||||
*/
|
||||
private void endCell() {
|
||||
// 补全单元格之间的空格
|
||||
fillBlankCell(preCoordinate, curCoordinate, false);
|
||||
|
||||
final String contentStr = StrUtil.trim(lastContent);
|
||||
Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString);
|
||||
if (false == this.lastFormula.isEmpty()) {
|
||||
value = new FormulaCellValue(StrUtil.trim(lastFormula), value);
|
||||
}
|
||||
addCellValue(curCell++, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在一行中的指定列增加值
|
||||
*
|
||||
* @param index 位置
|
||||
* @param value 值
|
||||
*/
|
||||
private void addCellValue(int index, Object value) {
|
||||
this.rowCellList.add(index, value);
|
||||
this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充空白单元格,如果前一个单元格大于后一个,不需要填充<br>
|
||||
*
|
||||
* @param preCoordinate 前一个单元格坐标
|
||||
* @param curCoordinate 当前单元格坐标
|
||||
* @param isEnd 是否为最后一个单元格
|
||||
*/
|
||||
private void fillBlankCell(String preCoordinate, String curCoordinate, boolean isEnd) {
|
||||
if (false == curCoordinate.equals(preCoordinate)) {
|
||||
int len = ExcelSaxUtil.countNullCell(preCoordinate, curCoordinate);
|
||||
if (isEnd) {
|
||||
len++;
|
||||
}
|
||||
while (len-- > 0) {
|
||||
addCellValue(curCell++, StrUtil.EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单元格的类型
|
||||
*
|
||||
* @param attributes 属性
|
||||
*/
|
||||
private void setCellType(Attributes attributes) {
|
||||
// numFmtString的值
|
||||
numFmtString = StrUtil.EMPTY;
|
||||
this.cellDataType = CellDataType.of(AttributeName.t.getValue(attributes));
|
||||
|
||||
// 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf
|
||||
if (null != this.stylesTable) {
|
||||
final String xfIndexStr = AttributeName.s.getValue(attributes);
|
||||
if (null != xfIndexStr) {
|
||||
this.xssfCellStyle = stylesTable.getStyleAt(Integer.parseInt(xfIndexStr));
|
||||
// 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引
|
||||
final int numFmtIndex = xssfCellStyle.getDataFormat();
|
||||
this.numFmtString = ObjectUtil.defaultIfNull(
|
||||
xssfCellStyle.getDataFormatString(),
|
||||
BuiltinFormats.getBuiltinFormat(numFmtIndex));
|
||||
if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) {
|
||||
cellDataType = CellDataType.DATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- Private method end
|
||||
}
|
@ -175,4 +175,12 @@ public class ExcelSaxReadTest {
|
||||
|
||||
ExcelUtil.getReader(file).read().forEach(Console::log);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void readXlsmTest(){
|
||||
ExcelUtil.readBySax("d:/test/WhiteListTemplate.xlsm", -1, (sheetIndex, rowIndex, rowlist) -> {
|
||||
Console.log("[{}] [{}] {}", sheetIndex, rowIndex, rowlist);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user