From ab6b12b3912873de1a5a4d5e26ff022d87b74b2e Mon Sep 17 00:00:00 2001 From: Piotr Gawron <piotr.gawron@uni.lu> Date: Tue, 21 Feb 2017 16:14:15 +0100 Subject: [PATCH] simple java parser for agenda (for import purpose) --- appointment-import/.classpath | 36 ++++ appointment-import/.gitignore | 1 + appointment-import/.project | 23 +++ .../org.eclipse.core.resources.prefs | 6 + .../.settings/org.eclipse.jdt.core.prefs | 5 + .../.settings/org.eclipse.m2e.core.prefs | 4 + appointment-import/pom.xml | 67 ++++++++ .../appointment/parse/AppointmentEntry.java | 99 +++++++++++ .../appointment/parse/AppointmentType.java | 34 ++++ .../smash/appointment/parse/CellParser.java | 158 ++++++++++++++++++ .../java/smash/appointment/parse/Main.java | 27 +++ .../appointment/parse/NameSurnameIndexer.java | 9 + .../appointment/parse/NdNumberIndexer.java | 9 + .../java/smash/appointment/parse/Subject.java | 89 ++++++++++ .../smash/appointment/parse/SubjectDao.java | 43 +++++ .../appointment/parse/SubjectIndexer.java | 22 +++ .../appointment/parse/SurnameIndexer.java | 9 + .../appointment/parse/SurnameNameIndexer.java | 9 + .../java/smash/appointment/parse/Utils.java | 7 + .../parse/XlsxCalendarProcessor.java | 142 ++++++++++++++++ .../src/main/resources/log4j.properties | 8 + .../smash/appointment/parse/AllTests.java | 14 ++ .../appointment/parse/CellParseTestCase.java | 15 ++ .../appointment/parse/CellParserTest.java | 77 +++++++++ .../appointment/parse/SubjectDaoTest.java | 32 ++++ .../smash/appointment/parse/TestBase.java | 26 +++ .../parse/XlsxCalendarProcessorTest.java | 42 +++++ .../src/test/resources/log4j.properties | 8 + .../testFiles/calendarExample.xlsx | Bin 0 -> 30585 bytes appointment-import/testFiles/subjects.txt | 2 + 30 files changed, 1023 insertions(+) create mode 100644 appointment-import/.classpath create mode 100644 appointment-import/.gitignore create mode 100644 appointment-import/.project create mode 100644 appointment-import/.settings/org.eclipse.core.resources.prefs create mode 100644 appointment-import/.settings/org.eclipse.jdt.core.prefs create mode 100644 appointment-import/.settings/org.eclipse.m2e.core.prefs create mode 100644 appointment-import/pom.xml create mode 100644 appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/CellParser.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/Main.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/Subject.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/Utils.java create mode 100644 appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java create mode 100644 appointment-import/src/main/resources/log4j.properties create mode 100644 appointment-import/src/test/java/smash/appointment/parse/AllTests.java create mode 100644 appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java create mode 100644 appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java create mode 100644 appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java create mode 100644 appointment-import/src/test/java/smash/appointment/parse/TestBase.java create mode 100644 appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java create mode 100644 appointment-import/src/test/resources/log4j.properties create mode 100644 appointment-import/testFiles/calendarExample.xlsx create mode 100644 appointment-import/testFiles/subjects.txt diff --git a/appointment-import/.classpath b/appointment-import/.classpath new file mode 100644 index 00000000..f7b62f18 --- /dev/null +++ b/appointment-import/.classpath @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" output="target/classes" path="src/main/java"> + <attributes> + <attribute name="optional" value="true"/> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="src" output="target/test-classes" path="src/test/java"> + <attributes> + <attribute name="optional" value="true"/> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="output" path="target/classes"/> +</classpath> diff --git a/appointment-import/.gitignore b/appointment-import/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/appointment-import/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/appointment-import/.project b/appointment-import/.project new file mode 100644 index 00000000..eee5e1c7 --- /dev/null +++ b/appointment-import/.project @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>appointment-import</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.m2e.core.maven2Builder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>org.eclipse.m2e.core.maven2Nature</nature> + </natures> +</projectDescription> diff --git a/appointment-import/.settings/org.eclipse.core.resources.prefs b/appointment-import/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..04cfa2c1 --- /dev/null +++ b/appointment-import/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/<project>=UTF-8 diff --git a/appointment-import/.settings/org.eclipse.jdt.core.prefs b/appointment-import/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..bbcbc934 --- /dev/null +++ b/appointment-import/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/appointment-import/.settings/org.eclipse.m2e.core.prefs b/appointment-import/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..14b697b7 --- /dev/null +++ b/appointment-import/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/appointment-import/pom.xml b/appointment-import/pom.xml new file mode 100644 index 00000000..d1022957 --- /dev/null +++ b/appointment-import/pom.xml @@ -0,0 +1,67 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>smash</groupId> + <artifactId>appointment-import</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>jar</packaging> + + <name>appointment-import</name> + <url>http://maven.apache.org</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> +<dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.17</version> +</dependency> +<dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-ooxml</artifactId> + <version>3.15</version> +</dependency> + </dependencies> + + <build> + <plugins> +<plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.5.1</version> + <configuration> + <source>1.7</source> + <target>1.7</target> + </configuration> +</plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <source>1.7</source> + <target>1.7</target> + <archive> + <manifest> + <addClasspath>true</addClasspath> + <mainClass>smash.appointment.parse.Main</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java new file mode 100644 index 00000000..8db01548 --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentEntry.java @@ -0,0 +1,99 @@ +package smash.appointment.parse; + +public class AppointmentEntry { + private String day; + private String time; + private Subject subject; + private AppointmentType type; + private String source; + + /** + * @return the time + * @see #time + */ + public String getTime() { + return time; + } + + /** + * @param time + * the time to set + * @see #time + */ + public void setTime(String time) { + this.time = time; + } + + /** + * @return the subject + * @see #subject + */ + public Subject getSubject() { + return subject; + } + + /** + * @param subject + * the subject to set + * @see #subject + */ + public void setSubject(Subject subject) { + this.subject = subject; + } + + /** + * @return the type + * @see #type + */ + public AppointmentType getType() { + return type; + } + + /** + * @param type + * the type to set + * @see #type + */ + public void setType(AppointmentType type) { + this.type = type; + } + + /** + * @return the day + * @see #day + */ + public String getDay() { + return day; + } + + /** + * @param day + * the day to set + * @see #day + */ + public void setDay(String day) { + this.day = day; + } + + /** + * @return the source + * @see #source + */ + public String getSource() { + return source; + } + + /** + * @param source + * the source to set + * @see #source + */ + public void setSource(String source) { + this.source = source; + } + + @Override + public String toString() { + return day + " " + time + " " + subject + " " + type + "\t\t[source: " + source + "]"; + } +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java b/appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java new file mode 100644 index 00000000..bb60199f --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/AppointmentType.java @@ -0,0 +1,34 @@ +package smash.appointment.parse; + +public enum AppointmentType { + + //most complex should be first + LEVEL_BV_BG_SB(new String[] { "evel BV + BG + SB","BV + BG + SB" }), // + LEVEL_BV_SB(new String[] { "evel BV + SB","BV + SB" }), // + LEVEL_BV_BG(new String[] { "evel BV + BG","BV + BG" }), // + LEVEL_BG_SB(new String[] { "evel BG + SB","BG + SB" }), // + LEVEL_BV(new String[] { "evel BV", "BV" }), // + LEVEL_BG(new String[] { "evel BG","BG" }), // + LEVEL_SB(new String[] { "evel SB", "SB" }), // + + LEVEL_A(new String[] { "level A" }), // + OTHER(new String[] {}), // + LEVEL_B(new String[] { "evel B" }), // + LEVEL_B_M_POWER(new String[] { "mPower" }), // + ; + + private String[] queryStrings; + + private AppointmentType(String[] queryStrings) { + this.queryStrings = queryStrings; + + } + + /** + * @return the queryStrings + * @see #queryStrings + */ + public String[] getQueryStrings() { + return queryStrings; + } +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/CellParser.java b/appointment-import/src/main/java/smash/appointment/parse/CellParser.java new file mode 100644 index 00000000..efc2e388 --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/CellParser.java @@ -0,0 +1,158 @@ +package smash.appointment.parse; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; + +public class CellParser { + Logger logger = Logger.getLogger(CellParser.class); + + private SubjectDao subjectDao; + + Pattern timePattern = Pattern.compile("^[0-9][0-9]\\:[0-9][0-9]"); + + public String extractTime(String content) { + String result = null; + Matcher matcher = timePattern.matcher(content); + if (matcher.find()) { + result = matcher.group(); + } + return result; + } + + public String removeTime(String content) { + Matcher matcher = timePattern.matcher(content); + if (matcher.find()) { + content = matcher.replaceFirst("").trim(); + } + return content; + } + + public AppointmentEntry parseAppointment(String query, String defaultTime) { + AppointmentEntry result = new AppointmentEntry(); + + String time = extractTime(query); + if (time != null) { + query = removeTime(query); + } else { + time = defaultTime; + } + result.setTime(time); + + Subject subject = extractSubject(query); + result.setSubject(subject); + + AppointmentType type = extractType(query); + if (type == null) { + type = AppointmentType.OTHER; + } + result.setType(type); + + result.setSource(query); + return result; + } + + private AppointmentType extractType(String query) { + String simplifiedQuery = Utils.simplifyString(query); + + AppointmentType result = null; + + String usedString = null; + for (AppointmentType type : AppointmentType.values()) { + boolean matchFound = false; + for (String string : type.getQueryStrings()) { + if (!matchFound) { + String simplifiedString = Utils.simplifyString(string); + + if (simplifiedQuery.contains(simplifiedString)) { + matchFound = true; + if (result == null) { + result = type; + usedString = string; + } else { + if (string.contains(usedString)) { + result = type; + usedString = string; + } else if (usedString.contains(string)) { + //new one is a substring of old + } else { //if there is no substring then we might have a problem + AppointmentType newType = result; + if (usedString.length() < string.length()) { + result = type; + usedString = string; + } + logger.warn("More than one type possible for query: " + query + ". Type 1: " + result + ". Type 2: " + type + ". Choosing: " + newType); + } + } + + } + } + } + } + return result; + } + + private Subject extractSubject(String query) { + Subject result = null; + String simplifiedQuery = Utils.simplifyString(query); + + SubjectIndexer[] mainIndices = new SubjectIndexer[] { // + new NameSurnameIndexer(), // + new SurnameNameIndexer(), // + new NdNumberIndexer(),// + }; + + result = getByIndices(query, simplifiedQuery, mainIndices); + if (result == null) { + SubjectIndexer[] secondaryIndices = new SubjectIndexer[] { // + new SurnameIndexer(), // + }; + result = getByIndices(query, simplifiedQuery, secondaryIndices); + } + return result; + } + + private Subject getByIndices(String query, String simplifiedQuery, SubjectIndexer[] mainIndices) { + Subject result = null; + for (Subject subject : subjectDao.getSubjects()) { + boolean matchFound = false; + for (SubjectIndexer indexer : mainIndices) { + if (!matchFound) { + if (indexer.match(subject, simplifiedQuery)) { + matchFound = true; + if (result == null) { + result = subject; + } else { + Subject newResult = result; + if (indexer.isBetter(subject, result)) { + newResult = subject; + } + logger.warn( + "More than one subject possible for query: " + query + ". Subject 1: " + result + ". Subject 2: " + subject + ". Choosing: " + newResult); + result = newResult; + } + } + } + } + } + return result; + } + + /** + * @return the subjectDao + * @see #subjectDao + */ + public SubjectDao getSubjectDao() { + return subjectDao; + } + + /** + * @param subjectDao + * the subjectDao to set + * @see #subjectDao + */ + public void setSubjectDao(SubjectDao subjectDao) { + this.subjectDao = subjectDao; + } +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/Main.java b/appointment-import/src/main/java/smash/appointment/parse/Main.java new file mode 100644 index 00000000..051de5b0 --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/Main.java @@ -0,0 +1,27 @@ +package smash.appointment.parse; + +import java.util.List; + +import org.apache.log4j.Logger; + +public class Main { + private static Logger logger = Logger.getLogger(Main.class); + + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.out.println("Usage: command <agenda.xlsx> <subjects.txt>"); + } else { + SubjectDao subjectDao = new SubjectDao(); + subjectDao.readFile(args[1]); + + XlsxCalendarProcessor processor = new XlsxCalendarProcessor(); + processor.setSubjectDao(subjectDao); + + List<AppointmentEntry> entries = processor.processExcel(args[0]); + for (AppointmentEntry appointmentEntry : entries) { + logger.debug(appointmentEntry); + } + } + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java new file mode 100644 index 00000000..2ed820ad --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/NameSurnameIndexer.java @@ -0,0 +1,9 @@ +package smash.appointment.parse; + +public class NameSurnameIndexer extends SubjectIndexer { + + public String getIndexedString(Subject subject) { + return Utils.simplifyString(subject.getName() + subject.getSurname()); + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java new file mode 100644 index 00000000..cd96a4a1 --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/NdNumberIndexer.java @@ -0,0 +1,9 @@ +package smash.appointment.parse; + +public class NdNumberIndexer extends SubjectIndexer { + + public String getIndexedString(Subject subject) { + return Utils.simplifyString(subject.getNdNumber()); + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/Subject.java b/appointment-import/src/main/java/smash/appointment/parse/Subject.java new file mode 100644 index 00000000..ee5cc02d --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/Subject.java @@ -0,0 +1,89 @@ +package smash.appointment.parse; + +public class Subject { + private String name; + private String surname; + private String ndNumber; + private String screeningNumber; + + public Subject(String name, String surname, String ndNumber, String screeningNumber) { + this.name = name; + this.surname = surname; + this.ndNumber = ndNumber; + this.screeningNumber = screeningNumber; + } + + /** + * @return the name + * @see #name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + * @see #name + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the surname + * @see #surname + */ + public String getSurname() { + return surname; + } + + /** + * @param surname + * the surname to set + * @see #surname + */ + public void setSurname(String surname) { + this.surname = surname; + } + + /** + * @return the ndNumber + * @see #ndNumber + */ + public String getNdNumber() { + return ndNumber; + } + + /** + * @param ndNumber + * the ndNumber to set + * @see #ndNumber + */ + public void setNdNumber(String ndNumber) { + this.ndNumber = ndNumber; + } + + /** + * @return the screeningNumber + * @see #screeningNumber + */ + public String getScreeningNumber() { + return screeningNumber; + } + + /** + * @param screeningNumber + * the screeningNumber to set + * @see #screeningNumber + */ + public void setScreeningNumber(String screeningNumber) { + this.screeningNumber = screeningNumber; + } + + @Override + public String toString() { + return this.getName() + " " + this.getSurname() + " (" + this.getNdNumber() + "; " + this.getScreeningNumber() + ")"; + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java new file mode 100644 index 00000000..c852c84c --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectDao.java @@ -0,0 +1,43 @@ +package smash.appointment.parse; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class SubjectDao { + private List<Subject> subjects = new ArrayList<Subject>(); + + public void addSubject(Subject subject) { + subjects.add(subject); + } + + public void readFile(String filename) throws IOException { + try (BufferedReader br = new BufferedReader(new FileReader(filename))) { + String line; + while ((line = br.readLine()) != null) { + String tmp[] = line.split("\t"); + addSubject(new Subject(tmp[0], tmp[1], tmp[2], tmp[3])); + } + } + } + + /** + * @return the subjects + * @see #subjects + */ + public List<Subject> getSubjects() { + return subjects; + } + + /** + * @param subjects + * the subjects to set + * @see #subjects + */ + public void setSubjects(List<Subject> subjects) { + this.subjects = subjects; + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java new file mode 100644 index 00000000..ff89c24f --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectIndexer.java @@ -0,0 +1,22 @@ +package smash.appointment.parse; + +import org.apache.log4j.Logger; + +public abstract class SubjectIndexer { + Logger logger = Logger.getLogger(SubjectIndexer.class); + + public abstract String getIndexedString(Subject subject); + + public boolean match(Subject subject, String simplifiedQuery) { + String indexedString = getIndexedString(subject); +// logger.debug("Check: " + simplifiedQuery + " against: " + indexedString); + if (simplifiedQuery.startsWith(indexedString)) { + return true; + } + return false; + } + + public boolean isBetter(Subject subject, Subject oldSubject) { + return getIndexedString(subject).length()>getIndexedString(oldSubject).length(); + } +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java new file mode 100644 index 00000000..10c1a29f --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/SurnameIndexer.java @@ -0,0 +1,9 @@ +package smash.appointment.parse; + +public class SurnameIndexer extends SubjectIndexer { + + public String getIndexedString(Subject subject) { + return Utils.simplifyString(subject.getSurname()); + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java b/appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java new file mode 100644 index 00000000..7de6d7c4 --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/SurnameNameIndexer.java @@ -0,0 +1,9 @@ +package smash.appointment.parse; + +public class SurnameNameIndexer extends SubjectIndexer { + + public String getIndexedString(Subject subject) { + return Utils.simplifyString(subject.getSurname() + subject.getName()); + } + +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/Utils.java b/appointment-import/src/main/java/smash/appointment/parse/Utils.java new file mode 100644 index 00000000..0667ea3e --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/Utils.java @@ -0,0 +1,7 @@ +package smash.appointment.parse; + +public class Utils { + public static String simplifyString(String query) { + return query.replaceAll("[\\s\\-©]", "").toLowerCase(); + } +} diff --git a/appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java b/appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java new file mode 100644 index 00000000..9066b957 --- /dev/null +++ b/appointment-import/src/main/java/smash/appointment/parse/XlsxCalendarProcessor.java @@ -0,0 +1,142 @@ +package smash.appointment.parse; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; +import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; + +public class XlsxCalendarProcessor { + Logger logger = Logger.getLogger(XlsxCalendarProcessor.class); + + private SubjectDao subjectDao; + + public List<AppointmentEntry> processExcel(String filename) throws EncryptedDocumentException, InvalidFormatException, IOException, ParseException { + List<AppointmentEntry> result = new ArrayList<AppointmentEntry>(); + InputStream inp = new FileInputStream(filename); + Workbook workbook = WorkbookFactory.create(inp); + Iterator<Sheet> sheetIter = workbook.sheetIterator(); + while (sheetIter.hasNext()) { + Sheet sheet = sheetIter.next(); + String name = sheet.getSheetName().trim(); + if (name.contains("2017")) { + String monthName = name.split(" ")[0]; + String monthNumber = parseMonth(monthName); + result.addAll(processSheet(sheet, "2017-" + monthNumber)); + } else { + logger.debug("Skipping sheet: " + name); + } + } + return result; + } + + int[] dayColumns = new int[] { 3, 4, 5, 6, 7 }; + + int[] weekStartRows = new int[] { 5, 23, 41, 60, 78, 96 }; + + int hourColum = 0; + int dayOfMonthRowOffset = 0; + int calendarRowStartOffset = 3; + int calendarRowEndOffset = 18; + + private List<AppointmentEntry> processSheet(Sheet sheet, String string) { + List<AppointmentEntry> result = new ArrayList<AppointmentEntry>(); + + CellParser parser = new CellParser(); + parser.setSubjectDao(subjectDao); + + for (int weekOffset : weekStartRows) { + Row weekRow = sheet.getRow(weekOffset + dayOfMonthRowOffset); + for (int dayColumnOffset : dayColumns) { + Cell dayCell = weekRow.getCell(dayColumnOffset); + String dayOfMonth = ((int) dayCell.getNumericCellValue()) + ""; + if (dayOfMonth.length() == 1) { + dayOfMonth = "0" + dayOfMonth; + } + if (!dayOfMonth.equals("00")) { + String day = string + "-" + dayOfMonth; + + String hour = "08:00"; + for (int hourOffset = calendarRowStartOffset; hourOffset < calendarRowEndOffset; hourOffset++) { + Row hourRow = sheet.getRow(weekOffset + hourOffset); + + Cell hourCell = hourRow.getCell(hourColum); + if (hourCell.getCellTypeEnum().equals(CellType.NUMERIC)) { + + SimpleDateFormat formatTime = new SimpleDateFormat("HH:mm"); + String hourString = formatTime.format(hourCell.getDateCellValue()); + + if (isHour(hourString)) { + hour = hourString; + } + } + + String query = hourRow.getCell(dayColumnOffset).getStringCellValue(); + + if (query != null && !query.isEmpty()) { + AppointmentEntry entry = parser.parseAppointment(query, hour); + entry.setDay(day); + + result.add(entry); + } + } + } + } + + } + return result; + } + + Pattern timePattern = Pattern.compile("^[0-9][0-9]\\:[0-9][0-9]"); + + private boolean isHour(String hourString) { + Matcher matcher = timePattern.matcher(hourString); + return matcher.find(); + } + + private String parseMonth(String monthName) throws ParseException { + Date date = new SimpleDateFormat("MMMM", Locale.ENGLISH).parse(monthName); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + String result = (cal.get(Calendar.MONTH) + 1) + ""; + if (result.length() == 1) { + result = "0" + result; + } + return result; + } + + /** + * @return the subjectDao + * @see #subjectDao + */ + public SubjectDao getSubjectDao() { + return subjectDao; + } + + /** + * @param subjectDao + * the subjectDao to set + * @see #subjectDao + */ + public void setSubjectDao(SubjectDao subjectDao) { + this.subjectDao = subjectDao; + } +} diff --git a/appointment-import/src/main/resources/log4j.properties b/appointment-import/src/main/resources/log4j.properties new file mode 100644 index 00000000..e108a78c --- /dev/null +++ b/appointment-import/src/main/resources/log4j.properties @@ -0,0 +1,8 @@ +#Set root logger 's level and its appender to an appender called CONSOLE which is defined below. +log4j.rootLogger=debug, CONSOLE + +#Set the behavior of the CONSOLE appender +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n +#log4j.appender.CONSOLE.layout.ConversionPattern=%m%n diff --git a/appointment-import/src/test/java/smash/appointment/parse/AllTests.java b/appointment-import/src/test/java/smash/appointment/parse/AllTests.java new file mode 100644 index 00000000..80dc2e92 --- /dev/null +++ b/appointment-import/src/test/java/smash/appointment/parse/AllTests.java @@ -0,0 +1,14 @@ +package smash.appointment.parse; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ CellParserTest.class, // + SubjectDaoTest.class, // + XlsxCalendarProcessorTest.class, // +}) +public class AllTests { + +} diff --git a/appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java b/appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java new file mode 100644 index 00000000..5bc51566 --- /dev/null +++ b/appointment-import/src/test/java/smash/appointment/parse/CellParseTestCase.java @@ -0,0 +1,15 @@ +package smash.appointment.parse; + +class CellParseTestCase { + String query; + Subject subject; + String time; + AppointmentType type; + + public CellParseTestCase(String query, Subject subject, String time, AppointmentType type) { + this.query = query; + this.subject = subject; + this.time = time; + this.type = type; + } +}; diff --git a/appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java new file mode 100644 index 00000000..40311eba --- /dev/null +++ b/appointment-import/src/test/java/smash/appointment/parse/CellParserTest.java @@ -0,0 +1,77 @@ +package smash.appointment.parse; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +public class CellParserTest extends TestBase { + List<CellParseTestCase> testCases; + CellParser parser; + + @Before + public void setUp() { + super.setUp(); + + parser = new CellParser(); + parser.setSubjectDao(subjectDao); + + testCases = new ArrayList<CellParseTestCase>(); + + testCases.add(new CellParseTestCase("Piotr Gawron level A FU V3", piotrGawron, null, AppointmentType.LEVEL_A)); + testCases.add(new CellParseTestCase("09:00 Jan Kowalski-Nowak level A", janKowalskiNowak, "09:00", AppointmentType.LEVEL_A)); + testCases.add(new CellParseTestCase("ND0002 l664574645 (sms)evel BV © + SB ©", janKowalskiNowak, null, AppointmentType.LEVEL_BV_SB)); + testCases.add(new CellParseTestCase("ND0001 654654631 level B ©", piotrGawron, null, AppointmentType.LEVEL_B)); + testCases.add(new CellParseTestCase("John Doe BV + BG + SB", johnDoe, null, AppointmentType.LEVEL_BV_BG_SB)); + testCases.add(new CellParseTestCase("Kowalski-Nowak m-Power", janKowalskiNowak, null, AppointmentType.LEVEL_B_M_POWER)); + testCases.add(new CellParseTestCase("ND0004 Name BV ©", cateKowalsky, null, AppointmentType.LEVEL_BV)); + + } + + + @Test + public void testExtractTime() { + for (CellParseTestCase testCase : testCases) { + String result = parser.extractTime(testCase.query); + assertEquals("Invalid time parsed from query: " + testCase.query, testCase.time, result); + } + } + + @Test + public void testRemoveTime() { + for (CellParseTestCase testCase : testCases) { + String result = parser.removeTime(testCase.query); + if (testCase.time == null) { + assertEquals("query after removing time should be the same for query: " + testCase.query, testCase.query, result); + } else { + assertFalse(testCase.query.equals(result)); + } + } + } + + @Test + public void testRemoveTime2() { + String result = parser.removeTime("09:00 John Doe level B"); + assertEquals("John Doe level B", result); + } + + @Test + public void testExtractAppointment() { + String defaultTime = "23:55"; + for (CellParseTestCase testCase : testCases) { + AppointmentEntry appointment = parser.parseAppointment(testCase.query, defaultTime); + if (testCase.time != null) { + assertEquals("Invalid time parsed from query: " + testCase.query, testCase.time, appointment.getTime()); + } else { + assertEquals("Invalid time parsed from query (default value expected): " + testCase.query, defaultTime, appointment.getTime()); + } + assertEquals("Invalid subject parsed from query: " + testCase.query, testCase.subject, appointment.getSubject()); + assertEquals("Invalid type parsed from query: " + testCase.query, testCase.type, appointment.getType()); + } + } + +} diff --git a/appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java b/appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java new file mode 100644 index 00000000..66fc18c5 --- /dev/null +++ b/appointment-import/src/test/java/smash/appointment/parse/SubjectDaoTest.java @@ -0,0 +1,32 @@ +package smash.appointment.parse; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +public class SubjectDaoTest { + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testReadFile() throws Exception { + SubjectDao subjectDao = new SubjectDao(); + subjectDao.readFile("testFiles/subjects.txt"); + assertEquals(2, subjectDao.getSubjects().size()); + assertEquals("Piotr", subjectDao.getSubjects().get(0).getName()); + } + +} diff --git a/appointment-import/src/test/java/smash/appointment/parse/TestBase.java b/appointment-import/src/test/java/smash/appointment/parse/TestBase.java new file mode 100644 index 00000000..e3b909e9 --- /dev/null +++ b/appointment-import/src/test/java/smash/appointment/parse/TestBase.java @@ -0,0 +1,26 @@ +package smash.appointment.parse; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; + +public class TestBase { + SubjectDao subjectDao; + + Subject piotrGawron = new Subject("Piotr", "Gawron", "ND0001", "1"); + Subject janKowalskiNowak = new Subject("Jan", "Kowalski-Nowak", "ND0002", "2"); + Subject johnDoe = new Subject("John", "Doe", "ND0003", "3"); + Subject cateKowalsky = new Subject("Cate", "Kowalsky", "ND0004", "4"); + + public void setUp() { + subjectDao = new SubjectDao(); + + subjectDao.addSubject(piotrGawron); + subjectDao.addSubject(janKowalskiNowak); + subjectDao.addSubject(johnDoe); + subjectDao.addSubject(cateKowalsky); + } + + +} diff --git a/appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java b/appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java new file mode 100644 index 00000000..8c07b601 --- /dev/null +++ b/appointment-import/src/test/java/smash/appointment/parse/XlsxCalendarProcessorTest.java @@ -0,0 +1,42 @@ +package smash.appointment.parse; + +import static org.junit.Assert.assertTrue; + +import java.io.FileNotFoundException; +import java.util.List; + +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +public class XlsxCalendarProcessorTest extends TestBase{ + Logger logger = Logger.getLogger(XlsxCalendarProcessorTest .class); + + XlsxCalendarProcessor processor = new XlsxCalendarProcessor(); + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() { + super.setUp(); + processor.setSubjectDao(subjectDao); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testReadExcel() throws Exception { + List<AppointmentEntry> entries = processor.processExcel("testFiles/calendarExample.xlsx"); + assertTrue(entries.size() > 0); +// for (AppointmentEntry appointmentEntry : entries) { +// logger.debug(appointmentEntry); +// } + } + +} diff --git a/appointment-import/src/test/resources/log4j.properties b/appointment-import/src/test/resources/log4j.properties new file mode 100644 index 00000000..e108a78c --- /dev/null +++ b/appointment-import/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +#Set root logger 's level and its appender to an appender called CONSOLE which is defined below. +log4j.rootLogger=debug, CONSOLE + +#Set the behavior of the CONSOLE appender +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n +#log4j.appender.CONSOLE.layout.ConversionPattern=%m%n diff --git a/appointment-import/testFiles/calendarExample.xlsx b/appointment-import/testFiles/calendarExample.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..516d8e548c74f5a7b2e1050b80918e0acfe80fc8 GIT binary patch literal 30585 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	b!frN-B%U!13DG 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 a*8RJ!|Irli2=_YJzz;nz=;3y}kNrQr;+^^c literal 0 HcmV?d00001 diff --git a/appointment-import/testFiles/subjects.txt b/appointment-import/testFiles/subjects.txt new file mode 100644 index 00000000..c55594f7 --- /dev/null +++ b/appointment-import/testFiles/subjects.txt @@ -0,0 +1,2 @@ +Piotr Gawron ND0001 1 +John Doe ND0002 2 -- GitLab