├── .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 | [![Build Status](https://travis-ci.org/nvenky/excel-parser.svg)](https://travis-ci.org/nvenky/excel-parser) 2 | 3 | 4 | # Excel Parser Examples 5 | 6 | [![Join the chat at https://gitter.im/nvenky/excel-parser](https://badges.gitter.im/Join%20Chat.svg)](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 | ![Student Information](http://3.bp.blogspot.com/_OAeb_UFifRg/S2WiIfweGiI/AAAAAAAACCA/Sv36FYxed1E/s640/Screenshot.png) 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 --------------------------------------------------------------------------------