diff --git a/app/src/main/java/io/github/muntashirakon/csv/CsvWriter.java b/app/src/main/java/io/github/muntashirakon/csv/CsvWriter.java index cf3411d0fe7..8076501f80b 100644 --- a/app/src/main/java/io/github/muntashirakon/csv/CsvWriter.java +++ b/app/src/main/java/io/github/muntashirakon/csv/CsvWriter.java @@ -43,20 +43,23 @@ public void addField(@Nullable String field) throws IOException { } public void addField(@Nullable String field, boolean addQuotes) throws IOException { + boolean previouslyInitialized = mInitialized; ++mCurrentFieldCount; checkFieldAvailable(); if (mCurrentFieldCount > 1) { // There are other fields mWriter.write(mSeparator); + } else if (previouslyInitialized) { + // This is the first field since the last line + mWriter.append(System.lineSeparator()); } mWriter.append(getFormattedField(field, addQuotes)); } - public void addLine() throws IOException { + public void addLine() { initIfNotAlready(); checkFieldCountSame(); mCurrentFieldCount = 0; - mWriter.append(System.lineSeparator()); } public void addLine(@NonNull String[] line) throws IOException { @@ -67,11 +70,16 @@ public void addLine(@NonNull String[] line) throws IOException { * @param addQuotes Whether all fields are to be enclosed in double quotes */ public void addLine(@NonNull String[] line, boolean addQuotes) throws IOException { + boolean previouslyInitialized = mInitialized; mCurrentFieldCount = line.length; initIfNotAlready(); checkFieldCountSame(); mCurrentFieldCount = 0; - mWriter.append(getFormattedLine(line, addQuotes)).append(System.lineSeparator()); + if (previouslyInitialized) { + // There were other lines + mWriter.append(System.lineSeparator()); + } + mWriter.append(getFormattedLine(line, addQuotes)); } public void addLines(@NonNull Collection lines) throws IOException { @@ -87,6 +95,7 @@ public void addLines(@NonNull Collection lines, boolean addQuotes) thr } } + @NonNull private String getFormattedLine(@NonNull String[] line, boolean addQuotes) { return Stream.of(line) .map(field -> getFormattedField(field, addQuotes)) @@ -102,7 +111,8 @@ private String getFormattedField(@Nullable String field, boolean addQuotes) { if (field.contains(COMMA) || field.contains(DOUBLE_QUOTES) || field.contains(NEW_LINE_UNIX) - || field.contains(NEW_LINE_WINDOWS)) { + || field.contains(NEW_LINE_WINDOWS) + || field.contains(mSeparator)) { // If the field contains double quotes, replace it with two double quotes \"\" String result = field.replace(DOUBLE_QUOTES, EMBEDDED_DOUBLE_QUOTES); diff --git a/app/src/test/java/io/github/muntashirakon/csv/CsvWriterTest.java b/app/src/test/java/io/github/muntashirakon/csv/CsvWriterTest.java new file mode 100644 index 00000000000..4e0314f2a85 --- /dev/null +++ b/app/src/test/java/io/github/muntashirakon/csv/CsvWriterTest.java @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: MIT AND GPL-3.0-or-later + +package io.github.muntashirakon.csv; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +// Copyright 2020 Yong Mook Kim +// Copyright 2024 Muntashir Al-Islam +public class CsvWriterTest { + + @Before + public void setUp() throws Exception { + } + + @Test + public void testCsvLineDefault() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "apple", "10", "9.99"}; + String expected = "1,apple,10,9.99"; + new CsvWriter(writer).addLine(record); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineQuoted() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "apple", "10", "9.99"}; + String expected = "\"1\",\"apple\",\"10\",\"9.99\""; + new CsvWriter(writer).addLine(record, true); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineContainsEmptyValue() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "", "10", ""}; + String expected = "1,,10,"; + new CsvWriter(writer).addLine(record); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvEmptyLine() throws IOException { + try (StringWriter writer = new StringWriter()) { + String expected = ""; + new CsvWriter(writer).addLine(); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineWithCustomSeparator() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "apple;orange", "10", "9.99"}; + String expected = "1;\"apple;orange\";10;9.99"; + new CsvWriter(writer, ";").addLine(record); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineContainsComma() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "apple,orange", "10", "9.99"}; + String expected = "1,\"apple,orange\",10,9.99"; + new CsvWriter(writer).addLine(record); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineContainsDoubleQuotes() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "12\"apple", "10", "9.99"}; + String expected = "1,\"12\"\"apple\",10,9.99"; + new CsvWriter(writer).addLine(record); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineContainsNewline() throws IOException { + try (StringWriter writer = new StringWriter()) { + String[] record = {"1", "promotion!\napple", "10", "9.99"}; + String expected = "1,\"promotion!\napple\",10,9.99"; + new CsvWriter(writer).addLine(record); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineDefaultTwoLines() throws IOException { + try (StringWriter writer = new StringWriter()) { + List records = new ArrayList() {{ + add(new String[]{"1", "apple", "10", "9.99"}); + add(new String[]{"2", "orange", "5", "4.99"}); + }}; + String expected = "1,apple,10,9.99\n2,orange,5,4.99"; + new CsvWriter(writer).addLines(records); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineTwoLinesDifferentSizeThrowsException() throws IOException { + try (StringWriter writer = new StringWriter()) { + List records = new ArrayList() {{ + add(new String[]{"1", "apple", "10", "9.99"}); + add(new String[]{"2", "orange", "5"}); + }}; + assertThrows(IndexOutOfBoundsException.class, () -> new CsvWriter(writer).addLines(records)); + records.remove(1); + records.add(new String[]{"2", "orange", "5", "4.99", "rotten"}); + assertThrows(IndexOutOfBoundsException.class, () -> new CsvWriter(writer).addLines(records)); + } + } + + @Test + public void testCsvLineDefaultViaField() throws IOException { + try (StringWriter writer = new StringWriter()) { + String expected = "1,apple,10,9.99"; + CsvWriter csvWriter = new CsvWriter(writer); + csvWriter.addField("1"); + csvWriter.addField("apple"); + csvWriter.addField("10"); + csvWriter.addField("9.99"); + csvWriter.addLine(); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineDefaultTwoLinesViaField() throws IOException { + try (StringWriter writer = new StringWriter()) { + String expected = "1,apple,10,9.99\n2,orange,5,4.99"; + CsvWriter csvWriter = new CsvWriter(writer); + csvWriter.addField("1"); + csvWriter.addField("apple"); + csvWriter.addField("10"); + csvWriter.addField("9.99"); + csvWriter.addLine(); + csvWriter.addField("2"); + csvWriter.addField("orange"); + csvWriter.addField("5"); + csvWriter.addField("4.99"); + csvWriter.addLine(); + String result = writer.toString(); + assertEquals(expected, result); + } + } + + @Test + public void testCsvLineDefaultTwoLinesDifferentSizeThrowsExceptionViaField() throws IOException { + try (StringWriter writer = new StringWriter()) { + CsvWriter csvWriter = new CsvWriter(writer); + csvWriter.addField("1"); + csvWriter.addField("apple"); + csvWriter.addField("10"); + csvWriter.addField("9.99"); + csvWriter.addLine(); + csvWriter.addField("2"); + csvWriter.addField("orange"); + csvWriter.addField("5"); + assertThrows(IndexOutOfBoundsException.class, csvWriter::addLine); + csvWriter.addField("4.99"); + assertThrows(IndexOutOfBoundsException.class, () -> csvWriter.addField("rotten")); + } + } +} \ No newline at end of file