├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties.example
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── samples
├── GettingDependencyUsingGrab.groovy
└── UsingAutoDetection.groovy
├── src
└── com
│ └── xlson
│ └── groovycsv
│ ├── AutoDetectHandler.groovy
│ ├── CsvIterator.groovy
│ ├── CsvParser.groovy
│ └── PropertyMapper.groovy
└── test
└── com
└── xlson
└── groovycsv
├── AutoDetectHandlerSpec.groovy
├── ConfigurableColumnNamesSpec.groovy
├── CsvIteratorSpec.groovy
├── CsvParserSpec.groovy
└── PropertyMapperSpec.groovy
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/*
2 | build/*
3 | lib/*
4 | _site
5 | gradle.properties
6 | .idea
7 | groovycsv.iml
8 | out/
9 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at leo@xlson.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Leonard Gram
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GroovyCSV
2 |
3 | GroovyCSV is a library for Groovy which aims to make csv data
4 | easier (and more idiomatically Groovy) to work with. The library was inspired by @[goeh's](http://twitter.com/goeh)
5 | [ExcelBuilder](http://www.technipelago.se/blog/show/groovy-poi-excel) that lets you
6 | iterate over rows in the excel file using `eachLine` and access values
7 | using the column names.
8 |
9 | ## Features
10 |
11 | * Value-access by header name or position
12 | * Iteration using the ordinary collection methods (`findAll`, `collect`
13 | and so on)
14 | * Support for guessing separator and/or quote character
15 | * Support for reading csv without headers
16 | * Support for skipping initial lines of the csv
17 |
18 | ## Example
19 |
20 | The parse method returns an iterator over the rows in the csv. This
21 | means we can use any of the default groovy ways to iterate, in this
22 | example we see the for each loop in use.
23 |
24 | @Grab('com.xlson.groovycsv:groovycsv:1.3')
25 | import static com.xlson.groovycsv.CsvParser.parseCsv
26 |
27 | def csv = '''Name,Lastname
28 | Mark,Andersson
29 | Pete,Hansen'''
30 |
31 | def data = parseCsv(csv)
32 | for(line in data) {
33 | println "$line.Name $line.Lastname"
34 | }
35 |
36 | The parse method takes a String or a Reader as argument.
37 |
38 | **Output:**
39 |
40 | Mark Andersson
41 | Pete Hansen
42 |
43 | ## Getting GroovyCSV
44 |
45 | GroovyCSV is available through Maven Central.
46 |
47 | ### Maven & Ivy configuration
48 |
49 | #### Latest stable
50 |
51 | * *GroupId:* com.xlson.groovycsv
52 | * *ArtifactId:* groovycsv
53 | * *Version:* 1.3
54 |
55 | #### Latest snapshot
56 |
57 | * *Version:* 1.3-SNAPSHOT
58 | * *Repository:* https://oss.sonatype.org/content/groups/public/
59 |
60 | ### Downloads
61 |
62 | *GroovyCSV 1.3*
63 |
64 |
65 | * [groovycsv-1.3.jar](http://repo1.maven.org/maven2/com/xlson/groovycsv/groovycsv/1.3/groovycsv-1.3.jar)
66 | * [groovycsv-1.3-javadoc.jar](http://repo1.maven.org/maven2/com/xlson/groovycsv/groovycsv/1.3/groovycsv-1.3-javadoc.jar)
67 | * [Javadoc Online](https://xlson.github.io/groovycsv/docs/1.3/javadoc/)
68 |
69 | ## Dependencies
70 |
71 | * [Groovy 1.8.x](http://groovy.codehaus.org) or later
72 | * [OpenCSV 4.x](http://opencsv.sourceforge.net/)
73 |
74 |
75 | Many thanks to everyone who's contributed to the project and everyone in the OpenCSV team for
76 | doing all the heavy lifting.
77 |
78 | ## Building
79 |
80 | GroovyCSV uses Gradle for building as is packaged with the gradle wrapper which will download and install gradle for you behind the scenes the first time you run it.
81 |
82 | **Build instruction**
83 |
84 | 1. Fetch the latest code: `git clone git://github.com/xlson/groovycsv.git`
85 | 2. (Optional) Run the tests using the gradle wrapper `./gradlew test`
86 | 4. Go to the project directory and run: `./gradlew jar`
87 |
88 | You will find the built jar in `./build/libs`. If you need any
89 | dependencies you can download them using `./gradlew downloadDeps`, they
90 | end up in the `lib` folder.
91 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 |
6 | dependencies {
7 | classpath 'de.huxhorn.gradle:de.huxhorn.gradle.pgp-plugin:0.0.4'
8 | }
9 | }
10 |
11 | apply plugin: 'groovy'
12 | apply plugin: 'maven'
13 | apply plugin: 'signing'
14 | apply plugin: 'idea'
15 |
16 | repositories {
17 | mavenCentral()
18 | maven { url "http://m2repo.spockframework.org/snapshots" }
19 | }
20 |
21 | dependencies {
22 | compile 'com.opencsv:opencsv:4.0'
23 | compileOnly 'org.codehaus.groovy:groovy-all:1.8.8'
24 | testCompile 'org.spockframework:spock-core:1.1-groovy-2.4-rc-2'
25 | testCompile 'cglib:cglib-nodep:2.2'
26 | testCompile 'org.objenesis:objenesis:1.2'
27 | }
28 |
29 | version = '1.3'
30 | group = 'com.xlson.groovycsv'
31 |
32 | sourceSets {
33 | main {
34 | groovy {
35 | srcDir 'src'
36 | }
37 | }
38 | test {
39 | groovy {
40 | srcDir 'test'
41 | }
42 | }
43 | }
44 |
45 | task groovydocJar(type: Jar, dependsOn: groovydoc) {
46 | classifier = 'javadoc'
47 | from 'build/docs/groovydoc'
48 | }
49 |
50 | task sourcesJar(type: Jar) {
51 | from sourceSets.main.allSource
52 | classifier = 'sources'
53 | }
54 |
55 | artifacts {
56 | archives groovydocJar, sourcesJar
57 | }
58 | if(isSetupForDeployToMavenCentral()) {
59 | signing {
60 | sign configurations.archives
61 | }
62 | }
63 |
64 | task wrapper(type: Wrapper) {
65 | gradleVersion = '4.3.1'
66 | }
67 |
68 | task downloadDeps << {
69 | def libDir = file('lib')
70 | ant.delete(dir: libDir)
71 | copy {
72 | from configurations.testRuntime
73 | into libDir
74 | }
75 | }
76 |
77 |
78 | def isSetupForDeployToMavenCentral() {
79 | def requiredProperties = ['ossrhUsername',
80 | 'ossrhPassword',
81 | 'signing.keyId',
82 | 'signing.password',
83 | 'signing.secretKeyRingFile']
84 | for(prop in requiredProperties) {
85 | if(!project.hasProperty(prop)) {
86 | return false
87 | }
88 | }
89 | return true
90 | }
91 |
92 | uploadArchives {
93 | repositories {
94 | mavenDeployer {
95 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
96 |
97 | if(isSetupForDeployToMavenCentral()) {
98 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
99 | authentication(userName: ossrhUsername, password: ossrhPassword)
100 | }
101 |
102 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
103 | authentication(userName: ossrhUsername, password: ossrhPassword)
104 | }
105 | }
106 |
107 | pom.project {
108 | name 'GroovyCSV'
109 | packaging 'jar'
110 | // optionally artifactId can be defined here
111 | description 'Library for parsing csv in Groovy'
112 | url 'http://github.com/xlson/groovycsv'
113 | inceptionYear '2010'
114 |
115 | scm {
116 | connection 'scm:git:git@github.com:xlson/groovycsv.git'
117 | developerConnection 'scm:git:git@github.com:xlson/groovycsv.git'
118 | url 'http://github.com/xlson/groovycsv'
119 | }
120 |
121 | licenses {
122 | license {
123 | name 'The Apache License, Version 2.0'
124 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
125 | }
126 | }
127 |
128 | developers {
129 | developer {
130 | id 'xlson'
131 | name 'Leonard Gram'
132 | email 'leo@xlson.com'
133 | url 'http://xlson.com/'
134 | timezone '+1'
135 | }
136 | }
137 | }
138 |
139 | pom.withXml { XmlProvider xmlProvider ->
140 | def xml = xmlProvider.asString()
141 | def pomXml = new XmlParser().parseText(xml.toString())
142 |
143 | pomXml.version[0] + { packaging('jar') }
144 |
145 | def newXml = new StringWriter()
146 | def printer = new XmlNodePrinter(new PrintWriter(newXml))
147 | printer.preserveWhitespace = true
148 | printer.print(pomXml)
149 | xml.setLength(0)
150 | xml.append(newXml.toString())
151 | }
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/gradle.properties.example:
--------------------------------------------------------------------------------
1 | // This must be configured if you wanna deploy to the Nexus OSS Public Repo
2 | signing.keyId=
3 | signing.password=
4 | signing.secretKeyRingFile=
5 |
6 | ossrhUsername=
7 | ossrhPassword=
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xlson/groovycsv/f6d25f8418391258e684aac1084eb939280abb47/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Nov 06 22:26:46 CET 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/samples/GettingDependencyUsingGrab.groovy:
--------------------------------------------------------------------------------
1 | @Grab('com.xlson.groovycsv:groovycsv:0.2')
2 | import com.xlson.groovycsv.CsvParser
3 |
4 | def csv = '''Name,Lastname
5 | Mark,Andersson
6 | Pete,Hansen'''
7 |
8 | def data = new CsvParser().parse(csv)
9 | for(line in data) {
10 | println "$line.Name $line.Lastname"
11 | }
12 |
--------------------------------------------------------------------------------
/samples/UsingAutoDetection.groovy:
--------------------------------------------------------------------------------
1 | import com.xlson.groovycsv.CsvParser
2 |
3 | def csv = '''Name:Lastname
4 | Mark:Andersson
5 | Pete:Hansen'''
6 |
7 | def data = new CsvParser().parse(csv, autoDetect:true)
8 | for(line in data) {
9 | println "$line.Name $line.Lastname"
10 | }
11 |
--------------------------------------------------------------------------------
/src/com/xlson/groovycsv/AutoDetectHandler.groovy:
--------------------------------------------------------------------------------
1 | package com.xlson.groovycsv
2 |
3 | class AutoDetectHandler {
4 |
5 | List autoDetectSeparators = [",", ";", ":", "|"]
6 |
7 | List autoDetectQuoteChars = ['"', "'", "%"]
8 |
9 | String linesToInspect
10 |
11 |
12 | String autoDetectQuoteChar() {
13 | return mostFrequentChar(linesToInspect, autoDetectQuoteChars)
14 | }
15 |
16 | String autoDetectSeparator() {
17 | return mostFrequentChar(linesToInspect, autoDetectSeparators)
18 | }
19 |
20 | /**
21 | * Find the most frequent character in a string among a list of characters.
22 | * Falls back on the first character in the list if no character is found.
23 | *
24 | * @param sequence The string to search.
25 | * @param characters The list of characters to search.
26 | * @return The most frequent character.
27 | */
28 | private mostFrequentChar(String sequence, List characters) {
29 | characters.max { sequence.count(it) }
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/xlson/groovycsv/CsvIterator.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Leonard Gram
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.xlson.groovycsv
18 |
19 | import com.opencsv.CSVReader
20 |
21 | /**
22 | * Iterates over the csv data in a non-synchronized way.
23 | *
24 | * @author Leonard Gram
25 | * @since 0.1
26 | */
27 | class CsvIterator implements Iterator {
28 |
29 | private def columns
30 |
31 | private CSVReader csvReader
32 |
33 | private def readValue
34 |
35 | private Boolean closed = false
36 |
37 | def CsvIterator(def columnNames, CSVReader csvReader) {
38 | this.columns = [:]
39 | columnNames.eachWithIndex { name, i ->
40 | columns."$name" = i
41 | }
42 |
43 | this.csvReader = csvReader
44 | }
45 |
46 | /**
47 | * Closes the underlying reader object. Could be useful if one would
48 | * not like to read all of the csv into memory.
49 | *
50 | * @throws IllegalStateException if the underlying dataset is already closed.
51 | */
52 | void close() {
53 | throwsExceptionIfClosed()
54 |
55 | closed = true
56 | csvReader.close()
57 | }
58 |
59 | /**
60 | * Checks if there is more data available. Will close the underlying dataset
61 | * if there isn't any more data.
62 | *
63 | * @return true if there is more data in the iterator
64 | */
65 | boolean hasNext() {
66 | if (isClosed()) {
67 | return false
68 | } else if (nextValueIsRead()) {
69 | return true
70 | } else {
71 | readValue = getNextValue()
72 | if (readValue == null) {
73 | close()
74 | }
75 | return readValue != null
76 | }
77 |
78 | }
79 |
80 | /**
81 | * Checks if the underlying reader is closed.
82 | *
83 | * @return true if the underlying reader is closed
84 | */
85 | boolean isClosed() {
86 | closed
87 | }
88 |
89 | private boolean nextValueIsRead() {
90 | readValue as boolean
91 | }
92 |
93 | private def getNextValue() {
94 | if (nextValueIsRead()) {
95 | def value = readValue
96 | readValue = null
97 | return value
98 | } else {
99 | def nextValue
100 | for (; ;) {
101 | nextValue = csvReader.readNext()
102 | if (nextValue == null) {
103 | break
104 | } else if (isEmptyLine(nextValue)) {
105 | // Continues the loop and reads another value
106 | } else {
107 | break
108 | }
109 | }
110 | return nextValue
111 | }
112 | }
113 |
114 | private boolean isEmptyLine(String[] nextValue) {
115 | nextValue.size() == 1 && nextValue.first().isEmpty()
116 | }
117 |
118 | /**
119 | * Gets the next row in the csv file.
120 | *
121 | * @return an instance of PropertyMapper
122 | */
123 | def next() {
124 | throwsExceptionIfClosed()
125 |
126 | new PropertyMapper(columns: columns, values: nextValue)
127 | }
128 |
129 | private def throwsExceptionIfClosed() {
130 | if (isClosed()) {
131 | throw new IllegalStateException("The connection the underlying dataset has already been closed.")
132 | }
133 | }
134 |
135 | /**
136 | * remove is not supported in CsvIterator.
137 | *
138 | * @throws UnsupportedOperationException when called
139 | */
140 | void remove() {
141 | throw new UnsupportedOperationException()
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/com/xlson/groovycsv/CsvParser.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Leonard Gram
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.xlson.groovycsv
18 |
19 | import com.opencsv.CSVReader
20 |
21 | /**
22 | * Helper class used to parse information from csv files using the column names
23 | * in the first line. Currently it only supports csv files where the first line
24 | * contains the column names.
25 | *
26 | * Usage: 27 | *
28 | * def csv = '''Name,Lastname 29 | * Mark,Andersson 30 | * Pete,Hansen''' 31 | * 32 | * def data = new CsvParser().parse(csv) 33 | * for(line in data) {* println "$line.Name $line.Lastname" 34 | *}35 | * 36 | * 37 | * @author Leonard Gram 38 | * @since 0.1 39 | */ 40 | class CsvParser { 41 | 42 | /** 43 | * Number of characters used to provide to autodetection (in case auto 44 | * detection is used. 45 | */ 46 | Integer autoDetectCharNumber = 1000 47 | 48 | /** 49 | * Parses a string as csv in the same way as CsvParser.parse(...). 50 | * 51 | * @param args 52 | * @param csv 53 | * @return 54 | */ 55 | static Iterator parseCsv(Map args = [:], String csv) { 56 | new CsvParser().parse(args, csv) 57 | } 58 | 59 | /** 60 | * Parses a reader as csv in the same way as CsvParser.parse(...). 61 | * 62 | * @param args 63 | * @param csv 64 | * @return 65 | */ 66 | static Iterator parseCsv(Map args = [:], Reader reader) { 67 | new CsvParser().parse(args, reader) 68 | } 69 | 70 | /** 71 | * Parses the csv supplied using the reader. See parse(Reader reader) for 72 | * more information about usage. 73 | * 74 | * @param args configurable parameters 75 | * @param csv the csv to parse 76 | * @return an instance of
com.xlson.groovycsv.CsvIterator
77 | */
78 | Iterator parse(Map args = [:], String csv) {
79 | parse(args, new StringReader(csv))
80 | }
81 |
82 | /**
83 | * Parses the supplied csv and returns a CsvIterator that can be
84 | * use to access the data. The first line of the csv will be used
85 | * as column-headers. Named paramenters can be used to configure the
86 | * parsing, see the class documentation for more more information on
87 | * usage. There's also support for autodetecting the quote and separator
88 | * characters.
89 | * 90 | * Arguments for configuration: 91 | *
101 | * Usage: 102 | *
103 | * def csv = '''Fruit-Quantity 104 | * Apple-2 105 | * Pear-5''' 106 | * 107 | * def data = new CsvParser().parse(csv, separator: '-') 108 | * 109 | * // Print all fruits that have a quantity higher than 3 110 | * data.findAll{ (it.Quantity as int) > 3 }.each{ println it }*111 | * 112 | * @param reader the csv to parse 113 | * @param args the configuration arguments 114 | * @return an instance of
com.xlson.groovycsv.CsvIterator
115 | */
116 | Iterator parse(Map args = [:], Reader reader) {
117 | def csvReader = createCSVReader(args, reader)
118 | def columnNames = parseColumnNames(args, csvReader)
119 | new CsvIterator(columnNames, csvReader)
120 | }
121 |
122 | private def parseColumnNames(Map args, CSVReader csvReader) {
123 | def columnNames
124 |
125 | if (!args.readFirstLine) {
126 | columnNames = csvReader.readNext()
127 | }
128 |
129 | if (args.columnNames) {
130 | columnNames = args.columnNames
131 | }
132 |
133 | if (args.trimWhitespaceFromColumnNames) {
134 | columnNames = columnNames.collect { it.toString().trim() }
135 | }
136 | return columnNames
137 | }
138 |
139 | private CSVReader createCSVReader(Map args = [:], Reader reader) {
140 | Character separator
141 | Character quoteChar
142 | Character escapeChar = args.escapeChar
143 | Integer skipLines = args.skipLines ?: 0
144 |
145 | if (args.autoDetect == true) {
146 | reader = new PushbackReader(reader, autoDetectCharNumber)
147 | doAutoDetection(args, reader)
148 | separator = args.separator
149 | quoteChar = args.quoteChar
150 | } else {
151 | separator = args.separator ?: ','
152 | quoteChar = args.quoteChar ?: '"'
153 | }
154 |
155 | if (escapeChar != null) {
156 | return new CSVReader(reader, separator, quoteChar, escapeChar, skipLines)
157 | } else {
158 | return new CSVReader(reader, separator, quoteChar, skipLines)
159 | }
160 | }
161 |
162 | /**
163 | * Performs automatic detection of separator and quote character.
164 | *
165 | * It will search arguments for values 'auto' in separator and quoteChar. It
166 | * will return a new version of the arguments modified with the values that
167 | * were found.
168 | *
169 | * If nothing is detected, the values are removed from the args.
170 | *
171 | * Note that
172 | *
173 | * @param args the configuration arguments.
174 | * @param text the CSV as a String.
175 | * @return modified args with detected.
176 | */
177 | private Map doAutoDetection(Map args, PushbackReader reader) {
178 | def buf = new char[autoDetectCharNumber]
179 | def charsRead = reader.read(buf)
180 | def linesToInspect = new String(buf)
181 | reader.unread(buf, 0, charsRead)
182 |
183 |
184 | def autoDetector = new AutoDetectHandler(linesToInspect: linesToInspect)
185 | if (args.autoDetectQuoteChars) {
186 | autoDetector.autoDetectQuoteChars = args.autoDetectQuoteChars
187 | }
188 | if (args.autoDetectSeparators) {
189 | autoDetector.autoDetectSeparators = args.autoDetectSeparators
190 | }
191 | if (!args.separator) {
192 | def detectedSeparator = autoDetector.autoDetectSeparator()
193 | if (detectedSeparator) args.separator = detectedSeparator
194 | else args.remove("separator")
195 | }
196 | if (!args.quoteChar) {
197 | def detectedQuoteChar = autoDetector.autoDetectQuoteChar()
198 | if (detectedQuoteChar) args.quoteChar = detectedQuoteChar
199 | else args.remove("quoteChar")
200 | }
201 | return args
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/src/com/xlson/groovycsv/PropertyMapper.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010 Leonard Gram
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.xlson.groovycsv
18 |
19 | import groovy.transform.EqualsAndHashCode
20 |
21 | /**
22 | * Maps between column names and values in a list. Uses propertyMissing
23 | * to allow for named access.
24 | *
25 | * @author Leonard Gram
26 | * @since 0.1
27 | */
28 | @EqualsAndHashCode
29 | class PropertyMapper {
30 |
31 | /**
32 | * A list of values for one csv line.
33 | */
34 | def values
35 |
36 | /**
37 | * The columns of the csv.
38 | */
39 | def columns = [:]
40 |
41 | /**
42 | * Maps properties to values.
43 | *
44 | * @param name the name of the property
45 | * @return the value as a String
46 | * @throws MissingPropertyException where the values-list doesn't contain enough data.
47 | */
48 | def propertyMissing(String name) {
49 | def index = columns[name]
50 | if (index != null) {
51 | if(values?.size() <= index) {
52 | return null
53 | }
54 | values[index]
55 | } else {
56 | throw new MissingPropertyException(name)
57 | }
58 | }
59 |
60 | /**
61 | * Allows values to be obtained using their position
62 | *
63 | * @param index
64 | * @return the value at that position
65 | */
66 | def getAt(Integer index) {
67 | values[index]
68 | }
69 |
70 | String toString() {
71 | columns.collect { key, index -> "$key: ${values[index]}" }.join(', ')
72 | }
73 |
74 | Map toMap() {
75 | def sortedKeys = columns.keySet().sort { columns[it] }
76 | [sortedKeys, values].transpose().collectEntries()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/test/com/xlson/groovycsv/AutoDetectHandlerSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.xlson.groovycsv
2 |
3 | import spock.lang.Specification;
4 |
5 | class AutoDetectHandlerSpec extends Specification {
6 |
7 |
8 | def "should auto detect quote character"() {
9 | setup:
10 | def adh = new AutoDetectHandler(linesToInspect: csvData)
11 |
12 | expect:
13 | adh.autoDetectQuoteChar() == quoteChar
14 |
15 | where:
16 | csvData | quoteChar
17 | testDataWithColumnNamesAnd3Rows | '"'
18 | testDataWithColumnNamesAnd2Rows | '"'
19 | csvUsingDoubleQuoteAsQuoteChar | '"'
20 | csvUsingPercentageAsQuoteChar | "%"
21 |
22 | }
23 |
24 | def "should auto detect separator"() {
25 | setup:
26 | def adh = new AutoDetectHandler(linesToInspect: csvData)
27 |
28 | expect:
29 | adh.autoDetectSeparator() == separator
30 |
31 | where:
32 | csvData | separator
33 | testDataWithColumnNamesAnd3Rows | ","
34 | testDataWithColumnNamesAnd2Rows | ","
35 | csvUsingDoubleQuoteAsQuoteChar | ','
36 | csvWithColonAsSeparator | ":"
37 | }
38 |
39 |
40 | def getTestDataWithColumnNamesAnd3Rows() {
41 | '''Name,Lastname,Age,Email
42 | Mark,Hamilton,45,mark@hamilton.com
43 | Bosse,Bildoktorn,50,bildoktorn@tv4.se
44 | Peps,Persson,65,peps.persson@hotmail.com'''
45 | }
46 |
47 | def getTestDataWithColumnNamesAnd2Rows() {
48 | '''Letter,Word,Number
49 | a,paris,5
50 | h,drink,60'''
51 | }
52 |
53 | def getCsvWithColonAsSeparator() {
54 | '''Fruit:Count
55 | Apple:5
56 | Pear:10
57 | Kiwi:200'''
58 | }
59 |
60 | def getCsvUsingDoubleQuoteAsQuoteChar() {
61 | '''Typo,Desc
62 | 123,"text ,and more"'''
63 | }
64 |
65 | def getCsvUsingPercentageAsQuoteChar() {
66 | '''Typo,Desc
67 | 1123,%bla, ha%'''
68 |
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/test/com/xlson/groovycsv/ConfigurableColumnNamesSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.xlson.groovycsv
2 |
3 | import com.opencsv.CSVReader
4 | import spock.lang.Specification
5 |
6 | class ConfigurableColumnNamesSpec extends Specification {
7 |
8 | def csvWithoutCoulmnNames = """field1,field2,field3,
9 | Joe,Doe,18
10 | Jane,Doe,24"""
11 |
12 | def "Replaces existing column names with new ones"() {
13 | setup:
14 | def persons = new CsvParser().parse(csvWithoutCoulmnNames, columnNames: ['name', 'lastname', 'age'])
15 |
16 | when:
17 | def joe = persons.next()
18 |
19 | then:
20 | joe.name == 'Joe'
21 | joe.lastname == 'Doe'
22 | joe.age == '18'
23 | }
24 |
25 | def "Read all lines of csv content using custom column as column names"() {
26 | setup:
27 | def persons = new CsvParser().parse(csvWithoutCoulmnNames, readFirstLine: true, columnNames: ['name', 'lastname', 'age'])
28 |
29 | when:
30 | def names = persons*.name
31 |
32 | then:
33 | names == ['field1', 'Joe', 'Jane']
34 | }
35 |
36 | def "Parses columns from the first line by default"() {
37 | setup:
38 | def reader = Mock(CSVReader)
39 |
40 | when:
41 | def columnNames = new CsvParser().parseColumnNames([:], reader)
42 |
43 | then:
44 | reader.readNext() >> ['a', 'b', 'c']
45 | columnNames == ['a', 'b', 'c']
46 | }
47 |
48 | def "Throws away the first line by default when using custom column names."() {
49 | setup:
50 | def reader = Mock(CSVReader)
51 |
52 | when:
53 | def customColumnNames = ['1', '2', '3']
54 | def columnNames = new CsvParser().parseColumnNames([columnNames: customColumnNames], reader)
55 |
56 | then:
57 | 1 * reader.readNext()
58 | columnNames == customColumnNames
59 | }
60 |
61 | def "Does not read the first line as header when readFirstLine is specified."() {
62 | setup:
63 | def reader = Mock(CSVReader)
64 |
65 | when:
66 | def customColumnNames = ['1', '2', '3']
67 | def columnNames = new CsvParser().parseColumnNames([columnNames : customColumnNames,
68 | readFirstLine: true],
69 | reader)
70 | then:
71 | 0 * reader.readNext()
72 | columnNames == customColumnNames
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/test/com/xlson/groovycsv/CsvIteratorSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.xlson.groovycsv
2 |
3 | import com.opencsv.CSVReader
4 | import spock.lang.Specification
5 |
6 | class CsvIteratorSpec extends Specification {
7 |
8 | def getCsvData() {
9 | def csv = """a,b,c
10 | 1,2,3
11 | 4,5,6"""
12 | def csvReader = new CSVReader(new StringReader(csv))
13 | def columnNames = csvReader.readNext()
14 | return [columnNames, csvReader]
15 | }
16 |
17 | def "CsvIterator iterates correctly over the CSVReader"() {
18 | setup:
19 | def (colNames, csvReader) = csvData
20 | def iter = new CsvIterator(colNames, csvReader)
21 |
22 | expect:
23 | iter.hasNext()
24 | iter.next().a == '1'
25 | iter.hasNext()
26 | iter.next().c == '6'
27 | !iter.hasNext()
28 | }
29 |
30 | def "CsvIterator should close the underlying CSVReader instance when reaching the end of the file."() {
31 | setup:
32 | CSVReader csvReader = Mock(CSVReader)
33 | def iter = new CsvIterator(["a", "b"], csvReader)
34 | csvReader.readNext() >>> [["1", "2"], ["3", "4"], null]
35 |
36 | when:
37 | iter.next()
38 |
39 | then:
40 | iter.hasNext()
41 | !iter.isClosed()
42 | 0 * csvReader.close()
43 |
44 | when:
45 | iter.next()
46 | iter.hasNext()
47 |
48 | then:
49 | iter.isClosed()
50 | 1 * csvReader.close()
51 | }
52 |
53 | def "CsvIterator isClosed after a full iteration."() {
54 | setup:
55 | def csvIterator = new CsvIterator(*csvData)
56 |
57 | when:
58 | csvIterator.each {}
59 |
60 | then:
61 | csvIterator.isClosed()
62 |
63 | when:
64 | csvIterator.next()
65 |
66 | then:
67 | thrown(IllegalStateException)
68 | }
69 |
70 | def "close can be called on the CsvIterator to close the connection to the reader."() {
71 | setup:
72 | def (colNames, csvReader) = csvData
73 | def iter = new CsvIterator(colNames, csvReader)
74 |
75 | when:
76 | iter.next()
77 | iter.close()
78 |
79 | then:
80 | iter.isClosed()
81 |
82 | when:
83 | iter.next()
84 |
85 | then:
86 | thrown(IllegalStateException)
87 |
88 | }
89 |
90 | def "CsvIterator.hasNext() returns false when the underlying reader instance is closed."() {
91 | setup: 'Create an instance of CsvIterator consisting of 2 rows.'
92 | def csvIterator = new CsvIterator(*csvData)
93 |
94 | when: 'Iterates over the iterator until hasNext() is false'
95 | csvIterator.each {}
96 |
97 | then: 'hasNext() should return false.'
98 | !csvIterator.hasNext()
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/test/com/xlson/groovycsv/CsvParserSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.xlson.groovycsv
2 |
3 | import com.opencsv.CSVReader
4 | import spock.lang.*
5 |
6 | class CsvParserSpec extends Specification {
7 | def getTestDataWithColumnNamesAnd3Rows() {
8 | '''Name,Lastname,Age,Email
9 | Mark,Hamilton,45,mark@hamilton.com
10 | Bosse,Bildoktorn,50,bildoktorn@tv4.se
11 | Peps,Persson,65,peps.persson@hotmail.com'''
12 | }
13 |
14 | def getTestDataWithColumnNamesAnd2Rows() {
15 | '''Letter,Word,Number
16 | a,paris,5
17 | h,drink,60'''
18 | }
19 |
20 | def getCsvWithColonAsSeparator() {
21 | '''Fruit:Count
22 | Apple:5
23 | Pear:10
24 | Kiwi:200'''
25 | }
26 |
27 | def getTestDataWithQuotedComma() {
28 | '''a,b
29 | -,abc-,4
30 | abc,-4-'''
31 | }
32 |
33 | def "Iterating over the parsed csv values are available by column name."() {
34 | setup:
35 | def data = new CsvParser().parse(getTestDataWithColumnNamesAnd3Rows())
36 |
37 | expect:
38 | data*."$columnName" == values
39 |
40 | where:
41 | columnName | values
42 | "Name" | ['Mark', 'Bosse', "Peps"]
43 | "Lastname" | ['Hamilton', 'Bildoktorn', 'Persson']
44 | 'Age' | ['45', '50', '65']
45 | "Email" | ['mark@hamilton.com', 'bildoktorn@tv4.se', 'peps.persson@hotmail.com']
46 | }
47 |
48 | def "Functional collection methods are available on parsed object."() {
49 | setup:
50 | def data = new CsvParser().parse(getTestDataWithColumnNamesAnd3Rows())
51 |
52 | expect:
53 | data.findAll { (it.Age as int) > 46 }.size() == 2
54 | }
55 |
56 | def "readAll should never be called on the CSVReader instance used to parse the csv."() {
57 | setup:
58 | CSVReader csvReader = Mock(CSVReader)
59 | def partiallyMockedCsvParser = new CsvParser()
60 | partiallyMockedCsvParser.metaClass.createCSVReader = { Reader reader ->
61 | csvReader
62 | }
63 |
64 | when: "csv is parsed and looped through"
65 | def data = partiallyMockedCsvParser.parse(getTestDataWithColumnNamesAnd2Rows())
66 | for (d in data) {
67 | }
68 |
69 | then: "readAll() should not be called."
70 | 0 * csvReader.readAll()
71 | }
72 |
73 | def "Parse supports a custom separator."() {
74 | setup:
75 | def data = new CsvParser().parse(csvWithColonAsSeparator, separator: ':')
76 |
77 | expect:
78 | data*."$columnName" == values
79 |
80 | where:
81 | columnName | values
82 | "Fruit" | ['Apple', 'Pear', 'Kiwi']
83 | "Count" | ["5", "10", "200"]
84 | }
85 |
86 | def getCsvUsingDoubleQuoteAsQuoteChar() {
87 | '''Typo,Desc
88 | 123,"text ,and more"'''
89 | }
90 |
91 | def getCsvUsingPercentageAsQuoteChar() {
92 | '''Typo,Desc
93 | 1123,%bla, ha%'''
94 |
95 | }
96 |
97 | def "Parse supports custom quote character."() {
98 | when:
99 | def csv = new CsvParser().parse(csvData, quoteChar: quoteChar)
100 |
101 | then:
102 | csv*."$columnName" == values
103 |
104 | where:
105 | csvData | quoteChar | values | columnName
106 | csvUsingDoubleQuoteAsQuoteChar | '"' | ['text ,and more'] | "Desc"
107 | csvUsingPercentageAsQuoteChar | "%" | ['bla, ha'] | "Desc"
108 | }
109 |
110 | def "Parse supports custom escape char."() {
111 | setup:
112 | def csvData = '''Test,It
113 | 1,"this is \"a quote\""'''
114 | def csv = new CsvParser().parse(csvData, escapeChar: "\\")
115 |
116 | expect:
117 | csv*.It == ['this is "a quote"']
118 |
119 | }
120 |
121 | def "Parser quietly ignores one empty line."() {
122 | setup:
123 | def csvData = '''Fruit,Country
124 |
125 | Apple,Sweden
126 | '''
127 | def csv = new CsvParser().parse(csvData)
128 |
129 | expect:
130 | for (line in csv) {
131 | line.Fruit == 'Apple'
132 | line.Country == 'Sweden'
133 | }
134 | }
135 |
136 | def "Parser quitely ignores multiple empty lines."() {
137 | setup:
138 | def csvData = '''Color,Day
139 | Red,Monday
140 |
141 |
142 |
143 | Black,Friday'''
144 |
145 | def csv = new CsvParser().parse(csvData)
146 |
147 | expect:
148 | def firstLine = csv.next()
149 | firstLine.Color == 'Red'
150 | firstLine.Day == 'Monday'
151 |
152 | def secondLine = csv.next()
153 | secondLine.Color == 'Black'
154 | secondLine.Day == 'Friday'
155 | csv.hasNext() == false
156 | }
157 |
158 | def "Parser quitely ignores multiple empty linessadadas."() {
159 | setup:
160 | def csvData = '''Color,Day
161 |
162 |
163 |
164 | Red,Monday
165 |
166 |
167 |
168 | Black,Friday
169 |
170 |
171 | '''
172 |
173 | def csv = new CsvParser().parse(csvData)
174 |
175 | expect:
176 | for (line in csv) {
177 | println line
178 | }
179 | }
180 |
181 | def "Parse supports java.io.Reader as input."() {
182 | when:
183 | def csv = new CsvParser().parse(new StringReader(testDataWithColumnNamesAnd2Rows))
184 |
185 | then:
186 | csv*.Number == ['5', '60']
187 | }
188 |
189 | def "CsvParser can auto detect separator and quote character"() {
190 | when: "a CSV file is parsed with auto detection"
191 | def csv = new CsvParser().parse(autoDetect: true, csvData)
192 |
193 | then: "it should return the correct columns"
194 | csv*."$property" == values
195 |
196 | where:
197 | csvData | property | values
198 | testDataWithColumnNamesAnd3Rows | "Age" | ["45", "50", "65"]
199 | csvWithColonAsSeparator | "Count" | ["5", "10", "200"]
200 | csvUsingDoubleQuoteAsQuoteChar | "Desc" | ["text ,and more"]
201 | csvUsingDoubleQuoteAsQuoteChar | "Typo" | ["123"]
202 | testDataWithColumnNamesAnd2Rows | "Word" | ["paris", "drink"]
203 | testDataWithColumnNamesAnd3Rows | "Email" | ["mark@hamilton.com", "bildoktorn@tv4.se", "peps.persson@hotmail.com"]
204 | }
205 |
206 | def "should allow to override auto detection"() {
207 | when: "autoDetect is active and a separator is provided"
208 | def csv = new CsvParser().parse(autoDetect: true, separator: ',', csvWithColonAsSeparator)
209 |
210 | then: "the separator provided is used"
211 | csv*."Fruit:Count" == ["Apple:5", "Pear:10", "Kiwi:200"]
212 | }
213 |
214 | def "The separator should be allowed in the csv data if its quoted"() {
215 | when:
216 | def csv = new CsvParser().parse(quoteChar: '-', testDataWithQuotedComma)
217 |
218 | then:
219 | csv*.a == [',abc', 'abc']
220 | }
221 |
222 | def "Values in the csv can be obtained by using the index of the column."() {
223 | when:
224 | def csv = new CsvParser().parse(testDataWithColumnNamesAnd2Rows)
225 | def line = csv.next()
226 |
227 | then:
228 | line[0] == 'a'
229 | line[1] == 'paris'
230 | line[2] == '5'
231 | }
232 |
233 | def csvWithoutHeaders = 'Joe,Doe,19'
234 |
235 | def "Parsing csv without headers using the position of the values."() {
236 | when:
237 | def csv = new CsvParser().parse(readFirstLine: true, csvWithoutHeaders)
238 | def line = csv.next()
239 |
240 | then:
241 | line[0] == 'Joe'
242 | line[1] == 'Doe'
243 | line[2] == '19'
244 |
245 | }
246 |
247 | def "CsvParser.parseCsv can be used statically."() {
248 | when:
249 | def csv = CsvParser.parseCsv(csvData)
250 |
251 | then:
252 | csv*.Letter == letterValues
253 |
254 | where:
255 | csvData | letterValues
256 | testDataWithColumnNamesAnd2Rows | ['a', 'h']
257 | new StringReader(testDataWithColumnNamesAnd2Rows) | ['a', 'h']
258 | }
259 |
260 | def getTestDataWithWhitespaceSurroundingColumnNames() {
261 | """ Name , Lastname,Country
262 | Leonard,Gram,Sweden"""
263 | }
264 |
265 | def "CsvParser can strip leading and trailing whitespaces on column names."() {
266 | when:
267 | def csv = CsvParser.parseCsv(testDataWithWhitespaceSurroundingColumnNames, trimWhitespaceFromColumnNames: true)
268 |
269 | then:
270 | for(line in csv) {
271 | line.Name == 'Leonard'
272 | line.Lastname == 'Gram'
273 | line.Country == 'Sweden'
274 | }
275 | }
276 |
277 | def "CsvParser can skip lines at the beginning of the file."() {
278 |
279 | def csvWithSkipLines = '''skipped line1
280 | skipped line2
281 | Letter,Word,Number
282 | a,paris,5
283 | h,drink,60'''
284 | when:
285 | def csv = new CsvParser().parse(skipLines: 2, csvWithSkipLines)
286 | def line = csv.next()
287 |
288 | then:
289 | line["Letter"] == "a"
290 | line["Word"] == "paris"
291 | line["Number"] == "5"
292 | }
293 |
294 | def "CsvParser and different cardinality lines"() {
295 | given:
296 | def csvWithDifferentCardinality = '''header1,header2,header3
297 | val1,val2
298 | val1,val2,val3'''
299 |
300 | when:
301 | def csv = new CsvParser().parse(csvWithDifferentCardinality)
302 | def line = csv.next()
303 |
304 | then:
305 | line["header3"] == null
306 |
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/test/com/xlson/groovycsv/PropertyMapperSpec.groovy:
--------------------------------------------------------------------------------
1 | package com.xlson.groovycsv
2 |
3 | import spock.lang.Specification
4 |
5 | class PropertyMapperSpec extends Specification {
6 |
7 | def getTestDataWithDuplicatedLines() {
8 | '''a,b
9 | 1,2
10 | 1,2
11 | 3,4'''
12 | }
13 |
14 | def "PropertyMapper has a toMap() that returns a map representation."() {
15 | setup:
16 | def pm = new PropertyMapper(values: values, columns: columns)
17 |
18 | expect:
19 | pm.toMap() == toMapRepresentation
20 |
21 | where:
22 | columns | values | toMapRepresentation
23 | ['a': 0, 'b': 1, 'c': 2] | ['1', '2', '3'] | ['a': '1', 'b': '2', 'c': '3']
24 | ['Name': 0, 'Age': 1] | ['Mark', '56'] | ['Name': 'Mark', 'Age': '56']
25 | }
26 |
27 | def "PropertyMapper has a toString() that returns all the data in it's columns."() {
28 | setup:
29 | def pm = new PropertyMapper(values: values, columns: columns)
30 |
31 | expect:
32 | pm.toString() == toStringRepresentation
33 |
34 | where:
35 | columns | values | toStringRepresentation
36 | ['a': 0, 'b': 1, 'c': 2] | ['1', '2', '3'] | "a: 1, b: 2, c: 3"
37 | ['Name': 0, 'Age': 1] | ['Mark', '56'] | "Name: Mark, Age: 56"
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------