Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ val writer = csvWriter {
| nullCode | `(empty string)` | Character used when a written field is null value. |
| lineTerminator | `\r\n` | Character used as line terminator. |
| outputLastLineTerminator | `true` | Output line break at the end of file or not. |
| prependBOM | `false` | Output BOM (Byte Order Mark) at the beginning of file or not. |
| quote.char | `"` | Character to quote each fields. |
| quote.mode | `CANONICAL` | Quote mode. <br />- `CANONICAL`: Not quote normally, but quote special characters (quoteChar, delimiter, line feed). This is [the specification of CSV](https://tools.ietf.org/html/rfc4180#section-2).<br />- `ALL`: Quote all fields.<br />- `NON_NUMERIC`: Quote non-numeric fields. (ex. 1,"a",2.3) |

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.github.doyaaaaaken.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.util.Const

/**
* buffered reader which can read line with line terminator
*/
internal class BufferedLineReader(
private val br: Reader
) {
companion object {
private const val BOM = '\uFEFF'
private const val BOM = Const.BOM
}

private fun StringBuilder.isEmptyLine(): Boolean =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ interface ICsvWriterContext {
*/
val outputLastLineTerminator: Boolean

/**
* Output BOM (Byte Order Mark) at the beginning of file or not.
* See https://github.com/doyaaaaaken/kotlin-csv/issues/84
*/
val prependBOM: Boolean

/**
* Options about quotes of each fields
*/
Expand All @@ -75,6 +81,7 @@ class CsvWriterContext : ICsvWriterContext {
override var nullCode: String = ""
override var lineTerminator: String = "\r\n"
override var outputLastLineTerminator = true
override var prependBOM = false
override val quote: CsvWriteQuoteContext = CsvWriteQuoteContext()

fun quote(init: CsvWriteQuoteContext.() -> Unit) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.doyaaaaaken.kotlincsv.parser

import com.github.doyaaaaaken.kotlincsv.util.CSVParseFormatException
import com.github.doyaaaaaken.kotlincsv.util.Const

/**
* @author doyaaaaaaken
Expand All @@ -11,7 +12,7 @@ internal class ParseStateMachine(
private val escapeChar: Char
) {

private val BOM = '\uFEFF'
private val BOM = Const.BOM

private var state = ParseState.START

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ package com.github.doyaaaaaken.kotlincsv.util
* @author doyaaaaaken
*/
internal object Const {
val defaultCharset = "UTF-8"
const val defaultCharset = "UTF-8"

const val BOM = '\uFEFF'
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.doyaaaaaken.kotlincsv.client

import com.github.doyaaaaaken.kotlincsv.dsl.context.CsvWriterContext
import com.github.doyaaaaaken.kotlincsv.dsl.context.WriteQuoteMode
import com.github.doyaaaaaken.kotlincsv.util.Const
import java.io.Closeable
import java.io.Flushable
import java.io.IOException
Expand Down Expand Up @@ -85,6 +86,10 @@ class CsvFileWriter internal constructor(
}

private fun writeNext(row: List<Any?>) {
if (!hasWroteInitialChar && ctx.prependBOM) {
writer.print(Const.BOM)
}

val rowStr = row.joinToString(ctx.delimiter.toString()) { field ->
if (field == null) {
ctx.nullCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class CsvWriterTest : WordSpec({
delimiter = '\t'
nullCode = "NULL"
lineTerminator = "\n"
outputLastLineTerminator = false
prependBOM = true
quote {
char = '\''
mode = WriteQuoteMode.ALL
Expand All @@ -43,6 +45,8 @@ class CsvWriterTest : WordSpec({
writer.delimiter shouldBe '\t'
writer.nullCode shouldBe "NULL"
writer.lineTerminator shouldBe "\n"
writer.outputLastLineTerminator shouldBe false
writer.prependBOM shouldBe true
writer.quote.char = '\''
writer.quote.mode = WriteQuoteMode.ALL
}
Expand Down Expand Up @@ -272,6 +276,18 @@ class CsvWriterTest : WordSpec({
val actual = readTestFile()
actual shouldBe expected
}
"write simple csv with prepending BOM" {
val row1 = listOf("a", "b")
val row2 = listOf("c", "d")
val expected = "\uFEFFa,b\r\nc,d\r\n"
csvWriter {
prependBOM = true
}.open(File(testFileName)) {
writeRows(listOf(row1, row2))
}
val actual = readTestFile()
actual shouldBe expected
}
"write simple csv with disabled last line terminator multiple writes" {
val row1 = listOf("a", "b")
val row2 = listOf("c", "d")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class CsvWriterDslTest : StringSpec({
nullCode = "NULL"
lineTerminator = "\n"
outputLastLineTerminator = false
prependBOM = true
quote {
char = '\''
mode = WriteQuoteMode.ALL
Expand All @@ -33,6 +34,7 @@ class CsvWriterDslTest : StringSpec({
writer.nullCode shouldBe "NULL"
writer.lineTerminator shouldBe "\n"
writer.outputLastLineTerminator shouldBe false
writer.prependBOM shouldBe true
writer.quote.char shouldBe '\''
writer.quote.mode = WriteQuoteMode.ALL
}
Expand Down