├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
├── main
└── java
│ └── org
│ └── javafunk
│ └── excelparser
│ ├── SheetParser.java
│ ├── annotations
│ ├── ExcelField.java
│ ├── ExcelObject.java
│ ├── MappedExcelObject.java
│ └── ParseType.java
│ ├── exception
│ ├── ExcelInvalidCell.java
│ ├── ExcelInvalidCellValuesException.java
│ └── ExcelParsingException.java
│ └── helper
│ ├── HSSFHelper.java
│ └── Locator.java
└── test
├── java
└── org
│ └── javafunk
│ ├── example
│ └── domain
│ │ ├── Section.java
│ │ ├── Student.java
│ │ └── Subject.java
│ └── excelparser
│ ├── SheetParserTest.java
│ └── helper
│ └── HSSFHelperTest.java
└── resources
├── Errors.xlsx
├── Student Profile.xls
├── Student Profile.xlsx
└── Subjects.xlsx
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | build
3 | .gradle
4 | .idea
5 | .settings
6 | excel-parser.iml
7 | excel-parser.ipr
8 | excel-parser.iws
9 | out
10 | repos
11 | /bin/
12 | .classpath
13 | .project
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | install: gradle assemble -x signArchives
5 | script: gradle check
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Venkatesh Nannan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/nvenky/excel-parser)
2 |
3 |
4 | # Excel Parser Examples
5 |
6 | [](https://gitter.im/nvenky/excel-parser?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 |
8 |
9 | HSSF - Horrible Spreadsheet Format – not anymore. With few annotations, excel parsing can be done in one line.
10 |
11 | We had a requirement in our current project to parse multiple excel sheets and store the information to database. I hope most of the projects involving excel sheet parsing would be doing the same. We built a extensible framework to parse multiple sheets and populate JAVA objects with annotations.
12 |
13 | ## Usage
14 |
15 | This JAR is currently available in [Sonatype maven repository](https://oss.sonatype.org/#nexus-search;quick~excel-parser).
16 |
17 | Maven:
18 |
19 | ````xml
20 |
21 | org.javafunk
22 | excel-parser
23 | 1.0
24 |
25 | ````
26 |
27 | Gradle:
28 |
29 | ````
30 | compile 'org.javafunk:excel-parser:1.0'
31 | ````
32 |
33 | Thanks to [tobyclemson](http://github.com/tobyclemson) for publishing this to Maven repository.
34 |
35 |
36 | ## Student Information Example
37 |
38 | Consider we have an excel sheet with student information.
39 |
40 | 
41 |
42 | While parsing this excel sheet, we need to populate one “Section” object and multiple “Student” objects related to a Section. You can see that Student information is available in multiple rows whereas the Section details (Year, Section) is available in column B.
43 |
44 | ### Step 1: Annotate Domain Classes
45 |
46 | First we will see the steps to annotate Section object:
47 |
48 | ````java
49 | @ExcelObject(parseType = ParseType.COLUMN, start = 2, end = 2)
50 | public class Section {
51 | @ExcelField(position = 2)
52 | private String year;
53 |
54 | @ExcelField(position = 3)
55 | private String section;
56 |
57 | @MappedExcelObject
58 | private List students;
59 | }
60 |
61 | ````
62 | You can find three different annotation in this class.
63 |
64 | * `ExcelObject`: This annotation tells the parser about the parse type (Row or Column), number of objects to create (start, end). Based on the above annotation, Section value should be parsed Columnwise and information can be found in Column 2 (“B”) of the Excelsheet.
65 | * `ExcelField`: This annotation tells the parser to fetch “year” information from Row 2 and “section” information from Row 3.
66 | * `MappedExcelObject`: Apart from Simple datatypes like “Double”,”String”, we might also try to populate complex java objects while parsing. In this case, each section has a list of student information to be parsed from excel sheet. This annotation will help the parser in identifying such fields.
67 |
68 | Then, annotate the Student class:
69 |
70 | ````java
71 | @ExcelObject(parseType = ParseType.ROW, start = 6, end = 8)
72 | public class Student {
73 | @ExcelField(position = 2)
74 | private Long roleNumber;
75 |
76 | @ExcelField(position = 3)
77 | private String name;
78 |
79 | @ExcelField(position = 4)
80 | private Date dateOfBirth;
81 |
82 | @ExcelField(position = 5)
83 | private String fatherName;
84 |
85 | @ExcelField(position = 6)
86 | private String motherName;
87 |
88 | @ExcelField(position = 7)
89 | private String address;
90 |
91 | @ExcelField(position = 8)
92 | private Double totalScore;
93 | }
94 | ````
95 |
96 | * `ExcelObject`: As shown above, this annotation tells parser to parse Rows 6 to 8 (create 3 student objects). NOTE: Optional field “zeroIfNull” , if set to true, will populate Zero to all number fields (Double,Long,Integer) by default if the data is not available in DB.
97 | * `ExcelField`: Student class has 7 values to be parsed and stored in the database. This is denoted in the domain class as annotation.
98 | * `MappedExcelObject`: Student class does not have any complex object, hence this annotation is not used in this domain class.
99 |
100 |
101 | ### Step 2: Invoke Sheet Parser
102 |
103 | Once the annotation is done, you have just invoke the parser with the Sheet and the Root class you want to populate.
104 |
105 | ````java
106 | //Get the sheet using POI API.
107 | String sheetName = "Sheet1";
108 | SheetParser parser = new SheetParser();
109 | InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Student Profile.xls");
110 | Sheet sheet = new HSSFWorkbook(inputStream).getSheet(sheetName);
111 |
112 | //Invoke the Sheet parser.
113 | List entityList = parser.createEntity(sheet, sheetName, Section.class);
114 | ````
115 |
116 | Thats all it requires. Parser would populate all the fields based on the annotation for you.
117 |
118 | ### Development
119 | * JDK 8
120 | * Run "gradle idea" to setup the project
121 | * Install Lombok plugin
122 | * Enable "Enable annotation processing" as this project uses Lombok library. [Compiler > Annotation Processors > Enable annotation processing: checked ]
123 |
124 |
125 | ### Contributors
126 | * @nvenky
127 | * @cv
128 | * @tobyclemson
129 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | defaultTasks 'clean', 'check', 'jar'
2 |
3 | apply plugin: 'java'
4 | apply plugin: 'idea'
5 | apply plugin: 'maven'
6 | apply plugin: 'signing'
7 | apply plugin: 'nexus-workflow'
8 |
9 | version = '1.0.1'
10 | project.group = 'org.javafunk'
11 | archivesBaseName = "excel-parser"
12 |
13 | buildscript {
14 | repositories {
15 | mavenCentral()
16 | }
17 | dependencies {
18 | classpath 'com.adaptc.gradle:nexus-workflow:0.6'
19 | }
20 | }
21 |
22 | repositories {
23 | mavenLocal()
24 | mavenCentral()
25 | }
26 |
27 | dependencies {
28 | compile 'org.projectlombok:lombok:1.14.4'
29 | compile 'org.apache.poi:poi-ooxml:3.10-FINAL'
30 |
31 | testCompile 'org.testng:testng:6.8.1'
32 | }
33 |
34 | signing {
35 | sign configurations.archives
36 | }
37 |
38 | jar {
39 | manifest {
40 | attributes 'Implementation-Title': 'Excel Parser', 'Implementation-Version': version
41 | }
42 | }
43 |
44 | task javadocJar(type: Jar) {
45 | classifier = 'javadoc'
46 | from javadoc
47 | manifest {
48 | attributes 'Implementation-Title': 'Excel Parser', 'Implementation-Version': version
49 | }
50 | }
51 |
52 | task sourcesJar(type: Jar) {
53 | classifier = 'sources'
54 | from sourceSets.main.allSource
55 | manifest {
56 | attributes 'Implementation-Title': 'Excel Parser', 'Implementation-Version': version
57 | }
58 | }
59 |
60 | artifacts {
61 | archives javadocJar, sourcesJar
62 | }
63 |
64 | uploadArchives {
65 | repositories {
66 | flatDir {
67 | dirs 'repos'
68 | }
69 |
70 | mavenDeployer {
71 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
72 |
73 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
74 | authentication(
75 | userName: project.property("oss-releases.username"),
76 | password: project.property("oss-releases.password"))
77 | }
78 |
79 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
80 | authentication(
81 | userName: project.property("oss-releases.username"),
82 | password: project.property("oss-releases.password"))
83 | }
84 |
85 | pom.project {
86 | name 'Excel Parser'
87 | packaging 'jar'
88 | description 'Annotations for Excel document parsing.'
89 | url 'https://github.com/nvenky/excel-parser'
90 |
91 | scm {
92 | connection 'scm:git:git://github.com/nvenky/excel-parser/'
93 | developerConnection 'scm:git:git://github.com/nvenky/excel-parser/'
94 | url 'https://github.com/nvenky/excel-parser'
95 | }
96 |
97 | licenses {
98 | license {
99 | name 'The MIT License (MIT)'
100 | url 'http://opensource.org/licenses/MIT'
101 | }
102 | }
103 |
104 | developers {
105 | developer {
106 | id 'nvenky'
107 | name 'Venkatesh Nannan'
108 | email 'nvenky@gmail.com'
109 | }
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | signing.keyId=
2 | signing.password=
3 | signing.secretKeyRingFile=
4 |
5 | oss-releases.username=
6 | oss-releases.password=
7 | oss-releases.url=https://oss.sonatype.org/index.html#stagingRepositories
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvenky/excel-parser/578d379dc33dd0b3ee1558d16fd6315b09c40b5c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 05 21:49:06 EST 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-2.0-all.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 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/SheetParser.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser;
2 |
3 | import static org.javafunk.excelparser.helper.HSSFHelper.getRow;
4 |
5 | import java.lang.reflect.Constructor;
6 | import java.lang.reflect.Field;
7 | import java.lang.reflect.ParameterizedType;
8 | import java.lang.reflect.Type;
9 | import java.util.ArrayList;
10 | import java.util.HashMap;
11 | import java.util.Iterator;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.OptionalInt;
15 | import java.util.Set;
16 | import java.util.TreeMap;
17 | import java.util.function.Consumer;
18 | import java.util.regex.Matcher;
19 | import java.util.regex.Pattern;
20 |
21 | import org.apache.poi.ss.usermodel.Row;
22 | import org.apache.poi.ss.usermodel.Sheet;
23 | import org.javafunk.excelparser.annotations.ExcelField;
24 | import org.javafunk.excelparser.annotations.ExcelObject;
25 | import org.javafunk.excelparser.annotations.MappedExcelObject;
26 | import org.javafunk.excelparser.annotations.ParseType;
27 | import org.javafunk.excelparser.exception.ExcelInvalidCell;
28 | import org.javafunk.excelparser.exception.ExcelInvalidCellValuesException;
29 | import org.javafunk.excelparser.exception.ExcelParsingException;
30 | import org.javafunk.excelparser.helper.HSSFHelper;
31 |
32 | import lombok.AccessLevel;
33 | import lombok.experimental.FieldDefaults;
34 |
35 | @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
36 | public class SheetParser {
37 | List excelInvalidCells;
38 |
39 | public SheetParser() {
40 | excelInvalidCells = new ArrayList<>();
41 | }
42 |
43 | public List createEntity(Sheet sheet, Class clazz, Consumer errorHandler) {
44 | List list = new ArrayList<>();
45 | ExcelObject excelObject = getExcelObject(clazz, errorHandler);
46 | if (excelObject.start() <= 0 || excelObject.end() < 0) {
47 | return list;
48 | }
49 | int end = getEnd(sheet, clazz, excelObject);
50 |
51 | for (int currentLocation = excelObject.start(); currentLocation <= end; currentLocation++) {
52 | T object = getNewInstance(sheet, clazz, excelObject.parseType(), currentLocation, excelObject.zeroIfNull(),
53 | errorHandler);
54 | List mappedExcelFields = getMappedExcelObjects(clazz);
55 | for (Field mappedField : mappedExcelFields) {
56 | Class> fieldType = mappedField.getType();
57 | Class> clazz1 = fieldType.equals(List.class) ? getFieldType(mappedField) : fieldType;
58 | List> fieldValue = createEntity(sheet, clazz1, errorHandler);
59 | if (fieldType.equals(List.class)) {
60 | setFieldValue(mappedField, object, fieldValue);
61 | } else if (!fieldValue.isEmpty()) {
62 | setFieldValue(mappedField, object, fieldValue.get(0));
63 | }
64 | }
65 | list.add(object);
66 | }
67 | return list;
68 | }
69 |
70 | public List createEntityWithIterator(Sheet sheet, Class clazz, Consumer errorHandler) {
71 | List list = new ArrayList();
72 |
73 | ExcelObject excelObject = getExcelObject(clazz, errorHandler);
74 | if (excelObject.start() <= 0 || excelObject.end() < 0) {
75 | return list;
76 | }
77 | int end = getEnd(sheet, clazz, excelObject);
78 | for (int currentLocation = excelObject.start(); currentLocation <= end; currentLocation++) {
79 | T object = getNewInstance(sheet.iterator(), sheet.getSheetName(), clazz, excelObject.parseType(), currentLocation, excelObject.zeroIfNull(),
80 | errorHandler);
81 | List mappedExcelFields = getMappedExcelObjects(clazz);
82 | for (Field mappedField : mappedExcelFields) {
83 | Class> fieldType = mappedField.getType();
84 | Class> clazz1 = fieldType.equals(List.class) ? getFieldType(mappedField) : fieldType;
85 | List> fieldValue = createEntityWithIterator(sheet, clazz1, errorHandler);
86 | if (fieldType.equals(List.class)) {
87 | setFieldValue(mappedField, object, fieldValue);
88 | } else if (!fieldValue.isEmpty()) {
89 | setFieldValue(mappedField, object, fieldValue.get(0));
90 | }
91 | }
92 | list.add(object);
93 | }
94 |
95 | return list;
96 | }
97 |
98 | private int getEnd(Sheet sheet, Class clazz, ExcelObject excelObject) {
99 | int end = excelObject.end();
100 | if (end > 0) {
101 | return end;
102 | }
103 | return getRowOrColumnEnd(sheet, clazz);
104 | }
105 |
106 | /**
107 | * @deprecated Pass an error handler lambda instead (see other signature)
108 | */
109 | @Deprecated
110 | public List createEntity(Sheet sheet, String sheetName, Class clazz) {
111 | return createEntity(sheet, clazz, error -> {
112 | throw error;
113 | });
114 | }
115 |
116 | public int getRowOrColumnEnd(Sheet sheet, Class clazz) {
117 | ExcelObject excelObject = getExcelObject(clazz, e -> {
118 | throw e;
119 | });
120 | ParseType parseType = excelObject.parseType();
121 | if (parseType == ParseType.ROW) {
122 | return sheet.getLastRowNum() + 1;
123 | }
124 |
125 | Set positions = getExcelFieldPositionMap(clazz).keySet();
126 | OptionalInt maxPosition = positions.stream().mapToInt((x) -> x).max();
127 | OptionalInt minPosition = positions.stream().mapToInt((x) -> x).min();
128 |
129 | int maxCellNumber = 0;
130 | for (int i = minPosition.getAsInt(); i < maxPosition.getAsInt(); i++) {
131 | int cellsNumber = sheet.getRow(i).getLastCellNum();
132 | if (maxCellNumber < cellsNumber) {
133 | maxCellNumber = cellsNumber;
134 | }
135 | }
136 | return maxCellNumber;
137 | }
138 |
139 | private Class> getFieldType(Field field) {
140 | Type type = field.getGenericType();
141 | if (type instanceof ParameterizedType) {
142 | ParameterizedType pt = (ParameterizedType) type;
143 | return (Class>) pt.getActualTypeArguments()[0];
144 | }
145 |
146 | return null;
147 | }
148 |
149 | private List getMappedExcelObjects(Class clazz) {
150 | List fieldList = new ArrayList<>();
151 | Field[] fields = clazz.getDeclaredFields();
152 | for (Field field : fields) {
153 | MappedExcelObject mappedExcelObject = field.getAnnotation(MappedExcelObject.class);
154 | if (mappedExcelObject != null) {
155 | field.setAccessible(true);
156 | fieldList.add(field);
157 | }
158 | }
159 | return fieldList;
160 | }
161 |
162 | private ExcelObject getExcelObject(Class clazz, Consumer errorHandler) {
163 | ExcelObject excelObject = clazz.getAnnotation(ExcelObject.class);
164 | if (excelObject == null) {
165 | errorHandler.accept(new ExcelParsingException("Invalid class configuration - ExcelObject annotation missing - " + clazz.getSimpleName()));
166 | }
167 | return excelObject;
168 | }
169 |
170 | private T getNewInstance(Sheet sheet, Class clazz, ParseType parseType, Integer currentLocation, boolean zeroIfNull, Consumer errorHandler) {
171 | T object = getInstance(clazz, errorHandler);
172 | Map excelPositionMap = getExcelFieldPositionMap(clazz);
173 | for (Integer position : excelPositionMap.keySet()) {
174 | Field field = excelPositionMap.get(position);
175 | Object cellValue;
176 | Object cellValueString;
177 | if (ParseType.ROW == parseType) {
178 | cellValue = HSSFHelper.getCellValue(sheet, field.getType(), currentLocation, position, zeroIfNull, errorHandler);
179 | cellValueString = HSSFHelper.getCellValue(sheet, String.class, currentLocation, position, zeroIfNull, errorHandler);
180 | } else {
181 | cellValue = HSSFHelper.getCellValue(sheet, field.getType(), position, currentLocation, zeroIfNull, errorHandler);
182 | cellValueString = HSSFHelper.getCellValue(sheet, String.class, position, currentLocation, zeroIfNull, errorHandler);
183 | }
184 | validateAnnotation(field, cellValueString, position, currentLocation);
185 | setFieldValue(field, object, cellValue);
186 | }
187 |
188 | return object;
189 | }
190 |
191 | private T getNewInstance(Iterator rowIterator, String sheetName, Class clazz, ParseType parseType, Integer currentLocation, boolean zeroIfNull, Consumer errorHandler) {
192 | T object = getInstance(clazz, errorHandler);
193 | Map excelPositionMap = getSortedExcelFieldPositionMap(clazz);
194 | Row row = null;
195 | for (Integer position : excelPositionMap.keySet()) {
196 | Field field = excelPositionMap.get(position);
197 | Object cellValue;
198 | Object cellValueString;
199 |
200 | if (ParseType.ROW == parseType) {
201 | if (null == row || row.getRowNum() + 1 != currentLocation) {
202 | row = getRow(rowIterator, currentLocation);
203 | }
204 | cellValue = HSSFHelper.getCellValue(row, sheetName, field.getType(), currentLocation, position, zeroIfNull, errorHandler);
205 | cellValueString = HSSFHelper.getCellValue(row, sheetName, String.class, currentLocation, position, zeroIfNull, errorHandler);
206 | } else {
207 | if (null == row || row.getRowNum() + 1 != position) {
208 | row = getRow(rowIterator, position);
209 | }
210 | cellValue = HSSFHelper.getCellValue(row, sheetName, field.getType(), position, currentLocation, zeroIfNull, errorHandler);
211 | cellValueString = HSSFHelper.getCellValue(row, sheetName, String.class, position, currentLocation, zeroIfNull, errorHandler);
212 | }
213 | validateAnnotation(field, cellValueString, position, currentLocation);
214 | setFieldValue(field, object, cellValue);
215 | }
216 |
217 | return object;
218 | }
219 |
220 | private void validateAnnotation(Field field, Object cellValueString, int position, int currentLocation) {
221 | ExcelField annotation = field.getAnnotation(ExcelField.class);
222 | if (annotation.validate()) {
223 | Pattern pattern = Pattern.compile(annotation.regex());
224 | cellValueString = cellValueString != null ? cellValueString.toString() : "";
225 | Matcher matcher = pattern.matcher((String) cellValueString);
226 | if (!matcher.matches()) {
227 | ExcelInvalidCell excelInvalidCell = new ExcelInvalidCell(position, currentLocation, (String) cellValueString);
228 | excelInvalidCells.add(excelInvalidCell);
229 | if (annotation.validationType() == ExcelField.ValidationType.HARD) {
230 | throw new ExcelInvalidCellValuesException("Invalid cell value at [" + currentLocation + ", " + position + "] in the sheet. This exception can be suppressed by setting 'validationType' in @ExcelField to 'ValidationType.SOFT");
231 | }
232 | }
233 | }
234 | }
235 |
236 | private T getInstance(Class clazz, Consumer errorHandler) {
237 | T object;
238 | try {
239 | Constructor constructor = clazz.getDeclaredConstructor();
240 | constructor.setAccessible(true);
241 | object = constructor.newInstance();
242 | } catch (Exception e) {
243 | errorHandler.accept(new ExcelParsingException("Exception occurred while instantiating the class " + clazz.getName(), e));
244 | return null;
245 | }
246 | return object;
247 | }
248 |
249 | private void setFieldValue(Field field, T object, Object cellValue) {
250 | try {
251 | field.set(object, cellValue);
252 | } catch (IllegalArgumentException | IllegalAccessException e) {
253 | throw new ExcelParsingException("Exception occurred while setting field value ", e);
254 | }
255 | }
256 |
257 | private Map getExcelFieldPositionMap(Class clazz) {
258 | Map fieldMap = new HashMap<>();
259 | return fillMap(clazz, fieldMap);
260 | }
261 |
262 | private Map getSortedExcelFieldPositionMap(Class clazz) {
263 | Map fieldMap = new TreeMap<>();
264 | return fillMap(clazz, fieldMap);
265 | }
266 |
267 | private Map fillMap(Class clazz, Map fieldMap) {
268 | Field[] fields = clazz.getDeclaredFields();
269 | for (Field field : fields) {
270 | ExcelField excelField = field.getAnnotation(ExcelField.class);
271 | if (excelField != null) {
272 | field.setAccessible(true);
273 | fieldMap.put(excelField.position(), field);
274 | }
275 | }
276 | return fieldMap;
277 | }
278 |
279 | }
280 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/annotations/ExcelField.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(value = RetentionPolicy.RUNTIME)
9 | @Target({ElementType.FIELD})
10 | public @interface ExcelField {
11 | int position();
12 | enum ValidationType
13 | {
14 | SOFT, HARD
15 | }
16 | ValidationType validationType() default ValidationType.SOFT;
17 | boolean validate() default false;
18 | String regex() default ".*";
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/annotations/ExcelObject.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Excel Object annotation should be used at Class level with the below fields
10 | * populated.
11 | *
12 | * @author Venkatesh Nannan.
13 | */
14 | @Retention(value = RetentionPolicy.RUNTIME)
15 | @Target({ElementType.TYPE})
16 | public @interface ExcelObject {
17 |
18 | /**
19 | * Parse type should be set to either Row level parsing or column level
20 | * parsing.
21 | *
22 | * @return ParseType.
23 | */
24 | ParseType parseType();
25 |
26 | /**
27 | * Start of the block. eg. If you want to parse Rows 8 to 408 (400 rows),
28 | * this field should be set as 8.
29 | *
30 | * @return int.
31 | */
32 | int start();
33 |
34 | /**
35 | * End of the block. eg. If you want to parse Rows 8 to 408 (400 rows), this
36 | * field should be set as 408.
37 | *
38 | * @return int.
39 | */
40 | int end() default 0;
41 |
42 | /**
43 | * Setting this field to true will return Zero for Number fields (Double,
44 | * Integer, Long) when the data is not found while parsing.
45 | *
46 | * @return boolean.
47 | */
48 | boolean zeroIfNull() default false;
49 |
50 | /**
51 | * This field should be set to true when you want to ignore the row or
52 | * column having just Zero or NULL data.
53 | *
54 | * @return boolean.
55 | */
56 | boolean ignoreAllZerosOrNullRows() default false;
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/annotations/MappedExcelObject.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(value = RetentionPolicy.RUNTIME)
9 | @Target({ElementType.FIELD})
10 | public @interface MappedExcelObject {
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/annotations/ParseType.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.annotations;
2 |
3 | public enum ParseType {
4 | ROW, COLUMN
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/exception/ExcelInvalidCell.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.exception;
2 |
3 | /**
4 | * Created by Victor.Ikoro on 4/14/2016.
5 | */
6 | public class ExcelInvalidCell {
7 |
8 | private int row;
9 | private int column;
10 | private String message;
11 | private String value;
12 |
13 | public ExcelInvalidCell(int row, int column)
14 | {
15 | this(row, column, null, null);
16 | }
17 | public ExcelInvalidCell(int row, int column, String message)
18 | {
19 | this(row, column, message, null);
20 | }
21 |
22 | public ExcelInvalidCell(int row, int column, String value, String message)
23 | {
24 | this.row = row;
25 | this.column = column;
26 | this.value = value;
27 | this.message = message;
28 | }
29 |
30 |
31 | public int getColumn() {
32 | return column;
33 | }
34 |
35 | public int getRow() {
36 | return row;
37 | }
38 |
39 | public String getMessage() {
40 | return message;
41 | }
42 |
43 | public String getValue() {
44 | return value;
45 | }
46 |
47 | public void setColumn(int column) {
48 | this.column = column;
49 | }
50 |
51 | public void setMessage(String message) {
52 | this.message = message;
53 | }
54 |
55 | public void setRow(int row) {
56 | this.row = row;
57 | }
58 |
59 | public void setValue(String value) {
60 | this.value = value;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/exception/ExcelInvalidCellValuesException.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.exception;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | /**
8 | * Created by Victor.Ikoro on 4/14/2016.
9 | */
10 | public class ExcelInvalidCellValuesException extends ExcelParsingException {
11 | List invalidCells;
12 | public ExcelInvalidCellValuesException(String message) {
13 | super(message);
14 | invalidCells = new ArrayList<>();
15 | }
16 |
17 | public ExcelInvalidCellValuesException(String message, Exception exception) {
18 | super(message, exception);
19 | invalidCells = new ArrayList<>();
20 | }
21 |
22 | public List getInvalidCells() {
23 | return invalidCells;
24 | }
25 |
26 | public void setInvalidCells(List invalidCells) {
27 | this.invalidCells = invalidCells;
28 | }
29 |
30 | public void addInvalidCell(ExcelInvalidCell excelInvalidCell)
31 | {
32 | invalidCells.add(excelInvalidCell);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/exception/ExcelParsingException.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.exception;
2 |
3 | public class ExcelParsingException extends RuntimeException {
4 |
5 | public ExcelParsingException(String message) {
6 | super(message);
7 | }
8 |
9 | public ExcelParsingException(String message, Exception exception) {
10 | super(message, exception);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/helper/HSSFHelper.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.helper;
2 |
3 | import org.javafunk.excelparser.exception.ExcelParsingException;
4 | import org.apache.poi.hssf.usermodel.HSSFCell;
5 | import org.apache.poi.hssf.usermodel.HSSFDateUtil;
6 | import org.apache.poi.ss.usermodel.Cell;
7 | import org.apache.poi.ss.usermodel.DataFormatter;
8 | import org.apache.poi.ss.usermodel.FormulaEvaluator;
9 | import org.apache.poi.ss.usermodel.Row;
10 | import org.apache.poi.ss.usermodel.Sheet;
11 |
12 | import java.math.BigDecimal;
13 | import java.text.DecimalFormat;
14 | import java.time.Instant;
15 | import java.time.LocalDate;
16 | import java.time.LocalDateTime;
17 | import java.time.ZoneId;
18 | import java.util.Date;
19 | import java.util.Iterator;
20 | import java.util.function.Consumer;
21 |
22 | import static java.text.MessageFormat.format;
23 |
24 | public class HSSFHelper {
25 |
26 | private static DataFormatter formatter = new DataFormatter();
27 |
28 | @SuppressWarnings("unchecked")
29 | public static T getCellValue(Sheet sheet, Class type, Integer row, Integer col, boolean zeroIfNull, Consumer errorHandler) {
30 | Cell cell = getCell(sheet, row, col);
31 |
32 | return validateAndParseValue(cell, sheet.getSheetName(), type, row, col, zeroIfNull, errorHandler);
33 | }
34 |
35 |
36 | public static T getCellValue(Row row, String sheetName, Class type, Integer rowIndex, Integer col, boolean zeroIfNull, Consumer errorHandler) {
37 | Cell cell = row.getCell(col - 1);
38 |
39 | return validateAndParseValue(cell, sheetName, type, rowIndex, col, zeroIfNull, errorHandler);
40 | }
41 |
42 | @SuppressWarnings("unchecked")
43 | private static T validateAndParseValue(Cell cell, String sheetName, Class type, Integer row, Integer col, boolean zeroIfNull, Consumer errorHandler) {
44 | if (type.equals(String.class)) {
45 | return (T) getStringCell(cell, errorHandler);
46 | }
47 |
48 | if (type.equals(Date.class)) {
49 | return cell == null ? null : (T) getDateCell(cell, new Locator(sheetName, row, col), errorHandler);
50 | }
51 |
52 | if (type.equals(LocalDate.class)) {
53 | return cell == null ? null : (T) getLocalDateCell(cell, new Locator(sheetName, row, col), errorHandler);
54 | }
55 |
56 | if (type.equals(LocalDateTime.class)) {
57 | return cell == null ? null : (T) getLocalDateTimeCell(cell, new Locator(sheetName, row, col), errorHandler);
58 | }
59 |
60 | if (type.equals(Integer.class)) {
61 | return (T) getIntegerCell(cell, zeroIfNull, new Locator(sheetName, row, col), errorHandler);
62 | }
63 |
64 | if (type.equals(Double.class)) {
65 | return (T) getDoubleCell(cell, zeroIfNull, new Locator(sheetName, row, col), errorHandler);
66 | }
67 |
68 | if (type.equals(Long.class)) {
69 | return (T) getLongCell(cell, zeroIfNull, new Locator(sheetName, row, col), errorHandler);
70 | }
71 |
72 | if (type.equals(BigDecimal.class)) {
73 | return (T) getBigDecimalCell(cell, zeroIfNull, new Locator(sheetName, row, col), errorHandler);
74 | }
75 |
76 | errorHandler.accept(new ExcelParsingException(format("{0} data type not supported for parsing", type.getName())));
77 | return null;
78 | }
79 |
80 | private static LocalDate getLocalDateCell(Cell cell, Locator locator, Consumer errorHandler) {
81 | try {
82 | if (!HSSFDateUtil.isCellDateFormatted(cell)) {
83 | errorHandler.accept(new ExcelParsingException(format("Invalid date found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
84 | }
85 |
86 | Instant instant = Instant.ofEpochMilli(HSSFDateUtil.getJavaDate(cell.getNumericCellValue()).getTime());
87 | LocalDateTime res = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
88 | return res.toLocalDate();
89 |
90 | } catch (IllegalStateException illegalStateException) {
91 | errorHandler.accept(new ExcelParsingException(format("Invalid date found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
92 | }
93 | return null;
94 | }
95 |
96 | private static LocalDateTime getLocalDateTimeCell(Cell cell, Locator locator, Consumer errorHandler) {
97 | try {
98 | if (!HSSFDateUtil.isCellDateFormatted(cell)) {
99 | errorHandler.accept(new ExcelParsingException(format("Invalid date found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
100 | }
101 |
102 | Instant instant = Instant.ofEpochMilli(HSSFDateUtil.getJavaDate(cell.getNumericCellValue()).getTime());
103 | return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
104 |
105 | } catch (IllegalStateException illegalStateException) {
106 | errorHandler.accept(new ExcelParsingException(format("Invalid date found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
107 | }
108 | return null;
109 | }
110 |
111 | private static BigDecimal getBigDecimalCell(Cell cell, boolean zeroIfNull, Locator locator, Consumer errorHandler) {
112 | String val = getStringCell(cell, errorHandler);
113 | if(val == null || val.trim().equals("")) {
114 | if(zeroIfNull) {
115 | return BigDecimal.ZERO;
116 | }
117 | return null;
118 | }
119 | try {
120 | return new BigDecimal(val);
121 | } catch (NumberFormatException e) {
122 | errorHandler.accept(new ExcelParsingException(format("Invalid number found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
123 | }
124 |
125 | if (zeroIfNull) {
126 | return BigDecimal.ZERO;
127 | }
128 | return null;
129 | }
130 |
131 | static Cell getCell(Sheet sheet, int rowNumber, int columnNumber) {
132 | Row row = sheet.getRow(rowNumber - 1);
133 | return row == null ? null : row.getCell(columnNumber - 1);
134 | }
135 |
136 | public static Row getRow(Iterator iterator, int rowNumber) {
137 | Row row;
138 | while (iterator.hasNext()) {
139 | row = iterator.next();
140 | if (row.getRowNum() == rowNumber - 1) {
141 | return row;
142 | }
143 | }
144 | throw new RuntimeException("No Row with index: " + rowNumber + " was found");
145 | }
146 |
147 | static String getStringCell(Cell cell, Consumer errorHandler) {
148 | if (cell == null) {
149 | return null;
150 | }
151 |
152 | if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
153 | int type = cell.getCachedFormulaResultType();
154 |
155 | if (type == HSSFCell.CELL_TYPE_NUMERIC) {
156 | FormulaEvaluator fe = cell.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator();
157 | return formatter.formatCellValue(cell, fe);
158 | }
159 |
160 | if (type == HSSFCell.CELL_TYPE_ERROR) {
161 | return "";
162 | }
163 |
164 | if (type == HSSFCell.CELL_TYPE_STRING) {
165 | return cell.getRichStringCellValue().getString().trim();
166 | }
167 |
168 | if (type == HSSFCell.CELL_TYPE_BOOLEAN) {
169 | return "" + cell.getBooleanCellValue();
170 | }
171 |
172 | } else if (cell.getCellType() != HSSFCell.CELL_TYPE_NUMERIC) {
173 | return cell.getRichStringCellValue().getString().trim();
174 | }
175 |
176 | return formatter.formatCellValue(cell);
177 | }
178 |
179 | static Date getDateCell(Cell cell, Locator locator, Consumer errorHandler) {
180 | try {
181 | if (!HSSFDateUtil.isCellDateFormatted(cell)) {
182 | errorHandler.accept(new ExcelParsingException(format("Invalid date found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
183 | }
184 | return HSSFDateUtil.getJavaDate(cell.getNumericCellValue());
185 | } catch (IllegalStateException illegalStateException) {
186 | errorHandler.accept(new ExcelParsingException(format("Invalid date found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
187 | }
188 | return null;
189 | }
190 |
191 | static Double getDoubleCell(Cell cell, boolean zeroIfNull, Locator locator, Consumer errorHandler) {
192 | if (cell == null) {
193 | return zeroIfNull ? 0d : null;
194 | }
195 |
196 | if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC || cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
197 | return cell.getNumericCellValue();
198 | }
199 |
200 | if (cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
201 | return zeroIfNull ? 0d : null;
202 | }
203 |
204 | errorHandler.accept(new ExcelParsingException(format("Invalid number found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
205 | return null;
206 | }
207 |
208 | static Long getLongCell(Cell cell, boolean zeroIfNull, Locator locator, Consumer errorHandler) {
209 | Double doubleValue = getNumberWithoutDecimals(cell, zeroIfNull, locator, errorHandler);
210 | return doubleValue == null ? null : doubleValue.longValue();
211 | }
212 |
213 | static Integer getIntegerCell(Cell cell, boolean zeroIfNull, Locator locator, Consumer errorHandler) {
214 | Double doubleValue = getNumberWithoutDecimals(cell, zeroIfNull, locator, errorHandler);
215 | return doubleValue == null ? null : doubleValue.intValue();
216 | }
217 |
218 | private static Double getNumberWithoutDecimals(Cell cell, boolean zeroIfNull, Locator locator, Consumer errorHandler) {
219 | Double doubleValue = getDoubleCell(cell, zeroIfNull, locator, errorHandler);
220 | if (doubleValue != null && doubleValue % 1 != 0) {
221 | errorHandler.accept(new ExcelParsingException(format("Invalid number found in sheet {0} at row {1}, column {2}", locator.getSheetName(), locator.getRow(), locator.getCol())));
222 | }
223 | return doubleValue;
224 | }
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/org/javafunk/excelparser/helper/Locator.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.helper;
2 |
3 | import lombok.Value;
4 |
5 | @Value
6 | public class Locator {
7 | String sheetName;
8 | int row;
9 | int col;
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/org/javafunk/example/domain/Section.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.example.domain;
2 |
3 | import org.javafunk.excelparser.annotations.ExcelField;
4 | import org.javafunk.excelparser.annotations.ExcelObject;
5 | import org.javafunk.excelparser.annotations.MappedExcelObject;
6 | import org.javafunk.excelparser.annotations.ParseType;
7 | import lombok.AccessLevel;
8 | import lombok.AllArgsConstructor;
9 | import lombok.Value;
10 | import lombok.experimental.FieldDefaults;
11 |
12 | import java.util.List;
13 |
14 | @Value
15 | @AllArgsConstructor
16 | @FieldDefaults(level = AccessLevel.PRIVATE)
17 | @ExcelObject(parseType = ParseType.COLUMN, start = 2, end = 2)
18 | public class Section {
19 |
20 | @ExcelField(position = 2)
21 | String year;
22 |
23 | @ExcelField(position = 3)
24 | String section;
25 |
26 | @MappedExcelObject
27 | List students;
28 |
29 | @SuppressWarnings("UnusedDeclaration")
30 | private Section() {
31 | this(null, null, null);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/org/javafunk/example/domain/Student.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.example.domain;
2 |
3 | import org.javafunk.excelparser.annotations.ExcelField;
4 | import org.javafunk.excelparser.annotations.ExcelObject;
5 | import org.javafunk.excelparser.annotations.ParseType;
6 | import lombok.AccessLevel;
7 | import lombok.AllArgsConstructor;
8 | import lombok.Value;
9 | import lombok.experimental.FieldDefaults;
10 |
11 | import java.math.BigDecimal;
12 | import java.time.LocalDate;
13 | import java.time.LocalDateTime;
14 | import java.util.Date;
15 |
16 | @Value
17 | @AllArgsConstructor
18 | @FieldDefaults(level = AccessLevel.PRIVATE)
19 | @ExcelObject(parseType = ParseType.ROW, start = 6, end = 8)
20 | public class Student {
21 |
22 | @ExcelField(position = 2, validationType = ExcelField.ValidationType.HARD, regex = "[0-2][0-9][0-9][0-9]")
23 | Long roleNumber;
24 |
25 | @ExcelField(position = 3)
26 | String name;
27 |
28 | @ExcelField(position = 4)
29 | Date dateOfBirth;
30 |
31 | @ExcelField(position = 5)
32 | String fatherName;
33 |
34 | @ExcelField(position = 6)
35 | String motherName;
36 |
37 | @ExcelField(position = 7)
38 | String address;
39 |
40 | @ExcelField(position = 8)
41 | BigDecimal totalScore;
42 |
43 | @ExcelField(position = 9)
44 | LocalDate admissionDate;
45 |
46 | @ExcelField(position = 10)
47 | LocalDateTime admissionDateTime;
48 |
49 | @SuppressWarnings("UnusedDeclaration")
50 | private Student() {
51 | this(null, null, null, null, null, null, null, null, null);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/java/org/javafunk/example/domain/Subject.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.example.domain;
2 |
3 | import org.javafunk.excelparser.annotations.ExcelField;
4 | import org.javafunk.excelparser.annotations.ExcelObject;
5 | import org.javafunk.excelparser.annotations.ParseType;
6 |
7 | import lombok.AccessLevel;
8 | import lombok.AllArgsConstructor;
9 | import lombok.Value;
10 | import lombok.experimental.FieldDefaults;
11 |
12 | @Value
13 | @AllArgsConstructor
14 | @FieldDefaults(level = AccessLevel.PRIVATE)
15 | @ExcelObject(parseType = ParseType.ROW, start = 2)
16 | public class Subject {
17 |
18 | @ExcelField(position = 1)
19 | String code;
20 |
21 | @ExcelField(position = 2)
22 | String name;
23 |
24 | @ExcelField(position = 3)
25 | Integer volume;
26 |
27 | @SuppressWarnings("UnusedDeclaration")
28 | private Subject() {
29 | this(null, null, null);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/org/javafunk/excelparser/SheetParserTest.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser;
2 |
3 | import org.javafunk.example.domain.Section;
4 | import org.javafunk.example.domain.Student;
5 | import org.javafunk.example.domain.Subject;
6 | import org.javafunk.excelparser.exception.ExcelParsingException;
7 |
8 | import org.apache.poi.hssf.usermodel.HSSFWorkbook;
9 | import org.apache.poi.ss.usermodel.Sheet;
10 | import org.apache.poi.ss.usermodel.Workbook;
11 | import org.apache.poi.xssf.usermodel.XSSFWorkbook;
12 | import org.junit.After;
13 | import org.junit.Test;
14 |
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.math.BigDecimal;
18 | import java.text.SimpleDateFormat;
19 | import java.time.LocalDate;
20 | import java.time.LocalDateTime;
21 | import java.util.ArrayList;
22 | import java.util.Date;
23 | import java.util.List;
24 |
25 | import static java.math.BigDecimal.ROUND_FLOOR;
26 | import static org.hamcrest.CoreMatchers.is;
27 | import static org.hamcrest.CoreMatchers.nullValue;
28 | import static org.junit.Assert.assertEquals;
29 | import static org.junit.Assert.assertThat;
30 |
31 | public class SheetParserTest {
32 |
33 | InputStream inputStream;
34 |
35 | @After
36 | public void tearDown() throws IOException {
37 | inputStream.close();
38 | }
39 |
40 | @Test
41 | public void shouldCreateEntityBasedOnAnnotationFromExcel97File() throws Exception {
42 | performTestUsing(openSheet("Student Profile.xls"));
43 | }
44 |
45 | @Test
46 | public void shouldCreateEntityBasedOnAnnotationFromExcel2007File() throws Exception {
47 | performTestUsing(openSheet("Student Profile.xlsx"));
48 | }
49 |
50 | @Test
51 | public void shouldCallErrorHandlerWhenRowCannotBeParsed() throws Exception {
52 | List errors = new ArrayList<>();
53 | SheetParser parser = new SheetParser();
54 |
55 | List entityList = parser.createEntity(openSheet("Errors.xlsx"), Section.class, errors::add);
56 |
57 | assertThat(entityList.size(), is(1));
58 | Section section = entityList.get(0);
59 | assertThat(section.getStudents().get(0).getDateOfBirth(), is(nullValue(Date.class)));
60 |
61 | assertThat(errors.size(), is(3));
62 | assertThat(errors.get(0).getMessage(), is("Invalid date found in sheet Sheet1 at row 6, column 4"));
63 | assertThat(errors.get(1).getMessage(), is("Invalid date found in sheet Sheet1 at row 7, column 4"));
64 | assertThat(errors.get(2).getMessage(), is("Invalid date found in sheet Sheet1 at row 8, column 4"));
65 | }
66 |
67 | private void performTestUsing(Sheet sheet) {
68 | SheetParser parser = new SheetParser();
69 | List entityList = parser.createEntity(sheet, Section.class, error -> { throw error; });
70 | assertThat(entityList.size(), is(1));
71 | Section section = entityList.get(0);
72 | assertThat(section.getYear(), is("IV"));
73 | assertThat("B", section.getSection(), is("B"));
74 | assertThat(section.getStudents().size(), is(3));
75 |
76 | assertThat(section.getStudents().get(0).getRoleNumber(), is(2001L));
77 | assertThat(section.getStudents().get(0).getName(), is("Adam"));
78 |
79 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
80 |
81 | assertThat(simpleDateFormat.format(section.getStudents().get(0).getDateOfBirth()), is("01/01/2002"));
82 | assertThat(section.getStudents().get(0).getFatherName(), is("A"));
83 | assertThat("D", section.getStudents().get(0).getMotherName(), is("D"));
84 | assertThat("XYZ", section.getStudents().get(0).getAddress(), is("XYZ"));
85 | assertThat(section.getStudents().get(0).getTotalScore(), is(nullValue(BigDecimal.class)));
86 | assertThat(section.getStudents().get(0).getAdmissionDate(), is(LocalDate.of(2002, 10, 10)));
87 | assertThat(section.getStudents().get(0).getAdmissionDateTime(), is(LocalDateTime.of(2002, 10, 10, 9, 0, 0)));
88 |
89 | assertThat(section.getStudents().get(1).getRoleNumber(), is(2002L));
90 | assertThat(section.getStudents().get(1).getName(), is("Even"));
91 | assertThat(simpleDateFormat.format(section.getStudents().get(1).getDateOfBirth()), is("05/01/2002"));
92 | assertThat(section.getStudents().get(1).getFatherName(), is("B"));
93 | assertThat("D", section.getStudents().get(1).getMotherName(), is("E"));
94 | assertThat("XYZ", section.getStudents().get(1).getAddress(), is("ABX"));
95 | assertThat(section.getStudents().get(1).getTotalScore().setScale(2, ROUND_FLOOR), is(new BigDecimal("450.35")));
96 | assertThat(section.getStudents().get(1).getAdmissionDate(), is(LocalDate.of(2002, 10, 11)));
97 | assertThat(section.getStudents().get(1).getAdmissionDateTime(), is(LocalDateTime.of(2002, 10, 11, 10, 0, 0)));
98 | }
99 |
100 | @Test
101 | public void shouldParseRowOrCoulmnEnd() throws IOException {
102 | Sheet sheet = openSheet("Student Profile.xlsx");
103 | SheetParser parser = new SheetParser();
104 |
105 | int end = parser.getRowOrColumnEnd(sheet, Section.class);
106 | assertEquals(end, 2);
107 |
108 | int rowEnd = parser.getRowOrColumnEnd(sheet, Student.class);
109 | assertEquals(rowEnd, 8);
110 | }
111 |
112 | @Test
113 | public void shouldCalculateEnd() throws IOException {
114 | Sheet sheet = openSheet("Subjects.xlsx");
115 | SheetParser parser = new SheetParser();
116 |
117 | List subjects = parser.createEntity(sheet, Subject.class, error -> { throw error; });
118 | Subject lastSubject = subjects.get(subjects.size() - 1);
119 |
120 | assertThat(lastSubject.getCode(), is("GE-101"));
121 | assertThat(lastSubject.getName(), is("Geography"));
122 | assertThat(lastSubject.getVolume(), is(7));
123 | }
124 |
125 | private Sheet openSheet(String fileName) throws IOException {
126 | inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
127 | Workbook workbook;
128 | if(fileName.endsWith("xls")) {
129 | workbook = new HSSFWorkbook(inputStream);
130 | } else {
131 | workbook = new XSSFWorkbook(inputStream);
132 | }
133 | return workbook.getSheet("Sheet1");
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/test/java/org/javafunk/excelparser/helper/HSSFHelperTest.java:
--------------------------------------------------------------------------------
1 | package org.javafunk.excelparser.helper;
2 |
3 | import org.javafunk.excelparser.exception.ExcelParsingException;
4 | import org.apache.poi.hssf.usermodel.HSSFWorkbook;
5 | import org.apache.poi.ss.usermodel.Cell;
6 | import org.apache.poi.ss.usermodel.Sheet;
7 | import org.junit.After;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 |
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.util.Date;
14 | import java.util.function.Consumer;
15 |
16 | import static org.hamcrest.CoreMatchers.is;
17 | import static org.hamcrest.CoreMatchers.not;
18 | import static org.hamcrest.core.IsNull.nullValue;
19 | import static org.junit.Assert.assertThat;
20 |
21 | public class HSSFHelperTest {
22 | Sheet sheet;
23 | String sheetName = "Sheet1";
24 | InputStream inputStream;
25 |
26 | @Before
27 | public void setUp() throws IOException {
28 | inputStream = getClass().getClassLoader().getResourceAsStream("Student Profile.xls");
29 | sheet = new HSSFWorkbook(inputStream).getSheet(sheetName);
30 | }
31 |
32 | @After
33 | public void tearDown() throws IOException {
34 | inputStream.close();
35 | }
36 |
37 | @Test(expected = ExcelParsingException.class)
38 | public void testShouldThrowExceptionOnInvalidDateCell() {
39 | int rowNumber = 2;
40 | int columnNumber = 2;
41 | Cell cell = HSSFHelper.getCell(sheet, rowNumber, columnNumber);
42 | HSSFHelper.getDateCell(cell, new Locator(sheetName, rowNumber, columnNumber), e -> {
43 | throw e;
44 | });
45 | }
46 |
47 | @Test
48 | public void testShouldReturnValidDate() {
49 | int rowNumber = 6;
50 | int columnNumber = 4;
51 | Cell cell = HSSFHelper.getCell(sheet, rowNumber, columnNumber);
52 | Date actual = HSSFHelper.getDateCell(cell, new Locator(sheetName, rowNumber, columnNumber), e -> {
53 | throw e;
54 | });
55 |
56 | assertThat(actual, is(not(nullValue(Date.class))));
57 | }
58 |
59 | @Test
60 | public void testShouldReturnValidStringValue() {
61 | Consumer errorHandler = e -> {
62 | throw e;
63 | };
64 | assertThat(HSSFHelper.getStringCell(HSSFHelper.getCell(sheet, 6, 1), errorHandler), is("1"));
65 | assertThat(HSSFHelper.getStringCell(HSSFHelper.getCell(sheet, 6, 5), errorHandler), is("A"));
66 | assertThat(HSSFHelper.getStringCell(HSSFHelper.getCell(sheet, 8, 3), errorHandler), is("James"));
67 | }
68 |
69 | @Test
70 | public void testShouldReturnValidNumericValue() {
71 | assertThat(HSSFHelper.getIntegerCell(HSSFHelper.getCell(sheet, 6, 1), false, new Locator(sheetName, 6, 1), e -> { throw e; }), is(1));
72 | assertThat(HSSFHelper.getIntegerCell(HSSFHelper.getCell(sheet, 6, 8), true, new Locator(sheetName, 6, 8),e -> { throw e; }), is(0));
73 | assertThat(HSSFHelper.getIntegerCell(HSSFHelper.getCell(sheet, 6, 8), false, new Locator(sheetName, 6, 8), e -> { throw e; }), is(nullValue()));
74 |
75 | assertThat(HSSFHelper.getLongCell(HSSFHelper.getCell(sheet, 6, 2), false, new Locator(sheetName, 6, 2), e -> { throw e; }), is(2001L));
76 | assertThat(HSSFHelper.getLongCell(HSSFHelper.getCell(sheet, 10, 2), true, new Locator(sheetName, 10, 2), e -> { throw e; }), is(0L));
77 | assertThat(HSSFHelper.getLongCell(HSSFHelper.getCell(sheet, 10, 2), false, new Locator(sheetName, 10, 2), e -> { throw e; }), is(nullValue(Long.class)));
78 |
79 | assertThat(HSSFHelper.getDoubleCell(HSSFHelper.getCell(sheet, 7, 8), false, new Locator(sheetName, 7, 8), e -> { throw e; }), is(450.35d));
80 | assertThat(HSSFHelper.getDoubleCell(HSSFHelper.getCell(sheet, 8, 8), false, new Locator(sheetName, 8, 8), e -> { throw e; }), is(300d));
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/test/resources/Errors.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvenky/excel-parser/578d379dc33dd0b3ee1558d16fd6315b09c40b5c/src/test/resources/Errors.xlsx
--------------------------------------------------------------------------------
/src/test/resources/Student Profile.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvenky/excel-parser/578d379dc33dd0b3ee1558d16fd6315b09c40b5c/src/test/resources/Student Profile.xls
--------------------------------------------------------------------------------
/src/test/resources/Student Profile.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvenky/excel-parser/578d379dc33dd0b3ee1558d16fd6315b09c40b5c/src/test/resources/Student Profile.xlsx
--------------------------------------------------------------------------------
/src/test/resources/Subjects.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvenky/excel-parser/578d379dc33dd0b3ee1558d16fd6315b09c40b5c/src/test/resources/Subjects.xlsx
--------------------------------------------------------------------------------