Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/main/scala/com/github/tototoshi/csv/CSVFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ trait CSVFormat extends Serializable {

val treatEmptyLineAsNil: Boolean

val trimUnquoted: Boolean

}
10 changes: 5 additions & 5 deletions src/main/scala/com/github/tototoshi/csv/CSVParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object CSVParser {
* res1: Option[List[String]] = Some(List(a, b, c))
* }}}
*/
def parse(input: String, escapeChar: Char, delimiter: Char, quoteChar: Char): Option[List[String]] = {
def parse(input: String, escapeChar: Char, delimiter: Char, quoteChar: Char, trimUnquoted: Boolean = false): Option[List[String]] = {
val buf: Array[Char] = input.toCharArray
var fields: Vector[String] = Vector()
var field = new StringBuilder
Expand Down Expand Up @@ -148,13 +148,13 @@ object CSVParser {
}
}
case `delimiter` => {
fields :+= field.toString
fields :+= (if (trimUnquoted) field.toString().trim else field.toString())
field = new StringBuilder
state = Delimiter
pos += 1
}
case '\n' | '\u2028' | '\u2029' | '\u0085' => {
fields :+= field.toString
fields :+= (if (trimUnquoted) field.toString().trim else field.toString())
field = new StringBuilder
state = End
pos += 1
Expand All @@ -163,7 +163,7 @@ object CSVParser {
if (pos + 1 < buflen && buf(1) == '\n') {
pos += 1
}
fields :+= field.toString
fields :+= (if (trimUnquoted) field.toString().trim else field.toString())
field = new StringBuilder
state = End
pos += 1
Expand Down Expand Up @@ -300,7 +300,7 @@ object CSVParser {
class CSVParser(format: CSVFormat) extends Serializable {

def parseLine(input: String): Option[List[String]] = {
val parsedResult = CSVParser.parse(input, format.escapeChar, format.delimiter, format.quoteChar)
val parsedResult = CSVParser.parse(input, format.escapeChar, format.delimiter, format.quoteChar, format.trimUnquoted)
if (parsedResult == Some(List("")) && format.treatEmptyLineAsNil) Some(Nil)
else parsedResult
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/com/github/tototoshi/csv/Formats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ trait DefaultCSVFormat extends CSVFormat {

val treatEmptyLineAsNil: Boolean = false

val trimUnquoted: Boolean = false

}

trait TSVFormat extends CSVFormat {
Expand All @@ -45,5 +47,7 @@ trait TSVFormat extends CSVFormat {

val treatEmptyLineAsNil: Boolean = false

val trimUnquoted: Boolean = false

}

2 changes: 2 additions & 0 deletions src/test/resources/trim-unquoted.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a, b, c
"a"," b","c "
71 changes: 24 additions & 47 deletions src/test/scala/com/github/tototoshi/csv/CSVReaderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ class CSVReaderSpec extends FunSpec with Matchers with Using {

}

private def read(fileName: String)(implicit format: CSVFormat): List[String] = {
var res: List[String] = Nil
using(CSVReader.open(fileName)(format)) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
res
}

describe("CSVReader") {

it("should be constructed with java.io.File") {
Expand All @@ -23,13 +33,7 @@ class CSVReaderSpec extends FunSpec with Matchers with Using {
}

it("should be constructed with filename") {
var res: List[String] = Nil
using(CSVReader.open("src/test/resources/simple.csv")) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
res.mkString should be("abcdef")
read("src/test/resources/simple.csv").mkString should be("abcdef")
}

it("should be constructed with CSVFormat") {
Expand Down Expand Up @@ -83,13 +87,7 @@ class CSVReaderSpec extends FunSpec with Matchers with Using {
}

it("read simple CSV from file") {
var res: List[String] = Nil
using(CSVReader.open(new FileReader("src/test/resources/simple.csv"))) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
res.mkString should be("abcdef")
read("src/test/resources/simple.csv").mkString should be("abcdef")
}

it("read simple CSV string") {
Expand All @@ -104,56 +102,35 @@ class CSVReaderSpec extends FunSpec with Matchers with Using {
}

it("issue #22") {
var res: List[String] = Nil
using(CSVReader.open("src/test/resources/issue22.csv")) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
read("src/test/resources/issue22.csv")
}

it("issue #32") {
var res: List[String] = Nil
using(CSVReader.open("src/test/resources/issue32.csv")(new DefaultCSVFormat {
read("src/test/resources/issue32.csv")(new DefaultCSVFormat {
override val escapeChar: Char = '\\'
})) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
})
}

it("should read csv file whose escape char is backslash") {
var res: List[String] = Nil
implicit val format = new DefaultCSVFormat {
override val escapeChar: Char = '\\'
}
using(CSVReader.open("src/test/resources/backslash-escape.csv")(format)) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
val res: List[String] = read("src/test/resources/backslash-escape.csv")(format)
res should be(List("field1", "field2", "field3 says, \"escaped with backslash\""))
}

it("read simple CSV file with empty quoted fields") {
var res: List[String] = Nil
using(CSVReader.open("src/test/resources/issue30.csv")) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
res.mkString(",") should be("h1,h2,h3,a1,,a3,b1,b2,b3")
read("src/test/resources/issue30.csv").mkString(",") should be("h1,h2,h3,a1,,a3,b1,b2,b3")
}

it("should read a file starting with BOM") {
var res: List[String] = Nil
using(CSVReader.open("src/test/resources/bom.csv")) { reader =>
reader foreach { fields =>
res = res ++ fields
}
}
res should be(List("a", "b", "c"))
read("src/test/resources/bom.csv") should be(List("a", "b", "c"))
}

it("should trim unquoted fields when wanted") {
read("src/test/resources/trim-unquoted.csv")(new DefaultCSVFormat {
override val trimUnquoted: Boolean = true
}) should be(List("a", "b", "c", "a", " b", "c "))
}

it("should be throw exception against malformed input") {
Expand Down