diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java index 413ea96bc..ec735f8e9 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileUtil.java @@ -2051,7 +2051,7 @@ public class FileUtil extends PathUtil { * @throws IORuntimeException IO异常 */ public static BufferedWriter getWriter(final File file, final Charset charset, final boolean isAppend) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(file, charset).getWriter(isAppend); + return FileWriter.of(file, charset).getWriter(isAppend); } /** @@ -2148,7 +2148,7 @@ public class FileUtil extends PathUtil { * @throws IORuntimeException IO异常 */ public static File writeString(final String content, final File file, final Charset charset) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(file, charset).write(content); + return FileWriter.of(file, charset).write(content); } /** @@ -2200,7 +2200,7 @@ public class FileUtil extends PathUtil { * @throws IORuntimeException IO异常 */ public static File appendString(final String content, final File file, final Charset charset) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(file, charset).append(content); + return FileWriter.of(file, charset).append(content); } /** @@ -2349,7 +2349,7 @@ public class FileUtil extends PathUtil { * @throws IORuntimeException IO异常 */ public static File writeLines(final Collection list, final File file, final Charset charset, final boolean isAppend) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(file, charset).writeLines(list, isAppend); + return FileWriter.of(file, charset).writeLines(list, isAppend); } /** @@ -2364,7 +2364,7 @@ public class FileUtil extends PathUtil { * @since 4.0.5 */ public static File writeUtf8Map(final Map map, final File file, final String kvSeparator, final boolean isAppend) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(file, CharsetUtil.UTF_8).writeMap(map, kvSeparator, isAppend); + return FileWriter.of(file, CharsetUtil.UTF_8).writeMap(map, kvSeparator, isAppend); } /** @@ -2380,7 +2380,7 @@ public class FileUtil extends PathUtil { * @since 4.0.5 */ public static File writeMap(final Map map, final File file, final Charset charset, final String kvSeparator, final boolean isAppend) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(file, charset).writeMap(map, kvSeparator, isAppend); + return FileWriter.of(file, charset).writeMap(map, kvSeparator, isAppend); } /** @@ -2420,7 +2420,7 @@ public class FileUtil extends PathUtil { * @throws IORuntimeException IO异常 */ public static File writeBytes(final byte[] data, final File dest, final int off, final int len, final boolean isAppend) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(dest).write(data, off, len, isAppend); + return FileWriter.of(dest).write(data, off, len, isAppend); } /** @@ -2447,7 +2447,7 @@ public class FileUtil extends PathUtil { * @since 5.5.6 */ public static File writeFromStream(final InputStream in, final File dest, final boolean isCloseIn) throws IORuntimeException { - return org.dromara.hutool.core.io.file.FileWriter.of(dest).writeFromStream(in, isCloseIn); + return FileWriter.of(dest).writeFromStream(in, isCloseIn); } /** diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriteConfig.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriteConfig.java index 168831818..4a1295f50 100644 --- a/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriteConfig.java +++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriteConfig.java @@ -35,7 +35,13 @@ public class CsvWriteConfig extends CsvConfig implements Seriali /** * 是否使用安全模式,对可能存在DDE攻击的内容进行替换 */ - protected boolean ddeSafe; + protected boolean ddeSafe; + + /** + * 文件末尾是否添加换行符
+ * 按照https://datatracker.ietf.org/doc/html/rfc4180#section-2 规范,末尾换行符可有可无。 + */ + protected boolean endingLineBreak; /** * 默认配置 @@ -75,9 +81,20 @@ public class CsvWriteConfig extends CsvConfig implements Seriali * @param ddeSafe dde安全 * @return this */ - public CsvWriteConfig setDdeSafe(final boolean ddeSafe){ + public CsvWriteConfig setDdeSafe(final boolean ddeSafe) { this.ddeSafe = ddeSafe; return this; } + /** + * 文件末尾是否添加换行符
+ * 按照https://datatracker.ietf.org/doc/html/rfc4180#section-2 规范,末尾换行符可有可无。 + * + * @param endingLineBreak 文件末尾是否添加换行符 + * @return this + */ + public CsvWriteConfig setEndingLineBreak(final boolean endingLineBreak) { + this.endingLineBreak = endingLineBreak; + return this; + } } diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriter.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriter.java index 3e25c6d67..73c33b7b4 100644 --- a/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriter.java +++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/csv/CsvWriter.java @@ -49,11 +49,12 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { */ private final CsvWriteConfig config; /** - * 是否处于新行开始 + * 是否处于新行开始,新行开始用于标识是否在写出字段前写出一个分隔符 */ private boolean newline = true; /** - * 是否首行,即CSV开始的位置,当初始化时默认为true,一旦写入内容,为false + * 是否首行,即CSV开始的位置,当初始化时默认为true,一旦写入内容,为false
+ * 用于标识是否补充换行符 */ private boolean isFirstLine = true; @@ -354,8 +355,12 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { return this; } + @SuppressWarnings("resource") @Override public void close() { + if(this.config.endingLineBreak){ + writeLine(); + } IoUtil.closeQuietly(this.writer); } diff --git a/hutool-poi/src/test/java/org/dromara/hutool/poi/csv/IssueI75K5GTest.java b/hutool-poi/src/test/java/org/dromara/hutool/poi/csv/IssueI75K5GTest.java new file mode 100644 index 000000000..2738351fa --- /dev/null +++ b/hutool-poi/src/test/java/org/dromara/hutool/poi/csv/IssueI75K5GTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.poi.csv; + +import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.util.CharsetUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class IssueI75K5GTest { + + @Test + @Disabled + void appendTest() { + final CsvWriter writer = CsvUtil.getWriter( + FileUtil.file("d:/test/csvAppendTest.csv"), + CharsetUtil.UTF_8, true, + CsvWriteConfig.defaultConfig().setEndingLineBreak(true)); + + writer.writeHeaderLine("name", "gender", "address"); + writer.writeLine("张三", "男", "XX市XX区"); + writer.writeLine("李四", "男", "XX市XX区,01号"); + + writer.close(); + } + + @Test + @Disabled + void readTest() { + final CsvReader reader = CsvUtil.getReader(FileUtil.getUtf8Reader("d:/test/csvAppendTest.csv")); + final CsvData read = reader.read(); + for (final CsvRow row : read) { + Console.log(row); + } + } +}