From 5e689df3c563927f3c5dd274032d65eddcbd24fa Mon Sep 17 00:00:00 2001 From: Andreas Isler Date: Fri, 14 Nov 2025 06:45:07 +0100 Subject: [PATCH] Added handling of Excel work books --- .../example/parser/FlowableExcelMapper.java | 83 ++++++---- .../example/parser/FlowableExcelParser.java | 144 +++++++++++------- .../java/com/example/parser/ParserApp.java | 4 +- .../parser/FlowableExcelMapperTest.java | 29 ++-- .../parser/FlowableExcelParserTest.java | 32 ++-- .../flowableExcelMapperTestData.xlsx | Bin 48393 -> 8907 bytes .../parser/FlowableExcelParser$1.class | Bin 825 -> 873 bytes .../example/parser/FlowableExcelParser.class | Bin 4058 -> 5969 bytes .../com/example/parser/ParserApp.class | Bin 3219 -> 3227 bytes .../parser/FlowableExcelParserTest.class | Bin 6316 -> 6463 bytes 10 files changed, 181 insertions(+), 111 deletions(-) diff --git a/src/main/java/com/example/parser/FlowableExcelMapper.java b/src/main/java/com/example/parser/FlowableExcelMapper.java index b9743c5..12bc072 100644 --- a/src/main/java/com/example/parser/FlowableExcelMapper.java +++ b/src/main/java/com/example/parser/FlowableExcelMapper.java @@ -2,7 +2,6 @@ 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; @@ -21,12 +20,37 @@ public class FlowableExcelMapper { protected static FlowableExcelParser flowableExcelParser = new FlowableExcelParser(); public FlowableExcelMapper() { + // Enable ObjectMapper for handling Instant objectMapper.registerModule(javaTimeModule); - objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); } - public ArrayNode excelToJsonNode(String excelPath) { - ArrayList> rows = flowableExcelParser.parseExcel(excelPath); + // 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 + // Delimiter is "." for an ObjectNode and "*" for an ArrayNode + // The 2nd row contains tht type of the variable. Supported types are: + // - String + // - Boolean + // - Integer + // - Double + // - Date + // 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); + return bookToJsonNode(book); + } + + protected ArrayNode bookToJsonNode(ArrayList>> book) { + ArrayNode bookNode = objectMapper.createArrayNode(); + for (ArrayList> sheet : book) { + bookNode.add(sheetToJsonNode(sheet)); + } + return bookNode; + } + + protected ArrayNode sheetToJsonNode(ArrayList> rows) { ArrayList paths = new ArrayList<>(); ArrayList types = new ArrayList<>(); ArrayNode arrayNode = objectMapper.createArrayNode(); @@ -39,31 +63,35 @@ public class FlowableExcelMapper { types.addAll(rows.get(rowNum)); } else { // All other rows contain the values of the variables - JsonNode jsonNode = null; - ArrayList row = rows.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(rowToJsonNode(types, paths, rows.get(rowNum), null)); } } return arrayNode; } + 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(); + 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); + } + return jsonNode; + } + protected JsonNode addNode(JsonNode parent, String path, Object value) { if (parent == null) { parent = objectMapper.createObjectNode(); @@ -77,7 +105,8 @@ public class FlowableExcelMapper { } else { // Index of a value in an ArrayNode int index = Integer.parseInt(path); - for (int i = 0; parent.size() <= index; i++) { + while (parent.size() <= index) { + // Create empty entries to parent ArrayNode ((ArrayNode) parent).add(objectMapper.createObjectNode()); } ((ArrayNode) parent).set(index, objectMapper.valueToTree(value)); @@ -112,8 +141,8 @@ public class FlowableExcelMapper { // Take the existing ArrayNode as child child = node; } - // Create empty entries to parent ArrayNode - for (int i = 0; parent.size() <= index; i++) { + while (parent.size() <= index) { + // Create empty entries to parent ArrayNode ((ArrayNode) parent).add(objectMapper.createObjectNode()); } ((ArrayNode) parent).set(index, addNode(child, newPath, value)); diff --git a/src/main/java/com/example/parser/FlowableExcelParser.java b/src/main/java/com/example/parser/FlowableExcelParser.java index 0ba5da8..2db2707 100644 --- a/src/main/java/com/example/parser/FlowableExcelParser.java +++ b/src/main/java/com/example/parser/FlowableExcelParser.java @@ -9,62 +9,104 @@ import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; + @Component public class FlowableExcelParser { - // 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); - 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; - 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; - } - } - 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 rows; - } - rows.add(cells); - } + // 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 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) { + try (FileInputStream fileInputStream = new FileInputStream(excelPath)) { + Workbook workBook = new XSSFWorkbook(fileInputStream); + return parseBook(workBook); } catch (IOException e) { throw new RuntimeException(e); } - return rows; + } + + protected ArrayList>> parseBook(Workbook workBook ) { + ArrayList>> book = new ArrayList<>(); + FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator(); + for (Sheet workSheet : workBook) { + book.add(parseSheet(workSheet, formulaEvaluator)); + } + return book; + } + + protected 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(); + for (int rowIndex = 0; rowIndex < lastRowNum; rowIndex++) { + Row workRow = workSheet.getRow(rowIndex); + ArrayList row = parseRow(workRow, maxCols, formulaEvaluator); + if (row == null) { + // Take all rows until the first empty row + break; + } else { + sheet.add(row); + } + } + return sheet; + } + + protected ArrayList parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator) { + if (workRow == null) { + // Row is empty + return null; + } + 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; + } + } + } + row.add(content); + } + if (allCellsNull) { + // Row is empty + return null; + } else { + return row; + } + } + + protected int getMaxCols(Row workRow) { + int maxCol = workRow.getLastCellNum(); + for (int colIndex = 0; colIndex < maxCol; colIndex++) { + Cell workCell = workRow.getCell(colIndex); + if (workCell == null || workCell.getCellType() == CellType.BLANK || workCell.getCellType() == CellType._NONE) { + // Count cells until the first empty cell + return colIndex; + } + } + return maxCol; } } diff --git a/src/main/java/com/example/parser/ParserApp.java b/src/main/java/com/example/parser/ParserApp.java index 414987a..0a740ca 100644 --- a/src/main/java/com/example/parser/ParserApp.java +++ b/src/main/java/com/example/parser/ParserApp.java @@ -31,10 +31,10 @@ public class ParserApp { URL resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelParserExampleData.xlsx"); assert resourceUrl != null; - logger.info("parsed excel={}", flowableExcelParser.parseExcel(resourceUrl.getPath())); + logger.info("parsed excel={}", flowableExcelParser.parseExcelBook(resourceUrl.getPath())); resourceUrl = ParserApp.class.getClassLoader().getResource("flowableExcelMapperExampleData.xlsx"); assert resourceUrl != null; - logger.info("mapped excel={}", flowableExcelMapper.excelToJsonNode(resourceUrl.getPath())); + logger.info("mapped excel={}", flowableExcelMapper.excelBookToJsonNode(resourceUrl.getPath())); } } diff --git a/src/test/java/com/example/parser/FlowableExcelMapperTest.java b/src/test/java/com/example/parser/FlowableExcelMapperTest.java index 6227178..deca194 100644 --- a/src/test/java/com/example/parser/FlowableExcelMapperTest.java +++ b/src/test/java/com/example/parser/FlowableExcelMapperTest.java @@ -3,7 +3,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.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -17,6 +16,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.stream.Stream; + @TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringBootTest class FlowableExcelMapperTest { @@ -34,25 +34,24 @@ class FlowableExcelMapperTest { return resourceUrl.getPath(); } - @Test - public void excelToJsonNodeTest() { - JsonNode node = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData.xlsx")); - logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString()); - } - - @NotNull - private Stream excelToJsonNodeParametrizedTestData() { + protected Stream getArgumentsFromExcel(String path) { ArrayList argumentList = new ArrayList<>(); - JsonNode nodes = flowableExcelMapper.excelToJsonNode(getResourcePath("flowableExcelMapperTestData.xlsx")); - for (JsonNode node : nodes) { - argumentList.add(Arguments.of(node)); + JsonNode book = flowableExcelMapper.excelBookToJsonNode(getResourcePath(path)); + for (JsonNode sheet : book) { + for (JsonNode row : sheet) { + argumentList.add(Arguments.of(row)); + } } return argumentList.stream(); } + @NotNull + protected Stream excelMapperTestData() { + return getArgumentsFromExcel("flowableExcelMapperTestData.xlsx"); + } @ParameterizedTest - @MethodSource("excelToJsonNodeParametrizedTestData") - public void excelToJsonNodeParametrizedTest(JsonNode node) { - logger.info("node2={}{}", System.lineSeparator(), node.toPrettyString()); + @MethodSource("excelMapperTestData") + public void excelMapperTest(JsonNode sheet) { + logger.info("node2={}{}", System.lineSeparator(), sheet.toPrettyString()); } } diff --git a/src/test/java/com/example/parser/FlowableExcelParserTest.java b/src/test/java/com/example/parser/FlowableExcelParserTest.java index 21d3352..c641cf2 100644 --- a/src/test/java/com/example/parser/FlowableExcelParserTest.java +++ b/src/test/java/com/example/parser/FlowableExcelParserTest.java @@ -13,6 +13,7 @@ import java.net.URL; import java.util.*; import java.util.stream.Stream; + @TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringBootTest class FlowableExcelParserTest { @@ -27,22 +28,23 @@ class FlowableExcelParserTest { return resourceUrl.getPath(); } - protected Stream GetArgumentsFromExcel(String path) { + protected Stream getArgumentsFromExcel(String path) { + ArrayList>> book = flowableExcelParser.parseExcelBook(getResourcePath(path)); ArrayList argumentList = new ArrayList<>(); - ArrayList> rowList = flowableExcelParser.parseExcel(getResourcePath(path)); - for (ArrayList row : rowList) { - argumentList.add(Arguments.of(row)); + for (ArrayList> sheet : book) { + for (ArrayList row : sheet) { + argumentList.add(Arguments.of(row)); + } } return argumentList.stream(); } @NotNull - private Stream booleanExcelParserTestDataProvider() { - return GetArgumentsFromExcel("flowableExcelParserTestData1.xlsx"); + protected Stream booleanExcelParserTestData() { + return getArgumentsFromExcel("flowableExcelParserTestData1.xlsx"); } - @ParameterizedTest - @MethodSource("booleanExcelParserTestDataProvider") + @MethodSource("booleanExcelParserTestData") 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); @@ -52,12 +54,11 @@ class FlowableExcelParserTest { } @NotNull - private Stream stringExcelParserTestDataProvider() { - return GetArgumentsFromExcel("flowableExcelParserTestData2.xlsx"); + protected Stream stringExcelParserTestData() { + return getArgumentsFromExcel("flowableExcelParserTestData2.xlsx"); } - @ParameterizedTest - @MethodSource("stringExcelParserTestDataProvider") + @MethodSource("stringExcelParserTestData") void stringExcelParserTest(ArrayList row) { String val12 = row.get(0).toString() + row.get(1).toString(); Assertions.assertThat(row.get(3)).isEqualTo(val12); @@ -70,12 +71,11 @@ class FlowableExcelParserTest { } @NotNull - private Stream numberExcelParserTestDataProvider() { - return GetArgumentsFromExcel("flowableExcelParserTestData3.xlsx"); + protected Stream numberExcelParserTestData() { + return getArgumentsFromExcel("flowableExcelParserTestData3.xlsx"); } - @ParameterizedTest - @MethodSource("numberExcelParserTestDataProvider") + @MethodSource("numberExcelParserTestData") void numberExcelParserTest(ArrayList row) { Double val12 = (Double) row.get(0) + (Double) row.get(1); Assertions.assertThat(row.get(3)).isEqualTo(val12); diff --git a/src/test/resources/flowableExcelMapperTestData.xlsx b/src/test/resources/flowableExcelMapperTestData.xlsx index effac2142d08e7c03ed59482a316bfb591eb18eb..89864929e6897104665c1027c9af384bf2ef0ff4 100644 GIT binary patch delta 6427 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 diff --git a/target/classes/com/example/parser/FlowableExcelParser$1.class b/target/classes/com/example/parser/FlowableExcelParser$1.class index 83a8f4b275757ddeb72ab0b93aea26a18c0b58c5..307048093c3529f08c76e90bf0432d7cc21a14cd 100644 GIT binary patch delta 241 zcmY+8y-vbl6o#L#H2vDzp-og+6sZ){`jeQ5Gl?N2N}5QFcf!iR1u%4XaA7V3i40zV zcfw5=Uc0%R=RGISIXPe6&I^Bjx8J}ccU3qu%>akfh&Hjoj*8dG+sn|>DHrC$T$iac z&dy|;xWxg{U&1HlTqNSCd*yo-Halnu# tu|cMFuRb!Bf!;wD9OJ)R8I%7-H!o`Ik}J_ASG}nxNzSc8SpTRH`+LHA8iN1; delta 172 zcmaFKwv&zP)W2Q(7#JAL8FV&sl`%52GssPz&Nz=zbFvwehpH@4hEYI*ft!H`#A4uJ zkY`W;(uzP{8v`?t1=KltF_RhdMh51|ubCtxm>HCSVr*a;Lk49an+b?jfLM@$Eu5V} ml))EHFt9MFf)%JSr~_#h1`Qyo1s2g@&<4_6U@L(P0S*A~4ie7* diff --git a/target/classes/com/example/parser/FlowableExcelParser.class b/target/classes/com/example/parser/FlowableExcelParser.class index b8e4d160518fb726f36ba96c92c62bf07c8c0b57..316f7a3fd4c18c9ec087b8ac300bf9206f1daf94 100644 GIT binary patch literal 5969 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 S^765r#oyQp>J}UGs{aqFK2=-* literal 4058 zcmb7HTXz%J75(gax*Gr)fLZo6cB$xqQ)0x&_P3YS2)l)YJ+@n)`1l z;&D1R*`b8j3+%NElQA=Irlzb|-cHA69cLm|bgV+w9=9^Fr;^Fub9UkUm~EfeP>0P* z;ynV}$`Yw}l}-u`BZ3Brtva^B5Qwf5?>MW3uLu^btE>W<`HeN!h2P75fDw3Dlv$?n(wIGFlMB0f~b;4&fnz2R5>x zBsW~vFf^C91e%*S4z%J?lpP@)M!Uoj9gpDA3RzuY67YBRcP36Up&Mp%h2B%rq2s91 z;D07Dm^iM$bWF!%cwAtUvYfzn({}EJmC0KLfwoHauc2qSy3j4rqvJSwZ-@RGY_3<; zkW#X&Ub~PjX3XOk%uLaAZQ5|4f(`3AXvGQiNj$0JB>DwfH%i)iW`UqpQqAn0Y_1sB zj>`ExOHWjS6Bv{@rQ_o`O<<(#Y|NT9v-u3mf>~fai1lXd8FMUS9iL5EnN!|g^q@d2 znw&|ysi^@ouP%GER7;|)B~izT-mE0il{5=fjrM66Qoub!pl{^9;_>X$h`)0>o>CWG z+J*6S&dewoBRWR$2?2GpN_EX67udqA+*a}w$J08V!KVc3$kAon8OzL-Sy*pJG>jo7 zF|NbH1Tl9@M46WAFQ|YG^0c9OWVMnqMTgQdr6Y~$l3l#%V^-mok{%5iWF>M+iM^%= z^w>oPSk>#E)sjow6fE(qjshH3E^~Z*U0p9VkPhX9hN6xOs=@eE8QZZWW>?yX*K3BR z3Z-0Xn8BxYd z+;r9hjCZc#MSM;*kmn>`VOh94kO}y@*^8L6{&dbNMRQ02QDAewoid4#LR!6-H+}9@ znh|R5zdP|A>^>gCJzNX4+^JWVu(Y-XlQGDf;1x*Ps2&A&R=$z~Aw^pT#Df(XJVGjy zDLdy{IX1}6tDs%Zx;my%ud9eY;)4lhPux(r>R|%9naOmM)kzeU17bvhM!geW|HJJ7 zg?pNUGv(2sQjK5f^wj5fRG$~vwV`dL7S9N0%~{Vnrhj7XfFod(?7MyTp&FsC(arN! zAXP3C;&!o+vQC-q)Y@>Cha={bJ~9LiYZ1m?SVmd-ey)YY(c2SqjQSqGQyj~*cOgKD zs7bnJ>O5<#r(WXMYwAYHYHkf!?vy>w;%sIu*DBC~P%=H4Gu>i=@ff;WX#an$0Qze1vD%XRvX(!F=+L_DQuGh|K=3WmCPF{_s6Uuspte8U|EZb$&W8HQ( zZ|4Yq1|yuX9CyG{WtFUYUoK}Ax-%xrmV+j|%n@7tRe@kbfd4vZ30*E@MYBBe2KpWEfQ`I%;R;# zw}g)gETNMjC-D>HuMzQDqviullo{f6Mx7_&j9TNDF>nu7Xv!a zwUNIle+L!GaJ+UNjq2%>@4{3sHR@&ZU7U~CR=0-?&8S^K{@g2JF~9ssB=iPqTI1pU z3vi9_l~zOZ&ZL;X@ej2(k8N>Z=|PYC4WHp(z?|WGpHdmz7gcaS`+n;cW^$8=i!+!J zog(3_=ftRZn*A#%j)^Ru=l-->;e$%-Z?KR5w;;$7auXUjfo;MF8Kycj@UU z{)O-1`v}pCf8q^XTT|UEHlA>qpC~d`4BaD~w+6^E;geyKeCf$Jan R^U?bR7WkxjPkg8k{uhniJm>%b diff --git a/target/classes/com/example/parser/ParserApp.class b/target/classes/com/example/parser/ParserApp.class index 0399618824c84f34cc45b9b99e5478463d169f1e..3e73505445ce68e92f3e6b0569023db1c8c52770 100644 GIT binary patch delta 43 scmbO%Ia_i=7#lxdL1IyHs%u4ZYK~KWe)i-ZjIxuP*e`{9WTpK9{h)O2T`K*>VZ3|q_dLJnp3gbw zdv~92yhJ&E<>31O7UQo0C0nprA!C7xg~(@c^NnUVUoRAy3se+h5swN>?8WfOC{a<0 zGKSo3OLJWe_Re5eB(!34YpA1o!-ncC|fle<<9|tE?;egtdmP;gi;4e#x3+s;XpI zPErbwlE$q(#M1ayC2ep(*~6H|d(>p!sY?7|wUnfostr!6DzhI}a0DO7_)x`zILa^D zj(R_45E!%I69$pd1@(?_IMh|s5sXAa5gDKH3A;}^!H~rl*|K<^!!AuQKZ}NTg6X1D>OHrB8^HqP=u%P3{lBwo6l8l(_ls#S<~S`2H)hW>tUtjW+A8c`~%3# zza7DP`PZ5;J$`lnCzPmdPH(GW9I+D&UZdZa5zm*F?=C6|$rU>5#Oke(!uEF-r}*x*Gn zNj_vFfcaR59JC-8A>C;MGV|U zsCpsd1#-=&N+p8q`Y*_f6eO|XC1Zo`|1$YU$zQ-L*iO{MD6(GWK7J%=-re8n07d8k z&0ql;rwk~>lcwOR{y1h*i;`Zzt9UK31FsW(gB}Z*2ZjXn<4s=f?q$3AN%u`IA050B zEJGP0@Z%9WWS8CEhCSFzaU!teE#eZG5|i*K6=oN`KT1&r?86qKNyNQP^c~W_yA~{h zzJJl|Ahm=Tq4BhK#<9BT7-VS#*9BH_gTMv|*p!EBOOZl=vQdrOP=i{mpx)LJm^!{K z+1tFIIwz2&5`)BvWOLvEagx!v1jz1Lfs9=Xa!3csGgIbVzilgT5M7+mJ)Ux=XFrB1 z{~9c({D&$3%|`yi*W|xtR{0NImA_K~eR-&VPf){l(xmDWR-g2v$vAsSKQ*T9B?Vpl z+}zZjpBww(ZjzEt6P-i!7oy?_rp9ogdEGGn94AL+jBN#^X)FMJ4FwrzWn7x3^DsM! z5PeE<^x-;=&+)}F4JR>)ub`D;gbV^kjp=g{cTk_a5XSHx4v|U!1G1BPoYWr~>#=_T DJL^;z delta 1629 zcma)6`*Raj6#nk+Cc9m>d8am|V@=Z`q_l=UfLa7Ag=WNpWy#g3f9^hNLwlt6h%SBx8eh+ii-RRI>fV^>eyc}JK1~Ao^$TG-?!h% zPE-$6si&?SJ_=v~E)S~#!75`mgPSwe?N$+lmPdYr?GFEO?ZPG$&n_4Amj0O2x zDM;(X(mEF5m!u{ZOwF}qM7I`Av~RdC7Ed;J=t%}S(Y29B@^0l}HNv;&HS%utfk5fH zuCC5lG;SWzVjPxO9n+IbqseGXqN}^(o>(HK&DhUM{dzLd5pTar9Y3ShFh4KJ_iEF& z7w2otc2va#j>$N#VI$t*^KB=}PA~|JDR`ejWOPBhB_59@8at!99@Axf$WX!i@+(9c zr}+wd4f}*|wwJR{`G7s5e2y|;iGI3JebLkJ0rcgOl4LoT0V|o+XzA{$`7Kvd`=qI zr%}DKGL5==$tR8BMuuk)FuWxU#}KTyR7gi4w23}T>nNJiXuhLAbG9OlmLc=Vy|mgP zT)|8kLG!mLghwe@WFA)nh46cO;YSrpu>=7uLzxM_mJn6JiJjO*9wD2eVR&2dBvDhS z{%Ohz@M0czlP3~5P(yKQ=N{Ubt$Oy~s^=)XkE#Sbj~!$!3|kH60{S@fdP1wyxV`Np zWN{Q<85~3y9IRj~Wqe~oC!ZoAzRm3pW;?xr7qc~ZiR{aCD_|a&Prxg9m7jL+3LAZF zu#}4p>@O^qv9^Jk{R&KeVbqX74XB1r-h-&+PkH<;{dkR#>9lV@d0Bp0SvS+0*+WRO z2o7Kib$3zi>tqKg{>GhPI{N%=p{)=4S zwH6(q09|}TVPWt0t^IJfNv