From 25817cf5aa38a3417a8ba26d27c866c532eb93ed Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Fri, 24 Feb 2017 17:53:52 +0100
Subject: [PATCH] java parser for prc subjects

---
 .../appointment/parse/PrcSubjectsParser.java  | 127 ++++++++++
 .../java/smash/appointment/parse/Subject.java | 238 ++++++++++++++++++
 .../appointment/parse/SubjectParser.java      | 147 +++++++++++
 .../smash/appointment/parse/AllTests.java     |   1 +
 .../parse/PrcSubjectsParserTest.java          |  60 +++++
 .../testFiles/prcSubjectsExample.xlsx         | Bin 0 -> 9293 bytes
 6 files changed, 573 insertions(+)
 create mode 100644 appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java
 create mode 100644 appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java
 create mode 100644 appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java
 create mode 100644 appointment-import/testFiles/prcSubjectsExample.xlsx

diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java
new file mode 100644
index 00000000..155399ce
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java
@@ -0,0 +1,127 @@
+package smash.appointment.parse;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+
+public class PrcSubjectsParser extends SubjectParser {
+
+	@Override
+	protected String parseScreeningNumber(Row row) {
+		return "P-" + getString(row.getCell(25));
+	}
+
+	@Override
+	protected String parseName(Row row) {
+		return getString(row.getCell(1));
+	}
+
+	@Override
+	protected String parseSurname(Row row) {
+		return getString(row.getCell(0));
+	}
+
+	@Override
+	protected String parseNdNumber(Row row) {
+		return getString(row.getCell(22));
+	}
+
+	@Override
+	protected String getSheetName() {
+		return "Feuil1";
+	}
+
+	@Override
+	protected int getInitRow() {
+		return 1;
+	}
+
+	@Override
+	protected String parseBirthDate(Row row) {
+		String date = getString(row.getCell(27)).replaceAll(" ", "");
+		String year = date.substring(0, 4);
+		String month = date.substring(4, 6);
+		String day = date.substring(6, 8);
+		return year + "-" + month + "-" + day;
+	}
+
+	@Override
+	protected String parsemPowerId(Row row) {
+		return getString(row.getCell(23));
+	}
+
+	@Override
+	protected String parseAddDate(Row row) {
+		return getDate(row.getCell(14));
+	}
+
+	@Override
+	protected String parseReferal(Row row) {
+		return getString(row.getCell(13));
+	}
+
+	@Override
+	protected String parseDiagnosisYear(Row row) {
+		return getString(row.getCell(11));
+	}
+
+	@Override
+	protected String parseMail(Row row) {
+		return getString(row.getCell(10));
+	}
+
+	@Override
+	protected String parsePhone3(Row row) {
+		return getString(row.getCell(9));
+	}
+
+	@Override
+	protected String parsePhone2(Row row) {
+		return getString(row.getCell(8));
+	}
+
+	@Override
+	protected String parsePhone1(Row row) {
+		return getString(row.getCell(7));
+	}
+
+	@Override
+	protected String parseCity(Row row) {
+		return getString(row.getCell(5));
+	}
+
+	@Override
+	protected String parseCountry(Row row) {
+		return getString(row.getCell(6));
+	}
+
+	@Override
+	protected String parseZipCode(Row row) {
+		return getString(row.getCell(4));
+	}
+
+	@Override
+	protected String parseAddress(Row row) {
+		return getString(row.getCell(3));
+	}
+
+	@Override
+	protected String parseRemarks(Row row) {
+		String remark1 = getString(row.getCell(2));
+		String remark2 = getString(row.getCell(20));
+
+		String result = "";
+		if (!remark1.trim().isEmpty()) {
+			result = result + remark1 + "\n";
+		}
+		if (!remark2.trim().isEmpty()) {
+			result = result + remark2 + "\n";
+		}
+		return result;
+	}
+
+	@Override
+	protected String parseDiagnosis(Row row) {
+		return getString(row.getCell(12));
+	}
+
+}
diff --git a/appointment-import/src/main/java/smash/appointment/parse/Subject.java b/appointment-import/src/main/java/smash/appointment/parse/Subject.java
index 92c2ab9f..70d87334 100644
--- a/appointment-import/src/main/java/smash/appointment/parse/Subject.java
+++ b/appointment-import/src/main/java/smash/appointment/parse/Subject.java
@@ -13,7 +13,21 @@ public class Subject {
 	private String ndNumber;
 	private String screeningNumber;
 	private String sex;
+	private String remarks;
 	private String birthDate;
+	private String address;
+	private String zipCode;
+	private String country;
+	private String city;
+	private String phone1;
+	private String phone2;
+	private String phone3;
+	private String mail;
+	private String diagnosisYear;
+	private String diagnosis;
+	private String referal;
+	private String addDate;
+	private String mPowerId;
 
 	private List<String>	 languages = new ArrayList<>();
 
@@ -153,4 +167,228 @@ public class Subject {
 		this.languages = languages;
 	}
 
+	/**
+	 * @return the remarks
+	 * @see #remarks
+	 */
+	public String getRemarks() {
+		return remarks;
+	}
+
+	/**
+	 * @param remarks the remarks to set
+	 * @see #remarks
+	 */
+	public void setRemarks(String remarks) {
+		this.remarks = remarks;
+	}
+
+	/**
+	 * @return the address
+	 * @see #address
+	 */
+	public String getAddress() {
+		return address;
+	}
+
+	/**
+	 * @param address the address to set
+	 * @see #address
+	 */
+	public void setAddress(String address) {
+		this.address = address;
+	}
+
+	/**
+	 * @return the zipCode
+	 * @see #zipCode
+	 */
+	public String getZipCode() {
+		return zipCode;
+	}
+
+	/**
+	 * @param zipCode the zipCode to set
+	 * @see #zipCode
+	 */
+	public void setZipCode(String zipCode) {
+		this.zipCode = zipCode;
+	}
+
+	/**
+	 * @return the country
+	 * @see #country
+	 */
+	public String getCountry() {
+		return country;
+	}
+
+	/**
+	 * @param country the country to set
+	 * @see #country
+	 */
+	public void setCountry(String country) {
+		this.country = country;
+	}
+
+	/**
+	 * @return the city
+	 * @see #city
+	 */
+	public String getCity() {
+		return city;
+	}
+
+	/**
+	 * @param city the city to set
+	 * @see #city
+	 */
+	public void setCity(String city) {
+		this.city = city;
+	}
+
+	/**
+	 * @return the phone1
+	 * @see #phone1
+	 */
+	public String getPhone1() {
+		return phone1;
+	}
+
+	/**
+	 * @param phone1 the phone1 to set
+	 * @see #phone1
+	 */
+	public void setPhone1(String phone1) {
+		this.phone1 = phone1;
+	}
+
+	/**
+	 * @return the phone2
+	 * @see #phone2
+	 */
+	public String getPhone2() {
+		return phone2;
+	}
+
+	/**
+	 * @param phone2 the phone2 to set
+	 * @see #phone2
+	 */
+	public void setPhone2(String phone2) {
+		this.phone2 = phone2;
+	}
+
+	/**
+	 * @return the phone3
+	 * @see #phone3
+	 */
+	public String getPhone3() {
+		return phone3;
+	}
+
+	/**
+	 * @param phone3 the phone3 to set
+	 * @see #phone3
+	 */
+	public void setPhone3(String phone3) {
+		this.phone3 = phone3;
+	}
+
+	/**
+	 * @return the mail
+	 * @see #mail
+	 */
+	public String getMail() {
+		return mail;
+	}
+
+	/**
+	 * @param mail the mail to set
+	 * @see #mail
+	 */
+	public void setMail(String mail) {
+		this.mail = mail;
+	}
+
+	/**
+	 * @return the diagnosisYear
+	 * @see #diagnosisYear
+	 */
+	public String getDiagnosisYear() {
+		return diagnosisYear;
+	}
+
+	/**
+	 * @param diagnosisYear the diagnosisYear to set
+	 * @see #diagnosisYear
+	 */
+	public void setDiagnosisYear(String diagnosisYear) {
+		this.diagnosisYear = diagnosisYear;
+	}
+
+	/**
+	 * @return the diagnosis
+	 * @see #diagnosis
+	 */
+	public String getDiagnosis() {
+		return diagnosis;
+	}
+
+	/**
+	 * @param diagnosis the diagnosis to set
+	 * @see #diagnosis
+	 */
+	public void setDiagnosis(String diagnosis) {
+		this.diagnosis = diagnosis;
+	}
+
+	/**
+	 * @return the referal
+	 * @see #referal
+	 */
+	public String getReferal() {
+		return referal;
+	}
+
+	/**
+	 * @param referal the referal to set
+	 * @see #referal
+	 */
+	public void setReferal(String referal) {
+		this.referal = referal;
+	}
+
+	/**
+	 * @return the mPowerId
+	 * @see #mPowerId
+	 */
+	public String getmPowerId() {
+		return mPowerId;
+	}
+
+	/**
+	 * @param mPowerId the mPowerId to set
+	 * @see #mPowerId
+	 */
+	public void setmPowerId(String mPowerId) {
+		this.mPowerId = mPowerId;
+	}
+
+	/**
+	 * @return the addDate
+	 * @see #addDate
+	 */
+	public String getAddDate() {
+		return addDate;
+	}
+
+	/**
+	 * @param addDate the addDate to set
+	 * @see #addDate
+	 */
+	public void setAddDate(String addDate) {
+		this.addDate = addDate;
+	}
+
 }
diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java
new file mode 100644
index 00000000..498dc960
--- /dev/null
+++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java
@@ -0,0 +1,147 @@
+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.Iterator;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.hssf.usermodel.HSSFDateUtil;
+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.DataFormatter;
+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 abstract class SubjectParser {
+	private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+	Logger				logger = Logger.getLogger(SubjectParser.class);
+	DataFormatter	df		 = new DataFormatter();
+
+	public List<Subject> processExcel(String filename) throws EncryptedDocumentException, InvalidFormatException, IOException, ParseException {
+		List<Subject> result = new ArrayList<>();
+		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.trim().toLowerCase().equals(getSheetName().toLowerCase())) {
+				result.addAll(processSheet(sheet));
+			} else {
+				logger.debug(filename + "Skipping sheet: " + name);
+			}
+		}
+		return result;
+	}
+
+	private List<Subject> processSheet(Sheet sheet) {
+		List<Subject> result = new ArrayList<>();
+		int rowCount = sheet.getPhysicalNumberOfRows();
+		for (int rowId = getInitRow(); rowId < rowCount; rowId++) {
+			Row subjectRow = sheet.getRow(rowId);
+			Subject subject = parseSubject(subjectRow);
+			if (subject != null) {
+				result.add(subject);
+			}
+		}
+		return result;
+	}
+
+	private Subject parseSubject(Row row) {
+		String screeningNumber = parseScreeningNumber(row);
+		if (screeningNumber == null || screeningNumber.isEmpty()) {
+			return null;
+		}
+		String name = parseName(row);
+		String surname = parseSurname(row);
+		String ndNumber = parseNdNumber(row);
+		Subject result = new Subject(name, surname, ndNumber, screeningNumber);
+
+		result.setRemarks(parseRemarks(row));
+		result.setAddress(parseAddress(row));
+		result.setZipCode(parseZipCode(row));
+		result.setCity(parseCity(row));
+		result.setCountry(parseCountry(row));
+		result.setPhone1(parsePhone1(row));
+		result.setPhone2(parsePhone2(row));
+		result.setPhone3(parsePhone3(row));
+		result.setMail(parseMail(row));
+		result.setDiagnosisYear(parseDiagnosisYear(row));
+		result.setDiagnosis(parseDiagnosis(row));
+		result.setReferal(parseReferal(row));
+		result.setAddDate(parseAddDate(row));
+		result.setmPowerId(parsemPowerId(row));
+		result.setBirthDate(parseBirthDate(row));
+
+		return result;
+	}
+
+	protected abstract String parseBirthDate(Row row);
+
+	protected abstract String parsemPowerId(Row row);
+
+	protected abstract String parseAddDate(Row row);
+
+	protected abstract String parseReferal(Row row);
+
+	protected abstract String parseDiagnosisYear(Row row);
+
+	protected abstract String parseDiagnosis(Row row);
+
+	protected abstract String parseMail(Row row);
+
+	protected abstract String parsePhone3(Row row);
+
+	protected abstract String parsePhone2(Row row);
+
+	protected abstract String parsePhone1(Row row);
+
+	protected abstract String parseCity(Row row);
+
+	protected abstract String parseCountry(Row row);
+
+	protected abstract String parseZipCode(Row row);
+
+	protected abstract String parseAddress(Row row);
+
+	protected abstract String parseRemarks(Row row);
+
+	protected abstract String parseScreeningNumber(Row row);
+
+	protected abstract String parseName(Row row);
+
+	protected abstract String parseSurname(Row row);
+
+	protected abstract String parseNdNumber(Row row);
+
+	protected abstract String getSheetName();
+
+	protected abstract int getInitRow();
+
+	protected String getString(Cell cell) {
+		if (cell.getCellTypeEnum().equals(CellType.NUMERIC)) {
+			return df.formatCellValue(cell).trim();
+		}
+		return cell.getStringCellValue().trim();
+	}
+
+	protected String getDate(Cell cell) {
+		String result = null;
+		if (HSSFDateUtil.isCellDateFormatted(cell)) {
+			result = DATE_FORMATTER.format(cell.getDateCellValue());
+		} else {
+			result = getString(cell);
+		}
+		return result;
+	}
+
+}
diff --git a/appointment-import/src/test/java/smash/appointment/parse/AllTests.java b/appointment-import/src/test/java/smash/appointment/parse/AllTests.java
index be53066b..a22dce9b 100644
--- a/appointment-import/src/test/java/smash/appointment/parse/AllTests.java
+++ b/appointment-import/src/test/java/smash/appointment/parse/AllTests.java
@@ -6,6 +6,7 @@ import org.junit.runners.Suite.SuiteClasses;
 
 @RunWith(Suite.class)
 @SuiteClasses({ CellParserTest.class, //
+		PrcSubjectsParserTest.class, //
 		RedcapParserTest.class, //
 
 		SubjectDaoTest.class, //
diff --git a/appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java
new file mode 100644
index 00000000..255d939b
--- /dev/null
+++ b/appointment-import/src/test/java/smash/appointment/parse/PrcSubjectsParserTest.java
@@ -0,0 +1,60 @@
+package smash.appointment.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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 PrcSubjectsParserTest extends TestBase {
+	Logger						logger		= Logger.getLogger(PrcSubjectsParserTest.class);
+
+	PrcSubjectsParser	processor	= new PrcSubjectsParser();
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception {
+	}
+
+	@Before
+	public void setUp() {
+		super.setUp();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+	}
+
+	@Test
+	public void test() throws Exception {
+		List<Subject> entries = processor.processExcel("testFiles/prcSubjectsExample.xlsx");
+		assertTrue(entries.size() > 0);
+
+		Subject subject = entries.get(0);
+		assertEquals("P-111", subject.getScreeningNumber());
+		assertEquals("abcdef", subject.getSurname());
+		assertEquals("Piotr", subject.getName());
+		assertTrue(subject.getRemarks().contains("this is remark"));
+		assertEquals("Jaskolki, 6", subject.getAddress());
+		assertEquals("D-66636", subject.getZipCode());
+		assertEquals("Trier", subject.getCity());
+		assertEquals("Deutschland", subject.getCountry());
+		assertEquals("0049 12 34 556 76", subject.getPhone1());
+		assertEquals("tel2", subject.getPhone2());
+		assertEquals("tel3", subject.getPhone3());
+		assertEquals("piotr.cos@uni.lu", subject.getMail());
+		assertEquals("1999", subject.getDiagnosisYear());
+		assertEquals("unknown", subject.getDiagnosis());
+		assertEquals("PG", subject.getReferal());
+		assertEquals("2015-10-14", subject.getAddDate());
+		assertTrue(subject.getRemarks().contains("Questionnaires OK"));
+		assertEquals("ND1111", subject.getNdNumber());
+		assertEquals("m_id", subject.getmPowerId());
+		assertEquals("1972-01-02", subject.getBirthDate());
+	}
+
+}
diff --git a/appointment-import/testFiles/prcSubjectsExample.xlsx b/appointment-import/testFiles/prcSubjectsExample.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..78e43d92529273733c3e26a0b37fa2d6983b5485
GIT binary patch
literal 9293
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
ZbdJjM$Z)#+*o}Yz7>ASGjQYpZ{{U)%D82vy

literal 0
HcmV?d00001

-- 
GitLab