From caa1e4b8f803a04db053b57807c79237b93f77ec Mon Sep 17 00:00:00 2001 From: Andreas Isler Date: Tue, 18 Nov 2025 20:36:53 +0100 Subject: [PATCH] Parse Excel as String only --- .idea/misc.xml | 2 +- .../example/parser/FlowableExcelMapper.java | 55 ++++++++----- .../example/parser/FlowableExcelParser.java | 72 +++++++++--------- .../java/com/example/parser/ParserApp.java | 9 +-- .../parser/FlowableExcelMapperTest.java | 13 +--- .../parser/FlowableExcelParserTest.java | 70 ++++++++--------- .../flowableExcelMapperTestData.xlsx | Bin 8907 -> 9034 bytes .../flowableExcelParserTestDataBoolean.xlsx | Bin 6734 -> 6772 bytes .../flowableExcelParserTestDataString.xlsx | Bin 7090 -> 7164 bytes 9 files changed, 105 insertions(+), 116 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index b169464..6cb1a77 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -74,7 +74,7 @@ - + \ No newline at end of file diff --git a/src/main/java/com/example/parser/FlowableExcelMapper.java b/src/main/java/com/example/parser/FlowableExcelMapper.java index a32b5a2..e2a5ee1 100644 --- a/src/main/java/com/example/parser/FlowableExcelMapper.java +++ b/src/main/java/com/example/parser/FlowableExcelMapper.java @@ -6,10 +6,11 @@ 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.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.*; @@ -28,7 +29,7 @@ public class FlowableExcelMapper { // excelPath defines the location of the Excel file // FlowableExcelParser is used to create an ArrayList from the Excel file - // The first row of every sheet contains the path of the variables to be created in the JsonNode + // The 1st row of every sheet contains the path of the variables to be created in the JsonNode // Delimiter is "." for an ObjectNode and "*" for an ArrayNode // The 2nd row contains tht type of the variable. Supported types are: // - String @@ -39,22 +40,22 @@ public class FlowableExcelMapper { // All sheets are added to an ArrayNode // All rows are added to an ArrayNode in the sheets ArrayNode // All cell values are added to an ObjectNode in the rows ArrayNode - public ArrayNode excelBookToJsonNode(String excelPath) { - ArrayList>> book = flowableExcelParser.parseExcelBook(excelPath); + public ArrayNode excelBookResourceToJsonNode(String resourcePath) { + ArrayList>> book = flowableExcelParser.parseExcelBookFromResource(resourcePath); return bookToJsonNode(book); } - protected ArrayNode bookToJsonNode(ArrayList>> book) { + protected ArrayNode bookToJsonNode(ArrayList>> book) { ArrayNode bookNode = objectMapper.createArrayNode(); - for (ArrayList> sheet : book) { + for (ArrayList> sheet : book) { bookNode.add(sheetToJsonNode(sheet)); } return bookNode; } - protected ArrayNode sheetToJsonNode(ArrayList> rows) { - ArrayList paths = new ArrayList<>(); - ArrayList types = new ArrayList<>(); + protected ArrayNode sheetToJsonNode(ArrayList> rows) { + ArrayList paths = new ArrayList<>(); + ArrayList types = new ArrayList<>(); ArrayNode arrayNode = objectMapper.createArrayNode(); for (int rowNum = 0; rowNum < rows.size(); rowNum++) { if (rowNum == 0) { @@ -71,25 +72,37 @@ public class FlowableExcelMapper { return arrayNode; } - protected JsonNode rowToJsonNode(ArrayList types, ArrayList paths, ArrayList row, JsonNode jsonNode) { + protected JsonNode rowToJsonNode(ArrayList types, ArrayList paths, ArrayList row, JsonNode jsonNode) { for (int cellNum = 0; cellNum < row.size(); cellNum++) { - Object content = row.get(cellNum); - if (content != null) { - String type = types.get(cellNum).toString(); + String cellValue = row.get(cellNum); + if (cellValue == null || cellValue.isEmpty()) { + jsonNode = addNode(jsonNode, paths.get(cellNum), null); + } else { + String type = types.get(cellNum); + Object content; 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 "String": content = cellValue; break; + case "Boolean": content = Boolean.parseBoolean(cellValue); break; + case "Integer": content = Integer.parseInt(cellValue); break; + case "Double": content = Double.parseDouble(cellValue); break; + case "Long": content = Long.parseLong(cellValue); 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()); + Instant instant; + try { + LocalDateTime localDateTime = LocalDateTime.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + } catch (DateTimeParseException e) { + LocalDate localDate = LocalDate.parse(cellValue, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); + } + content = instant.plusSeconds(ZonedDateTime.now().getOffset().getTotalSeconds()); break; - default: break; + default: + throw new RuntimeException("Variable type not supported: " + type); } + jsonNode = addNode(jsonNode, paths.get(cellNum), content); } - jsonNode = addNode(jsonNode, paths.get(cellNum).toString(), content); } return jsonNode; } diff --git a/src/main/java/com/example/parser/FlowableExcelParser.java b/src/main/java/com/example/parser/FlowableExcelParser.java index 1150503..4f9ce68 100644 --- a/src/main/java/com/example/parser/FlowableExcelParser.java +++ b/src/main/java/com/example/parser/FlowableExcelParser.java @@ -7,6 +7,7 @@ import org.springframework.stereotype.Component; import java.io.FileInputStream; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -16,15 +17,15 @@ public class FlowableExcelParser { // excelPath defines the location of the Excel file // Return all cells in all sheets with the following restrictions: - // - Max columns per sheet is defined in the first row + // - Max column count per sheet is defined in the first row // - Max column count starts with the 1st cell and ends with the 1st empty cell // - Parsing the sheet stops after the 1st empty row // All sheets are added to an ArrayList // All rows are added to an ArrayList in the sheets ArrayList // All cell values are added to an ArrayList in the rows ArrayList // Formulas in cells are evaluated - // Excel values can have only 3 types: String, boolean, double - public ArrayList>> parseExcelBook(String excelPath) { + // All values are returned as String + private ArrayList>> parseExcelBook(String excelPath) { try (FileInputStream fileInputStream = new FileInputStream(excelPath)) { Workbook workBook = new XSSFWorkbook(fileInputStream); return parseBook(workBook); @@ -33,9 +34,13 @@ public class FlowableExcelParser { } } + public ArrayList>> parseExcelBookFromResource(String excelResourcePath) { + return parseExcelBook(getResourcePath(excelResourcePath)); + } + @Deprecated - public ArrayList> parseExcelSheet(String excelPath) { - try (FileInputStream fileInputStream = new FileInputStream(excelPath)) { + public ArrayList> parseExcelSheetFromResource(String excelResourcePath) { + try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(excelResourcePath))) { Workbook workBook = new XSSFWorkbook(fileInputStream); FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator(); Sheet workSheet = workBook.getSheetAt(0); @@ -45,8 +50,17 @@ public class FlowableExcelParser { } } - protected ArrayList>> parseBook(Workbook workBook ) { - ArrayList>> book = new ArrayList<>(); + private String getResourcePath(String resourcePath) { + ClassLoader classLoader = FlowableExcelParser.class.getClassLoader(); + URL resourceUrl = classLoader.getResource(resourcePath); + if (resourceUrl == null) { + throw new RuntimeException("Resource not found: " + resourcePath); + } + return resourceUrl.getPath(); + } + + private ArrayList>> parseBook(Workbook workBook ) { + ArrayList>> book = new ArrayList<>(); FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator(); for (Sheet workSheet : workBook) { book.add(parseSheet(workSheet, formulaEvaluator)); @@ -54,14 +68,15 @@ public class FlowableExcelParser { return book; } - protected ArrayList> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) { - ArrayList> sheet = new ArrayList<>(); + private ArrayList> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) { + ArrayList> sheet = new ArrayList<>(); // Count columns of first row int maxCols = getMaxCols(workSheet.getRow(0)); int lastRowNum = workSheet.getLastRowNum(); + DataFormatter dataFormatter = new DataFormatter(); for (int rowIndex = 0; rowIndex < lastRowNum; rowIndex++) { Row workRow = workSheet.getRow(rowIndex); - ArrayList row = parseRow(workRow, maxCols, formulaEvaluator); + ArrayList row = parseRow(workRow, maxCols, formulaEvaluator, dataFormatter); if (row == null) { // Take all rows until the first empty row break; @@ -72,34 +87,19 @@ public class FlowableExcelParser { return sheet; } - protected ArrayList parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator) { + private ArrayList parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator, DataFormatter dataFormatter) { if (workRow == null) { // Row is empty return null; } - ArrayList row = new ArrayList<>(); + ArrayList row = new ArrayList<>(); boolean allCellsNull = true; for (int colIndex = 0; colIndex < maxCols; colIndex++) { - Object content = null; Cell workCell = workRow.getCell(colIndex); - if (workCell != null) { - CellType workCellType = workCell.getCellType(); - if (workCellType != CellType.BLANK && workCellType != CellType._NONE) { - allCellsNull = false; - switch (workCellType) { - case STRING : content = workCell.getStringCellValue(); break; - case BOOLEAN : content = workCell.getBooleanCellValue(); break; - case NUMERIC : content = workCell.getNumericCellValue(); break; - case FORMULA : - CellValue evaluatedValue = formulaEvaluator.evaluate(workCell); - switch (evaluatedValue.getCellType()) { - case STRING : content = evaluatedValue.getStringValue(); break; - case BOOLEAN : content = evaluatedValue.getBooleanValue(); break; - case NUMERIC : content = evaluatedValue.getNumberValue(); break; - } - break; - } - } + formulaEvaluator.evaluate(workCell); + String content = dataFormatter.formatCellValue(workCell, formulaEvaluator); + if (content != null && !content.isEmpty()) { + allCellsNull = false; } row.add(content); } @@ -111,7 +111,7 @@ public class FlowableExcelParser { } } - protected int getMaxCols(Row workRow) { + private int getMaxCols(Row workRow) { int maxCol = workRow.getLastCellNum(); for (int colIndex = 0; colIndex < maxCol; colIndex++) { Cell workCell = workRow.getCell(colIndex); @@ -124,15 +124,15 @@ public class FlowableExcelParser { } @Deprecated - public ArrayList> parseExcel(String resourcePath) { - try (FileInputStream fileInputStream = new FileInputStream(resourcePath)) { + public ArrayList> parseExcelFromResource(String resourcePath) { + try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(resourcePath))) { Workbook workBook = new XSSFWorkbook(fileInputStream); FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator(); - ArrayList> sheet = parseSheet(workBook.getSheetAt(0), formulaEvaluator); + ArrayList> sheet = parseSheet(workBook.getSheetAt(0), formulaEvaluator); ArrayList colHeaders = new ArrayList<>(); ArrayList> rowList = new ArrayList<>(); for (int rowIndex = 0; rowIndex < sheet.size(); rowIndex++) { - ArrayList row = sheet.get(rowIndex); + ArrayList row = sheet.get(rowIndex); if (rowIndex == 0) { // Header row for (Object cell : row) { diff --git a/src/main/java/com/example/parser/ParserApp.java b/src/main/java/com/example/parser/ParserApp.java index 0a740ca..1ef30ac 100644 --- a/src/main/java/com/example/parser/ParserApp.java +++ b/src/main/java/com/example/parser/ParserApp.java @@ -8,7 +8,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.util.Map; @SpringBootApplication @@ -29,12 +28,8 @@ public class ParserApp { throw new RuntimeException(e); } - URL resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelParserExampleData.xlsx"); - assert resourceUrl != null; - logger.info("parsed excel={}", flowableExcelParser.parseExcelBook(resourceUrl.getPath())); + logger.info("parsed excel={}", flowableExcelParser.parseExcelBookFromResource("flowableExcelParserExampleData.xlsx")); - resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelMapperExampleData.xlsx"); - assert resourceUrl != null; - logger.info("mapped excel={}", flowableExcelMapper.excelBookToJsonNode(resourceUrl.getPath())); + logger.info("mapped excel={}", flowableExcelMapper.excelBookResourceToJsonNode("flowableExcelMapperExampleData.xlsx")); } } diff --git a/src/test/java/com/example/parser/FlowableExcelMapperTest.java b/src/test/java/com/example/parser/FlowableExcelMapperTest.java index deca194..035c9a1 100644 --- a/src/test/java/com/example/parser/FlowableExcelMapperTest.java +++ b/src/test/java/com/example/parser/FlowableExcelMapperTest.java @@ -1,8 +1,6 @@ package com.example.parser; import com.fasterxml.jackson.databind.JsonNode; -import org.assertj.core.api.Assertions; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -12,7 +10,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.net.URL; import java.util.ArrayList; import java.util.stream.Stream; @@ -27,16 +24,9 @@ class FlowableExcelMapperTest { private static final Logger logger = LoggerFactory.getLogger(FlowableExcelMapperTest.class); - protected String getResourcePath(String path) { - ClassLoader classLoader = FlowableExcelMapperTest.class.getClassLoader(); - URL resourceUrl = classLoader.getResource(path); - Assertions.assertThat(resourceUrl).isNotNull(); - return resourceUrl.getPath(); - } - protected Stream getArgumentsFromExcel(String path) { ArrayList argumentList = new ArrayList<>(); - JsonNode book = flowableExcelMapper.excelBookToJsonNode(getResourcePath(path)); + JsonNode book = flowableExcelMapper.excelBookResourceToJsonNode(path); for (JsonNode sheet : book) { for (JsonNode row : sheet) { argumentList.add(Arguments.of(row)); @@ -45,7 +35,6 @@ class FlowableExcelMapperTest { return argumentList.stream(); } - @NotNull protected Stream excelMapperTestData() { return getArgumentsFromExcel("flowableExcelMapperTestData.xlsx"); } diff --git a/src/test/java/com/example/parser/FlowableExcelParserTest.java b/src/test/java/com/example/parser/FlowableExcelParserTest.java index 936b2c2..4531e13 100644 --- a/src/test/java/com/example/parser/FlowableExcelParserTest.java +++ b/src/test/java/com/example/parser/FlowableExcelParserTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import java.net.URL; import java.util.*; import java.util.stream.Stream; @@ -21,23 +20,16 @@ class FlowableExcelParserTest { @Autowired private FlowableExcelParser flowableExcelParser; - protected String getResourcePath(String path) { - ClassLoader classLoader = FlowableExcelParserTest.class.getClassLoader(); - URL resourceUrl = classLoader.getResource(path); - Assertions.assertThat(resourceUrl).isNotNull(); - return resourceUrl.getPath(); - } - - protected Stream getRowsFromExcel(String path) { + protected Stream getArgumentsFromExcelBook(String resourcePath) { ArrayList argumentList = new ArrayList<>(); - ArrayList>> book = flowableExcelParser.parseExcelBook(getResourcePath(path)); - for (ArrayList> sheet : book) { - for (ArrayList row : sheet) { + ArrayList>> book = flowableExcelParser.parseExcelBookFromResource(resourcePath); + for (ArrayList> sheet : book) { + for (ArrayList row : sheet) { argumentList.add(Arguments.of(row)); } } - ArrayList> sheet = flowableExcelParser.parseExcelSheet(getResourcePath(path)); - for (ArrayList row : sheet) { + ArrayList> sheet = flowableExcelParser.parseExcelSheetFromResource(resourcePath); + for (ArrayList row : sheet) { argumentList.add(Arguments.of(row)); } return argumentList.stream(); @@ -45,21 +37,21 @@ class FlowableExcelParserTest { @NotNull protected Stream booleanExcelParserTestData() { - return getRowsFromExcel("flowableExcelParserTestDataBoolean.xlsx"); + return getArgumentsFromExcelBook("flowableExcelParserTestDataBoolean.xlsx"); } @ParameterizedTest @MethodSource("booleanExcelParserTestData") public 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 valOr = Boolean.parseBoolean(row.get(0).toString()) || Boolean.parseBoolean(row.get(1).toString()) || Boolean.parseBoolean(row.get(2).toString()); + Assertions.assertThat(Boolean.parseBoolean(row.get(3).toString())).isEqualTo(valOr); - Boolean valAnd = (Boolean) row.get(0) && (Boolean) row.get(1) && (Boolean) row.get(2); - Assertions.assertThat(row.get(4)).isEqualTo(valAnd); + Boolean valAnd = Boolean.parseBoolean(row.get(0).toString()) && Boolean.parseBoolean(row.get(1).toString()) && Boolean.parseBoolean(row.get(2).toString()); + Assertions.assertThat(Boolean.parseBoolean(row.get(4).toString())).isEqualTo(valAnd); } @NotNull protected Stream stringExcelParserTestData() { - return getRowsFromExcel("flowableExcelParserTestDataString.xlsx"); + return getArgumentsFromExcelBook("flowableExcelParserTestDataString.xlsx"); } @ParameterizedTest @MethodSource("stringExcelParserTestData") @@ -76,25 +68,25 @@ class FlowableExcelParserTest { @NotNull protected Stream numberExcelParserTestData() { - return getRowsFromExcel("flowableExcelParserTestDataInteger.xlsx"); + return getArgumentsFromExcelBook("flowableExcelParserTestDataInteger.xlsx"); } @ParameterizedTest @MethodSource("numberExcelParserTestData") public void numberExcelParserTest(ArrayList row) { - Double val12 = (Double) row.get(0) + (Double) row.get(1); - Assertions.assertThat(row.get(3)).isEqualTo(val12); + Integer val12 = Integer.parseInt(row.get(0).toString()) + Integer.parseInt(row.get(1).toString()); + Assertions.assertThat(Integer.parseInt(row.get(3).toString())).isEqualTo(val12); - Double val23 = (Double) row.get(1) + (Double) row.get(2); - Assertions.assertThat(row.get(4)).isEqualTo(val23); + Integer val23 = Integer.parseInt(row.get(1).toString()) + Integer.parseInt(row.get(2).toString()); + Assertions.assertThat(Integer.parseInt(row.get(4).toString())).isEqualTo(val23); - Double val123 = (Double) row.get(0) + (Double) row.get(1) + (Double) row.get(2); - Assertions.assertThat(row.get(5)).isEqualTo(val123); + Integer val123 = Integer.parseInt(row.get(0).toString()) + Integer.parseInt(row.get(1).toString()) + Integer.parseInt(row.get(2).toString()); + Assertions.assertThat(Integer.parseInt(row.get(5).toString())).isEqualTo(val123); } @Deprecated - protected Stream getArgumentsFromExcel(String path) { + protected Stream getArgumentsFromExcel(String resourcePath) { ArrayList argumentList = new ArrayList<>(); - ArrayList> rowList = flowableExcelParser.parseExcel(getResourcePath(path)); + ArrayList> rowList = flowableExcelParser.parseExcelFromResource(resourcePath); for (LinkedHashMap row : rowList) { LinkedHashMap inputMap = new LinkedHashMap<>(); LinkedHashMap outputMap = new LinkedHashMap<>(); @@ -122,12 +114,12 @@ class FlowableExcelParserTest { 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); + Boolean val = Boolean.parseBoolean(inputMap.get("input1").toString()) || Boolean.parseBoolean(inputMap.get("input2").toString()) || Boolean.parseBoolean(inputMap.get("input3").toString()); + Assertions.assertThat(Boolean.parseBoolean(entry.getValue().toString())).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); + Boolean val = Boolean.parseBoolean(inputMap.get("input1").toString()) && Boolean.parseBoolean(inputMap.get("input2").toString()) && Boolean.parseBoolean(inputMap.get("input3").toString()); + Assertions.assertThat(Boolean.parseBoolean(entry.getValue().toString())).isEqualTo(val); } } } @@ -166,16 +158,16 @@ class FlowableExcelParserTest { 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); + Integer val12 = Integer.parseInt(inputMap.get("input1").toString()) + Integer.parseInt(inputMap.get("input2").toString()); + Assertions.assertThat(Integer.parseInt(entry.getValue().toString())).isEqualTo(val12); } if (key.equals("output2")) { - Double val = (Double)inputMap.get("input2") + (Double)inputMap.get("input3"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); + Integer val23 = Integer.parseInt(inputMap.get("input2").toString()) + Integer.parseInt(inputMap.get("input3").toString()); + Assertions.assertThat(Integer.parseInt(entry.getValue().toString())).isEqualTo(val23); } if (key.equals("output3")) { - Double val = (Double)inputMap.get("input1") + (Double)inputMap.get("input2") + (Double)inputMap.get("input3"); - Assertions.assertThat(entry.getValue()).isEqualTo(val); + Integer val12 = Integer.parseInt(inputMap.get("input1").toString()) + Integer.parseInt(inputMap.get("input2").toString()) + Integer.parseInt(inputMap.get("input3").toString()); + Assertions.assertThat(Integer.parseInt(entry.getValue().toString())).isEqualTo(val12); } } } diff --git a/src/test/resources/flowableExcelMapperTestData.xlsx b/src/test/resources/flowableExcelMapperTestData.xlsx index 89864929e6897104665c1027c9af384bf2ef0ff4..cdb53f0e6bbe4f662b2a92b93e2303ac1e0c7819 100644 GIT binary patch delta 5245 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 delta 5135 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 diff --git a/src/test/resources/flowableExcelParserTestDataBoolean.xlsx b/src/test/resources/flowableExcelParserTestDataBoolean.xlsx index 8d775a646ef9d8e9b1113235da4a5b861f83bf92..6ef263833411e1e03695a46521c42fd94f8e57ee 100644 GIT binary patch delta 3167 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 delta 3196 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 GIsXG42?W~! delta 3379 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 u42-Z2F)qK%JO*~qk5qbcC