diff --git a/.gitignore b/.gitignore index 549e00a..e92c632 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build/ ### VS Code ### .vscode/ +/target/ diff --git a/.idea/misc.xml b/.idea/misc.xml index b268cf8..b169464 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + @@ -73,7 +74,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index c85fa9d..2a38a9a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,12 @@ - 4.0.0 org.springframework.boot spring-boot-starter-parent - 3.3.4 + 3.5.7 com.example @@ -27,7 +28,7 @@ - 17 + 11 @@ -54,12 +55,12 @@ org.apache.poi poi - 5.2.5 + 5.4.1 org.apache.poi poi-ooxml - 5.2.5 + 5.4.1 org.jetbrains diff --git a/src/main/java/com/example/parser/FlowableExcelMapper.java b/src/main/java/com/example/parser/FlowableExcelMapper.java new file mode 100644 index 0000000..16c0dd0 --- /dev/null +++ b/src/main/java/com/example/parser/FlowableExcelMapper.java @@ -0,0 +1,100 @@ +package com.example.parser; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +public class FlowableExcelMapper { + + protected static ObjectMapper objectMapper = new ObjectMapper(); + protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser(); + + public ArrayNode excelToJsonNode(String excelPath) { + ArrayList> rowList = flowableExcelParser.parseExcel(excelPath); + 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()); + } + arrayNode.add(jsonNode); + } + return arrayNode; + } + + public JsonNode addNode(JsonNode parent, String path, Object value) { + objectMapper = new ObjectMapper(); + + if (parent == null) { + parent = objectMapper.createObjectNode(); + } + + int delimiterPosition = indexOfFirstDelimiter(path, ".*"); + if (delimiterPosition <= 0) { + if (parent instanceof ObjectNode) { + // Key of a value in an ObjectNode + ((ObjectNode) parent).set(path, objectMapper.valueToTree(value)); + } else { + // Index of a value in an ArrayNode + int index = Integer.parseInt(path); + for (int i = 0; parent.size() <= index; i++) { + ((ArrayNode) parent).add(objectMapper.createObjectNode()); + } + ((ArrayNode) parent).set(index, objectMapper.valueToTree(value)); + } + return parent; + } + + String key = path.substring(0, delimiterPosition); + String newPath = path.substring(delimiterPosition + 1); + String delimiter = path.substring(delimiterPosition, delimiterPosition + 1); + JsonNode child = null; + if (delimiter.equals(".")) { + child = objectMapper.createObjectNode(); + } else if (delimiter.equals("*")) { + child = objectMapper.createArrayNode(); + } + + JsonNode node; + if (parent instanceof ObjectNode) { + // Key of a JsonNode in an ObjectNode + node = parent.get(key); + if (node != null) { + // Take the existing ObjectNode as child + child = node; + } + ((ObjectNode) parent).set(key, addNode(child, newPath, value)); + } else { + // Index of a JsonNode in an ArrayNode + int index = Integer.parseInt(key); + node = parent.get(index); + if (node != null) { + // Take the existing ArrayNode as child + child = node; + } + // Create empty entries to parent ArrayNode + for (int i = 0; parent.size() <= index; i++) { + ((ArrayNode) parent).add(objectMapper.createObjectNode()); + } + ((ArrayNode) parent).set(index, addNode(child, newPath, value)); + } + return parent; + } + + public static int indexOfFirstDelimiter(String str, String delimiters) { + for (int i = 0; i < str.length(); i++) { + for (char c : delimiters.toCharArray()) { + if (str.charAt(i) == c) { + return i; + } + } + } + return -1; + } +} diff --git a/src/main/java/com/example/parser/FlowableExcelParser.java b/src/main/java/com/example/parser/FlowableExcelParser.java index 1efdc27..8ff5a7a 100644 --- a/src/main/java/com/example/parser/FlowableExcelParser.java +++ b/src/main/java/com/example/parser/FlowableExcelParser.java @@ -9,11 +9,11 @@ import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; @Component public class FlowableExcelParser { - public ArrayList> parseExcel(String resourcePath, List colNumberList) { + + public ArrayList> parseExcel(String resourcePath) { ArrayList> rowList = new ArrayList<>(); try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) { Workbook workbook = new XSSFWorkbook(fileInputStream); @@ -22,25 +22,36 @@ public class FlowableExcelParser { for (Row row : sheet) { if (row.getRowNum() == 0) { // Header row - for (Integer colNumber : colNumberList) { - colHeaders.add(row.getCell(colNumber).getStringCellValue()); + 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; } + boolean allCellsNull = true; LinkedHashMap rowMap = new LinkedHashMap<>(); - for (int i = 0; i < colNumberList.size(); i++) { - Integer colNumber = colNumberList.get(i); + for (int colNum = 0; colNum < colHeaders.size(); colNum++) { Object content = null; - Cell cell = row.getCell(colNumber); + Cell cell = row.getCell(colNum); CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell); - if (cellValue != null) { + 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; } + allCellsNull = false; } - rowMap.put(colHeaders.get(i), content); + rowMap.put(colHeaders.get(colNum), content); + } + if (allCellsNull) { + // Take all rows until the first empty row + return rowList; } rowList.add(rowMap); } diff --git a/src/main/java/com/example/parser/ParserApp.java b/src/main/java/com/example/parser/ParserApp.java index 2dc04e3..74ed2aa 100644 --- a/src/main/java/com/example/parser/ParserApp.java +++ b/src/main/java/com/example/parser/ParserApp.java @@ -9,7 +9,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.List; import java.util.Map; @SpringBootApplication @@ -32,6 +31,6 @@ public class ParserApp { ClassLoader classLoader = ParserApp.class.getClassLoader(); URL resourceUrl = classLoader.getResource("flowableExcelParserExampleData.xlsx"); assert resourceUrl != null; - logger.info("parsed excel={}", flowableExcelParser.parseExcel(resourceUrl.getPath(), List.of(0,1,2,4,5,6))); + logger.info("parsed excel={}", flowableExcelParser.parseExcel(resourceUrl.getPath())); } } diff --git a/src/test/java/com/example/parser/FlowableExcelMapperTest.java b/src/test/java/com/example/parser/FlowableExcelMapperTest.java new file mode 100644 index 0000000..e25becc --- /dev/null +++ b/src/test/java/com/example/parser/FlowableExcelMapperTest.java @@ -0,0 +1,50 @@ +package com.example.parser; + +import com.fasterxml.jackson.databind.JsonNode; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.net.URL; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SpringBootTest +class FlowableExcelMapperTest { + + @Autowired + private FlowableExcelMapper flowableExcelMapper; + + private static final Logger logger = LoggerFactory.getLogger(ParserApp.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); + Assertions.assertThat(resourceUrl).isNotNull(); + return resourceUrl.getPath(); + } + + @Test + public void excelToJsonNodeTest() { + JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData1.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 d16e72c..ed4f4de 100644 --- a/src/test/java/com/example/parser/FlowableExcelParserTest.java +++ b/src/test/java/com/example/parser/FlowableExcelParserTest.java @@ -20,12 +20,12 @@ class FlowableExcelParserTest { @Autowired private FlowableExcelParser flowableExcelParser; - private Stream GetArgumentsFromExcel(String path, List colNumberList) { + private Stream GetArgumentsFromExcel(String path) { ArrayList argumentList = new ArrayList<>(); ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader(); URL resourceUrl = classLoader.getResource(path); Assertions.assertThat(resourceUrl).isNotNull(); - ArrayList> rowList = flowableExcelParser.parseExcel(resourceUrl.getPath(), colNumberList); + ArrayList> rowList = flowableExcelParser.parseExcel(resourceUrl.getPath()); Assertions.assertThat(rowList).isNotNull(); for (LinkedHashMap row : rowList) { LinkedHashMap inputMap = new LinkedHashMap<>(); @@ -46,7 +46,7 @@ class FlowableExcelParserTest { @NotNull private Stream booleanExcelParserTestDataProvider() { - return GetArgumentsFromExcel("flowableExcelParserTestData1.xlsx", List.of(0,1,2,4,5)); + return GetArgumentsFromExcel("flowableExcelParserTestData1.xlsx"); } @ParameterizedTest @@ -67,7 +67,7 @@ class FlowableExcelParserTest { @NotNull private Stream stringExcelParserTestDataProvider() { - return GetArgumentsFromExcel("flowableExcelParserTestData2.xlsx", List.of(0,1,2,4,5,6)); + return GetArgumentsFromExcel("flowableExcelParserTestData2.xlsx"); } @ParameterizedTest @@ -92,7 +92,7 @@ class FlowableExcelParserTest { @NotNull private Stream numberExcelParserTestDataProvider() { - return GetArgumentsFromExcel("flowableExcelParserTestData3.xlsx", List.of(0,1,2,4,5,6)); + return GetArgumentsFromExcel("flowableExcelParserTestData3.xlsx"); } @ParameterizedTest diff --git a/src/test/resources/flowableExcelMapperTestData1.xlsx b/src/test/resources/flowableExcelMapperTestData1.xlsx new file mode 100644 index 0000000..60d1406 Binary files /dev/null and b/src/test/resources/flowableExcelMapperTestData1.xlsx differ