diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/csv/CsvExcelReadExecutor.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/csv/CsvExcelReadExecutor.java index 8754b9763..950d100ea 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/csv/CsvExcelReadExecutor.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/analysis/csv/CsvExcelReadExecutor.java @@ -220,9 +220,9 @@ private void dealRecord(CSVRecord record, int rowIndex) { Iterator cellIterator = record.iterator(); int columnIndex = 0; Boolean autoTrim = - csvReadContext.csvReadWorkbookHolder().globalConfiguration().getAutoTrim(); + csvReadContext.currentReadHolder().globalConfiguration().getAutoTrim(); Boolean autoStrip = - csvReadContext.csvReadWorkbookHolder().globalConfiguration().getAutoStrip(); + csvReadContext.currentReadHolder().globalConfiguration().getAutoStrip(); while (cellIterator.hasNext()) { String cellString = cellIterator.next(); diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/read/builder/CsvReaderBuilder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/read/builder/CsvReaderBuilder.java index 5be1fe1de..909d833e4 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/read/builder/CsvReaderBuilder.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/read/builder/CsvReaderBuilder.java @@ -142,9 +142,14 @@ public CsvReaderBuilder escape(Character escape) { } private ExcelReader buildExcelReader() { - this.csvFormatBuilder.setTrim(this.readWorkbook.getAutoTrim() == null - || this.readWorkbook.getAutoTrim() - || Boolean.TRUE.equals(this.readWorkbook.getAutoStrip())); + // The inherited autoTrim()/autoStrip() setters land on the ReadSheet, so the sheet-level + // flags must take precedence over the workbook-level ones here. + Boolean autoTrim = + this.readSheet.getAutoTrim() != null ? this.readSheet.getAutoTrim() : this.readWorkbook.getAutoTrim(); + Boolean autoStrip = this.readSheet.getAutoStrip() != null + ? this.readSheet.getAutoStrip() + : this.readWorkbook.getAutoStrip(); + this.csvFormatBuilder.setTrim(autoTrim == null || autoTrim || Boolean.TRUE.equals(autoStrip)); if (this.readWorkbook.getIgnoreEmptyRow() != null) { this.csvFormatBuilder.setIgnoreEmptyLines(this.readWorkbook.getIgnoreEmptyRow()); } diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/CsvWriterBuilder.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/CsvWriterBuilder.java index ac9d78fdf..2e1ee62bd 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/CsvWriterBuilder.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/builder/CsvWriterBuilder.java @@ -126,9 +126,15 @@ public CsvWriterBuilder escape(Character escape) { } private ExcelWriter buildExcelWriter() { - this.csvFormatBuilder.setTrim(this.writeWorkbook.getAutoTrim() == null - || this.writeWorkbook.getAutoTrim() - || Boolean.TRUE.equals(this.writeWorkbook.getAutoStrip())); + // The builder's parameter is the sheet, so flags set after .csv() land on the sheet and must + // take precedence over the workbook flags; a null sheet flag inherits from the workbook. + Boolean autoTrim = this.writeSheet.getAutoTrim() != null + ? this.writeSheet.getAutoTrim() + : this.writeWorkbook.getAutoTrim(); + Boolean autoStrip = this.writeSheet.getAutoStrip() != null + ? this.writeSheet.getAutoStrip() + : this.writeWorkbook.getAutoStrip(); + this.csvFormatBuilder.setTrim(autoTrim == null || autoTrim || Boolean.TRUE.equals(autoStrip)); if (this.writeWorkbook.getNeedHead() != null) { this.csvFormatBuilder.setSkipHeaderRecord(!this.writeWorkbook.getNeedHead()); } diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvFormatTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvFormatTest.java index 4cd889ad7..7978ade63 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvFormatTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/format/CsvFormatTest.java @@ -23,11 +23,14 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; @@ -149,12 +152,150 @@ public void testAutoTrim() throws Exception { .autoTrim(Boolean.FALSE) .csv() .doWrite(dataList(10, " " + STRING_PREFIX)); - List dataList = FesodSheet.read(csvFile, CsvData.class, new CsvDataListener()) + Assertions.assertTrue( + readCsvContent(csvFile).contains("\" " + STRING_PREFIX + "0\""), + "workbook-level autoTrim(false) should keep surrounding spaces"); + List dataList = FesodSheet.read(csvFile, CsvData.class, new CsvDataListener()) .autoTrim(Boolean.FALSE) .csv() .doReadSync(); Assertions.assertEquals(10, dataList.size()); - Assertions.assertNotNull(dataList.get(0)); + // workbook-level autoTrim(false) must preserve the leading space + Assertions.assertEquals(" " + STRING_PREFIX + 0, dataList.get(0).getString()); + } + + @Test + public void testSheetAutoTrim() throws Exception { + File csvFile = createTempFile("csv-sheet-auto-trim", ExcelFormat.CSV); + Files.write(csvFile.toPath(), " a , b \n".getBytes(StandardCharsets.UTF_8)); + + // default configuration trims surrounding whitespace + assertFirstRow("a", "b", FesodSheet.read(csvFile).headRowNumber(0).csv().doReadSync()); + + // sheet-level autoTrim(false) set after .csv() keeps the raw values + assertFirstRow( + " a ", + " b ", + FesodSheet.read(csvFile) + .headRowNumber(0) + .csv() + .autoTrim(Boolean.FALSE) + .doReadSync()); + + // sheet-level autoTrim(false) set on .sheet() keeps the raw values + assertFirstRow( + " a ", + " b ", + FesodSheet.read(csvFile) + .headRowNumber(0) + .sheet() + .autoTrim(Boolean.FALSE) + .doReadSync()); + + // workbook-level autoTrim(false) set before .csv() keeps working + assertFirstRow( + " a ", + " b ", + FesodSheet.read(csvFile) + .headRowNumber(0) + .autoTrim(Boolean.FALSE) + .csv() + .doReadSync()); + + // the sheet-level flag takes precedence over the workbook-level one + assertFirstRow( + "a", + "b", + FesodSheet.read(csvFile) + .headRowNumber(0) + .autoTrim(Boolean.FALSE) + .csv() + .autoTrim(Boolean.TRUE) + .doReadSync()); + assertFirstRow( + " a ", + " b ", + FesodSheet.read(csvFile) + .headRowNumber(0) + .autoStrip(Boolean.TRUE) + .csv() + .autoTrim(Boolean.FALSE) + .autoStrip(Boolean.FALSE) + .doReadSync()); + } + + @Test + public void testSheetAutoStrip() throws Exception { + // U+3000 (ideographic space) is removed by strip but not by trim + File csvFile = createTempFile("csv-sheet-auto-strip", ExcelFormat.CSV); + Files.write(csvFile.toPath(), "\u3000a\u3000,\u3000b\u3000\n".getBytes(StandardCharsets.UTF_8)); + + assertFirstRow( + "\u3000a\u3000", + "\u3000b\u3000", + FesodSheet.read(csvFile) + .headRowNumber(0) + .charset(StandardCharsets.UTF_8) + .csv() + .autoTrim(Boolean.FALSE) + .autoStrip(Boolean.FALSE) + .doReadSync()); + + assertFirstRow( + "a", + "b", + FesodSheet.read(csvFile) + .headRowNumber(0) + .charset(StandardCharsets.UTF_8) + .csv() + .autoTrim(Boolean.FALSE) + .autoStrip(Boolean.TRUE) + .doReadSync()); + } + + @Test + public void testWriteSheetAutoTrim() throws Exception { + List> data = Collections.singletonList(Collections.singletonList(" a ")); + + // default configuration trims surrounding whitespace + File csvFile = createTempFile(ExcelFormat.CSV); + FesodSheet.write(csvFile).csv().doWrite(data); + Assertions.assertEquals("a\r\n", readCsvContent(csvFile)); + + // sheet-level autoTrim(false) set after .csv() keeps the surrounding spaces + csvFile = createTempFile(ExcelFormat.CSV); + FesodSheet.write(csvFile).csv().autoTrim(Boolean.FALSE).doWrite(data); + Assertions.assertEquals("\" a \"\r\n", readCsvContent(csvFile)); + + // the sheet-level flags take precedence over the workbook-level one + csvFile = createTempFile(ExcelFormat.CSV); + FesodSheet.write(csvFile) + .autoTrim(Boolean.FALSE) + .csv() + .autoTrim(Boolean.TRUE) + .doWrite(data); + Assertions.assertEquals("a\r\n", readCsvContent(csvFile)); + + csvFile = createTempFile(ExcelFormat.CSV); + FesodSheet.write(csvFile) + .autoTrim(Boolean.FALSE) + .csv() + .autoStrip(Boolean.TRUE) + .doWrite(data); + Assertions.assertEquals("a\r\n", readCsvContent(csvFile)); + } + + private static void assertFirstRow(String expected0, String expected1, List rows) { + Assertions.assertEquals(1, rows.size()); + Map row = (Map) rows.get(0); + Assertions.assertEquals(expected0, row.get(0)); + Assertions.assertEquals(expected1, row.get(1)); + } + + private static String readCsvContent(File csvFile) throws IOException { + String content = new String(Files.readAllBytes(csvFile.toPath()), StandardCharsets.UTF_8); + // CSV output starts with a UTF-8 BOM by default + return content.startsWith("\uFEFF") ? content.substring(1) : content; } @Test