Skip to content

Commit

Permalink
CsvReport: escape special characters #223 (#245)
Browse files Browse the repository at this point in the history
the default escape character for CSV is " which has to be escaped with a
double ""

Additionally, when a column contains any of these character it has to be put inside
quotes:

- " (double quote)
- , (comma)
- ' (single quote)
- \ (backslash)
- \n (new line)

Co-authored-by: Daniele Segato <daniele.segato@neosperience.com>
  • Loading branch information
danielesegato and danielesegato committed Mar 1, 2023
1 parent 917914c commit f0b7ebc
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,21 @@ class CsvReport(private val projects: List<Model>) : Report {

/** Add elements to Csv. */
private fun MutableList<String?>.addCsvString(element: String): Boolean {
return this.add(element.valueOrNull())
val escaped = element.valueOrNull()
?.replace("\"", "\"\"")
?.let { el ->
when {
el.contains(",") ||
el.contains("\n") ||
el.contains("'") ||
el.contains("\\") ||
el.contains("\"")
-> "\"$el\""

else -> el
}
}
return this.add(escaped)
}

/** Add List of elements to Csv as comma separated list with quotes. */
Expand All @@ -76,13 +90,7 @@ class CsvReport(private val projects: List<Model>) : Report {
): Boolean {
return when {
elements.isEmpty() -> this.add(null)
else -> {
val element = elements.joinToString(separator = ",", transform = transform)
when (elements.size) {
1 -> this.add(element)
else -> this.add("\"${element}\"")
}
}
else -> addCsvString(elements.joinToString(separator = ",", transform = transform))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,37 @@ final class CsvReportSpec extends Specification {
then:
assertCsv(expected, actual)
}

def 'open source csv - escape characters'() {
given:
def developerA = new Developer(id: 'Joe')
def developerB = new Developer(id: '5\" Above Ground')
def developers = [developerA, developerB]
def license = new License(
name: 'Apache, 2.0',
url: 'url'
)
def project = new Model(
name: "Joe's project",
description: 'Copyright "Joe" 2023\n\nAll right reserved\\to me',
licenses: [license],
url: 'url',
developers: developers,
inceptionYear: 'year',
groupId: 'foo',
artifactId: 'bar',
version: '1.2.3',
)
def projects = [project]
def sut = new CsvReport(projects)

when:
def actual = sut.toString()
def expected =
"project,description,version,developers,url,year,licenses,license urls,dependency\n" +
"\"Joe\'s project\",\"Copyright \"\"Joe\"\" 2023\n\nAll right reserved\\to me\",1.2.3,\"Joe,5\"\" Above Ground\",url,year,\"Apache, 2.0\",url,foo:bar:1.2.3"

then:
assertCsv(expected, actual)
}
}

0 comments on commit f0b7ebc

Please sign in to comment.