From 3054ad58552fb6651dce0b3aa9a7214eb4be56e9 Mon Sep 17 00:00:00 2001 From: vaa25 Date: Sun, 17 Mar 2024 12:08:00 +0200 Subject: [PATCH] 1.5.0 add Unmarshaller.readSheetNames() --- src/main/java/com/poiji/bind/FromExcel.java | 29 ++- .../java/com/poiji/bind/Unmarshaller.java | 3 + .../bind/mapping/CsvUnmarshallerStream.java | 26 ++- .../poiji/bind/mapping/HSSFUnmarshaller.java | 29 +-- .../poiji/bind/mapping/SheetUnmarshaller.java | 12 +- .../poiji/bind/mapping/XSSFUnmarshaller.java | 181 ++++++++++-------- .../bind/mapping/XSSFUnmarshallerFile.java | 36 ++-- .../bind/mapping/XSSFUnmarshallerStream.java | 41 ++-- src/main/java/com/poiji/util/ReflectUtil.java | 13 ++ .../deserialize/ReadExcelBySheetNameTest.java | 69 +++---- .../poiji/deserialize/ReadSheetNamesTest.java | 57 ++++++ src/test/resources/calculations.xls | Bin 0 -> 34304 bytes 12 files changed, 310 insertions(+), 186 deletions(-) create mode 100644 src/test/java/com/poiji/deserialize/ReadSheetNamesTest.java create mode 100644 src/test/resources/calculations.xls diff --git a/src/main/java/com/poiji/bind/FromExcel.java b/src/main/java/com/poiji/bind/FromExcel.java index d189b41..68240f6 100644 --- a/src/main/java/com/poiji/bind/FromExcel.java +++ b/src/main/java/com/poiji/bind/FromExcel.java @@ -5,6 +5,8 @@ import com.poiji.exception.PoijiExcelType; import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; +import org.apache.poi.ss.usermodel.Sheet; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -13,7 +15,6 @@ import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; -import org.apache.poi.ss.usermodel.Sheet; public class FromExcel { @@ -42,6 +43,12 @@ public List toList() { return result; } + public List readSheetNames() { + validateSource(); + validateOptions(); + return source.getDeserializer(options).readSheetNames(); + } + public Stream toStream() { validate(); final Stream stream = source.getDeserializer(options).stream(javaType); @@ -53,17 +60,29 @@ public Stream toStream() { } private void validate() { - if (source == null) { - throw new PoijiException("Source must be set"); - } + validateSource(); + validateJavaType(); + validateOptions(); + } + + private void validateJavaType() { if (javaType == null) { throw new PoijiException("Class must be set"); } + } + + private void validateOptions() { if (options == null){ options = PoijiOptions.PoijiOptionsBuilder.settings().build(); } } + private void validateSource() { + if (source == null) { + throw new PoijiException("Source must be set"); + } + } + public FromExcel withConsumer(final Consumer consumer) { this.consumer = consumer; return this; @@ -173,4 +192,4 @@ public Unmarshaller getDeserializer(final PoijiOptions options) { } } -} \ No newline at end of file +} diff --git a/src/main/java/com/poiji/bind/Unmarshaller.java b/src/main/java/com/poiji/bind/Unmarshaller.java index 5c71e73..5baff30 100644 --- a/src/main/java/com/poiji/bind/Unmarshaller.java +++ b/src/main/java/com/poiji/bind/Unmarshaller.java @@ -1,5 +1,6 @@ package com.poiji.bind; +import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; @@ -11,4 +12,6 @@ public interface Unmarshaller { void unmarshal(Class type, Consumer consumer); Stream stream(Class type); + + List readSheetNames(); } diff --git a/src/main/java/com/poiji/bind/mapping/CsvUnmarshallerStream.java b/src/main/java/com/poiji/bind/mapping/CsvUnmarshallerStream.java index 1e44bf9..e66dc18 100644 --- a/src/main/java/com/poiji/bind/mapping/CsvUnmarshallerStream.java +++ b/src/main/java/com/poiji/bind/mapping/CsvUnmarshallerStream.java @@ -4,10 +4,12 @@ import com.poiji.bind.Unmarshaller; import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import com.poiji.util.ReflectUtil; + +import java.io.*; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; @@ -47,6 +49,20 @@ public Stream stream(final Class type) { } } + /** + * Excel uses file name as sheet name for CSV format. + */ + @Override + public List readSheetNames() { + final InputStream stream = poijiStream.stream(); + if (stream instanceof FileInputStream) { + final String path = ReflectUtil.getFieldData("path", stream); + final String fileName = Paths.get(path).getFileName().toString(); + return Collections.singletonList(fileName.substring(0, fileName.lastIndexOf("."))); + } + return options.preferNullOverDefault() ? null : Collections.emptyList(); + } + private static class BomInputStream extends InputStream{ private final InputStream inner; @@ -73,7 +89,7 @@ public Optional getCharset(){ } @Override - public int read() throws IOException { + public int read() { return 0; } diff --git a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java index ee1c13e..40e46f1 100644 --- a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java +++ b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java @@ -4,22 +4,16 @@ import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; import com.poiji.save.TransposeUtil; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.BaseFormulaEvaluator; +import org.apache.poi.ss.usermodel.*; + import java.io.IOException; -import java.util.Iterator; -import java.util.Optional; -import java.util.Spliterator; -import java.util.Spliterators; +import java.util.*; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.formula.BaseFormulaEvaluator; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellType; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; /** * This is the main class that converts the excel sheet fromExcel Java object @@ -47,6 +41,17 @@ public void unmarshal(Class type, Consumer consumer) { } } + @Override + public List readSheetNames() { + try (final HSSFWorkbook workbook = (HSSFWorkbook) workbook()) { + final List result = new ArrayList<>(); + workbook.forEach(sheet-> result.add(sheet.getSheetName())); + return result; + } catch (final IOException e) { + throw new PoijiException("Problem occurred while closing HSSFWorkbook", e); + } + } + private Sheet getSheet(final Class type, final HSSFWorkbook workbook) { if (options.getTransposed()){ TransposeUtil.transpose(workbook); diff --git a/src/main/java/com/poiji/bind/mapping/SheetUnmarshaller.java b/src/main/java/com/poiji/bind/mapping/SheetUnmarshaller.java index eb6c049..3f60d6e 100644 --- a/src/main/java/com/poiji/bind/mapping/SheetUnmarshaller.java +++ b/src/main/java/com/poiji/bind/mapping/SheetUnmarshaller.java @@ -2,8 +2,6 @@ import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; -import java.util.function.Consumer; -import java.util.stream.Stream; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Sheet; @@ -11,6 +9,11 @@ import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator; import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + /** * Created by hakan on 11.10.2020 */ @@ -29,6 +32,11 @@ public void unmarshal(Class type, Consumer consumer) { processRowsToObjects(sheet, type, consumer); } + @Override + public List readSheetNames() { + return Collections.singletonList(sheet.getSheetName()); + } + private void setBaseFormulaEvaluator() { Workbook workbook = workbook(); if (workbook instanceof HSSFWorkbook) { diff --git a/src/main/java/com/poiji/bind/mapping/XSSFUnmarshaller.java b/src/main/java/com/poiji/bind/mapping/XSSFUnmarshaller.java index a24a732..4dd0071 100644 --- a/src/main/java/com/poiji/bind/mapping/XSSFUnmarshaller.java +++ b/src/main/java/com/poiji/bind/mapping/XSSFUnmarshaller.java @@ -4,18 +4,6 @@ import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; import com.poiji.save.TransposeUtil; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.function.Consumer; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import javax.xml.parsers.ParserConfigurationException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.poifs.filesystem.DocumentFactoryHelper; @@ -34,6 +22,15 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + import static org.apache.poi.openxml4j.opc.PackageAccess.READ_WRITE; /** @@ -47,78 +44,84 @@ abstract class XSSFUnmarshaller implements Unmarshaller { this.options = options; } - protected void unmarshal0(Class type, Consumer consumer, OPCPackage open) - throws ParserConfigurationException, IOException, SAXException, OpenXML4JException - { - if (options.getTransposed()) { - if (open.getPackageAccess() == READ_WRITE) { - final XSSFWorkbook workbook = new XSSFWorkbook(open); - TransposeUtil.transpose(workbook); - workbook.write(new OutputStream() { - @Override - public void write(final int b) { - } - }); - } else { - throw new UnsupportedOperationException("Can't apply transposition for streamed XLSX source"); - } - } - - ReadOnlySharedStringsTable readOnlySharedStringsTable = new ReadOnlySharedStringsTable(open); - XSSFReader workbookReader = new XSSFReader(open); - StylesTable styles = workbookReader.getStylesTable(); - XMLReader reader = XMLHelper.newXMLReader(); - - InputSource is = new InputSource(workbookReader.getWorkbookData()); + @Override + public void unmarshal(Class type, Consumer consumer) { + openFileAndExecute(opcPackage -> unmarshal0(type, consumer, opcPackage)); + } - reader.setContentHandler(new WorkBookContentHandler(options)); - reader.parse(is); + protected void unmarshal0(Class type, Consumer consumer, OPCPackage open) { + try { + if (options.getTransposed()) { + if (open.getPackageAccess() == READ_WRITE) { + final XSSFWorkbook workbook = new XSSFWorkbook(open); + TransposeUtil.transpose(workbook); + workbook.write(new OutputStream() { + @Override + public void write(final int b) { + } + }); + } else { + throw new UnsupportedOperationException("Can't apply transposition for streamed XLSX source"); + } + } + ReadOnlySharedStringsTable readOnlySharedStringsTable = new ReadOnlySharedStringsTable(open); + XSSFReader workbookReader = new XSSFReader(open); + StylesTable styles = workbookReader.getStylesTable(); + XMLReader reader = XMLHelper.newXMLReader(); - WorkBookContentHandler wbch = (WorkBookContentHandler) reader.getContentHandler(); - List sheets = wbch.getSheets(); - if (sheets.isEmpty()) { - throw new PoijiException("no excel sheets found"); - } - PoijiNumberFormat poijiNumberFormat = options.getPoijiNumberFormat(); - if (poijiNumberFormat != null) { - poijiNumberFormat.overrideExcelNumberFormats(styles); - } + InputSource is = new InputSource(workbookReader.getWorkbookData()); - SheetIterator iter = (SheetIterator) workbookReader.getSheetsData(); - int sheetCounter = 0; + reader.setContentHandler(new WorkBookContentHandler(options)); + reader.parse(is); - Optional maybeSheetName = SheetNameExtractor.getSheetName(type, options); + WorkBookContentHandler wbch = (WorkBookContentHandler) reader.getContentHandler(); + List sheets = wbch.getSheets(); + if (sheets.isEmpty()) { + throw new PoijiException("no excel sheets found"); + } + PoijiNumberFormat poijiNumberFormat = options.getPoijiNumberFormat(); + if (poijiNumberFormat != null) { + poijiNumberFormat.overrideExcelNumberFormats(styles); + } - if (!maybeSheetName.isPresent()) { - int requestedIndex = options.sheetIndex(); - int nonHiddenSheetIndex = 0; - while (iter.hasNext()) { - try (InputStream stream = iter.next()) { - WorkBookSheet wbs = sheets.get(sheetCounter); - if (wbs.getState().equals("visible")) { - if (nonHiddenSheetIndex == requestedIndex) { - processSheet(styles, reader, readOnlySharedStringsTable, type, stream, consumer); - return; + SheetIterator iter = (SheetIterator) workbookReader.getSheetsData(); + int sheetCounter = 0; + + Optional maybeSheetName = SheetNameExtractor.getSheetName(type, options); + + if (!maybeSheetName.isPresent()) { + int requestedIndex = options.sheetIndex(); + int nonHiddenSheetIndex = 0; + while (iter.hasNext()) { + try (InputStream stream = iter.next()) { + WorkBookSheet wbs = sheets.get(sheetCounter); + if (wbs.getState().equals("visible")) { + if (nonHiddenSheetIndex == requestedIndex) { + processSheet(styles, reader, readOnlySharedStringsTable, type, stream, consumer); + return; + } + nonHiddenSheetIndex++; } - nonHiddenSheetIndex++; } + sheetCounter++; } - sheetCounter++; - } - } else { - String sheetName = maybeSheetName.get(); - while (iter.hasNext()) { - try (InputStream stream = iter.next()) { - WorkBookSheet wbs = sheets.get(sheetCounter); - if (wbs.getState().equals("visible")) { - if (iter.getSheetName().equalsIgnoreCase(sheetName)) { - processSheet(styles, reader, readOnlySharedStringsTable, type, stream, consumer); - return; + } else { + String sheetName = maybeSheetName.get(); + while (iter.hasNext()) { + try (InputStream stream = iter.next()) { + WorkBookSheet wbs = sheets.get(sheetCounter); + if (wbs.getState().equals("visible")) { + if (iter.getSheetName().equalsIgnoreCase(sheetName)) { + processSheet(styles, reader, readOnlySharedStringsTable, type, stream, consumer); + return; + } } } + sheetCounter++; } - sheetCounter++; } + } catch (IOException | OpenXML4JException | ParserConfigurationException | SAXException e) { + throw new PoijiException("Problem occurred while reading data: " + e.getMessage(), e); } } @@ -268,15 +271,13 @@ private Stream streamSheet( } - protected final void listOfEncryptedItems(Class type, Consumer consumer, POIFSFileSystem fs) throws IOException { - InputStream stream = DocumentFactoryHelper.getDecryptedStream(fs, options.getPassword()); - - try (OPCPackage open = OPCPackage.open(stream)) { - unmarshal0(type, consumer, open); - - } catch (ParserConfigurationException | SAXException | IOException | OpenXML4JException e) { - IOUtils.closeQuietly(fs); - throw new PoijiException("Problem occurred while reading data", e); + protected final void applyInEncryptedOpcPackage(Consumer process, POIFSFileSystem fileSystem) { + try (InputStream stream = DocumentFactoryHelper.getDecryptedStream(fileSystem, options.getPassword()); + OPCPackage open = OPCPackage.open(stream)) { + process.accept(open); + } catch (IOException | OpenXML4JException e) { + IOUtils.closeQuietly(fileSystem); + throw new PoijiException("Problem occurred while reading data: " + e.getMessage(), e); } } @@ -292,4 +293,22 @@ protected final Stream streamOfEncryptedItems(Class type, POIFSFileSys } } + protected abstract void openFileAndExecute(Consumer process); + + @Override + public List readSheetNames() { + final List result = new ArrayList<>(); + openFileAndExecute(opcPackage -> readSheetNames(opcPackage, result)); + return result; + } + + private void readSheetNames(OPCPackage opcPackage, List result) { + try { + final XSSFWorkbook workbook = new XSSFWorkbook(opcPackage); + workbook.forEach(sheet -> result.add(sheet.getSheetName())); + } catch (IOException e) { + throw new PoijiException("Problem occurred while reading data: " + e.getMessage(), e); + } + } + } diff --git a/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerFile.java b/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerFile.java index 8a1b67c..3ccd364 100644 --- a/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerFile.java +++ b/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerFile.java @@ -3,16 +3,17 @@ import com.poiji.bind.PoijiFile; import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; -import java.io.IOException; -import java.util.function.Consumer; -import java.util.stream.Stream; -import javax.xml.parsers.ParserConfigurationException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.function.Consumer; +import java.util.stream.Stream; + import static org.apache.poi.openxml4j.opc.PackageAccess.READ; import static org.apache.poi.openxml4j.opc.PackageAccess.READ_WRITE; @@ -28,13 +29,13 @@ final class XSSFUnmarshallerFile extends XSSFUnmarshaller { this.poijiFile = poijiFile; } - @Override - public void unmarshal(Class type, Consumer consumer) { + + protected void openFileAndExecute(Consumer process) { if (options.getPassword() != null) { - returnFromEncryptedFile(type, consumer); + applyInFileSystem(fileSystem-> applyInEncryptedOpcPackage(process, fileSystem)); } else { - returnFromExcelFile(type,consumer); + applyInOpcPackage(process); } } @@ -52,27 +53,20 @@ public Stream stream(final Class type) { } catch (ParserConfigurationException | SAXException | IOException | OpenXML4JException e) { throw new PoijiException("Problem occurred while reading data", e); } - } - public void returnFromExcelFile(Class type, Consumer consumer) { + private void applyInOpcPackage(Consumer process) { final PackageAccess packageAccess = options.getTransposed() ? READ_WRITE : READ; - try (OPCPackage open = OPCPackage.open(poijiFile.file(), packageAccess)) { - - unmarshal0(type, consumer, open); - - } catch (ParserConfigurationException | SAXException | IOException | OpenXML4JException e) { - throw new PoijiException("Problem occurred while reading data", e); + process.accept(open); + } catch (IOException | OpenXML4JException e) { + throw new PoijiException("Problem occurred while reading data: " + e.getMessage(), e); } } - private void returnFromEncryptedFile(Class type, Consumer consumer) { - + private void applyInFileSystem(Consumer process) { try (POIFSFileSystem fs = new POIFSFileSystem(poijiFile.file(), true)) { - - listOfEncryptedItems(type, consumer, fs); - + process.accept(fs); } catch (IOException e) { throw new PoijiException("Problem occurred while reading data", e); } diff --git a/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerStream.java b/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerStream.java index 6290be5..2a2597b 100644 --- a/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerStream.java +++ b/src/main/java/com/poiji/bind/mapping/XSSFUnmarshallerStream.java @@ -3,15 +3,16 @@ import com.poiji.bind.PoijiInputStream; import com.poiji.exception.PoijiException; import com.poiji.option.PoijiOptions; -import java.io.IOException; -import java.util.function.Consumer; -import java.util.stream.Stream; -import javax.xml.parsers.ParserConfigurationException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.function.Consumer; +import java.util.stream.Stream; + /** * Created by hakan on 22/10/2017 */ @@ -24,16 +25,6 @@ final class XSSFUnmarshallerStream extends XSSFUnmarshaller { this.poijiInputStream = poijiInputStream; } - @Override - public void unmarshal(Class type, Consumer consumer) { - - if (options.getPassword() != null) { - returnFromEncryptedFile(type, consumer); - } else { - returnFromExcelFile(type, consumer); - } - } - @Override public Stream stream(final Class type) { try { @@ -49,20 +40,28 @@ public Stream stream(final Class type) { } } - private void returnFromExcelFile(Class type, Consumer consumer) { + private void applyInOpcPackage(Consumer process) { try (OPCPackage open = OPCPackage.open(poijiInputStream.stream())) { - unmarshal0(type, consumer, open); - } catch (ParserConfigurationException | SAXException | IOException | OpenXML4JException e) { - throw new PoijiException("Problem occurred while reading data", e); + process.accept(open); + } catch (IOException | OpenXML4JException e) { + throw new PoijiException("Problem occurred while reading data: " + e.getMessage(), e); } } - private void returnFromEncryptedFile(Class type, Consumer consumer) { - try (POIFSFileSystem fs = new POIFSFileSystem(poijiInputStream.stream())) { - listOfEncryptedItems(type, consumer, fs); + private void applyInFileSystem(Consumer process) { + try (POIFSFileSystem fileSystem = new POIFSFileSystem(poijiInputStream.stream())) { + applyInEncryptedOpcPackage(process, fileSystem); } catch (IOException e) { throw new PoijiException("Problem occurred while reading data", e); } } + @Override + protected void openFileAndExecute(Consumer process) { + if (options.getPassword() != null) { + applyInFileSystem(process); + } else { + applyInOpcPackage(process); + } + } } diff --git a/src/main/java/com/poiji/util/ReflectUtil.java b/src/main/java/com/poiji/util/ReflectUtil.java index 4d703c8..9dca4f6 100644 --- a/src/main/java/com/poiji/util/ReflectUtil.java +++ b/src/main/java/com/poiji/util/ReflectUtil.java @@ -5,6 +5,7 @@ import com.poiji.exception.IllegalCastException; import com.poiji.exception.PoijiException; import com.poiji.exception.PoijiInstantiationException; + import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -170,4 +171,16 @@ public static void setAccessible(Field field) { field.setAccessible(true); } } + + public static T getFieldData(String fieldName, Object instance) { + try { + final Field field = instance.getClass().getDeclaredField(fieldName); + setAccessible(field); + return (T) field.get(instance); + } catch (IllegalAccessException e) { + throw new IllegalCastException("Unexpected cast type {"); + } catch (NoSuchFieldException e) { + throw new PoijiException(e.getMessage(), e); + } + } } diff --git a/src/test/java/com/poiji/deserialize/ReadExcelBySheetNameTest.java b/src/test/java/com/poiji/deserialize/ReadExcelBySheetNameTest.java index 9ddf384..2ebfbb7 100644 --- a/src/test/java/com/poiji/deserialize/ReadExcelBySheetNameTest.java +++ b/src/test/java/com/poiji/deserialize/ReadExcelBySheetNameTest.java @@ -1,56 +1,47 @@ package com.poiji.deserialize; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -import java.io.File; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; -import java.util.Arrays; -import java.util.List; - +import com.poiji.bind.Poiji; +import com.poiji.deserialize.model.byid.Calculation; +import com.poiji.option.PoijiOptions; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import com.poiji.bind.Poiji; -import com.poiji.deserialize.model.byid.Calculation; -import com.poiji.option.PoijiOptions; +import java.io.File; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; @RunWith(Parameterized.class) public class ReadExcelBySheetNameTest { - - private String path; - - public ReadExcelBySheetNameTest(String path) { - this.path = path; - } - - @Parameterized.Parameters(name = "{index}: ({0})={1}") - public static Iterable queries() { - return Arrays.asList(new Object[][]{ - {"src/test/resources/calculations.xlsx"}, - }); - } - @Test - public void shouldReadExcelBySheetName() { + private String path; - final DateTimeFormatter formatter = new DateTimeFormatterBuilder() - .appendPattern("M/d/") - .appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2, LocalDate.now().minusYears(80)).toFormatter(); + public ReadExcelBySheetNameTest(String path) { + this.path = path; + } - PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().sheetName("SI calculations").dateFormatter(formatter).build(); + @Parameterized.Parameters(name = "{index}: ({0})={1}") + public static Iterable queries() { + return asList(new Object[][]{ + {"src/test/resources/calculations.xlsx"}, + {"src/test/resources/calculations.xls"}, + {"src/test/resources/SI calculations.csv"}, + }); + } - List calculations = Poiji.fromExcel(new File(path), Calculation.class, options); + @Test + public void shouldReadExcelBySheetName() { + PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().sheetName("SI calculations").build(); - for (Calculation calculation : calculations) { + List calculations = Poiji.fromExcel(new File(path), Calculation.class, options); - assertThat(calculation.getToDate().toString(), is("2018-06-30")); - assertThat(calculation.getFromDate().toString(), is("2018-01-01")); - } - } + assertThat(calculations.get(0).getIs(), is("ABXXXXXXXXXX")); + assertThat(calculations.get(1).getIs(), is("BCXXXXXXXXXX")); + assertThat(calculations.get(2).getIs(), is("CDXXXXXXXXXX")); + assertThat(calculations.get(3).getIs(), is("DEXXXXXXXXXX")); + } } diff --git a/src/test/java/com/poiji/deserialize/ReadSheetNamesTest.java b/src/test/java/com/poiji/deserialize/ReadSheetNamesTest.java new file mode 100644 index 0000000..f915f18 --- /dev/null +++ b/src/test/java/com/poiji/deserialize/ReadSheetNamesTest.java @@ -0,0 +1,57 @@ +package com.poiji.deserialize; + +import com.poiji.bind.Poiji; +import com.poiji.exception.PoijiExcelType; +import com.poiji.option.PoijiOptions; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Collections; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ReadSheetNamesTest { + + @Test + public void readSheetNamesFromExcelFormat() throws IOException { + readFromExcelFormat(new File("src/test/resources/calculations.xlsx")); + readFromExcelFormat(new File("src/test/resources/calculations.xls")); + } + + private void readFromExcelFormat(File file) throws IOException { + final List expected = asList("Explanatory note", "SI calculations"); + + assertEquals(expected, Poiji.fromExcel().withSource(file).readSheetNames()); + + final InputStream inputStream = Files.newInputStream(file.toPath()); + final PoijiExcelType excelType = PoijiExcelType.fromFileName(file.getName()); + assertEquals(expected, Poiji.fromExcel().withSource(inputStream, excelType).readSheetNames()); + } + + @Test + public void readSheetNamesFromCsvFile() { + final List expected = Collections.singletonList("SI calculations"); + + final File file = new File("src/test/resources/SI calculations.csv"); + assertEquals(expected, Poiji.fromExcel().withSource(file).readSheetNames()); + } + + @Test + public void readSheetNamesFromCsvStream() throws IOException { + + final File file = new File("src/test/resources/SI calculations.csv"); + final PoijiExcelType excelType = PoijiExcelType.CSV; + + assertEquals(Collections.emptyList(), Poiji.fromExcel().withSource(Files.newInputStream(file.toPath()), excelType).readSheetNames()); + + final PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().preferNullOverDefault(true).build(); + assertNull(Poiji.fromExcel().withSource(Files.newInputStream(file.toPath()), excelType).withOptions(options).readSheetNames()); + } + +} diff --git a/src/test/resources/calculations.xls b/src/test/resources/calculations.xls new file mode 100644 index 0000000000000000000000000000000000000000..b457d911ed632917e697feb6be4a94a8310c3618 GIT binary patch literal 34304 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