diff --git a/src/main/java/com/example/parser/FlowableExcelMapper.java b/src/main/java/com/example/parser/FlowableExcelMapper.java index 16c0dd0..d606d9b 100644 --- a/src/main/java/com/example/parser/FlowableExcelMapper.java +++ b/src/main/java/com/example/parser/FlowableExcelMapper.java @@ -2,35 +2,69 @@ package com.example.parser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.apache.poi.ss.usermodel.DateUtil; import org.springframework.stereotype.Component; +import java.time.*; import java.util.*; + @Component public class FlowableExcelMapper { protected static ObjectMapper objectMapper = new ObjectMapper(); + protected static JavaTimeModule javaTimeModule = new JavaTimeModule(); protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser(); + public FlowableExcelMapper() { + objectMapper.registerModule(javaTimeModule); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + } + public ArrayNode excelToJsonNode(String excelPath) { - ArrayList> rowList = flowableExcelParser.parseExcel(excelPath); + ArrayList> rawList = flowableExcelParser.parseExcel(excelPath); + ArrayList paths = new ArrayList<>(); + ArrayList types = new ArrayList<>(); ArrayNode arrayNode = objectMapper.createArrayNode(); - for (LinkedHashMap row : rowList) { - JsonNode jsonNode = null; - Set> entries = row.entrySet(); - for (Map.Entry entry : entries) { - jsonNode = addNode(jsonNode, entry.getKey(), entry.getValue()); + for (int rowNum = 0; rowNum < rawList.size(); rowNum++) { + if (rowNum == 0) { + // The 1st row contains the paths + paths.addAll(rawList.get(rowNum)); + } else if (rowNum == 1) { + // The 1st row contains the types of the variables + types.addAll(rawList.get(rowNum)); + } else { + // All other rows contain the values of the variables + JsonNode jsonNode = null; + ArrayList row = rawList.get(rowNum); + for (int cellNum = 0; cellNum < row.size(); cellNum++) { + Object content = row.get(cellNum); + String type = types.get(cellNum).toString(); + switch (type) { + case "String": content = content.toString(); break; + case "Boolean": content = Boolean.parseBoolean(content.toString()); break; + case "Integer": content = (int) Double.parseDouble(content.toString()); break; + case "Double": content = Double.parseDouble(content.toString()); break; + case "Date": + // Excel considers dates as local dates, but we want them as UTC + Date dateTime = DateUtil.getJavaDate(Double.parseDouble(content.toString())); + content = dateTime.toInstant().plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds()); + break; + default: break; + } + jsonNode = addNode(jsonNode, paths.get(cellNum).toString(), content); + } + arrayNode.add(jsonNode); } - arrayNode.add(jsonNode); } return arrayNode; } - public JsonNode addNode(JsonNode parent, String path, Object value) { - objectMapper = new ObjectMapper(); - + protected JsonNode addNode(JsonNode parent, String path, Object value) { if (parent == null) { parent = objectMapper.createObjectNode(); } @@ -87,7 +121,7 @@ public class FlowableExcelMapper { return parent; } - public static int indexOfFirstDelimiter(String str, String delimiters) { + protected int indexOfFirstDelimiter(String str, String delimiters) { for (int i = 0; i < str.length(); i++) { for (char c : delimiters.toCharArray()) { if (str.charAt(i) == c) { diff --git a/src/main/java/com/example/parser/FlowableExcelParser.java b/src/main/java/com/example/parser/FlowableExcelParser.java index 8ff5a7a..0ba5da8 100644 --- a/src/main/java/com/example/parser/FlowableExcelParser.java +++ b/src/main/java/com/example/parser/FlowableExcelParser.java @@ -8,56 +8,63 @@ import org.springframework.stereotype.Component; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.LinkedHashMap; @Component public class FlowableExcelParser { - public ArrayList> parseExcel(String resourcePath) { - ArrayList> rowList = new ArrayList<>(); + // resourcePath defines the location of the Excel file + // Return all cells in the 1st sheet which have values as objects (String, boolean or double) + // The 1st row defines the amount of cells per row + // Parsing the sheet stops after the 1st empty row + public ArrayList> parseExcel(String resourcePath) { + ArrayList> rows = new ArrayList<>(); try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) { Workbook workbook = new XSSFWorkbook(fileInputStream); Sheet sheet = workbook.getSheetAt(0); - ArrayList colHeaders = new ArrayList<>(); - for (Row row : sheet) { - if (row.getRowNum() == 0) { - // Header row - for (int colNum = 0; colNum < row.getLastCellNum(); colNum++) { - Cell cell = row.getCell(colNum); - if (cell == null || cell.getCellType() == CellType.BLANK || cell.getCellType() == CellType._NONE) { - // Take all columns until the first empty header cell - break; - } else { - colHeaders.add(cell.getStringCellValue()); - } - } - continue; + int maxCells = 0; + for (int rowNum = 0; rowNum < sheet.getLastRowNum(); rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + break; + } + if (maxCells == 0) { + maxCells = row.getLastCellNum(); } boolean allCellsNull = true; - LinkedHashMap rowMap = new LinkedHashMap<>(); - for (int colNum = 0; colNum < colHeaders.size(); colNum++) { - Object content = null; - Cell cell = row.getCell(colNum); - CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell); - if (cellValue != null && cell.getCellType() != CellType.BLANK && cell.getCellType() != CellType._NONE) { - switch (cellValue.getCellType()) { - case STRING : content = cellValue.getStringValue(); break; - case BOOLEAN : content = cellValue.getBooleanValue(); break; - case NUMERIC : content = cellValue.getNumberValue(); break; + ArrayList cells = new ArrayList<>(); + for (int cellNum = 0; cellNum < maxCells; cellNum++) { + Cell cell = row.getCell(cellNum); + if(rowNum == 0) { + if (cell == null || cell.getCellType() == CellType.BLANK || cell.getCellType() == CellType._NONE) { + // Count cells of 1st row until the first empty cell + maxCells = cellNum; + break; } - allCellsNull = false; } - rowMap.put(colHeaders.get(colNum), content); + CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell); + Object content = null; + if (cellValue != null) { + CellType cellType = cell.getCellType(); + if (cellType != CellType.BLANK && cellType != CellType._NONE) { + switch (cellValue.getCellType()) { + case STRING : content = cellValue.getStringValue(); break; + case BOOLEAN : content = cellValue.getBooleanValue(); break; + case NUMERIC : content = cellValue.getNumberValue(); break; + } + allCellsNull = false; + } + } + cells.add(content); } if (allCellsNull) { // Take all rows until the first empty row - return rowList; + return rows; } - rowList.add(rowMap); + rows.add(cells); } } catch (IOException e) { throw new RuntimeException(e); } - return rowList; + return rows; } } diff --git a/src/main/java/com/example/parser/ParserApp.java b/src/main/java/com/example/parser/ParserApp.java index 74ed2aa..414987a 100644 --- a/src/main/java/com/example/parser/ParserApp.java +++ b/src/main/java/com/example/parser/ParserApp.java @@ -18,6 +18,7 @@ public class ParserApp { private static final ObjectMapper objectMapper = new ObjectMapper(); private static final FlowableJsonParser flowableJsonParser = new FlowableJsonParser(); private static final FlowableExcelParser flowableExcelParser = new FlowableExcelParser(); + private static final FlowableExcelMapper flowableExcelMapper = new FlowableExcelMapper(); public static void main(String[] args) { SpringApplication.run(ParserApp.class, args); @@ -28,9 +29,12 @@ public class ParserApp { throw new RuntimeException(e); } - ClassLoader classLoader = ParserApp.class.getClassLoader(); - URL resourceUrl = classLoader.getResource("flowableExcelParserExampleData.xlsx"); + URL resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelParserExampleData.xlsx"); assert resourceUrl != null; logger.info("parsed excel={}", flowableExcelParser.parseExcel(resourceUrl.getPath())); + + resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelMapperExampleData.xlsx"); + assert resourceUrl != null; + logger.info("mapped excel={}", flowableExcelMapper.excelToJsonNode(resourceUrl.getPath())); } } diff --git a/src/main/resources/flowableExcelMapperExampleData.xlsx b/src/main/resources/flowableExcelMapperExampleData.xlsx new file mode 100644 index 0000000..effac21 Binary files /dev/null and b/src/main/resources/flowableExcelMapperExampleData.xlsx differ diff --git a/src/test/java/com/example/parser/FlowableExcelMapperTest.java b/src/test/java/com/example/parser/FlowableExcelMapperTest.java index e25becc..41d13ce 100644 --- a/src/test/java/com/example/parser/FlowableExcelMapperTest.java +++ b/src/test/java/com/example/parser/FlowableExcelMapperTest.java @@ -18,23 +18,9 @@ class FlowableExcelMapperTest { @Autowired private FlowableExcelMapper flowableExcelMapper; - private static final Logger logger = LoggerFactory.getLogger(ParserApp.class); + private static final Logger logger = LoggerFactory.getLogger(FlowableExcelMapperTest.class); - @Test - public void addNodeTest() { - JsonNode node = null; - node = flowableExcelMapper.addNode(node, "3*0*2", "String1"); - node = flowableExcelMapper.addNode(node, "3*0*3", "String1"); - node = flowableExcelMapper.addNode(node, "3*1.1", 234); - node = flowableExcelMapper.addNode(node, "3*1.2", 234); - node = flowableExcelMapper.addNode(node, "3*2", false); - node = flowableExcelMapper.addNode(node, "4.5", "String1"); - node = flowableExcelMapper.addNode(node, "4.6", 234); - node = flowableExcelMapper.addNode(node, "4.7", false); - logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString()); - } - protected String getResourcePath(String path) { ClassLoader classLoader = FlowableExcelMapperTest.class.getClassLoader(); URL resourceUrl = classLoader.getResource(path); @@ -44,7 +30,7 @@ class FlowableExcelMapperTest { @Test public void excelToJsonNodeTest() { - JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData1.xlsx")); + JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData.xlsx")); logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString()); } } diff --git a/src/test/java/com/example/parser/FlowableExcelParserTest.java b/src/test/java/com/example/parser/FlowableExcelParserTest.java index ed4f4de..21d3352 100644 --- a/src/test/java/com/example/parser/FlowableExcelParserTest.java +++ b/src/test/java/com/example/parser/FlowableExcelParserTest.java @@ -20,26 +20,18 @@ class FlowableExcelParserTest { @Autowired private FlowableExcelParser flowableExcelParser; - private Stream GetArgumentsFromExcel(String path) { - ArrayList argumentList = new ArrayList<>(); + protected String getResourcePath(String path) { ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader(); URL resourceUrl = classLoader.getResource(path); Assertions.assertThat(resourceUrl).isNotNull(); - ArrayList> rowList = flowableExcelParser.parseExcel(resourceUrl.getPath()); - Assertions.assertThat(rowList).isNotNull(); - for (LinkedHashMap row : rowList) { - LinkedHashMap inputMap = new LinkedHashMap<>(); - LinkedHashMap outputMap = new LinkedHashMap<>(); - for (Map.Entry entry : row.entrySet()) { - String key = entry.getKey(); - if (List.of("input1", "input2", "input3").contains(key)) { - inputMap.put(key, entry.getValue()); - } - if (List.of("output1", "output2").contains(key)) { - outputMap.put(key, entry.getValue()); - } - } - argumentList.add(Arguments.of(inputMap, outputMap)); + return resourceUrl.getPath(); + } + + protected Stream GetArgumentsFromExcel(String path) { + ArrayList argumentList = new ArrayList<>(); + ArrayList> rowList = flowableExcelParser.parseExcel(getResourcePath(path)); + for (ArrayList row : rowList) { + argumentList.add(Arguments.of(row)); } return argumentList.stream(); } @@ -51,18 +43,12 @@ class FlowableExcelParserTest { @ParameterizedTest @MethodSource("booleanExcelParserTestDataProvider") - void booleanExcelParserTest(LinkedHashMap inputMap, LinkedHashMap outputMap) { - for (Map.Entry entry : outputMap.entrySet()) { - String key = entry.getKey(); - if (key.equals("output1")) { - Boolean val = (Boolean)inputMap.get("input1") || (Boolean)inputMap.get("input2") || (Boolean)inputMap.get("input3"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - if (key.equals("output2")) { - Boolean val = (Boolean)inputMap.get("input1") && (Boolean)inputMap.get("input2") && (Boolean)inputMap.get("input3"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - } + void booleanExcelParserTest(ArrayList row) { + Boolean valOr = (Boolean) row.get(0) || (Boolean) row.get(1) || (Boolean) row.get(2); + Assertions.assertThat(row.get(3)).isEqualTo(valOr); + + Boolean valAnd = (Boolean) row.get(0) && (Boolean) row.get(1) && (Boolean) row.get(2); + Assertions.assertThat(row.get(4)).isEqualTo(valAnd); } @NotNull @@ -72,22 +58,15 @@ class FlowableExcelParserTest { @ParameterizedTest @MethodSource("stringExcelParserTestDataProvider") - void stringExcelParserTest(LinkedHashMap inputMap, LinkedHashMap outputMap) { - for (Map.Entry entry : outputMap.entrySet()) { - String key = entry.getKey(); - if (key.equals("output1")) { - String val = inputMap.get("input1").toString() + inputMap.get("input2").toString(); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - if (key.equals("output2")) { - String val = inputMap.get("input2").toString() + inputMap.get("input3").toString(); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - if (key.equals("output3")) { - String val = inputMap.get("input1").toString() + inputMap.get("input2").toString() + inputMap.get("input3").toString(); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - } + void stringExcelParserTest(ArrayList row) { + String val12 = row.get(0).toString() + row.get(1).toString(); + Assertions.assertThat(row.get(3)).isEqualTo(val12); + + String val23 = row.get(1).toString() + row.get(2).toString(); + Assertions.assertThat(row.get(4)).isEqualTo(val23); + + String val123 = row.get(0).toString() + row.get(1).toString() + row.get(2).toString(); + Assertions.assertThat(row.get(5)).isEqualTo(val123); } @NotNull @@ -97,21 +76,14 @@ class FlowableExcelParserTest { @ParameterizedTest @MethodSource("numberExcelParserTestDataProvider") - void numberExcelParserTest(LinkedHashMap inputMap, LinkedHashMap outputMap) { - for (Map.Entry entry : outputMap.entrySet()) { - String key = entry.getKey(); - if (key.equals("output1")) { - Double val = (Double)inputMap.get("input1") + (Double)inputMap.get("input2"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - if (key.equals("output2")) { - Double val = (Double)inputMap.get("input2") + (Double)inputMap.get("input3"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - if (key.equals("output3")) { - Double val = (Double)inputMap.get("input1") + (Double)inputMap.get("input2") + (Double)inputMap.get("input3"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); - } - } + void numberExcelParserTest(ArrayList row) { + Double val12 = (Double) row.get(0) + (Double) row.get(1); + Assertions.assertThat(row.get(3)).isEqualTo(val12); + + Double val23 = (Double) row.get(1) + (Double) row.get(2); + Assertions.assertThat(row.get(4)).isEqualTo(val23); + + Double val123 = (Double) row.get(0) + (Double) row.get(1) + (Double) row.get(2); + Assertions.assertThat(row.get(5)).isEqualTo(val123); } } diff --git a/src/test/resources/flowableExcelMapperTestData.xlsx b/src/test/resources/flowableExcelMapperTestData.xlsx new file mode 100644 index 0000000..effac21 Binary files /dev/null and b/src/test/resources/flowableExcelMapperTestData.xlsx differ diff --git a/src/test/resources/flowableExcelMapperTestData1.xlsx b/src/test/resources/flowableExcelMapperTestData1.xlsx deleted file mode 100644 index 60d1406..0000000 Binary files a/src/test/resources/flowableExcelMapperTestData1.xlsx and /dev/null differ diff --git a/src/test/resources/flowableExcelParserTestData1.xlsx b/src/test/resources/flowableExcelParserTestData1.xlsx index c1739f9..8d775a6 100644 Binary files a/src/test/resources/flowableExcelParserTestData1.xlsx and b/src/test/resources/flowableExcelParserTestData1.xlsx differ diff --git a/src/test/resources/flowableExcelParserTestData2.xlsx b/src/test/resources/flowableExcelParserTestData2.xlsx index 99b5d78..6973424 100644 Binary files a/src/test/resources/flowableExcelParserTestData2.xlsx and b/src/test/resources/flowableExcelParserTestData2.xlsx differ diff --git a/src/test/resources/flowableExcelParserTestData3.xlsx b/src/test/resources/flowableExcelParserTestData3.xlsx index 3209621..25a8929 100644 Binary files a/src/test/resources/flowableExcelParserTestData3.xlsx and b/src/test/resources/flowableExcelParserTestData3.xlsx differ diff --git a/target/classes/com/example/parser/FlowableExcelParser$1.class b/target/classes/com/example/parser/FlowableExcelParser$1.class index c7cab7a..83a8f4b 100644 Binary files a/target/classes/com/example/parser/FlowableExcelParser$1.class and b/target/classes/com/example/parser/FlowableExcelParser$1.class differ diff --git a/target/classes/com/example/parser/FlowableExcelParser.class b/target/classes/com/example/parser/FlowableExcelParser.class index 0a6af2a..b8e4d16 100644 Binary files a/target/classes/com/example/parser/FlowableExcelParser.class and b/target/classes/com/example/parser/FlowableExcelParser.class differ diff --git a/target/classes/com/example/parser/FlowableJsonParser.class b/target/classes/com/example/parser/FlowableJsonParser.class index 3b2ace5..0d2f218 100644 Binary files a/target/classes/com/example/parser/FlowableJsonParser.class and b/target/classes/com/example/parser/FlowableJsonParser.class differ diff --git a/target/classes/com/example/parser/ParserApp.class b/target/classes/com/example/parser/ParserApp.class index 1e71c4b..0399618 100644 Binary files a/target/classes/com/example/parser/ParserApp.class and b/target/classes/com/example/parser/ParserApp.class differ diff --git a/target/test-classes/com/example/parser/FlowableExcelParserTest.class b/target/test-classes/com/example/parser/FlowableExcelParserTest.class index d54342b..8249fde 100644 Binary files a/target/test-classes/com/example/parser/FlowableExcelParserTest.class and b/target/test-classes/com/example/parser/FlowableExcelParserTest.class differ diff --git a/target/test-classes/com/example/parser/FlowableJsonParserTest.class b/target/test-classes/com/example/parser/FlowableJsonParserTest.class index 0edff27..e261e48 100644 Binary files a/target/test-classes/com/example/parser/FlowableJsonParserTest.class and b/target/test-classes/com/example/parser/FlowableJsonParserTest.class differ diff --git a/target/test-classes/flowableExcelParserTestData1.xlsx b/target/test-classes/flowableExcelParserTestData1.xlsx index c1739f9..8d775a6 100644 Binary files a/target/test-classes/flowableExcelParserTestData1.xlsx and b/target/test-classes/flowableExcelParserTestData1.xlsx differ diff --git a/target/test-classes/flowableExcelParserTestData2.xlsx b/target/test-classes/flowableExcelParserTestData2.xlsx index 99b5d78..6973424 100644 Binary files a/target/test-classes/flowableExcelParserTestData2.xlsx and b/target/test-classes/flowableExcelParserTestData2.xlsx differ diff --git a/target/test-classes/flowableExcelParserTestData3.xlsx b/target/test-classes/flowableExcelParserTestData3.xlsx index 3209621..25a8929 100644 Binary files a/target/test-classes/flowableExcelParserTestData3.xlsx and b/target/test-classes/flowableExcelParserTestData3.xlsx differ