From 6578e53f4baf785402e09aea95dc940b29e3916d Mon Sep 17 00:00:00 2001 From: Nikita Marunko Date: Sun, 18 Feb 2024 14:58:36 +0900 Subject: [PATCH] feat(core): support regular expression for column name Feature request Refs: 304 --- .../com/poiji/annotation/ExcelCellName.java | 10 ++- .../poiji/bind/mapping/HSSFUnmarshaller.java | 11 +++ .../com/poiji/bind/mapping/PoijiHandler.java | 11 +++ .../ReadExcelWithRegexInColumnNameTest.java | 65 ++++++++++++++++++ .../deserialize/model/InventoryData.java | 45 ++++++++++++ src/test/java/com/poiji/util/Data.java | 14 ++++ src/test/resources/regex/inventory.xls | Bin 0 -> 26624 bytes src/test/resources/regex/inventory.xlsx | Bin 0 -> 9564 bytes 8 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/poiji/deserialize/ReadExcelWithRegexInColumnNameTest.java create mode 100644 src/test/java/com/poiji/deserialize/model/InventoryData.java create mode 100644 src/test/resources/regex/inventory.xls create mode 100644 src/test/resources/regex/inventory.xlsx diff --git a/src/main/java/com/poiji/annotation/ExcelCellName.java b/src/main/java/com/poiji/annotation/ExcelCellName.java index 9bd09ca..8d51cbc 100644 --- a/src/main/java/com/poiji/annotation/ExcelCellName.java +++ b/src/main/java/com/poiji/annotation/ExcelCellName.java @@ -16,7 +16,7 @@ /** * Specifies the column name where the corresponding value is mapped from the - * excel data + * Excel data * * @return column name */ @@ -35,4 +35,12 @@ * @return mandatory cell signal. Default is false. */ boolean mandatoryCell() default false; + + /** + * Specifies the column regular expression where the corresponding value is mapped from the + * Excel data + * + * @return column regular expression + */ + String expression() default ""; } diff --git a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java index b39e773..9a0f462 100644 --- a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java +++ b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java @@ -36,6 +36,7 @@ import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -279,6 +280,16 @@ private Integer findTitleColumn(ExcelCellName excelCellName) { return titleToIndex.get(titleName); } + if (!StringUtil.isBlank(excelCellName.expression())) { + final String titleName = formatting.transform(options, excelCellName.expression()); + Pattern pattern = Pattern.compile(titleName); + return titleToIndex.entrySet().stream() + .filter(entry -> pattern.matcher(entry.getKey()).matches()) + .findFirst() + .map(Map.Entry::getValue) + .orElse(null); + } + return null; } diff --git a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java index cfb583e..4f2a159 100644 --- a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java +++ b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import java.util.regex.Pattern; import java.util.stream.Stream; import static java.lang.String.valueOf; @@ -190,6 +191,16 @@ private Integer findTitleColumn(ExcelCellName excelCellName) { return titleToIndex.get(titleName); } + if (!StringUtil.isBlank(excelCellName.expression())) { + final String titleName = formatting.transform(options, excelCellName.expression()); + Pattern pattern = Pattern.compile(titleName); + return titleToIndex.entrySet().stream() + .filter(entry -> pattern.matcher(entry.getKey()).matches()) + .findFirst() + .map(Map.Entry::getValue) + .orElse(null); + } + return null; } diff --git a/src/test/java/com/poiji/deserialize/ReadExcelWithRegexInColumnNameTest.java b/src/test/java/com/poiji/deserialize/ReadExcelWithRegexInColumnNameTest.java new file mode 100644 index 0000000..6ab16aa --- /dev/null +++ b/src/test/java/com/poiji/deserialize/ReadExcelWithRegexInColumnNameTest.java @@ -0,0 +1,65 @@ +package com.poiji.deserialize; + +import com.poiji.bind.Poiji; +import com.poiji.deserialize.model.InventoryData; +import com.poiji.option.PoijiOptions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; +import java.util.List; + +import static com.poiji.option.PoijiOptions.PoijiOptionsBuilder.settings; +import static com.poiji.util.Data.unmarshallingInventoryData; +import static org.junit.Assert.assertEquals; + +/** + * Test for Reading Excel columns with Regular expression (Regex) + */ +@RunWith(Parameterized.class) +public class ReadExcelWithRegexInColumnNameTest { + private final String path; + private final List expectedData; + private final PoijiOptions options; + + public ReadExcelWithRegexInColumnNameTest(String path, List expectedData, PoijiOptions options) { + this.path = path; + this.expectedData = expectedData; + this.options = options; + } + + @Parameterized.Parameters(name = "{index}: ({0})={1}") + public static Iterable queries() { + return List.of(new Object[][]{ + { + "src/test/resources/regex/inventory.xlsx", + unmarshallingInventoryData(), + settings().sheetName("Books").build() + }, + { + "src/test/resources/regex/inventory.xlsx", + unmarshallingInventoryData(), + settings().sheetName("Songs").build() + }, + { + "src/test/resources/regex/inventory.xls", + unmarshallingInventoryData(), + settings().sheetName("Books").build() + }, + { + "src/test/resources/regex/inventory.xls", + unmarshallingInventoryData(), + settings().sheetName("Songs").build() + }, + }); + } + + @Test + public void shouldReadInventoryData() { + List actualData = Poiji.fromExcel(new File(path), InventoryData.class, options); + + assertEquals(expectedData, actualData); + } + +} diff --git a/src/test/java/com/poiji/deserialize/model/InventoryData.java b/src/test/java/com/poiji/deserialize/model/InventoryData.java new file mode 100644 index 0000000..ef02aa2 --- /dev/null +++ b/src/test/java/com/poiji/deserialize/model/InventoryData.java @@ -0,0 +1,45 @@ +package com.poiji.deserialize.model; + +import com.poiji.annotation.ExcelCellName; + +import java.util.Objects; + +/** + * An InventoryData POJO. + */ +public class InventoryData { + @ExcelCellName(value = "Id") + private Integer id; + + @ExcelCellName(value = "", expression = "Author|Composer") + private String author; + + public void setId(Integer id) { + this.id = id; + } + + public void setAuthor(String author) { + this.author = author; + } + + @Override + public String toString() { + return "InventoryData{" + + "id='" + id + '\'' + + ", author='" + author + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InventoryData that = (InventoryData) o; + return Objects.equals(id, that.id) && Objects.equals(author, that.author); + } + + @Override + public int hashCode() { + return Objects.hash(id, author); + } +} diff --git a/src/test/java/com/poiji/util/Data.java b/src/test/java/com/poiji/util/Data.java index fc09097..e2af6f0 100644 --- a/src/test/java/com/poiji/util/Data.java +++ b/src/test/java/com/poiji/util/Data.java @@ -1,5 +1,6 @@ package com.poiji.util; +import com.poiji.deserialize.model.InventoryData; import com.poiji.deserialize.model.Student; import com.poiji.deserialize.model.byid.Employee; import com.poiji.deserialize.model.byid.Person; @@ -126,4 +127,17 @@ public static List unmarshallingStudents() { students.add(student); return students; } + + + public static List unmarshallingInventoryData() { + InventoryData record1 = new InventoryData(); + record1.setId(1); + record1.setAuthor("Peter"); + + InventoryData record2 = new InventoryData(); + record2.setId(2); + record2.setAuthor("Maria"); + + return List.of(record1, record2); + } } diff --git a/src/test/resources/regex/inventory.xls b/src/test/resources/regex/inventory.xls new file mode 100644 index 0000000000000000000000000000000000000000..8db604c5f3623693a8b8284377f6cd448d92bce4 GIT binary patch literal 26624 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 AE&u=k literal 0 HcmV?d00001 diff --git a/src/test/resources/regex/inventory.xlsx b/src/test/resources/regex/inventory.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4f02ae8d35714fa8f963dc69ca25c9785b69abb2 GIT binary patch literal 9564 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 EKP~`j