Merge branch 'v5-dev' of https://github.com/totalo/hutool into v5-dev

This commit is contained in:
totalo 2020-10-10 23:11:43 +08:00
commit d97db8c2e1
53 changed files with 1024 additions and 502 deletions

View File

@ -3,6 +3,23 @@
-------------------------------------------------------------------------------------------------------------
# 5.4.5 (2020-10-09)
### 新特性
* 【core 】 ConsoleTable代码优化pr#190@Gitee
* 【http 】 HttpRequest增加setProxy重载pr#190@Gitee
* 【core 】 XmlUtil.cleanCommentpr#191@Gitee
* 【core 】 ArrayUtil.unWrap增加默认值pr#1149@Github
* 【core 】 ArrayUtil.indexOf修改double的equals判断pr#1147@Github
* 【core 】 优化StrUtil中部分参数校验以及逻辑处理pr#1144@Github
* 【core 】 简化CreditCode逻辑去除无用Character.toUpperCasepr#1145@Github
### Bug修复
* 【core 】 解决农历判断节日未判断大小月导致的问题issue#I1XHSF@Gitee
* 【core 】 解决ListUtil计算总量可能的int溢出问题pr#1150@Github
-------------------------------------------------------------------------------------------------------------
# 5.4.4 (2020-09-28)
### 新特性
@ -31,6 +48,8 @@
* 【core 】 NumberUtil.factorial注释明确pr#1126@Github
* 【core 】 NumberUtil增加isPowerOfTwo方法pr#1132@Github
* 【core 】 优化BooleanUtil的校验逻辑pr#1137@Github
* 【poi 】 改进sax方式读取逻辑支持sheetIdissue#1141@Github
* 【core 】 XmlUtil增加readBySax方法
### Bug修复
* 【crypto 】 修复SM2验签后无法解密问题issue#I1W0VP@Gitee

View File

@ -120,19 +120,19 @@ Each module can be introduced individually, or all modules can be introduced by
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.4</version>
<version>5.4.5</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.4.4'
compile 'cn.hutool:hutool-all:5.4.5'
```
## Download
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.5/)
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.5/)
> note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.

View File

@ -119,21 +119,21 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.4</version>
<version>5.4.5</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.4.4'
compile 'cn.hutool:hutool-all:5.4.5'
```
### 非Maven项目
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.4/)
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.5/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.5/)
> 注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。
@ -141,7 +141,7 @@ compile 'cn.hutool:hutool-all:5.4.4'
### 编译安装
访问Hutool的码云主页:[https://gitee.com/loolly/hutool](https://gitee.com/loolly/hutool) 下载整个项目源码v5-master或v5-dev分支都可然后进入Hutool项目目录执行
访问Hutool的Gitee主页:[https://gitee.com/loolly/hutool](https://gitee.com/loolly/hutool) 下载整个项目源码v5-master或v5-dev分支都可然后进入Hutool项目目录执行
```sh
./hutool.sh install
@ -166,7 +166,7 @@ Hutool的源码分为两个分支功能如下
提交问题反馈请说明正在使用的JDK版本呢、Hutool版本和相关依赖库版本。
- [码云Gitee issue](https://gitee.com/loolly/hutool/issues)
- [Gitee issue](https://gitee.com/loolly/hutool/issues)
- [Github issue](https://github.com/looly/hutool/issues)
@ -177,14 +177,14 @@ Hutool的源码分为两个分支功能如下
3. 修改代码记得一定要修改v5-dev分支
4. commit后push到自己的库v5-dev分支
5. 登录Gitee或Github在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
6. 等待者合并
6. 等待维护者合并
### PR遵照的原则
Hutool欢迎任何人为Hutool添砖加瓦贡献代码不过者是一个强迫症患者为了照顾病人需要提交的prpull request符合一些规范规范如下
Hutool欢迎任何人为Hutool添砖加瓦贡献代码不过维护者是一个强迫症患者为了照顾病人需要提交的prpull request符合一些规范规范如下
1. 注释完备尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息必要时请添加单元测试如果愿意也可以加上你的大名。
2. Hutool的缩进按照Eclipse~~不要跟我说IDEA多好用者非常懒,学不会~~IDEA真香改了Eclipse快捷键后舒服多了默认tab缩进所以请遵守不要和我争执空格与tab的问题这是一个病人的习惯
2. Hutool的缩进按照Eclipse~~不要跟我说IDEA多好用维护者非常懒,学不会~~IDEA真香改了Eclipse快捷键后舒服多了默认tab缩进所以请遵守不要和我争执空格与tab的问题这是一个病人的习惯
3. 新加的方法不要使用第三方库的方法Hutool遵循无依赖原则除非在extra模块中加方法工具
4. 请pull request到`v5-dev`分支。Hutool在5.x版本后使用了新的分支`v5-master`是主分支表示已经发布中央库的版本这个分支不允许pr也不允许修改。
@ -192,7 +192,7 @@ Hutool欢迎任何人为Hutool添砖加瓦贡献代码不过作者是一
## 捐赠
如果你觉得Hutool不错可以捐赠请者吃包辣条~,在此表示感谢^_^。
如果你觉得Hutool不错可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
点击以下链接,将页面拉到最下方点击“捐赠”即可。

View File

@ -1 +1 @@
5.4.4
5.4.5

View File

@ -1 +1 @@
var version = '5.4.4'
var version = '5.4.5'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-aop</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-captcha</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-core</artifactId>

View File

@ -252,8 +252,8 @@ public class ListUtil {
return new ArrayList<>(0);
}
}
if ((pageNo * pageSize) > resultSize) {
// 相乘可能会导致越界 临时用long
if (((long)pageNo * pageSize) > resultSize) {
// 越界直接返回空
return new ArrayList<>(0);
}

View File

@ -167,7 +167,7 @@ public class ChineseDate {
* @return 是否为闰月
* @since 5.4.2
*/
public boolean isLeapMonth(){
public boolean isLeapMonth() {
return ChineseMonth.isLeapMonth(this.year, this.month);
}
@ -230,7 +230,7 @@ public class ChineseDate {
* @return 获得农历节日
*/
public String getFestivals() {
return StrUtil.join(",", LunarFestival.getFestivals(this.month, this.day));
return StrUtil.join(",", LunarFestival.getFestivals(this.year, this.month, day));
}
/**
@ -258,7 +258,7 @@ public class ChineseDate {
* @return 获得天干地支的年月日信息
*/
public String getCyclicalYMD() {
if (gyear >= LunarInfo.BASE_YEAR && gmonth > 0 && gday > 0){
if (gyear >= LunarInfo.BASE_YEAR && gmonth > 0 && gday > 0) {
return (cyclicalm(gyear, gmonth, gday));
}
return null;

View File

@ -17,6 +17,7 @@ public class LunarFestival {
// 来自https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD%E4%BC%A0%E7%BB%9F%E8%8A%82%E6%97%A5/396100
private static final TableMap<Pair<Integer, Integer>, String> L_FTV = new TableMap<>(16);
static{
// 节日
L_FTV.put(new Pair<>(1, 1), "春节");
L_FTV.put(new Pair<>(1, 2), "犬日");
L_FTV.put(new Pair<>(1, 3), "猪日");
@ -78,7 +79,6 @@ public class LunarFestival {
L_FTV.put(new Pair<>(12, 8), "腊八节");
L_FTV.put(new Pair<>(12, 16), "尾牙");
L_FTV.put(new Pair<>(12, 23), "小年");
L_FTV.put(new Pair<>(12, 29), "除夕");
L_FTV.put(new Pair<>(12, 30), "除夕");
}
@ -88,6 +88,24 @@ public class LunarFestival {
* @param month
* @param day
* @return 获得农历节日
* @since 5.4.5
*/
public static List<String> getFestivals(int year, int month, int day) {
// 春节判断如果12月是小月则29为除夕否则30为除夕
if(12 == month && 29 == day){
if(29 == LunarInfo.monthDays(year, month)){
day++;
}
}
return getFestivals(month, day);
}
/**
* 获得节日列表此方法无法判断月是否为大月或小月
*
* @param month
* @param day
* @return 获得农历节日
*/
public static List<String> getFestivals(int month, int day) {
return L_FTV.getValues(new Pair<>(month, day));

View File

@ -0,0 +1,151 @@
package cn.hutool.core.lang;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 控制台打印表格工具
*
* @author 孙宇
* @since 5.4.4
*/
public class ConsoleTable {
private static final char ROW_LINE = '-';
private static final char COLUMN_LINE = '|';
private static final char CORNER = '+';
private static final char SPACE = '\u3000';
private static final char LF = CharUtil.LF;
/**
* 表格头信息
*/
private final List<List<String>> HEADER_LIST = new ArrayList<>();
/**
* 表格体信息
*/
private final List<List<String>> BODY_LIST = new ArrayList<>();
/**
* 每列最大字符个数
*/
private List<Integer> columnCharNumber;
/**
* 添加头信息
*
* @param titles 列名
* @return 自身对象
*/
public ConsoleTable addHeader(String... titles) {
if (columnCharNumber == null) {
columnCharNumber = new ArrayList<>(Collections.nCopies(titles.length, 0));
}
List<String> l = new ArrayList<>();
fillColumns(l, titles);
HEADER_LIST.add(l);
return this;
}
/**
* 添加体信息
*
* @param values 列值
* @return 自身对象
*/
public ConsoleTable addBody(String... values) {
List<String> l = new ArrayList<>();
BODY_LIST.add(l);
fillColumns(l, values);
return this;
}
/**
* 填充表格头或者体
*
* @param l 被填充列表
* @param columns 填充内容
*/
private void fillColumns(List<String> l, String[] columns) {
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
String col = Convert.toSBC(column);
l.add(col);
int width = col.length();
if (width > columnCharNumber.get(i)) {
columnCharNumber.set(i, width);
}
}
}
/**
* 获取表格字符串
*
* @return 表格字符串
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
fillBorder(sb);
fillRow(sb, HEADER_LIST);
fillBorder(sb);
fillRow(sb, BODY_LIST);
fillBorder(sb);
return sb.toString();
}
/**
* 填充表头或者表体信息
*
* @param sb
* @param list 表头列表或者表体列表
*/
private void fillRow(StringBuilder sb, List<List<String>> list) {
for (List<String> r : list) {
for (int i = 0; i < r.size(); i++) {
if (i == 0) {
sb.append(COLUMN_LINE);
}
String header = r.get(i);
sb.append(SPACE);
sb.append(header);
sb.append(SPACE);
int l = header.length();
int lw = columnCharNumber.get(i);
if (lw > l) {
for (int j = 0; j < (lw - l); j++) {
sb.append(SPACE);
}
}
sb.append(COLUMN_LINE);
}
sb.append(LF);
}
}
/**
* 拼装边框
*
* @param sb StringBuilder
*/
private void fillBorder(StringBuilder sb) {
sb.append(CORNER);
for (Integer width : columnCharNumber) {
sb.append(Convert.toSBC(StrUtil.fillAfter("", ROW_LINE, width + 2)));
sb.append(CORNER);
}
sb.append(LF);
}
/**
* 打印到控制台
*/
public void print() {
Console.print(toString());
}
}

View File

@ -1,177 +0,0 @@
package cn.hutool.core.lang;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 控制台打印表格工具
*
* @author 孙宇
*/
public class ConsoleTableUtil {
/**
* 表格头信息
*/
private final List<List<String>> HEADER_LIST = new ArrayList<>();
/**
* 表格体信息
*/
private final List<List<String>> BODY_LIST = new ArrayList<>();
/**
* 每列最大字符个数
*/
private List<Integer> columnCharNumber;
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
ConsoleTableUtil t = new ConsoleTableUtil();
t.addHeader("姓名", "年龄");
t.addBody("张三", "15");
t.addBody("李四", "29");
t.addBody("王二麻子", "37");
t.print();
t = new ConsoleTableUtil();
t.addHeader("体温", "占比");
t.addHeader("", "%");
t.addBody("36.8", "10");
t.addBody("37", "5");
t.print();
t = new ConsoleTableUtil();
t.addHeader("标题1", "标题2");
t.addBody("12345", "混合321654asdfcSDF");
t.addBody("sd e3ee ff22", "ff值");
t.print();
}
/**
* 添加头信息
*
* @param titles 列名
* @return 自身对象
*/
public ConsoleTableUtil addHeader(String... titles) {
if (columnCharNumber == null) {
columnCharNumber = new ArrayList<>(Collections.nCopies(titles.length, 0));
}
List<String> l = new ArrayList<>();
HEADER_LIST.add(l);
fillColumns(l, titles);
return this;
}
/**
* 添加体信息
*
* @param values 列值
* @return 自身对象
*/
public ConsoleTableUtil addBody(String... values) {
List<String> l = new ArrayList<>();
BODY_LIST.add(l);
fillColumns(l, values);
return this;
}
/**
* 填充表格头或者体
*
* @param l
* @param columns
*/
private void fillColumns(List<String> l, String[] columns) {
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
String col = Convert.toSBC(column);
l.add(col);
int width = col.length();
if (width > columnCharNumber.get(i)) {
columnCharNumber.set(i, width);
}
}
}
/**
* 获取表格字符串
*
* @return 表格字符串
*/
public String toString() {
StringBuilder sb = new StringBuilder();
fillBorder(sb);
for (List<String> headers : HEADER_LIST) {
for (int i = 0; i < headers.size(); i++) {
if (i == 0) {
sb.append('|');
}
String header = headers.get(i);
sb.append(Convert.toSBC(" "));
sb.append(header);
sb.append(Convert.toSBC(" "));
int l = header.length();
int lw = columnCharNumber.get(i);
if (lw > l) {
for (int j = 0; j < (lw - l); j++) {
sb.append(Convert.toSBC(" "));
}
}
sb.append('|');
}
sb.append('\n');
}
fillBorder(sb);
for (List<String> bodys : BODY_LIST) {
for (int i = 0; i < bodys.size(); i++) {
if (i == 0) {
sb.append('|');
}
String body = bodys.get(i);
sb.append(Convert.toSBC(" "));
sb.append(body);
sb.append(Convert.toSBC(" "));
int l = body.length();
int lw = columnCharNumber.get(i);
if (lw > l) {
for (int j = 0; j < (lw - l); j++) {
sb.append(Convert.toSBC(" "));
}
}
sb.append('|');
}
sb.append('\n');
}
fillBorder(sb);
return sb.toString();
}
/**
* 拼装边框
*
* @param sb
*/
private void fillBorder(StringBuilder sb) {
sb.append('*');
for (Integer width : columnCharNumber) {
sb.append(Convert.toSBC(StrUtil.fillAfter("", '-', width + 2)));
sb.append('*');
}
sb.append('\n');
}
/**
* 打印到控制台
*/
public void print() {
Console.print(toString());
}
}

View File

@ -1613,7 +1613,7 @@ public class ArrayUtil {
public static int indexOf(double[] array, double value) {
if (null != array) {
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@ -1632,7 +1632,7 @@ public class ArrayUtil {
public static int lastIndexOf(double[] array, double value) {
if (null != array) {
for (int i = array.length - 1; i >= 0; i--) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@ -1663,7 +1663,7 @@ public class ArrayUtil {
public static int indexOf(float[] array, float value) {
if (null != array) {
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@ -1682,7 +1682,7 @@ public class ArrayUtil {
public static int lastIndexOf(float[] array, float value) {
if (null != array) {
for (int i = array.length - 1; i >= 0; i--) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@ -1777,7 +1777,7 @@ public class ArrayUtil {
}
/**
* 包装类数组转为原始类型数组
* 包装类数组转为原始类型数组null转为0
*
* @param values 包装类型数组
* @return 原始类型数组
@ -1793,7 +1793,7 @@ public class ArrayUtil {
final int[] array = new int[length];
for (int i = 0; i < length; i++) {
array[i] = values[i];
array[i] = ObjectUtil.defaultIfNull(values[i], 0);
}
return array;
}
@ -1837,7 +1837,7 @@ public class ArrayUtil {
final long[] array = new long[length];
for (int i = 0; i < length; i++) {
array[i] = values[i];
array[i] = ObjectUtil.defaultIfNull(values[i], 0L);
}
return array;
}
@ -1881,7 +1881,7 @@ public class ArrayUtil {
char[] array = new char[length];
for (int i = 0; i < length; i++) {
array[i] = values[i];
array[i] = ObjectUtil.defaultIfNull(values[i], Character.MIN_VALUE);
}
return array;
}

View File

@ -98,11 +98,11 @@ public class CreditCodeUtil {
}
for (int i = 2; i < 8; i++) {
int num = RandomUtil.randomInt(10);
buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num]));
buf.append(BASE_CODE_ARRAY[num]);
}
for (int i = 8; i < 17; i++) {
int num = RandomUtil.randomInt(BASE_CODE_ARRAY.length - 1);
buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num]));
buf.append(BASE_CODE_ARRAY[num]);
}
final String code = buf.toString();

View File

@ -1712,7 +1712,7 @@ public class NumberUtil {
/**
* 比较大小值相等 返回true<br>
* 此方法通过调用{@link BigDecimal#compareTo(BigDecimal)}方法来判断是否相等<br>
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br>
* 此方法判断值相等时忽略精度的即0.00 == 0
*
* @param num1 数字1
@ -1721,7 +1721,21 @@ public class NumberUtil {
* @since 5.4.2
*/
public static boolean equals(double num1, double num2) {
return equals(toBigDecimal(num1), toBigDecimal(num2));
return Double.doubleToLongBits(num1) == Double.doubleToLongBits(num2);
}
/**
* 比较大小值相等 返回true<br>
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br>
* 此方法判断值相等时忽略精度的即0.00 == 0
*
* @param num1 数字1
* @param num2 数字2
* @return 是否相等
* @since 5.4.5
*/
public static boolean equals(float num1, float num2) {
return Float.floatToIntBits(num1) == Float.floatToIntBits(num2);
}
/**

View File

@ -48,8 +48,8 @@ public class RandomUtil {
* ThreadLocalRandom是JDK 7之后提供并发产生随机数能够解决多个线程发生的竞争争夺
*
* <p>
* 注意此方法返回的{@link ThreadLocalRandom}不可以在多线程环境下共享对象否则有重复随机数问题
* https://www.jianshu.com/p/89dfe990295c
* 注意此方法返回的{@link ThreadLocalRandom}不可以在多线程环境下共享对象否则有重复随机数问题
* https://www.jianshu.com/p/89dfe990295c
* </p>
*
* @return {@link ThreadLocalRandom}
@ -522,9 +522,10 @@ public class RandomUtil {
*
* @return 随机颜色
* @since 4.1.5
* @deprecated 使用{@link ImagUtil#randomColor()}
* @deprecated 使用ImgUtil.randomColor()
*/
public static Color randomColor() {
@Deprecated
public static Color randomColor() {
final Random random = getRandom();
return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));
}

View File

@ -34,53 +34,95 @@ public class StrUtil {
public static final int INDEX_NOT_FOUND = -1;
/** 字符常量:空格符 ' ' */
/**
* 字符常量空格符 ' '
*/
public static final char C_SPACE = CharUtil.SPACE;
/** 字符常量:制表符 \t */
/**
* 字符常量制表符 \t
*/
public static final char C_TAB = CharUtil.TAB;
/** 字符常量:点 . */
/**
* 字符常量 .
*/
public static final char C_DOT = CharUtil.DOT;
/** 字符常量:斜杠 / */
/**
* 字符常量斜杠 /
*/
public static final char C_SLASH = CharUtil.SLASH;
/** 字符常量:反斜杠 \ */
/**
* 字符常量反斜杠 \
*/
public static final char C_BACKSLASH = CharUtil.BACKSLASH;
/** 字符常量:回车符 \r */
/**
* 字符常量回车符 \r
*/
public static final char C_CR = CharUtil.CR;
/** 字符常量:换行符 \n */
/**
* 字符常量换行符 \n
*/
public static final char C_LF = CharUtil.LF;
/** 字符常量:下划线 _ */
/**
* 字符常量下划线 _
*/
public static final char C_UNDERLINE = CharUtil.UNDERLINE;
/** 字符常量:逗号 , */
/**
* 字符常量逗号 ,
*/
public static final char C_COMMA = CharUtil.COMMA;
/** 字符常量:花括号(左) { */
/**
* 字符常量花括号 {
*/
public static final char C_DELIM_START = CharUtil.DELIM_START;
/** 字符常量:花括号(右) } */
/**
* 字符常量花括号 }
*/
public static final char C_DELIM_END = CharUtil.DELIM_END;
/** 字符常量:中括号(左) [ */
/**
* 字符常量中括号 [
*/
public static final char C_BRACKET_START = CharUtil.BRACKET_START;
/** 字符常量:中括号(右) ] */
/**
* 字符常量中括号 ]
*/
public static final char C_BRACKET_END = CharUtil.BRACKET_END;
/** 字符常量:冒号 : */
/**
* 字符常量冒号 :
*/
public static final char C_COLON = CharUtil.COLON;
/** 字符常量:艾特 @ */
/**
* 字符常量艾特 @
*/
public static final char C_AT = CharUtil.AT;
/** 字符串常量:空格符 ' ' */
/**
* 字符串常量空格符 ' '
*/
public static final String SPACE = " ";
/** 字符串常量:制表符 \t */
/**
* 字符串常量制表符 \t
*/
public static final String TAB = " ";
/** 字符串常量:点 . */
/**
* 字符串常量 .
*/
public static final String DOT = ".";
/**
* 字符串常量双点 ..
* 用途作为指向上级文件夹的路径 "../path"
*/
public static final String DOUBLE_DOT = "..";
/** 字符串常量:斜杠 / */
/**
* 字符串常量斜杠 /
*/
public static final String SLASH = "/";
/** 字符串常量:反斜杠 \ */
/**
* 字符串常量反斜杠 \
*/
public static final String BACKSLASH = "\\";
/** 字符串常量:空字符串 "" */
/**
* 字符串常量空字符串 ""
*/
public static final String EMPTY = "";
/**
* 字符串常量"null"
@ -92,45 +134,79 @@ public class StrUtil {
* 解释该字符常用于表示 Linux 系统和 MacOS 系统下的文本换行
*/
public static final String CR = "\r";
/** 字符串常量:换行符 \n */
/**
* 字符串常量换行符 \n
*/
public static final String LF = "\n";
/**
* 字符串常量Windows 换行 \r\n
* 解释该字符串常用于表示 Windows 系统下的文本换行
*/
public static final String CRLF = "\r\n";
/** 字符串常量:下划线 _ */
/**
* 字符串常量下划线 _
*/
public static final String UNDERLINE = "_";
/** 字符串常量:减号(中划线) - */
/**
* 字符串常量减号中划线 -
*/
public static final String DASHED = "-";
/** 字符串常量:逗号 , */
/**
* 字符串常量逗号 ,
*/
public static final String COMMA = ",";
/** 字符串常量:花括号(左) { */
/**
* 字符串常量花括号 {
*/
public static final String DELIM_START = "{";
/** 字符串常量:花括号(右) } */
/**
* 字符串常量花括号 }
*/
public static final String DELIM_END = "}";
/** 字符串常量:中括号(左) [ */
/**
* 字符串常量中括号 [
*/
public static final String BRACKET_START = "[";
/** 字符串常量:中括号(右) ] */
/**
* 字符串常量中括号 ]
*/
public static final String BRACKET_END = "]";
/** 字符串常量:冒号 : */
/**
* 字符串常量冒号 :
*/
public static final String COLON = ":";
/** 字符串常量:艾特 @ */
/**
* 字符串常量艾特 @
*/
public static final String AT = "@";
/** 字符串常量HTML 空格转义 */
/**
* 字符串常量HTML 空格转义
*/
public static final String HTML_NBSP = "&nbsp;";
/** 字符串常量HTML And 符转义 &amp; */
/**
* 字符串常量HTML And 符转义 &amp;
*/
public static final String HTML_AMP = "&amp;";
/** 字符串常量HTML 双引号转义 " */
/**
* 字符串常量HTML 双引号转义 "
*/
public static final String HTML_QUOTE = "&quot;";
/** 字符串常量HTML 单引号转义 ' */
/**
* 字符串常量HTML 单引号转义 '
*/
public static final String HTML_APOS = "&apos;";
/** 字符串常量HTML 小于号转义 &lt; */
/**
* 字符串常量HTML 小于号转义 &lt;
*/
public static final String HTML_LT = "&lt;";
/** 字符串常量HTML 大于号转义 &gt; */
/**
* 字符串常量HTML 大于号转义 &gt;
*/
public static final String HTML_GT = "&gt;";
/** 字符串常量:空 JSON "{}" */
/**
* 字符串常量 JSON "{}"
*/
public static final String EMPTY_JSON = "{}";
// ------------------------------------------------------------------------ Blank
@ -189,8 +265,7 @@ public class StrUtil {
*
* @param obj 对象
* @return 如果为字符串是否为空串
*
* @see StrUtil#isBlank(CharSequence)
* @see StrUtil#isBlank(CharSequence)
* @since 3.3.0
*/
public static boolean isBlankIfStr(Object obj) {
@ -667,6 +742,9 @@ public class StrUtil {
* @return 是否开始
*/
public static boolean startWith(CharSequence str, char c) {
if (isEmpty(str)) {
return false;
}
return c == str.charAt(0);
}
@ -674,8 +752,8 @@ public class StrUtil {
* 是否以指定字符串开头<br>
* 如果给定的字符串和开头字符串都为null则返回true否则任意一个值为null返回false
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @param str 被监测字符串
* @param prefix 开头字符串
* @param ignoreCase 是否忽略大小写
* @return 是否以指定字符串开头
* @since 5.4.3
@ -690,14 +768,14 @@ public class StrUtil {
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @param ignoreCase 是否忽略大小写
* @param ignoreCase 是否忽略大小写
* @param ignoreEquals 是否忽略字符串相等的情况
* @return 是否以指定字符串开头
* @since 5.4.3
*/
public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase, boolean ignoreEquals) {
if (null == str || null == prefix) {
if(false == ignoreEquals){
if (false == ignoreEquals) {
return false;
}
return null == str && null == prefix;
@ -710,7 +788,7 @@ public class StrUtil {
isStartWith = str.toString().startsWith(prefix.toString());
}
if(isStartWith){
if (isStartWith) {
return (false == ignoreEquals) || (false == equals(str, prefix, ignoreCase));
}
return false;
@ -779,6 +857,9 @@ public class StrUtil {
* @return 是否结尾
*/
public static boolean endWith(CharSequence str, char c) {
if (isEmpty(str)) {
return false;
}
return c == str.charAt(str.length() - 1);
}
@ -1073,7 +1154,8 @@ public class StrUtil {
* @return 移除后的字符串
*/
public static String removeAll(CharSequence str, CharSequence strToRemove) {
if (isEmpty(str)) {
// strToRemove如果为空 也不用继续后面的逻辑
if (isEmpty(str) || isEmpty(strToRemove)) {
return str(str);
}
return str.toString().replace(strToRemove, EMPTY);
@ -2125,7 +2207,6 @@ public class StrUtil {
}
final List<String> result = new LinkedList<>();
final String[] split = split(str, prefix);
for (String fragment : split(str, prefix)) {
int suffixIndex = fragment.indexOf(suffix.toString());
if (suffixIndex > 0) {
@ -2205,10 +2286,10 @@ public class StrUtil {
if (null == str) {
return null;
}
if (count <= 0) {
if (count <= 0 || str.length() == 0) {
return EMPTY;
}
if (count == 1 || str.length() == 0) {
if (count == 1) {
return str.toString();
}
@ -3997,11 +4078,12 @@ public class StrUtil {
return false;
}
int len = value.length();
boolean isAllMatch = true;
for (int i = 0; i < len; i++) {
isAllMatch &= matcher.match(value.charAt(i));
if (false == matcher.match(value.charAt(i))) {
return false;
}
}
return isAllMatch;
return true;
}
/**

View File

@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.BiMap;
@ -13,7 +14,11 @@ import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
@ -21,6 +26,8 @@ import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
@ -62,6 +69,10 @@ public class XmlUtil {
* 在XML中无效的字符 正则
*/
public static final String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]";
/**
* 在XML中注释的内容 正则
*/
public static final String COMMENT_REGEX = "(?s)<!--.+?-->";
/**
* XML格式化输出默认缩进量
*/
@ -76,6 +87,10 @@ public class XmlUtil {
* 是否打开命名空间支持
*/
private static boolean namespaceAware = true;
/**
* Sax读取器工厂缓存
*/
private static SAXParserFactory factory;
/**
* 禁用默认的DocumentBuilderFactory禁用后如果有第三方的实现如oracle的xdb包中的xmlparse将会自动加载实现
@ -184,6 +199,92 @@ public class XmlUtil {
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler}则其接口都会被处理
*
* @param file XML源文件,使用后自动关闭
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(File file, ContentHandler contentHandler) {
InputStream in = null;
try{
in = FileUtil.getInputStream(file);
readBySax(new InputSource(in), contentHandler);
} finally {
IoUtil.close(in);
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler}则其接口都会被处理
*
* @param reader XML源Reader,使用后自动关闭
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(Reader reader, ContentHandler contentHandler) {
try{
readBySax(new InputSource(reader), contentHandler);
} finally {
IoUtil.close(reader);
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler}则其接口都会被处理
*
* @param source XML源流,使用后自动关闭
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(InputStream source, ContentHandler contentHandler) {
try{
readBySax(new InputSource(source), contentHandler);
} finally {
IoUtil.close(source);
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler}则其接口都会被处理
*
* @param source XML源可以是文件路径等
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(InputSource source, ContentHandler contentHandler) {
// 1.获取解析工厂
if (null == factory) {
factory = SAXParserFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(namespaceAware);
}
// 2.从解析工厂获取解析器
final SAXParser parse;
XMLReader reader;
try {
parse = factory.newSAXParser();
if (contentHandler instanceof DefaultHandler) {
parse.parse(source, (DefaultHandler) contentHandler);
return;
}
// 3.得到解读器
reader = parse.getXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(source);
} catch (ParserConfigurationException | SAXException e) {
throw new UtilException(e);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 将String类型的XML转换为XML文档
*
@ -574,6 +675,20 @@ public class XmlUtil {
return xmlContent.replaceAll(INVALID_REGEX, "");
}
/**
* 去除XML文本中的注释内容
*
* @param xmlContent XML文本
* @return 当传入为null时返回null
* @since 5.4.5
*/
public static String cleanComment(String xmlContent) {
if (xmlContent == null) {
return null;
}
return xmlContent.replaceAll(COMMENT_REGEX, StrUtil.EMPTY);
}
/**
* 根据节点名获得子节点列表
*

View File

@ -1,5 +1,6 @@
package cn.hutool.core.date;
import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Test;
@ -77,4 +78,11 @@ public class ChineseDateTest {
chineseDate = new ChineseDate(2020,4,15);
Assert.assertEquals("闰四月", chineseDate.getChineseMonth());
}
@Test
public void getFestivalsTest(){
// issue#I1XHSF@Gitee2023-01-20对应农历腊月29非除夕
ChineseDate chineseDate = new ChineseDate(DateUtil.parseDate("2023-01-20"));
Assert.assertTrue(StrUtil.isEmpty(chineseDate.getFestivals()));
}
}

View File

@ -25,7 +25,9 @@ public class ImgUtilTest {
@Test
@Ignore
public void scaleTest2() {
ImgUtil.scale(FileUtil.file("e:/pic/test.jpg"), FileUtil.file("e:/pic/test_result.jpg"), 0.8f);
ImgUtil.scale(
FileUtil.file("d:/test/2.png"),
FileUtil.file("d:/test/2_result.jpg"), 600, 337, null);
}
@Test

View File

@ -0,0 +1,35 @@
package cn.hutool.core.lang;
import org.junit.Ignore;
import org.junit.Test;
public class ConsoleTableTest {
@Test
@Ignore
public void printTest() {
ConsoleTable t = new ConsoleTable();
t.addHeader("姓名", "年龄");
t.addBody("张三", "15");
t.addBody("李四", "29");
t.addBody("王二麻子", "37");
t.print();
Console.log();
t = new ConsoleTable();
t.addHeader("体温", "占比");
t.addHeader("", "%");
t.addBody("36.8", "10");
t.addBody("37", "5");
t.print();
Console.log();
t = new ConsoleTable();
t.addHeader("标题1", "标题2");
t.addBody("12345", "混合321654asdfcSDF");
t.addBody("sd e3ee ff22", "ff值");
t.print();
}
}

View File

@ -263,10 +263,10 @@ public class NumberUtilTest {
@Test
public void isPowerOfTwoTest() {
Assert.assertEquals(false, NumberUtil.isPowerOfTwo(-1));
Assert.assertEquals(true, NumberUtil.isPowerOfTwo(16));
Assert.assertEquals(true, NumberUtil.isPowerOfTwo(65536));
Assert.assertEquals(true, NumberUtil.isPowerOfTwo(1));
Assert.assertEquals(false, NumberUtil.isPowerOfTwo(17));
Assert.assertFalse(NumberUtil.isPowerOfTwo(-1));
Assert.assertTrue(NumberUtil.isPowerOfTwo(16));
Assert.assertTrue(NumberUtil.isPowerOfTwo(65536));
Assert.assertTrue(NumberUtil.isPowerOfTwo(1));
Assert.assertFalse(NumberUtil.isPowerOfTwo(17));
}
}

View File

@ -9,10 +9,13 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.xpath.XPathConstants;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* {@link XmlUtil} 工具类
@ -148,6 +151,18 @@ public class XmlUtilTest {
Assert.assertNotNull(doc);
}
@Test
public void readBySaxTest(){
final Set<String> eles = CollUtil.newHashSet(
"returnsms", "returnstatus", "message", "remainpoint", "taskID", "successCounts");
XmlUtil.readBySax(ResourceUtil.getStream("test.xml"), new DefaultHandler(){
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
Assert.assertTrue(eles.contains(localName));
}
});
}
@Test
public void mapToXmlTestWithOmitXmlDeclaration() {
@ -196,6 +211,13 @@ public class XmlUtilTest {
Assert.assertEquals(testBean.getBankCode(), testBean2.getBankCode());
}
@Test
public void cleanCommentTest() {
final String xmlContent = "<info><title>hutool</title><!-- 这是注释 --><lang>java</lang></info>";
final String ret = XmlUtil.cleanComment(xmlContent);
Assert.assertEquals("<info><title>hutool</title><lang>java</lang></info>", ret);
}
@Data
public static class TestBean {
private String ReqCode;

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-crypto</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-db</artifactId>
@ -19,9 +19,9 @@
<properties>
<!-- versions -->
<c3p0.version>0.9.5.5</c3p0.version>
<dbcp2.version>2.7.0</dbcp2.version>
<dbcp2.version>2.8.0</dbcp2.version>
<tomcat-jdbc.version>9.0.30</tomcat-jdbc.version>
<druid.version>1.1.23</druid.version>
<druid.version>1.1.24</druid.version>
<hikariCP.version>2.4.13</hikariCP.version>
<mongo.version>3.12.7</mongo.version>
<sqlite.version>3.32.3.2</sqlite.version>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-dfa</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-extra</artifactId>
@ -19,15 +19,15 @@
<properties>
<!-- versions -->
<velocity.version>2.2</velocity.version>
<beetl.version>3.2.0.RELEASE</beetl.version>
<beetl.version>3.2.1.RELEASE</beetl.version>
<rythm.version>1.3.0</rythm.version>
<freemarker.version>2.3.30</freemarker.version>
<enjoy.version>4.9.01</enjoy.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<mail.version>1.6.2</mail.version>
<jsch.version>0.1.55</jsch.version>
<zxing.version>3.4.0</zxing.version>
<net.version>3.6</net.version>
<zxing.version>3.4.1</zxing.version>
<net.version>3.7</net.version>
<emoji-java.version>5.1.1</emoji-java.version>
<servlet-api.version>4.0.1</servlet-api.version>
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-http</artifactId>

View File

@ -28,6 +28,7 @@ import java.io.OutputStream;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URLStreamHandler;
import java.util.Collection;
@ -841,6 +842,20 @@ public class HttpRequest extends HttpBase<HttpRequest> {
return this;
}
/**
* 设置Http代理
*
* @param host 代理 主机
* @param port 代理 端口
* @return this
* @since 5.4.5
*/
public HttpRequest setHttpProxy(String host, int port) {
final Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(host, port));
return setProxy(proxy);
}
/**
* 设置代理
*

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-json</artifactId>

View File

@ -154,4 +154,11 @@ public class JSONUtilTest {
Assert.assertEquals("aa", json.get("name"));
Assert.assertEquals(1, json.get("gender"));
}
@Test
public void doubleTest(){
String json = "{\"test\": 12.00}";
final JSONObject jsonObject = JSONUtil.parseObj(json);
Assert.assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString());
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-log</artifactId>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-poi</artifactId>

View File

@ -1,9 +1,10 @@
package cn.hutool.poi.excel;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import org.apache.poi.poifs.filesystem.FileMagic;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -48,11 +49,24 @@ public class ExcelFileUtil {
* @return 是否为XLSX格式的Excel文件XSSF
*/
public static boolean isXlsx(InputStream in) {
if (false == in.markSupported()) {
in = new BufferedInputStream(in);
}
try {
return FileMagic.valueOf(in) == FileMagic.OOXML;
return FileMagic.valueOf(IoUtil.toMarkSupportStream(in)) == FileMagic.OOXML;
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 是否为XLSX格式的Excel文件XSSF<br>
* XLSX文件主要用于Excel 2007+创建
*
* @param file excel文件
* @return 是否为XLSX格式的Excel文件XSSF
* @since 5.4.4
*/
public static boolean isXlsx(File file) {
try {
return FileMagic.valueOf(file) == FileMagic.OOXML;
} catch (IOException e) {
throw new IORuntimeException(e);
}

View File

@ -2,7 +2,6 @@ package cn.hutool.poi.excel;
import cn.hutool.core.exceptions.DependencyException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
@ -10,9 +9,10 @@ import cn.hutool.poi.PoiChecker;
import cn.hutool.poi.excel.cell.CellLocation;
import cn.hutool.poi.excel.sax.Excel03SaxReader;
import cn.hutool.poi.excel.sax.Excel07SaxReader;
import cn.hutool.poi.excel.sax.ExcelSaxReader;
import cn.hutool.poi.excel.sax.ExcelSaxUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
@ -29,67 +29,92 @@ public class ExcelUtil {
* 通过Sax方式读取Excel同时支持03和07格式
*
* @param path Excel文件路径
* @param sheetIndex sheet序号
* @param rid sheet rid-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @since 3.2.0
*/
public static void readBySax(String path, int sheetIndex, RowHandler rowHandler) {
BufferedInputStream in = null;
try {
in = FileUtil.getInputStream(path);
readBySax(in, sheetIndex, rowHandler);
} finally {
IoUtil.close(in);
}
public static void readBySax(String path, int rid, RowHandler rowHandler) {
readBySax(FileUtil.file(path), rid, rowHandler);
}
/**
* 通过Sax方式读取Excel同时支持03和07格式
*
* @param path Excel文件路径
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @param rowHandler 行处理器
* @since 5.4.4
*/
public static void readBySax(String path, String idOrRid, RowHandler rowHandler) {
readBySax(FileUtil.file(path), idOrRid, rowHandler);
}
/**
* 通过Sax方式读取Excel同时支持03和07格式
*
* @param file Excel文件
* @param sheetIndex sheet序号
* @param rid sheet rid-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @since 3.2.0
*/
public static void readBySax(File file, int sheetIndex, RowHandler rowHandler) {
BufferedInputStream in = null;
try {
in = FileUtil.getInputStream(file);
readBySax(in, sheetIndex, rowHandler);
} finally {
IoUtil.close(in);
}
public static void readBySax(File file, int rid, RowHandler rowHandler) {
final ExcelSaxReader<?> reader = ExcelSaxUtil.createSaxReader(ExcelFileUtil.isXlsx(file), rowHandler);
reader.read(file, rid);
}
/**
* 通过Sax方式读取Excel同时支持03和07格式
*
* @param file Excel文件
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @param rowHandler 行处理器
* @since 5.4.4
*/
public static void readBySax(File file, String idOrRid, RowHandler rowHandler) {
final ExcelSaxReader<?> reader = ExcelSaxUtil.createSaxReader(ExcelFileUtil.isXlsx(file), rowHandler);
reader.read(file, idOrRid);
}
/**
* 通过Sax方式读取Excel同时支持03和07格式
*
* @param in Excel流
* @param sheetIndex sheet序号
* @param rid sheet rid-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @since 3.2.0
*/
public static void readBySax(InputStream in, int sheetIndex, RowHandler rowHandler) {
in = IoUtil.toMarkSupportStream(in);
if (ExcelFileUtil.isXlsx(in)) {
read07BySax(in, sheetIndex, rowHandler);
} else {
read03BySax(in, sheetIndex, rowHandler);
}
public static void readBySax(InputStream in, int rid, RowHandler rowHandler) {
final ExcelSaxReader<?> reader = ExcelSaxUtil.createSaxReader(ExcelFileUtil.isXlsx(in), rowHandler);
reader.read(in, rid);
}
/**
* 通过Sax方式读取Excel同时支持03和07格式
*
* @param in Excel流
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @param rowHandler 行处理器
* @since 5.4.4
*/
public static void readBySax(InputStream in, String idOrRid, RowHandler rowHandler) {
final ExcelSaxReader<?> reader = ExcelSaxUtil.createSaxReader(ExcelFileUtil.isXlsx(in), rowHandler);
reader.read(in, idOrRid);
}
/**
* Sax方式读取Excel07
*
* @param in 输入流
* @param sheetIndex Sheet索引-1表示全部Sheet, 0表示第一个Sheet
* @param rid Sheet rid-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @return {@link Excel07SaxReader}
* @since 3.2.0
* @deprecated 请使用 {@link #readBySax(InputStream, int, RowHandler)}
*/
public static Excel07SaxReader read07BySax(InputStream in, int sheetIndex, RowHandler rowHandler) {
@Deprecated
public static Excel07SaxReader read07BySax(InputStream in, int rid, RowHandler rowHandler) {
try {
return new Excel07SaxReader(rowHandler).read(in, sheetIndex);
return new Excel07SaxReader(rowHandler).read(in, rid);
} catch (NoClassDefFoundError e) {
throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG);
}
@ -99,14 +124,16 @@ public class ExcelUtil {
* Sax方式读取Excel07
*
* @param file 文件
* @param sheetIndex Sheet索引-1表示全部Sheet, 0表示第一个Sheet
* @param rid Sheet rid-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @return {@link Excel07SaxReader}
* @since 3.2.0
* @deprecated 请使用 {@link #readBySax(File, int, RowHandler)}
*/
public static Excel07SaxReader read07BySax(File file, int sheetIndex, RowHandler rowHandler) {
@Deprecated
public static Excel07SaxReader read07BySax(File file, int rid, RowHandler rowHandler) {
try {
return new Excel07SaxReader(rowHandler).read(file, sheetIndex);
return new Excel07SaxReader(rowHandler).read(file, rid);
} catch (NoClassDefFoundError e) {
throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG);
}
@ -116,14 +143,16 @@ public class ExcelUtil {
* Sax方式读取Excel07
*
* @param path 路径
* @param sheetIndex Sheet索引-1表示全部Sheet, 0表示第一个Sheet
* @param rid Sheet rid-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @return {@link Excel07SaxReader}
* @since 3.2.0
* @deprecated 请使用 {@link #readBySax(String, int, RowHandler)}
*/
public static Excel07SaxReader read07BySax(String path, int sheetIndex, RowHandler rowHandler) {
@Deprecated
public static Excel07SaxReader read07BySax(String path, int rid, RowHandler rowHandler) {
try {
return new Excel07SaxReader(rowHandler).read(path, sheetIndex);
return new Excel07SaxReader(rowHandler).read(path, rid);
} catch (NoClassDefFoundError e) {
throw new DependencyException(ObjectUtil.defaultIfNull(e.getCause(), e), PoiChecker.NO_POI_ERROR_MSG);
}
@ -135,9 +164,11 @@ public class ExcelUtil {
* @param in 输入流
* @param sheetIndex Sheet索引-1表示全部Sheet, 0表示第一个Sheet
* @param rowHandler 行处理器
* @return {@link Excel07SaxReader}
* @return {@link Excel03SaxReader}
* @since 3.2.0
* @deprecated 请使用 {@link #readBySax(InputStream, int, RowHandler)}
*/
@Deprecated
public static Excel03SaxReader read03BySax(InputStream in, int sheetIndex, RowHandler rowHandler) {
try {
return new Excel03SaxReader(rowHandler).read(in, sheetIndex);
@ -154,7 +185,9 @@ public class ExcelUtil {
* @param rowHandler 行处理器
* @return {@link Excel03SaxReader}
* @since 3.2.0
* @deprecated 请使用 {@link #readBySax(File, int, RowHandler)}
*/
@Deprecated
public static Excel03SaxReader read03BySax(File file, int sheetIndex, RowHandler rowHandler) {
try {
return new Excel03SaxReader(rowHandler).read(file, sheetIndex);
@ -171,7 +204,9 @@ public class ExcelUtil {
* @param rowHandler 行处理器
* @return {@link Excel03SaxReader}
* @since 3.2.0
* @deprecated 请使用 {@link #readBySax(String, int, RowHandler)}
*/
@Deprecated
public static Excel03SaxReader read03BySax(String path, int sheetIndex, RowHandler rowHandler) {
try {
return new Excel03SaxReader(rowHandler).read(path, sheetIndex);

View File

@ -1,38 +0,0 @@
package cn.hutool.poi.excel.sax;
import java.io.File;
import java.io.InputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.poi.exceptions.POIException;
/**
* 抽象的Sax方式Excel读取器提供一些共用方法
*
* @author looly
*
* @param <T> 子对象类型用于标记返回值this
* @since 3.2.0
*/
public abstract class AbstractExcelSaxReader<T> implements ExcelSaxReader<T> {
@Override
public T read(String path) throws POIException {
return read(FileUtil.file(path));
}
@Override
public T read(File file) throws POIException {
return read(file, -1);
}
@Override
public T read(InputStream in) throws POIException {
return read(in, -1);
}
@Override
public T read(String path, int sheetIndex) throws POIException {
return read(FileUtil.file(path), sheetIndex);
}
}

View File

@ -30,7 +30,7 @@ import java.util.List;
*
* @author looly
*/
public class Excel03SaxReader extends AbstractExcelSaxReader<Excel03SaxReader> implements HSSFListener {
public class Excel03SaxReader implements HSSFListener, ExcelSaxReader<Excel03SaxReader> {
/**
* 如果为公式true表示输出公式计算后的结果值false表示输出公式本身
@ -83,18 +83,18 @@ public class Excel03SaxReader extends AbstractExcelSaxReader<Excel03SaxReader> i
// ------------------------------------------------------------------------------ Read start
@Override
public Excel03SaxReader read(File file, int rid) throws POIException {
public Excel03SaxReader read(File file, String idOrRid) throws POIException {
try {
return read(new POIFSFileSystem(file), rid);
return read(new POIFSFileSystem(file), idOrRid);
} catch (IOException e) {
throw new POIException(e);
}
}
@Override
public Excel03SaxReader read(InputStream excelStream, int rid) throws POIException {
public Excel03SaxReader read(InputStream excelStream, String idOrRid) throws POIException {
try {
return read(new POIFSFileSystem(excelStream), rid);
return read(new POIFSFileSystem(excelStream), idOrRid);
} catch (IOException e) {
throw new POIException(e);
}
@ -104,12 +104,12 @@ public class Excel03SaxReader extends AbstractExcelSaxReader<Excel03SaxReader> i
* 读取
*
* @param fs {@link POIFSFileSystem}
* @param rid sheet序号
* @param id sheet序号
* @return this
* @throws POIException IO异常包装
*/
public Excel03SaxReader read(POIFSFileSystem fs, int rid) throws POIException {
this.rid = rid;
public Excel03SaxReader read(POIFSFileSystem fs, String id) throws POIException {
this.rid = Integer.parseInt(id);
formatListener = new FormatTrackingHSSFListener(new MissingRecordAwareHSSFListener(this));
final HSSFRequest request = new HSSFRequest();

View File

@ -1,10 +1,14 @@
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.StrUtil;
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;
@ -12,10 +16,10 @@ 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.ContentHandler;
import org.xml.sax.Locator;
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;
@ -28,7 +32,7 @@ import java.util.List;
* @author Looly
* @since 3.1.2
*/
public class Excel07SaxReader extends AbstractExcelSaxReader<Excel07SaxReader> implements ContentHandler {
public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<Excel07SaxReader> {
// sheet r:Id前缀
private static final String RID_PREFIX = "rId";
@ -92,20 +96,30 @@ public class Excel07SaxReader extends AbstractExcelSaxReader<Excel07SaxReader> i
// ------------------------------------------------------------------------------ Read start
@Override
public Excel07SaxReader read(File file, int rid) throws POIException {
return read(file, RID_PREFIX + rid);
}
@Override
public Excel07SaxReader read(File file, String idOrRid) throws POIException {
try {
return read(OPCPackage.open(file), rid);
} catch (Exception e) {
return read(OPCPackage.open(file), idOrRid);
} catch (InvalidFormatException e) {
throw new POIException(e);
}
}
@Override
public Excel07SaxReader read(InputStream in, int rid) throws POIException {
try {
return read(OPCPackage.open(in), rid);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
return read(in, RID_PREFIX + rid);
}
@Override
public Excel07SaxReader read(InputStream in, String idOrRid) throws POIException {
try (final OPCPackage opcPackage = OPCPackage.open(in)) {
return read(opcPackage, idOrRid);
} catch (IOException e) {
throw new IORuntimeException(e);
} catch (InvalidFormatException e) {
throw new POIException(e);
}
}
@ -113,53 +127,60 @@ public class Excel07SaxReader extends AbstractExcelSaxReader<Excel07SaxReader> i
/**
* 开始读取ExcelSheet编号从0开始计数
*
* @param opcPackage {@link OPCPackage}Excel包
* @param opcPackage {@link OPCPackage}Excel包读取后不关闭
* @param rid Excel中的sheet rid编号如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
public Excel07SaxReader read(OPCPackage opcPackage, int rid) throws POIException {
InputStream sheetInputStream = null;
return read(opcPackage, RID_PREFIX + rid);
}
/**
* 开始读取ExcelSheet编号从0开始计数
*
* @param opcPackage {@link OPCPackage}Excel包读取后不关闭
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
public Excel07SaxReader read(OPCPackage opcPackage, String idOrRid) throws POIException {
try {
final XSSFReader xssfReader = new XSSFReader(opcPackage);
// 获取共享样式表
try {
stylesTable = xssfReader.getStylesTable();
} catch (Exception e) {
//ignore
}
// 获取共享字符串表
this.sharedStringsTable = xssfReader.getSharedStringsTable();
if (rid > -1) {
this.sheetIndex = rid;
// 根据 rId# rSheet# 查找sheet
sheetInputStream = xssfReader.getSheet(RID_PREFIX + (rid + 1));
ExcelSaxUtil.readFrom(sheetInputStream, this);
rowHandler.doAfterAllAnalysed();
} else {
this.sheetIndex = -1;
// 遍历所有sheet
final Iterator<InputStream> sheetInputStreams = xssfReader.getSheetsData();
while (sheetInputStreams.hasNext()) {
// 重新读取一个sheet时行归零
index = 0;
this.sheetIndex++;
sheetInputStream = sheetInputStreams.next();
ExcelSaxUtil.readFrom(sheetInputStream, this);
rowHandler.doAfterAllAnalysed();
}
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
return read(new XSSFReader(opcPackage), idOrRid);
} catch (OpenXML4JException e) {
throw new POIException(e);
} finally {
IoUtil.close(sheetInputStream);
IoUtil.close(opcPackage);
} catch (IOException e) {
throw new IORuntimeException(e);
}
return this;
}
/**
* 开始读取ExcelSheet编号从0开始计数
*
* @param xssfReader {@link XSSFReader}Excel读取器
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
* @since 5.4.4
*/
public Excel07SaxReader read(XSSFReader xssfReader, String idOrRid) throws POIException {
// 获取共享样式表
try {
stylesTable = xssfReader.getStylesTable();
} catch (Exception e) {
//ignore
}
// 获取共享字符串表
try {
this.sharedStringsTable = xssfReader.getSharedStringsTable();
} catch (IOException e) {
throw new IORuntimeException(e);
} catch (InvalidFormatException e) {
throw new POIException(e);
}
return readSheets(xssfReader, idOrRid);
}
// ------------------------------------------------------------------------------ Read end
@ -196,53 +217,57 @@ public class Excel07SaxReader extends AbstractExcelSaxReader<Excel07SaxReader> i
lastContent.append(ch, start, length);
}
// --------------------------------------------------------------------------------------- Pass method start
@Override
public void setDocumentLocator(Locator locator) {
// pass
}
// --------------------------------------------------------------------------------------- Private method start
/**
* ?xml标签的回调处理方法
* 开始读取ExcelSheet编号从0开始计数
*
* @param xssfReader {@link XSSFReader}Excel读取器
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId0如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
* @since 5.4.4
*/
@Override
public void startDocument() {
// pass
private Excel07SaxReader readSheets(XSSFReader xssfReader, String idOrRid) throws POIException {
// 将sheetId转换为rid
if (NumberUtil.isInteger(idOrRid)) {
final SheetRidReader ridReader = new SheetRidReader();
final String rid = ridReader.read(xssfReader).getRidBySheetId(idOrRid);
if (StrUtil.isNotEmpty(rid)) {
idOrRid = rid;
}
}
this.sheetIndex = Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRid, RID_PREFIX));
InputStream sheetInputStream = null;
try {
if (this.sheetIndex > -1) {
// 根据 rId# rSheet# 查找sheet
sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.sheetIndex + 1));
ExcelSaxUtil.readFrom(sheetInputStream, this);
rowHandler.doAfterAllAnalysed();
} else {
this.sheetIndex = -1;
// 遍历所有sheet
final Iterator<InputStream> sheetInputStreams = xssfReader.getSheetsData();
while (sheetInputStreams.hasNext()) {
// 重新读取一个sheet时行归零
index = 0;
this.sheetIndex++;
sheetInputStream = sheetInputStreams.next();
ExcelSaxUtil.readFrom(sheetInputStream, this);
rowHandler.doAfterAllAnalysed();
}
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new POIException(e);
} finally {
IoUtil.close(sheetInputStream);
}
return this;
}
@Override
public void endDocument() {
// pass
}
@Override
public void startPrefixMapping(String prefix, String uri) {
// pass
}
@Override
public void endPrefixMapping(String prefix) {
// pass
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) {
// pass
}
@Override
public void processingInstruction(String target, String data) {
// pass
}
@Override
public void skippedEntity(String name) {
// pass
}
// --------------------------------------------------------------------------------------- Pass method end
// --------------------------------------------------------------------------------------- Private method start
/**
* 行开始
*
@ -317,10 +342,11 @@ public class Excel07SaxReader extends AbstractExcelSaxReader<Excel07SaxReader> i
/**
* 在一行中的指定列增加值
*
* @param index 位置
* @param value
*/
private void addCellValue(int index, Object value){
private void addCellValue(int index, Object value) {
this.rowCellList.add(index, value);
this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle);
}

View File

@ -1,10 +1,11 @@
package cn.hutool.poi.excel.sax;
import cn.hutool.core.io.FileUtil;
import cn.hutool.poi.exceptions.POIException;
import java.io.File;
import java.io.InputStream;
import cn.hutool.poi.exceptions.POIException;
/**
* Sax方式读取Excel接口提供一些共用方法
* @author looly
@ -13,60 +14,105 @@ import cn.hutool.poi.exceptions.POIException;
* @since 3.2.0
*/
public interface ExcelSaxReader<T> {
/**
* 开始读取Excel
*
* @param file Excel文件
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
T read(File file, String idOrRid) throws POIException;
/**
* 开始读取Excel读取结束后并不关闭流
*
* @param in Excel流
* @param idOrRid Excel中的sheet id或者rid编号rid必须加rId前缀例如rId1如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
T read(InputStream in, String idOrRid) throws POIException;
/**
* 开始读取Excel读取所有sheet
*
*
* @param path Excel文件路径
* @return this
* @throws POIException POI异常
*/
T read(String path) throws POIException;
default T read(String path) throws POIException {
return read(FileUtil.file(path));
}
/**
* 开始读取Excel读取所有sheet
*
*
* @param file Excel文件
* @return this
* @throws POIException POI异常
*/
T read(File file) throws POIException;
default T read(File file) throws POIException {
return read(file, -1);
}
/**
* 开始读取Excel读取所有sheet读取结束后并不关闭流
*
*
* @param in Excel包流
* @return this
* @throws POIException POI异常
*/
T read(InputStream in) throws POIException;
default T read(InputStream in) throws POIException {
return read(in, -1);
}
/**
* 开始读取Excel
*
*
* @param path 文件路径
* @param rid Excel中的sheet rid编号如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
T read(String path, int rid) throws POIException;
default T read(String path, int rid) throws POIException {
return read(FileUtil.file(path), rid);
}
/**
* 开始读取Excel
*
*
* @param path 文件路径
* @param rid Excel中的sheet rid编号如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
default T read(String path, String rid) throws POIException {
return read(FileUtil.file(path), rid);
}
/**
* 开始读取Excel
*
* @param file Excel文件
* @param rid Excel中的sheet rid编号如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
T read(File file, int rid) throws POIException;
default T read(File file, int rid) throws POIException{
return read(file, String.valueOf(rid));
}
/**
* 开始读取Excel读取结束后并不关闭流
*
*
* @param in Excel流
* @param rid Excel中的sheet rid编号如果为-1处理所有编号的sheet
* @return this
* @throws POIException POI异常
*/
T read(InputStream in, int rid) throws POIException;
default T read(InputStream in, int rid) throws POIException{
return read(in, String.valueOf(rid));
};
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.DependencyException;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler;
import cn.hutool.poi.exceptions.POIException;
import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.ss.usermodel.DataFormatter;
@ -73,9 +74,9 @@ public class ExcelSaxUtil {
}
break;
case NUMBER:
try{
try {
result = getNumberValue(value, numFmtString);
}catch (NumberFormatException e){
} catch (NumberFormatException e) {
result = value;
}
break;
@ -150,6 +151,7 @@ public class ExcelSaxUtil {
public static void readFrom(InputStream xmlDocStream, ContentHandler handler) throws DependencyException, POIException, IORuntimeException {
XMLReader xmlReader;
try {
// xmlReader = XMLReaderFactory.createXMLReader();
//noinspection deprecation
xmlReader = SAXHelper.newXMLReader();
} catch (SAXException | ParserConfigurationException e) {
@ -191,6 +193,20 @@ public class ExcelSaxUtil {
return DateUtil.date(org.apache.poi.ss.usermodel.DateUtil.getJavaDate(value, false));
}
/**
* 创建 {@link ExcelSaxReader}
*
* @param isXlsx 是否为xlsx格式07格式
* @param rowHandler 行处理器
* @return {@link ExcelSaxReader}
* @since 5.4.4
*/
public static ExcelSaxReader<?> createSaxReader(boolean isXlsx, RowHandler rowHandler) {
return isXlsx
? new Excel07SaxReader(rowHandler)
: new Excel03SaxReader(rowHandler);
}
/**
* 获取数字类型值
*

View File

@ -0,0 +1,111 @@
package cn.hutool.poi.excel.sax;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.exceptions.POIException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 在Sax方式读取Excel时读取sheet标签中sheetId和rid的对应关系类似于:
* <pre>
* &lt;sheet name="Sheet6" sheetId="4" r:id="6"/&gt;
* </pre>
* <p>
* 读取结果为
*
* <pre>
* {"4": "6"}
* </pre>
*
* @author looly
* @since 5.4.4
*/
public class SheetRidReader extends DefaultHandler {
private final static String TAG_NAME = "sheet";
private final static String RID_ATTR = "r:id";
private final static String SHEET_ID_ATTR = "sheetId";
private final static String NAME_ATTR = "name";
private final Map<String, String> ID_RID_MAP = new HashMap<>();
private final Map<String, String> NAME_RID_MAP = new HashMap<>();
/**
* 读取Wordkbook的XML中sheet标签中sheetId和rid的对应关系
*
* @param xssfReader XSSF读取器
* @return this
*/
public SheetRidReader read(XSSFReader xssfReader) {
InputStream workbookData = null;
try {
workbookData = xssfReader.getWorkbookData();
ExcelSaxUtil.readFrom(workbookData, this);
} catch (InvalidFormatException e) {
throw new POIException(e);
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
IoUtil.close(workbookData);
}
return this;
}
/**
* 根据sheetId获取rid
*
* @param sheetId Sheet的ID
* @return rid
*/
public String getRidBySheetId(String sheetId) {
return ID_RID_MAP.get(sheetId);
}
/**
* 根据sheet name获取rid
*
* @param sheetName Sheet的name
* @return rid
*/
public String getRidByName(String sheetName) {
return NAME_RID_MAP.get(sheetName);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (TAG_NAME.equalsIgnoreCase(localName)) {
final int length = attributes.getLength();
String sheetId = null;
String rid = null;
String name = null;
for (int i = 0; i < length; i++) {
switch (attributes.getLocalName(i)) {
case SHEET_ID_ATTR:
sheetId = attributes.getValue(i);
break;
case RID_ATTR:
rid = attributes.getValue(i);
break;
case NAME_ATTR:
name = attributes.getValue(i);
break;
}
if (StrUtil.isNotEmpty(sheetId)) {
ID_RID_MAP.put(sheetId, rid);
}
if (StrUtil.isNotEmpty(name)) {
NAME_RID_MAP.put(name, rid);
}
}
}
}
}

View File

@ -25,7 +25,7 @@ public class ExcelSaxReadTest {
@Test
public void excel07Test() {
// 工具化快速读取
ExcelUtil.read07BySax("aaa.xlsx", 0, createRowHandler());
ExcelUtil.readBySax("aaa.xlsx", 0, createRowHandler());
}
@Test
@ -33,7 +33,7 @@ public class ExcelSaxReadTest {
Excel03SaxReader reader = new Excel03SaxReader(createRowHandler());
reader.read("aaa.xls", 1);
// Console.log("Sheet index: [{}], Sheet name: [{}]", reader.getSheetIndex(), reader.getSheetName());
ExcelUtil.read03BySax("aaa.xls", 1, createRowHandler());
ExcelUtil.readBySax("aaa.xls", 1, createRowHandler());
}
@Test
@ -60,7 +60,7 @@ public class ExcelSaxReadTest {
private RowHandler createRowHandler() {
return (sheetIndex, rowIndex, rowlist) -> {
// Console.log("[{}] [{}] {}", sheetIndex, rowIndex, rowlist);
// Console.log("[{}] [{}] {}", sheetIndex, rowIndex, rowlist);
if (5 != rowIndex && 6 != rowIndex) {
// 测试样例中除第五行第六行都为非空行
Assert.assertTrue(CollUtil.isNotEmpty(rowlist));
@ -105,14 +105,14 @@ public class ExcelSaxReadTest {
@Test
@Ignore
public void dateReadTest(){
public void dateReadTest() {
ExcelUtil.readBySax("d:/test/sax_date_test.xlsx", 0, (i, i1, list) ->
Console.log(StrUtil.join(", ", list)));
}
@Test
@Ignore
public void readBlankTest(){
public void readBlankTest() {
File file = new File("D:/test/b.xlsx");
ExcelUtil.readBySax(file, 0, (sheetIndex, rowIndex, rowList) -> rowList.forEach(Console::log));

View File

@ -8,7 +8,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-script</artifactId>
@ -18,7 +18,7 @@
<properties>
<jython.version>2.7.2</jython.version>
<luaj.version>3.0.1</luaj.version>
<groovy.version>3.0.2</groovy.version>
<groovy.version>3.0.6</groovy.version>
</properties>
<dependencies>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-setting</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-socket</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
</parent>
<artifactId>hutool-system</artifactId>

View File

@ -8,7 +8,7 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.4.4-SNAPSHOT</version>
<version>5.4.5-SNAPSHOT</version>
<name>hutool</name>
<description>Hutool是一个小而全的Java工具类库通过静态方法封装降低相关API的学习成本提高工作效率使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。</description>
<url>https://github.com/looly/hutool</url>