mirror of
				https://gitee.com/dromara/hutool.git
				synced 2025-11-01 00:46:56 +08:00 
			
		
		
		
	🤤release 5.8.33
This commit is contained in:
		
							
								
								
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,25 @@ | ||||
|  | ||||
| # 🚀Changelog | ||||
|  | ||||
| ------------------------------------------------------------------------------------------------------------- | ||||
| # 5.8.34(2024-11-25) | ||||
|  | ||||
| ### 🐣新特性 | ||||
| * 【http   】      增加Windows微信浏览器识别(issue#IB3SJF@Gitee) | ||||
| * 【core   】      ZipUtil.unzip增加编码容错(issue#I3UZ28@Gitee) | ||||
| * 【core   】      Calculator兼容`x`字符作为乘号(issue#3787@Github) | ||||
| * 【poi    】      Excel07SaxReader中,对于小数类型,增加精度判断(issue#IB0EJ9@Gitee) | ||||
| * 【extra  】      SpringUtil增加getBean重载(issue#3779@Github) | ||||
| * 【core   】      DataSizeUtil 新增format方法(issue#IB6UUX@Gitee) | ||||
|  | ||||
| ### 🐞Bug修复 | ||||
| * 【core   】      修复DateUtil.rangeToList中step小于等于0时无限循环问题(issue#3783@Github) | ||||
| * 【cron   】      修复cron模块依赖log模块问题 | ||||
| * 【extra  】      修复MailUtil发送html格式邮件无法正常展示图片问题(pr#1279@Gitee) | ||||
| * 【core   】      【可能的向下兼容问题】修复双引号转义符转义错误问题,修改规则后,对非闭合双引号字段的策略变更,如"aa,则被识别为aa(issue#IB5UQ8@Gitee) | ||||
| * 【extra  】      修复Sftp中传入Session重连时逻辑错误问题(issue#IB69U8@Gitee) | ||||
| * 【json   】      修复JSONUtil.toBean()中将JSON数组字符串转Map对象返回错误问题(issue#3795@Github) | ||||
|  | ||||
| ------------------------------------------------------------------------------------------------------------- | ||||
| # 5.8.33(2024-11-05) | ||||
|  | ||||
|   | ||||
| @@ -40,7 +40,7 @@ | ||||
|  | ||||
| <br/> | ||||
| <p align="center"> | ||||
| 	<a href="https://qm.qq.com/cgi-bin/qm/qr?k=QtsqXLkHpLjE99tkre19j6pjPMhSay1a&jump_from=webapi"> | ||||
| 	<a href="https://qm.qq.com/q/I7pPlTzCa4"> | ||||
| 	<img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A6-715292493-orange"/></a> | ||||
| </p> | ||||
|  | ||||
| @@ -150,18 +150,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: | ||||
| <dependency> | ||||
|     <groupId>cn.hutool</groupId> | ||||
|     <artifactId>hutool-all</artifactId> | ||||
|     <version>5.8.33</version> | ||||
|     <version>5.8.34</version> | ||||
| </dependency> | ||||
| ``` | ||||
|  | ||||
| ### 🍐Gradle | ||||
| ``` | ||||
| implementation 'cn.hutool:hutool-all:5.8.33' | ||||
| implementation 'cn.hutool:hutool-all:5.8.34' | ||||
| ``` | ||||
|  | ||||
| ## 📥Download | ||||
|  | ||||
| - [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.33/) | ||||
| - [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/) | ||||
|  | ||||
| > 🔔️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. | ||||
|   | ||||
| @@ -40,7 +40,7 @@ | ||||
|  | ||||
| <br/> | ||||
| <p align="center"> | ||||
| 	<a href="https://qm.qq.com/cgi-bin/qm/qr?k=QtsqXLkHpLjE99tkre19j6pjPMhSay1a&jump_from=webapi"> | ||||
| 	<a href="https://qm.qq.com/q/I7pPlTzCa4"> | ||||
| 	<img src="https://img.shields.io/badge/QQ%E7%BE%A4%E2%91%A6-715292493-orange"/></a> | ||||
| </p> | ||||
|  | ||||
| @@ -143,20 +143,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu | ||||
| <dependency> | ||||
|     <groupId>cn.hutool</groupId> | ||||
|     <artifactId>hutool-all</artifactId> | ||||
|     <version>5.8.33</version> | ||||
|     <version>5.8.34</version> | ||||
| </dependency> | ||||
| ``` | ||||
|  | ||||
| ### 🍐Gradle | ||||
| ``` | ||||
| implementation 'cn.hutool:hutool-all:5.8.33' | ||||
| implementation 'cn.hutool:hutool-all:5.8.34' | ||||
| ``` | ||||
|  | ||||
| ### 📥下载jar | ||||
|  | ||||
| 点击以下链接,下载`hutool-all-X.X.X.jar`即可: | ||||
|  | ||||
| - [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.33/) | ||||
| - [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/) | ||||
|  | ||||
| > 🔔️注意 | ||||
| > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| 5.8.33 | ||||
| 5.8.34 | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| var version = '5.8.33' | ||||
| var version = '5.8.34' | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-all</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-aop</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-bloomFilter</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-bom</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-cache</artifactId> | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package cn.hutool.cache.impl; | ||||
|  | ||||
| import cn.hutool.core.collection.CopiedIter; | ||||
| import cn.hutool.core.thread.ThreadUtil; | ||||
|  | ||||
| import java.util.Iterator; | ||||
| import java.util.concurrent.locks.StampedLock; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-captcha</artifactId> | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import cn.hutool.captcha.generator.CodeGenerator; | ||||
| import cn.hutool.captcha.generator.RandomGenerator; | ||||
| import cn.hutool.core.img.GraphicsUtil; | ||||
| import cn.hutool.core.img.ImgUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
|  | ||||
| import java.awt.*; | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import cn.hutool.captcha.generator.CodeGenerator; | ||||
| import cn.hutool.captcha.generator.RandomGenerator; | ||||
| import cn.hutool.core.img.ImgUtil; | ||||
| import cn.hutool.core.img.gif.AnimatedGifEncoder; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
|  | ||||
| import java.awt.AlphaComposite; | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import cn.hutool.captcha.generator.CodeGenerator; | ||||
| import cn.hutool.captcha.generator.RandomGenerator; | ||||
| import cn.hutool.core.img.GraphicsUtil; | ||||
| import cn.hutool.core.img.ImgUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
|  | ||||
| import java.awt.*; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-core</artifactId> | ||||
|   | ||||
| @@ -1,13 +1,10 @@ | ||||
| package cn.hutool.core.comparator; | ||||
|  | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.core.lang.Version; | ||||
| import cn.hutool.core.util.*; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 版本比较器<br> | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package cn.hutool.core.convert; | ||||
|  | ||||
| import cn.hutool.core.lang.Console; | ||||
| import cn.hutool.core.util.NumberUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
|   | ||||
| @@ -74,6 +74,11 @@ public class MapConverter extends AbstractConverter<Map<?, ?>> { | ||||
| 			} | ||||
| 			convertMapToMap((Map) value, map); | ||||
| 		} else if (BeanUtil.isBean(value.getClass())) { | ||||
| 			if(value.getClass().getName().equals("cn.hutool.json.JSONArray")){ | ||||
| 				// issue#3795 增加JSONArray转Map错误检查 | ||||
| 				throw new UnsupportedOperationException(StrUtil.format("Unsupported {} to Map.", value.getClass().getName())); | ||||
| 			} | ||||
|  | ||||
| 			map = BeanUtil.beanToMap(value); | ||||
| 			// 二次转换,转换键值类型 | ||||
| 			map = convertInternal(map); | ||||
|   | ||||
| @@ -48,6 +48,10 @@ public class DateRange extends Range<DateTime> { | ||||
| 	 */ | ||||
| 	public DateRange(Date start, Date end, DateField unit, int step, boolean isIncludeStart, boolean isIncludeEnd) { | ||||
| 		super(DateUtil.date(start), DateUtil.date(end), (current, end1, index) -> { | ||||
| 			if(step <= 0){ | ||||
| 				// issue#3783 | ||||
| 				return null; | ||||
| 			} | ||||
| 			final DateTime dt = DateUtil.date(start).offsetNew(unit, (index + 1) * step); | ||||
| 			if (dt.isAfter(end1)) { | ||||
| 				return null; | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package cn.hutool.core.io.unit; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
|  | ||||
| import java.text.DecimalFormat; | ||||
|  | ||||
| /** | ||||
| @@ -35,4 +37,20 @@ public class DataSizeUtil { | ||||
| 		return new DecimalFormat("#,##0.##") | ||||
| 				.format(size / Math.pow(1024, digitGroups)) + " " + DataUnit.UNIT_NAMES[digitGroups]; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 根据单位,将文件大小转换为对应单位的大小 | ||||
| 	 * | ||||
| 	 * @param size 文件大小 | ||||
| 	 * @param fileDataUnit 单位 | ||||
| 	 * @return 大小 | ||||
| 	 * @since 5.8.34 | ||||
| 	 */ | ||||
| 	public static String format(Long size, DataUnit fileDataUnit){ | ||||
| 		if (size <= 0) { | ||||
| 			return "0"; | ||||
| 		} | ||||
| 		int digitGroups = ArrayUtil.indexOf(DataUnit.UNIT_NAMES,fileDataUnit.getSuffix()); | ||||
| 		return new DecimalFormat("##0.##").format(size / Math.pow(1024, digitGroups)) + " " + DataUnit.UNIT_NAMES[digitGroups]; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -44,10 +44,12 @@ public enum DataUnit { | ||||
| 	 */ | ||||
| 	TERABYTES("TB", DataSize.ofTerabytes(1)); | ||||
|  | ||||
| 	/** | ||||
| 	 * 单位后缀 | ||||
| 	 */ | ||||
| 	public static final String[] UNIT_NAMES = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB"}; | ||||
|  | ||||
| 	private final String suffix; | ||||
|  | ||||
| 	private final DataSize size; | ||||
|  | ||||
|  | ||||
| @@ -56,6 +58,16 @@ public enum DataUnit { | ||||
| 		this.size = size; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 单位后缀 | ||||
| 	 * | ||||
| 	 * @return 单位后缀 | ||||
| 	 * @since 5.8.34 | ||||
| 	 */ | ||||
| 	public String getSuffix() { | ||||
| 		return this.suffix; | ||||
| 	} | ||||
|  | ||||
| 	DataSize size() { | ||||
| 		return this.size; | ||||
| 	} | ||||
| @@ -76,5 +88,4 @@ public enum DataUnit { | ||||
| 		} | ||||
| 		throw new IllegalArgumentException("Unknown data unit suffix '" + suffix + "'"); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package cn.hutool.core.lang; | ||||
|  | ||||
| import cn.hutool.core.date.SystemClock; | ||||
| import cn.hutool.core.lang.id.IdConstants; | ||||
| import cn.hutool.core.util.IdUtil; | ||||
| import cn.hutool.core.util.RandomUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package cn.hutool.core.math; | ||||
|  | ||||
| import cn.hutool.core.util.CharUtil; | ||||
| import cn.hutool.core.util.NumberUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| @@ -192,6 +193,9 @@ public class Calculator { | ||||
| 						arr[i] = '~'; | ||||
| 					} | ||||
| 				} | ||||
| 			} else if(CharUtil.equals(arr[i], 'x', true)){ | ||||
| 				// issue#3787 x转换为* | ||||
| 				arr[i] = '*'; | ||||
| 			} | ||||
| 		} | ||||
| 		if (arr[0] == '~' && (arr.length > 1 && arr[1] == '(')) { | ||||
|   | ||||
| @@ -1,24 +1,33 @@ | ||||
| /* | ||||
|  * Copyright (c) 2013-2024 Hutool Team and hutool.cn | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package cn.hutool.core.text.csv; | ||||
|  | ||||
| import cn.hutool.core.collection.ComputeIter; | ||||
| import cn.hutool.core.io.IORuntimeException; | ||||
| import cn.hutool.core.io.IoUtil; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.text.StrBuilder; | ||||
| import cn.hutool.core.util.CharUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.ObjUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import java.io.Closeable; | ||||
| import java.io.IOException; | ||||
| import java.io.Reader; | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * CSV行解析器,参考:FastCSV | ||||
| @@ -30,10 +39,8 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
|  | ||||
| 	private static final int DEFAULT_ROW_CAPACITY = 10; | ||||
|  | ||||
| 	private final Reader reader; | ||||
| 	private final CsvReadConfig config; | ||||
|  | ||||
| 	private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE); | ||||
| 	private final CsvTokener tokener; | ||||
| 	/** | ||||
| 	 * 前一个特殊分界字符 | ||||
| 	 */ | ||||
| @@ -45,7 +52,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 	/** | ||||
| 	 * 当前读取字段 | ||||
| 	 */ | ||||
| 	private final StrBuilder currentField = new StrBuilder(512); | ||||
| 	private final StringBuilder currentField = new StringBuilder(512); | ||||
|  | ||||
| 	/** | ||||
| 	 * 标题行 | ||||
| @@ -78,9 +85,9 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 	 * @param reader Reader | ||||
| 	 * @param config 配置,null则为默认配置 | ||||
| 	 */ | ||||
| 	public CsvParser(final Reader reader, CsvReadConfig config) { | ||||
| 		this.reader = Objects.requireNonNull(reader, "reader must not be null"); | ||||
| 		this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig); | ||||
| 	public CsvParser(final Reader reader, final CsvReadConfig config) { | ||||
| 		this.config = ObjUtil.defaultIfNull(config, CsvReadConfig::defaultConfig); | ||||
| 		this.tokener = new CsvTokener(reader); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -96,7 +103,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 		if (lineNo < config.beginLineNo) { | ||||
| 			throw new IllegalStateException("No header available - call nextRow() first"); | ||||
| 		} | ||||
| 		return header.fields; | ||||
| 		return header.getRawList(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| @@ -107,13 +114,13 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 	/** | ||||
| 	 * 读取下一行数据 | ||||
| 	 * | ||||
| 	 * @return CsvRow | ||||
| 	 * @return CsvRow,{@code null}表示 | ||||
| 	 * @throws IORuntimeException IO读取异常 | ||||
| 	 */ | ||||
| 	public CsvRow nextRow() throws IORuntimeException { | ||||
| 		List<String> currentFields; | ||||
| 		int fieldCount; | ||||
| 		while (false == finished) { | ||||
| 		while (!finished) { | ||||
| 			currentFields = readLine(); | ||||
| 			fieldCount = currentFields.size(); | ||||
| 			if (fieldCount < 1) { | ||||
| @@ -122,11 +129,11 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 			} | ||||
|  | ||||
| 			// 读取范围校验 | ||||
| 			if(lineNo < config.beginLineNo){ | ||||
| 			if (lineNo < config.beginLineNo) { | ||||
| 				// 未达到读取起始行,继续 | ||||
| 				continue; | ||||
| 			} | ||||
| 			if(lineNo > config.endLineNo){ | ||||
| 			if (lineNo > config.endLineNo) { | ||||
| 				// 超出结束行,读取结束 | ||||
| 				break; | ||||
| 			} | ||||
| @@ -175,9 +182,9 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 			String field = currentFields.get(i); | ||||
| 			if (MapUtil.isNotEmpty(this.config.headerAlias)) { | ||||
| 				// 自定义别名 | ||||
| 				field = ObjectUtil.defaultIfNull(this.config.headerAlias.get(field), field); | ||||
| 				field = ObjUtil.defaultIfNull(this.config.headerAlias.get(field), field); | ||||
| 			} | ||||
| 			if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) { | ||||
| 			if (StrUtil.isNotEmpty(field) && !localHeaderMap.containsKey(field)) { | ||||
| 				localHeaderMap.put(field, i); | ||||
| 			} | ||||
| 		} | ||||
| @@ -199,70 +206,67 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 	private List<String> readLine() throws IORuntimeException { | ||||
| 		// 矫正行号 | ||||
| 		// 当一行内容包含多行数据时,记录首行行号,但是读取下一行时,需要把多行内容的行数加上 | ||||
| 		if(inQuotesLineCount > 0){ | ||||
| 		if (inQuotesLineCount > 0) { | ||||
| 			this.lineNo += this.inQuotesLineCount; | ||||
| 			this.inQuotesLineCount = 0; | ||||
| 		} | ||||
|  | ||||
| 		final List<String> currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY); | ||||
|  | ||||
| 		final StrBuilder currentField = this.currentField; | ||||
| 		final Buffer buf = this.buf; | ||||
| 		final StringBuilder currentField = this.currentField; | ||||
| 		int preChar = this.preChar;//前一个特殊分界字符 | ||||
| 		int copyLen = 0; //拷贝长度 | ||||
| 		boolean inComment = false; | ||||
|  | ||||
| 		int c; | ||||
| 		while (true) { | ||||
| 			if (false == buf.hasRemaining()) { | ||||
| 				// 此Buffer读取结束,开始读取下一段 | ||||
| 				if (copyLen > 0) { | ||||
| 					buf.appendTo(currentField, copyLen); | ||||
| 					// 此处无需mark,read方法会重置mark | ||||
| 			c = tokener.next(); | ||||
| 			if(c < 0){ | ||||
| 				if (currentField.length() > 0 || preChar == config.fieldSeparator) { | ||||
| 					if(this.inQuotes){ | ||||
| 						// 未闭合的文本包装,在末尾补充包装符 | ||||
| 						currentField.append(config.textDelimiter); | ||||
| 					} | ||||
| 				if (buf.read(this.reader) < 0) { | ||||
| 					// CSV读取结束 | ||||
| 					finished = true; | ||||
|  | ||||
| 					if (currentField.hasContent() || preChar == config.fieldSeparator) { | ||||
| 					//剩余部分作为一个字段 | ||||
| 						addField(currentFields, currentField.toStringAndReset()); | ||||
| 					addField(currentFields, currentField.toString()); | ||||
| 					currentField.setLength(0); | ||||
| 				} | ||||
| 				// 读取结束 | ||||
| 				this.finished = true; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 				//重置 | ||||
| 				copyLen = 0; | ||||
| 			} | ||||
|  | ||||
| 			final char c = buf.get(); | ||||
|  | ||||
| 			// 注释行标记 | ||||
| 			if(preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF){ | ||||
| 			if (preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF) { | ||||
| 				// 判断行首字符为指定注释字符的注释开始,直到遇到换行符 | ||||
| 				// 行首分两种,1是preChar < 0表示文本开始,2是换行符后紧跟就是下一行的开始 | ||||
| 				// issue#IA8WE0 如果注释符出现在包装符内,被认为是普通字符 | ||||
| 				if((false == inQuotes) && null != this.config.commentCharacter && c == this.config.commentCharacter){ | ||||
| 				if (!inQuotes && null != this.config.commentCharacter && c == this.config.commentCharacter) { | ||||
| 					inComment = true; | ||||
| 				} | ||||
| 			} | ||||
| 			// 注释行处理 | ||||
| 			if(inComment){ | ||||
| 			if (inComment) { | ||||
| 				if (c == CharUtil.CR || c == CharUtil.LF) { | ||||
| 					// 注释行以换行符为结尾 | ||||
| 					lineNo++; | ||||
| 					inComment = false; | ||||
| 				} | ||||
| 				// 跳过注释行中的任何字符 | ||||
| 				buf.mark(); | ||||
| 				preChar = c; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (inQuotes) { | ||||
| 				//引号内,作为内容,直到引号结束 | ||||
| 				if (c == config.textDelimiter) { | ||||
| 					// End of quoted text | ||||
| 					// issue#IB5UQ8 文本包装符转义 | ||||
| 					final int next = tokener.next(); | ||||
| 					if(next != config.textDelimiter){ | ||||
| 						// 包装结束 | ||||
| 						inQuotes = false; | ||||
| 						tokener.back(); | ||||
| 					} | ||||
| 					// https://datatracker.ietf.org/doc/html/rfc4180#section-2 跳过转义符,只保留被转义的包装符 | ||||
| 				} else { | ||||
| 					// 字段内容中新行 | ||||
| 					if (isLineEnd(c, preChar)) { | ||||
| @@ -270,46 +274,34 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 					} | ||||
| 				} | ||||
| 				// 普通字段字符 | ||||
| 				copyLen++; | ||||
| 				currentField.append((char)c); | ||||
| 			} else { | ||||
| 				// 非引号内 | ||||
| 				if (c == config.fieldSeparator) { | ||||
| 					//一个字段结束 | ||||
| 					if (copyLen > 0) { | ||||
| 						buf.appendTo(currentField, copyLen); | ||||
| 						copyLen = 0; | ||||
| 					} | ||||
| 					buf.mark(); | ||||
| 					addField(currentFields, currentField.toStringAndReset()); | ||||
| 					addField(currentFields, currentField.toString()); | ||||
| 					currentField.setLength(0); | ||||
| 				} else if (c == config.textDelimiter && isFieldBegin(preChar)) { | ||||
| 					// 引号开始且出现在字段开头 | ||||
| 					inQuotes = true; | ||||
| 					copyLen++; | ||||
| 					currentField.append((char)c); | ||||
| 				} else if (c == CharUtil.CR) { | ||||
| 					// \r,直接结束 | ||||
| 					if (copyLen > 0) { | ||||
| 						buf.appendTo(currentField, copyLen); | ||||
| 					} | ||||
| 					buf.mark(); | ||||
| 					addField(currentFields, currentField.toStringAndReset()); | ||||
| 					// \r | ||||
| 					addField(currentFields, currentField.toString()); | ||||
| 					currentField.setLength(0); | ||||
| 					preChar = c; | ||||
| 					break; | ||||
| 				} else if (c == CharUtil.LF) { | ||||
| 					// \n | ||||
| 					if (preChar != CharUtil.CR) { | ||||
| 						if (copyLen > 0) { | ||||
| 							buf.appendTo(currentField, copyLen); | ||||
| 						} | ||||
| 						buf.mark(); | ||||
| 						addField(currentFields, currentField.toStringAndReset()); | ||||
| 						addField(currentFields, currentField.toString()); | ||||
| 						currentField.setLength(0); | ||||
| 						preChar = c; | ||||
| 						break; | ||||
| 					} | ||||
| 					// 前一个字符是\r,已经处理过这个字段了,此处直接跳过 | ||||
| 					buf.mark(); | ||||
| 				} else { | ||||
| 					// 普通字符 | ||||
| 					copyLen++; | ||||
| 					currentField.append((char)c); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -325,7 +317,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
|  | ||||
| 	@Override | ||||
| 	public void close() throws IOException { | ||||
| 		reader.close(); | ||||
| 		tokener.close(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -334,7 +326,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 	 * @param currentFields 当前的字段列表(即为行) | ||||
| 	 * @param field         字段 | ||||
| 	 */ | ||||
| 	private void addField(List<String> currentFields, String field) { | ||||
| 	private void addField(final List<String> currentFields, String field) { | ||||
| 		final char textDelimiter = this.config.textDelimiter; | ||||
|  | ||||
| 		// 忽略多余引号后的换行符 | ||||
| @@ -342,12 +334,9 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
|  | ||||
| 		if(StrUtil.isWrap(field, textDelimiter)){ | ||||
| 			field = StrUtil.sub(field, 1, field.length() - 1); | ||||
| 			// https://datatracker.ietf.org/doc/html/rfc4180#section-2 | ||||
| 			// 第七条规则,只有包装内的包装符需要转义 | ||||
| 			field = StrUtil.replace(field, String.valueOf(textDelimiter) + textDelimiter, String.valueOf(textDelimiter)); | ||||
| 		} | ||||
|  | ||||
| 		if(this.config.trimField){ | ||||
| 		if (this.config.trimField) { | ||||
| 			// issue#I49M0C@Gitee | ||||
| 			field = StrUtil.trim(field); | ||||
| 		} | ||||
| @@ -362,7 +351,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 	 * @return 是否结束 | ||||
| 	 * @since 5.7.4 | ||||
| 	 */ | ||||
| 	private boolean isLineEnd(char c, int preChar) { | ||||
| 	private boolean isLineEnd(final int c, final int preChar) { | ||||
| 		return (c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR; | ||||
| 	} | ||||
|  | ||||
| @@ -383,89 +372,4 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S | ||||
| 			|| preChar == CharUtil.LF | ||||
| 			|| preChar == CharUtil.CR; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 内部Buffer | ||||
| 	 * | ||||
| 	 * @author looly | ||||
| 	 */ | ||||
| 	private static class Buffer implements Serializable{ | ||||
| 		private static final long serialVersionUID = 1L; | ||||
|  | ||||
| 		final char[] buf; | ||||
|  | ||||
| 		/** | ||||
| 		 * 标记位置,用于读数据 | ||||
| 		 */ | ||||
| 		private int mark; | ||||
| 		/** | ||||
| 		 * 当前位置 | ||||
| 		 */ | ||||
| 		private int position; | ||||
| 		/** | ||||
| 		 * 读取的数据长度,一般小于buf.length,-1表示无数据 | ||||
| 		 */ | ||||
| 		private int limit; | ||||
|  | ||||
| 		Buffer(int capacity) { | ||||
| 			buf = new char[capacity]; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * 是否还有未读数据 | ||||
| 		 * | ||||
| 		 * @return 是否还有未读数据 | ||||
| 		 */ | ||||
| 		public final boolean hasRemaining() { | ||||
| 			return position < limit; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * 读取到缓存<br> | ||||
| 		 * 全量读取,会重置Buffer中所有数据 | ||||
| 		 * | ||||
| 		 * @param reader {@link Reader} | ||||
| 		 */ | ||||
| 		int read(Reader reader) { | ||||
| 			int length; | ||||
| 			try { | ||||
| 				length = reader.read(this.buf); | ||||
| 			} catch (IOException e) { | ||||
| 				throw new IORuntimeException(e); | ||||
| 			} | ||||
| 			this.mark = 0; | ||||
| 			this.position = 0; | ||||
| 			this.limit = length; | ||||
| 			return length; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * 先获取当前字符,再将当前位置后移一位<br> | ||||
| 		 * 此方法不检查是否到了数组末尾,请自行使用{@link #hasRemaining()}判断。 | ||||
| 		 * | ||||
| 		 * @return 当前位置字符 | ||||
| 		 * @see #hasRemaining() | ||||
| 		 */ | ||||
| 		char get() { | ||||
| 			return this.buf[this.position++]; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * 标记位置记为下次读取位置 | ||||
| 		 */ | ||||
| 		void mark() { | ||||
| 			this.mark = this.position; | ||||
| 		} | ||||
|  | ||||
| 		/** | ||||
| 		 * 将数据追加到{@link StrBuilder},追加结束后需手动调用{@link #mark()} 重置读取位置 | ||||
| 		 * | ||||
| 		 * @param builder {@link StrBuilder} | ||||
| 		 * @param length  追加的长度 | ||||
| 		 * @see #mark() | ||||
| 		 */ | ||||
| 		void appendTo(StrBuilder builder, int length) { | ||||
| 			builder.append(this.buf, this.mark, length); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,102 @@ | ||||
| /* | ||||
|  * Copyright (c) 2024 Hutool Team and hutool.cn | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package cn.hutool.core.text.csv; | ||||
|  | ||||
| import cn.hutool.core.io.IORuntimeException; | ||||
| import cn.hutool.core.io.IoUtil; | ||||
|  | ||||
| import java.io.Closeable; | ||||
| import java.io.IOException; | ||||
| import java.io.Reader; | ||||
|  | ||||
| /** | ||||
|  * CSV解析器,用于解析CSV文件 | ||||
|  * | ||||
|  * @author looly | ||||
|  * @since 5.8.0 | ||||
|  */ | ||||
| public class CsvTokener implements Closeable { | ||||
|  | ||||
| 	private final Reader raw; | ||||
| 	/** | ||||
| 	 * 在Reader的位置(解析到第几个字符) | ||||
| 	 */ | ||||
| 	private long index; | ||||
| 	/** | ||||
| 	 * 前一个字符 | ||||
| 	 */ | ||||
| 	private int prev; | ||||
| 	/** | ||||
| 	 * 是否使用前一个字符 | ||||
| 	 */ | ||||
| 	private boolean usePrev; | ||||
|  | ||||
| 	/** | ||||
| 	 * 构造 | ||||
| 	 * | ||||
| 	 * @param reader {@link Reader} | ||||
| 	 */ | ||||
| 	public CsvTokener(final Reader reader) { | ||||
| 		this.raw = IoUtil.toBuffered(reader); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 读取下一个字符,并记录位置 | ||||
| 	 * | ||||
| 	 * @return 下一个字符 | ||||
| 	 */ | ||||
| 	public int next() { | ||||
| 		if(this.usePrev){ | ||||
| 			this.usePrev = false; | ||||
| 		}else{ | ||||
| 			try { | ||||
| 				this.prev = this.raw.read(); | ||||
| 			} catch (final IOException e) { | ||||
| 				throw new IORuntimeException(e); | ||||
| 			} | ||||
| 		} | ||||
| 		this.index++; | ||||
| 		return this.prev; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 将标记回退到第一个字符 | ||||
| 	 * | ||||
| 	 * @throws IllegalStateException 当多次调用back时,抛出此异常 | ||||
| 	 */ | ||||
| 	public void back() throws IllegalStateException { | ||||
| 		if (this.usePrev || this.index <= 0) { | ||||
| 			throw new IllegalStateException("Stepping back two steps is not supported"); | ||||
| 		} | ||||
| 		this.index --; | ||||
| 		this.usePrev = true; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 获取当前位置 | ||||
| 	 * | ||||
| 	 * @return 位置 | ||||
| 	 */ | ||||
| 	public long getIndex() { | ||||
| 		return this.index; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void close() throws IOException { | ||||
| 		IoUtil.close(this.raw); | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,5 @@ | ||||
| package cn.hutool.core.util; | ||||
|  | ||||
| import cn.hutool.core.lang.Console; | ||||
| import cn.hutool.core.lang.ParameterizedTypeImpl; | ||||
| import cn.hutool.core.lang.reflect.ActualTypeMapperPool; | ||||
|  | ||||
|   | ||||
| @@ -34,10 +34,7 @@ import java.util.ArrayList; | ||||
| import java.util.Enumeration; | ||||
| import java.util.List; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.zip.ZipEntry; | ||||
| import java.util.zip.ZipFile; | ||||
| import java.util.zip.ZipInputStream; | ||||
| import java.util.zip.ZipOutputStream; | ||||
| import java.util.zip.*; | ||||
|  | ||||
| /** | ||||
|  * 压缩工具类 | ||||
| @@ -65,6 +62,17 @@ public class ZipUtil { | ||||
| 		try { | ||||
| 			return new ZipFile(file, ObjectUtil.defaultIfNull(charset, CharsetUtil.CHARSET_UTF_8)); | ||||
| 		} catch (IOException e) { | ||||
| 			// issue#I3UZ28 可能编码错误提示 | ||||
| 			if(e instanceof ZipException){ | ||||
| 				if(e.getMessage().contains("invalid CEN header")){ | ||||
| 					try { | ||||
| 						// 尝试使用不同编码 | ||||
| 						return new ZipFile(file, CharsetUtil.CHARSET_UTF_8.equals(charset) ? CharsetUtil.CHARSET_GBK : CharsetUtil.CHARSET_UTF_8); | ||||
| 					} catch (final IOException ex) { | ||||
| 						throw new IORuntimeException(ex); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			throw new IORuntimeException(e); | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -0,0 +1,27 @@ | ||||
| package cn.hutool.core.date; | ||||
|  | ||||
| import org.junit.jupiter.api.Assertions; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
|  | ||||
| public class DateRangeTest { | ||||
| 	@Test | ||||
| 	void issue3783Test() { | ||||
| 		final Date start = DateUtil.parse("2024-01-01"); | ||||
| 		final Date end = DateUtil.parse("2024-02-01"); | ||||
| 		final List<DateTime> dateTimes = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH, 0); | ||||
| 		Assertions.assertEquals(1, dateTimes.size()); | ||||
| 		Assertions.assertEquals("2024-01-01 00:00:00", dateTimes.get(0).toString()); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	void issue3783Test2() { | ||||
| 		final Date start = DateUtil.parse("2024-01-01"); | ||||
| 		final Date end = DateUtil.parse("2024-02-01"); | ||||
| 		final List<DateTime> dateTimes = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH, -2); | ||||
| 		Assertions.assertEquals(1, dateTimes.size()); | ||||
| 		Assertions.assertEquals("2024-01-01 00:00:00", dateTimes.get(0).toString()); | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,22 @@ | ||||
| package cn.hutool.core.date; | ||||
|  | ||||
| import org.junit.jupiter.api.Assertions; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import java.util.TimeZone; | ||||
|  | ||||
| public class Issue3798Test { | ||||
| 	@Test | ||||
| 	void parseTest() { | ||||
| 		final String iso_datetime1 = "2000-01-01T12:00:00+08:00"; | ||||
| 		final DateTime parse1 = DateUtil.parse(iso_datetime1); | ||||
| 		Assertions.assertEquals(TimeZone.getTimeZone("GMT+08:00"), parse1.getTimeZone()); | ||||
| 		Assertions.assertEquals("2000-01-01 12:00:00", parse1.toString()); | ||||
|  | ||||
| 		// 伦敦时间(Greenwich Mean Time, GMT)和北京时间(China Standard Time, CST)之间的时差是8小时。北京时间比伦敦时间快8小时 | ||||
| 		final String iso_datetime2 = "2000-01-01T12:00:00+00:00"; | ||||
| 		final DateTime parse2 = DateUtil.parse(iso_datetime2); | ||||
| 		Assertions.assertEquals(TimeZone.getTimeZone("GMT+00:00"), parse2.getTimeZone()); | ||||
| 		Assertions.assertEquals("2000-01-01 20:00:00", parse2.toString(TimeZone.getTimeZone("GMT+08:00"))); | ||||
| 	} | ||||
| } | ||||
| @@ -1,8 +1,9 @@ | ||||
| package cn.hutool.core.io.unit; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
|  | ||||
| public class DataSizeUtilTest { | ||||
|  | ||||
| 	@Test | ||||
| @@ -61,6 +62,18 @@ public class DataSizeUtilTest { | ||||
| 		assertEquals("1 TB", format); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	public void formatWithUnitTest(){ | ||||
| 		String format = DataSizeUtil.format(Long.MAX_VALUE, DataUnit.TERABYTES); | ||||
| 		assertEquals("8388608 TB", format); | ||||
|  | ||||
| 		format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024 * 1024, DataUnit.GIGABYTES); | ||||
| 		assertEquals("1048576 GB", format); | ||||
|  | ||||
| 		format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024, DataUnit.GIGABYTES); | ||||
| 		assertEquals("1024 GB", format); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	public void issueI88Z4ZTest() { | ||||
| 		final String size = DataSizeUtil.format(10240000); | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| package cn.hutool.core.math; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
|  | ||||
| public class CalculatorTest { | ||||
|  | ||||
| 	@Test | ||||
| @@ -55,4 +56,14 @@ public class CalculatorTest { | ||||
| 		final double calcValue = Calculator.conversion("(11+2)12"); | ||||
| 		assertEquals(156D, calcValue, 0.001); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	void issue3787Test() { | ||||
| 		final Calculator calculator1 = new Calculator(); | ||||
| 		double result = calculator1.calculate("0+50/100x(1/0.5)"); | ||||
| 		assertEquals(1D, result); | ||||
|  | ||||
| 		result = calculator1.calculate("0+50/100X(1/0.5)"); | ||||
| 		assertEquals(1D, result); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import cn.hutool.core.lang.Console; | ||||
| import cn.hutool.core.util.CharsetUtil; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| import org.junit.jupiter.api.Disabled; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| @@ -17,6 +16,8 @@ import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
|  | ||||
| public class CsvUtilTest { | ||||
|  | ||||
| 	@Test | ||||
| @@ -32,7 +33,8 @@ public class CsvUtilTest { | ||||
| 		assertEquals("关注\"对象\"", row0.get(3)); | ||||
| 		assertEquals("年龄", row0.get(4)); | ||||
| 		assertEquals("", row0.get(5)); | ||||
| 		assertEquals("\"", row0.get(6)); | ||||
| 		// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n | ||||
| 		assertEquals("\"\n", row0.get(6)); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| @@ -46,7 +48,8 @@ public class CsvUtilTest { | ||||
| 			assertEquals("关注\"对象\"", csvRow.get(3)); | ||||
| 			assertEquals("年龄", csvRow.get(4)); | ||||
| 			assertEquals("", csvRow.get(5)); | ||||
| 			assertEquals("\"", csvRow.get(6)); | ||||
| 			// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n | ||||
| 			assertEquals("\"\n", csvRow.get(6)); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| @@ -70,7 +73,8 @@ public class CsvUtilTest { | ||||
| 		assertEquals("关注\"对象\"", row0.get(3)); | ||||
| 		assertEquals("年龄", row0.get(4)); | ||||
| 		assertEquals("", row0.get(5)); | ||||
| 		assertEquals("\"", row0.get(6)); | ||||
| 		// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n | ||||
| 		assertEquals("\"\n", row0.get(6)); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| @@ -84,7 +88,8 @@ public class CsvUtilTest { | ||||
| 			assertEquals("关注\"对象\"", csvRow.get(3)); | ||||
| 			assertEquals("年龄", csvRow.get(4)); | ||||
| 			assertEquals("", csvRow.get(5)); | ||||
| 			assertEquals("\"", csvRow.get(6)); | ||||
| 			// 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n | ||||
| 			assertEquals("\"\n", csvRow.get(6)); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,16 @@ | ||||
| package cn.hutool.core.text.csv; | ||||
|  | ||||
| import cn.hutool.core.lang.Console; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import java.io.StringReader; | ||||
|  | ||||
| public class IssueIB5UQ8Test { | ||||
| 	@Test | ||||
| 	void parseEscapeTest() { | ||||
| 		String csv = "\"Consultancy, 10\"\",, food\""; | ||||
| 		final CsvReader reader = CsvUtil.getReader(new StringReader(csv)); | ||||
| 		final String s = reader.read().getRow(0).get(0); | ||||
| 		Console.log(s); | ||||
| 	} | ||||
| } | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-cron</artifactId> | ||||
| @@ -26,6 +26,11 @@ | ||||
| 			<artifactId>hutool-core</artifactId> | ||||
| 			<version>${project.parent.version}</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>cn.hutool</groupId> | ||||
| 			<artifactId>hutool-log</artifactId> | ||||
| 			<version>${project.parent.version}</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>cn.hutool</groupId> | ||||
| 			<artifactId>hutool-setting</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-crypto</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-db</artifactId> | ||||
|   | ||||
| @@ -2,10 +2,8 @@ package cn.hutool.db; | ||||
|  | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import cn.hutool.core.lang.PatternPool; | ||||
| import cn.hutool.core.lang.RegexPool; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.ReUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.db.dialect.Dialect; | ||||
| import cn.hutool.db.dialect.DialectFactory; | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package cn.hutool.db.dialect.impl; | ||||
| import cn.hutool.core.lang.Assert; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.db.Entity; | ||||
| import cn.hutool.db.Page; | ||||
| import cn.hutool.db.StatementUtil; | ||||
| import cn.hutool.db.dialect.DialectName; | ||||
| import cn.hutool.db.sql.SqlBuilder; | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package cn.hutool.db.ds.pooled; | ||||
|  | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.util.ClassUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.db.DbRuntimeException; | ||||
| import cn.hutool.db.DbUtil; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-dfa</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-extra</artifactId> | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| package cn.hutool.extra.compress.archiver; | ||||
|  | ||||
| import cn.hutool.core.lang.Filter; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import java.io.Closeable; | ||||
| import java.io.File; | ||||
|   | ||||
| @@ -268,6 +268,8 @@ public class JakartaMail implements Builder<MimeMessage> { | ||||
| 					if (StrUtil.startWith(attachment.getContentType(), "image/")) { | ||||
| 						// 图片附件,用于正文中引用图片 | ||||
| 						bodyPart.setContentID(nameEncoded); | ||||
| 						// 图片附件设置内联,否则无法正常引用图片 | ||||
| 						bodyPart.setDisposition(MimeBodyPart.INLINE); | ||||
| 					} | ||||
| 					this.multipart.addBodyPart(bodyPart); | ||||
| 				} | ||||
|   | ||||
| @@ -8,10 +8,7 @@ import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import javax.activation.DataHandler; | ||||
| import javax.activation.DataSource; | ||||
| import javax.activation.FileDataSource; | ||||
| import javax.activation.FileTypeMap; | ||||
| import javax.activation.*; | ||||
| import javax.mail.Address; | ||||
| import javax.mail.MessagingException; | ||||
| import javax.mail.Multipart; | ||||
| @@ -273,6 +270,8 @@ public class Mail implements Builder<MimeMessage> { | ||||
| 					if (StrUtil.startWith(attachment.getContentType(), "image/")) { | ||||
| 						// 图片附件,用于正文中引用图片 | ||||
| 						bodyPart.setContentID(nameEncoded); | ||||
| 						// 图片附件设置内联,否则无法正常引用图片 | ||||
| 						bodyPart.setDisposition(MimeBodyPart.INLINE); | ||||
| 					} | ||||
| 					this.multipart.addBodyPart(bodyPart); | ||||
| 				} | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package cn.hutool.extra.pinyin.engine.tinypinyin; | ||||
|  | ||||
| import cn.hutool.core.lang.Opt; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.pinyin.PinyinEngine; | ||||
| import com.github.promeg.pinyinhelper.Pinyin; | ||||
|   | ||||
| @@ -122,6 +122,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA | ||||
| 		return getBeanFactory().getBean(clazz); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 通过class获取Bean | ||||
| 	 * | ||||
| 	 * @param <T>   Bean类型 | ||||
| 	 * @param clazz Bean类 | ||||
| 	 * @param args  创建bean需要的参数属性 | ||||
| 	 * @return Bean对象 | ||||
| 	 * @since 5.8.34 | ||||
| 	 */ | ||||
| 	public static <T> T getBean(Class<T> clazz, Object... args) { | ||||
| 		return getBeanFactory().getBean(clazz, args); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 通过name,以及Clazz返回指定的Bean | ||||
| 	 * | ||||
| @@ -134,6 +147,18 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA | ||||
| 		return getBeanFactory().getBean(name, clazz); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 通过name,以及Clazz返回指定的Bean | ||||
| 	 * | ||||
| 	 * @param name Bean名称 | ||||
| 	 * @param args 创建bean需要的参数属性 | ||||
| 	 * @return Bean对象 | ||||
| 	 * @since 5.8.34 | ||||
| 	 */ | ||||
| 	public static Object getBean(String name, Object... args) { | ||||
| 		return getBeanFactory().getBean(name, args); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 通过类型参考返回带泛型参数的Bean | ||||
| 	 * | ||||
|   | ||||
| @@ -8,13 +8,9 @@ import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.ftp.AbstractFtp; | ||||
| import cn.hutool.extra.ftp.FtpConfig; | ||||
| import cn.hutool.extra.ftp.FtpException; | ||||
| import com.jcraft.jsch.ChannelSftp; | ||||
| import com.jcraft.jsch.*; | ||||
| import com.jcraft.jsch.ChannelSftp.LsEntry; | ||||
| import com.jcraft.jsch.ChannelSftp.LsEntrySelector; | ||||
| import com.jcraft.jsch.Session; | ||||
| import com.jcraft.jsch.SftpATTRS; | ||||
| import com.jcraft.jsch.SftpException; | ||||
| import com.jcraft.jsch.SftpProgressMonitor; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.InputStream; | ||||
| @@ -111,7 +107,7 @@ public class Sftp extends AbstractFtp { | ||||
| 	 * @since 4.1.14 | ||||
| 	 */ | ||||
| 	public Sftp(Session session, Charset charset) { | ||||
| 		super(FtpConfig.create().setCharset(charset)); | ||||
| 		super(FtpConfig.create().setCharset(charset).setHost(session.getHost()).setPort(session.getPort())); | ||||
| 		init(session, charset); | ||||
| 	} | ||||
|  | ||||
| @@ -172,6 +168,17 @@ public class Sftp extends AbstractFtp { | ||||
| 	 * @since 5.3.3 | ||||
| 	 */ | ||||
| 	public void init() { | ||||
| 		// issue#IB69U8 如果用户传入Session对象,则不能使用配置初始化,而是尝试重新连接 | ||||
| 		if(StrUtil.isEmpty(this.ftpConfig.getHost()) && null != this.session){ | ||||
| 			try { | ||||
| 				this.session.connect((int) this.ftpConfig.getConnectionTimeout()); | ||||
| 			} catch (JSchException e) { | ||||
| 				throw new JschRuntimeException(e); | ||||
| 			} | ||||
| 			init(this.session, this.ftpConfig.getCharset()); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		init(this.ftpConfig); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -38,6 +38,15 @@ public class JakartaMailTest { | ||||
| 		JakartaMailUtil.sendHtml("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:testImage\" />", map); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	@Disabled | ||||
| 	public void sendHtmlWithImageTest() { | ||||
| 		Map<String, InputStream> map = new HashMap<>(); | ||||
| 		InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png"); | ||||
| 		map.put("<image-1>", in); | ||||
| 		JakartaMailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:image-1\" />", map); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	@Disabled | ||||
| 	public void sendHtmlTest() { | ||||
|   | ||||
| @@ -38,6 +38,15 @@ public class MailTest { | ||||
| 		MailUtil.sendHtml("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:testImage\" />", map); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	@Disabled | ||||
| 	public void sendHtmlWithImageTest() { | ||||
| 		Map<String, InputStream> map = new HashMap<>(); | ||||
| 		InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png"); | ||||
| 		map.put("<image-1>", in); | ||||
| 		MailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:image-1\" />", map); | ||||
| 	} | ||||
|  | ||||
| 	@Test | ||||
| 	@Disabled | ||||
| 	public void sendHtmlTest() { | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								hutool-extra/src/test/resources/image/Dromara.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hutool-extra/src/test/resources/image/Dromara.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 29 KiB | 
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-http</artifactId> | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import cn.hutool.core.io.IoUtil; | ||||
| import cn.hutool.core.io.StreamProgress; | ||||
| import cn.hutool.core.lang.Console; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.net.RFC3986; | ||||
| import cn.hutool.core.net.url.UrlQuery; | ||||
|   | ||||
| @@ -31,6 +31,8 @@ public class Browser extends UserAgentInfo { | ||||
| 		// 部分特殊浏览器是基于安卓、Iphone等的,需要优先判断 | ||||
| 		// 企业微信 企业微信使用微信浏览器内核,会包含 MicroMessenger 所以要放在前面 | ||||
| 		new Browser("wxwork", "wxwork", "wxwork\\/([\\d\\w\\.\\-]+)"), | ||||
| 		// issue#IB3SJF 微信电脑端 | ||||
| 		new Browser("WindowsWechat", "WindowsWechat", "MicroMessenger" + Other_Version), | ||||
| 		// 微信 | ||||
| 		new Browser("MicroMessenger", "MicroMessenger", Other_Version), | ||||
| 		// 微信小程序 | ||||
|   | ||||
| @@ -0,0 +1,16 @@ | ||||
| package cn.hutool.http.useragent; | ||||
|  | ||||
| import org.junit.jupiter.api.Assertions; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| public class IssueIB3SJFTest { | ||||
| 	@Test | ||||
| 	void isMobileTest() { | ||||
| 		String str="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 NetType/WIFI " + | ||||
| 			"MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63090c11) XWEB/11275 Flue"; | ||||
| 		UserAgent ua = UserAgentUtil.parse(str); | ||||
|  | ||||
| 		Assertions.assertFalse(ua.isMobile()); | ||||
| 		Assertions.assertEquals("7.0.20.1781", ua.getBrowser().getVersion(str)); | ||||
| 	} | ||||
| } | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-json</artifactId> | ||||
|   | ||||
| @@ -2,7 +2,6 @@ package cn.hutool.json; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanPath; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.Console; | ||||
| import cn.hutool.core.lang.Filter; | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.lang.mutable.Mutable; | ||||
|   | ||||
							
								
								
									
										25
									
								
								hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| package cn.hutool.json; | ||||
|  | ||||
| import org.junit.jupiter.api.Assertions; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| import java.math.RoundingMode; | ||||
|  | ||||
| public class Issue3790Test { | ||||
| 	@Test | ||||
| 	void bigDecimalToStringTest() { | ||||
| 		BigDecimal bigDecimal = new BigDecimal("0.01"); | ||||
| 		bigDecimal = bigDecimal.setScale(4, RoundingMode.HALF_UP); | ||||
|  | ||||
| 		Dto dto = new Dto(); | ||||
| 		dto.remain = bigDecimal; | ||||
|  | ||||
| 		final String jsonStr = JSONUtil.toJsonStr(dto, JSONConfig.create().setStripTrailingZeros(false)); | ||||
| 		Assertions.assertEquals("{\"remain\":0.0100}", jsonStr); | ||||
| 	} | ||||
|  | ||||
| 	static class Dto { | ||||
| 		public BigDecimal remain; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										17
									
								
								hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package cn.hutool.json; | ||||
|  | ||||
| import cn.hutool.core.lang.TypeReference; | ||||
| import org.junit.jupiter.api.Assertions; | ||||
| import org.junit.jupiter.api.Test; | ||||
|  | ||||
| import java.util.Map; | ||||
|  | ||||
| public class Issue3795Test { | ||||
| 	@Test | ||||
| 	void toBeanTest() { | ||||
| 		String fieldMapping = "[{\"lable\":\"id\",\"value\":\"id\"},{\"lable\":\"name\",\"value\":\"name\"},{\"lable\":\"age\",\"value\":\"age\"}]"; | ||||
| 		Assertions.assertThrows(UnsupportedOperationException.class, ()->{ | ||||
| 			JSONUtil.toBean(fieldMapping, new TypeReference<Map<String, String>>() {}, false); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-jwt</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-log</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-poi</artifactId> | ||||
|   | ||||
| @@ -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.CharUtil; | ||||
| import cn.hutool.core.util.NumberUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.poi.excel.ExcelDateUtil; | ||||
| import cn.hutool.poi.excel.sax.handler.RowHandler; | ||||
| @@ -264,7 +265,15 @@ public class ExcelSaxUtil { | ||||
| 		if (StrUtil.isBlank(value)) { | ||||
| 			return null; | ||||
| 		} | ||||
| 		return getNumberValue(Double.parseDouble(value), numFmtString); | ||||
|  | ||||
| 		// issue#IB0EJ9 可能精度丢失,对含有小数的value判断并转为BigDecimal | ||||
| 		final double number = Double.parseDouble(value); | ||||
| 		if(StrUtil.contains(value, CharUtil.DOT) && !value.equals(Double.toString(number))){ | ||||
| 			// 精度丢失 | ||||
| 			return NumberUtil.toBigDecimal(value); | ||||
| 		} | ||||
|  | ||||
| 		return getNumberValue(number, numFmtString); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -18,9 +18,10 @@ import java.util.List; | ||||
| /** | ||||
|  * sheetData标签内容读取处理器 | ||||
|  * | ||||
|  * <pre> | ||||
|  * <sheetData></sheetData> | ||||
|  * </pre> | ||||
|  * <pre>{@code | ||||
|  *   <sheetData></sheetData> | ||||
|  * }</pre> | ||||
|  * | ||||
|  * @since 5.5.3 | ||||
|  */ | ||||
| public class SheetDataSaxHandler extends DefaultHandler { | ||||
| @@ -62,7 +63,12 @@ public class SheetDataSaxHandler extends DefaultHandler { | ||||
| 	// 存储每行的列元素 | ||||
| 	private List<Object> rowCellList = new ArrayList<>(); | ||||
|  | ||||
| 	public SheetDataSaxHandler(RowHandler rowHandler){ | ||||
| 	/** | ||||
| 	 * 构造 | ||||
| 	 * | ||||
| 	 * @param rowHandler 行处理器 | ||||
| 	 */ | ||||
| 	public SheetDataSaxHandler(RowHandler rowHandler) { | ||||
| 		this.rowHandler = rowHandler; | ||||
| 	} | ||||
|  | ||||
| @@ -156,7 +162,7 @@ public class SheetDataSaxHandler extends DefaultHandler { | ||||
| 					lastFormula.append(ch, start, length); | ||||
| 					break; | ||||
| 			} | ||||
| 		} else{ | ||||
| 		} else { | ||||
| 			// 按理说内容应该为"<v>内容</v>",但是某些特别的XML内容不在v或f标签中,此处做一些兼容 | ||||
| 			// issue#1303@Github | ||||
| 			lastContent.append(ch, start, length); | ||||
|   | ||||
| @@ -9,7 +9,7 @@ public class IssueIB0EJ9Test { | ||||
| 	@Test | ||||
| 	@Disabled | ||||
| 	void saxReadTest() { | ||||
| 		ExcelUtil.readBySax(FileUtil.file("d:/test/bbb.xlsx"), "Sheet1", | ||||
| 		ExcelUtil.readBySax(FileUtil.file("d:/test/数值型测试.xlsx"), "hcm工资表", | ||||
| 			(sheetIndex, rowIndex, rowlist) -> Console.log("[{}] [{}] {}", sheetIndex, rowIndex, rowlist)); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-script</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-setting</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-socket</artifactId> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| 	<parent> | ||||
| 		<groupId>cn.hutool</groupId> | ||||
| 		<artifactId>hutool-parent</artifactId> | ||||
| 		<version>5.8.33</version> | ||||
| 		<version>5.8.34-SNAPSHOT</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<artifactId>hutool-system</artifactId> | ||||
|   | ||||
							
								
								
									
										2
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ | ||||
|  | ||||
| 	<groupId>cn.hutool</groupId> | ||||
| 	<artifactId>hutool-parent</artifactId> | ||||
| 	<version>5.8.33</version> | ||||
| 	<version>5.8.34-SNAPSHOT</version> | ||||
| 	<name>hutool</name> | ||||
| 	<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description> | ||||
| 	<url>https://github.com/dromara/hutool</url> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Looly
					Looly