Added ExcelMapper

This commit is contained in:
Andreas Isler
2025-11-07 19:38:42 +01:00
parent bbb97952f0
commit 8279a73c0d
9 changed files with 185 additions and 22 deletions
+1
View File
@@ -31,3 +31,4 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
/target/
+2 -1
View File
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager"> <component name="MavenProjectsManager">
@@ -73,7 +74,7 @@
</profile-state> </profile-state>
</entry> </entry>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="homebrew-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
+6 -5
View File
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version> <version>3.5.7</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.example</groupId> <groupId>com.example</groupId>
@@ -27,7 +28,7 @@
<url/> <url/>
</scm> </scm>
<properties> <properties>
<java.version>17</java.version> <java.version>11</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -54,12 +55,12 @@
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId> <artifactId>poi</artifactId>
<version>5.2.5</version> <version>5.4.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId> <artifactId>poi-ooxml</artifactId>
<version>5.2.5</version> <version>5.4.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains</groupId> <groupId>org.jetbrains</groupId>
@@ -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<LinkedHashMap<String, Object>> rowList = flowableExcelParser.parseExcel(excelPath);
ArrayNode arrayNode = objectMapper.createArrayNode();
for (LinkedHashMap<String, Object> row : rowList) {
JsonNode jsonNode = null;
Set<Map.Entry<String, Object>> entries = row.entrySet();
for (Map.Entry<String, Object> 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;
}
}
@@ -9,11 +9,11 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
@Component @Component
public class FlowableExcelParser { public class FlowableExcelParser {
public ArrayList<LinkedHashMap<String, Object>> parseExcel(String resourcePath, List<Integer> colNumberList) {
public ArrayList<LinkedHashMap<String, Object>> parseExcel(String resourcePath) {
ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>(); ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>();
try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) { try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) {
Workbook workbook = new XSSFWorkbook(fileInputStream); Workbook workbook = new XSSFWorkbook(fileInputStream);
@@ -22,25 +22,36 @@ public class FlowableExcelParser {
for (Row row : sheet) { for (Row row : sheet) {
if (row.getRowNum() == 0) { if (row.getRowNum() == 0) {
// Header row // Header row
for (Integer colNumber : colNumberList) { for (int colNum = 0; colNum < row.getLastCellNum(); colNum++) {
colHeaders.add(row.getCell(colNumber).getStringCellValue()); 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; continue;
} }
boolean allCellsNull = true;
LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>(); LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>();
for (int i = 0; i < colNumberList.size(); i++) { for (int colNum = 0; colNum < colHeaders.size(); colNum++) {
Integer colNumber = colNumberList.get(i);
Object content = null; Object content = null;
Cell cell = row.getCell(colNumber); Cell cell = row.getCell(colNum);
CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell); CellValue cellValue = workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell);
if (cellValue != null) { if (cellValue != null && cell.getCellType() != CellType.BLANK && cell.getCellType() != CellType._NONE) {
switch (cellValue.getCellType()) { switch (cellValue.getCellType()) {
case STRING : content = cellValue.getStringValue(); break; case STRING : content = cellValue.getStringValue(); break;
case BOOLEAN : content = cellValue.getBooleanValue(); break; case BOOLEAN : content = cellValue.getBooleanValue(); break;
case NUMERIC : content = cellValue.getNumberValue(); 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); rowList.add(rowMap);
} }
@@ -9,7 +9,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.List;
import java.util.Map; import java.util.Map;
@SpringBootApplication @SpringBootApplication
@@ -32,6 +31,6 @@ public class ParserApp {
ClassLoader classLoader = ParserApp.class.getClassLoader(); ClassLoader classLoader = ParserApp.class.getClassLoader();
URL resourceUrl = classLoader.getResource("flowableExcelParserExampleData.xlsx"); URL resourceUrl = classLoader.getResource("flowableExcelParserExampleData.xlsx");
assert resourceUrl != null; 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()));
} }
} }
@@ -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());
}
}
@@ -20,12 +20,12 @@ class FlowableExcelParserTest {
@Autowired @Autowired
private FlowableExcelParser flowableExcelParser; private FlowableExcelParser flowableExcelParser;
private Stream<Arguments> GetArgumentsFromExcel(String path, List<Integer> colNumberList) { private Stream<Arguments> GetArgumentsFromExcel(String path) {
ArrayList<Arguments> argumentList = new ArrayList<>(); ArrayList<Arguments> argumentList = new ArrayList<>();
ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader(); ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader();
URL resourceUrl = classLoader.getResource(path); URL resourceUrl = classLoader.getResource(path);
Assertions.assertThat(resourceUrl).isNotNull(); Assertions.assertThat(resourceUrl).isNotNull();
ArrayList<LinkedHashMap<String, Object>> rowList = flowableExcelParser.parseExcel(resourceUrl.getPath(), colNumberList); ArrayList<LinkedHashMap<String, Object>> rowList = flowableExcelParser.parseExcel(resourceUrl.getPath());
Assertions.assertThat(rowList).isNotNull(); Assertions.assertThat(rowList).isNotNull();
for (LinkedHashMap<String, Object> row : rowList) { for (LinkedHashMap<String, Object> row : rowList) {
LinkedHashMap<String, Object> inputMap = new LinkedHashMap<>(); LinkedHashMap<String, Object> inputMap = new LinkedHashMap<>();
@@ -46,7 +46,7 @@ class FlowableExcelParserTest {
@NotNull @NotNull
private Stream<Arguments> booleanExcelParserTestDataProvider() { private Stream<Arguments> booleanExcelParserTestDataProvider() {
return GetArgumentsFromExcel("flowableExcelParserTestData1.xlsx", List.of(0,1,2,4,5)); return GetArgumentsFromExcel("flowableExcelParserTestData1.xlsx");
} }
@ParameterizedTest @ParameterizedTest
@@ -67,7 +67,7 @@ class FlowableExcelParserTest {
@NotNull @NotNull
private Stream<Arguments> stringExcelParserTestDataProvider() { private Stream<Arguments> stringExcelParserTestDataProvider() {
return GetArgumentsFromExcel("flowableExcelParserTestData2.xlsx", List.of(0,1,2,4,5,6)); return GetArgumentsFromExcel("flowableExcelParserTestData2.xlsx");
} }
@ParameterizedTest @ParameterizedTest
@@ -92,7 +92,7 @@ class FlowableExcelParserTest {
@NotNull @NotNull
private Stream<Arguments> numberExcelParserTestDataProvider() { private Stream<Arguments> numberExcelParserTestDataProvider() {
return GetArgumentsFromExcel("flowableExcelParserTestData3.xlsx", List.of(0,1,2,4,5,6)); return GetArgumentsFromExcel("flowableExcelParserTestData3.xlsx");
} }
@ParameterizedTest @ParameterizedTest
Binary file not shown.