Ver Fonte

new: assira.xls

Ranides Atterwim há 4 anos atrás
pai
commit
d1b701b8df

+ 70 - 0
assira.xls/pom.xml

@@ -0,0 +1,70 @@
+<?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"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>assira.project</artifactId>
+        <groupId>net.ranides</groupId>
+        <version>2.8.2-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>assira.xls</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.groupId}:${project.artifactId}</name>
+    <description>assira: excel support</description>
+
+    <properties>
+        <assira.junit.debug>false</assira.junit.debug>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>net.ranides</groupId>
+            <artifactId>assira.commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.ranides</groupId>
+            <artifactId>assira.core</artifactId>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>net.ranides</groupId>
+            <artifactId>assira.junit</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>5.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>5.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 125 - 0
assira.xls/src/main/java/net/ranides/assira/xls/XLSReader.java

@@ -0,0 +1,125 @@
+package net.ranides.assira.xls;
+
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import net.ranides.assira.collection.iterators.IteratorUtils;
+import net.ranides.assira.collection.query.CQuery;
+import net.ranides.assira.math.MathStats;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Random;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class XLSReader {
+
+    private final Path path;
+    private final XSSFWorkbook wb;
+    private final XSSFSheet sh;
+
+    private double accuracy = 0.1;
+    private boolean skipHeader = false;
+
+    public XLSReader(String path) throws IOException {
+        this(Paths.get(path));
+    }
+
+    public XLSReader(Path path) throws IOException {
+        this.path = path;
+        this.wb = new XSSFWorkbook(new FileInputStream(path.toFile()));
+        this.sh = wb.getSheetAt(0);
+    }
+
+    public static Function<Path, Optional<XLSReader>> loader(BiConsumer<Path, IOException> onError) {
+        return file -> load(file, onError);
+    }
+
+    public static Optional<XLSReader> load(Path path, BiConsumer<Path, IOException> onError) {
+        try {
+            return Optional.of(new XLSReader(path));
+        } catch (IOException ex) {
+            onError.accept(path, ex);
+            return Optional.empty();
+        }
+    }
+
+    public Path path() {
+        return path;
+    }
+
+    public XLSReader accuracy(double accuracy) {
+        this.accuracy = accuracy;
+        return this;
+    }
+
+    public int detectColumns() {
+        return CQuery.from().iterable(sh)
+                .skip(1)
+                .filter(heuristicFilter())
+                .map(Row::getLastCellNum)
+                .applyThis(MathStats::mode)
+                .getValue();
+    }
+
+    public boolean detectHeader() {
+        CQuery<XLSUtils.RowInfo> stat = CQuery.from().iterable(sh)
+                .skip(1)
+                .filter(heuristicFilter())
+                .map(XLSUtils::info);
+
+
+        MathStats.Mode<String> back = MathStats.mode(stat.map(r -> r.get(0)).filter(Objects::nonNull).map(XLSUtils.CellInfo::getBack));
+        MathStats.Mode<String> fore = MathStats.mode(stat.map(r -> r.get(0)).filter(Objects::nonNull).map(XLSUtils.CellInfo::getFore));
+
+        XLSUtils.RowInfo head = XLSUtils.info(CQuery.from().iterable(sh).first().orElse(null));
+
+        if(2 * back.getCount() > back.getDomain() && !head.get(0).getBack().equals(back.getValue())) {
+            return true;
+        }
+        if(2 * fore.getCount() > fore.getDomain() && !head.get(0).getFore().equals(fore.getValue())) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private Predicate<Row> heuristicFilter() {
+        Random rand = new Random(3);
+        return v -> v.getRowNum() < 20 || rand.nextDouble()<=accuracy;
+    }
+
+    public XLSReader skipHeader() {
+        return skipHeader(detectHeader());
+    }
+
+    public XLSReader skipHeader(boolean option) {
+        this.skipHeader = option;
+        return this;
+    }
+
+    public Optional<Row> header() {
+        return header(false);
+    }
+
+    public Optional<Row> header(boolean force) {
+        return force || detectHeader() ? IteratorUtils.first(sh.iterator()) : Optional.empty();
+    }
+
+    public CQuery<Row> rows(){
+        return CQuery.from().iterable(sh).skip(skipHeader ? 1 : 0);
+    }
+
+    public CQuery<List<String>> cells(){
+        return CQuery.from().iterable(sh).skip(skipHeader ? 1 : 0).map(XLSUtils::text);
+    }
+
+}

+ 178 - 0
assira.xls/src/main/java/net/ranides/assira/xls/XLSUtils.java

@@ -0,0 +1,178 @@
+package net.ranides.assira.xls;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.UtilityClass;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFFont;
+
+import net.ranides.assira.collection.lists.VirtualList;
+import net.ranides.assira.collection.query.CQuery;
+import net.ranides.assira.text.StringTraits;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@UtilityClass
+public class XLSUtils {
+
+    public static List<String> text(Optional<Row> row) {
+        return text(row.orElse(null));
+    }
+
+    public static List<String> text(Row row) {
+        if(row == null) {
+            return Collections.emptyList();
+        }
+        return new RowAsList(row);
+    }
+
+    public static String text(Cell cell) {
+        if(cell == null) {
+            return "";
+        }
+        switch(cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case NUMERIC:
+                return String.valueOf(cell.getNumericCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return "";
+        }
+    }
+
+    public static CellInfo info(Cell cell) {
+        return new CellInfo(cell);
+    }
+
+    public static RowInfo info(Row row) {
+        return new RowInfo(row);
+    }
+
+    public static String fore(Cell cell) {
+        CellStyle style = cell.getCellStyle();
+        if(style instanceof HSSFCellStyle) {
+            HSSFCellStyle hs = (HSSFCellStyle) style;
+            HSSFWorkbook wb = (HSSFWorkbook)cell.getSheet().getWorkbook();
+            return color(hs.getFont(wb).getHSSFColor(wb)).orElse("#000000");
+        }
+        if(style instanceof XSSFCellStyle) {
+            XSSFCellStyle xs = (XSSFCellStyle) style;
+            XSSFFont font = xs.getFont();
+            return color(font.getXSSFColor()).orElse("#000000");
+        }
+        return "#000000";
+    }
+
+
+    public static String back(Cell cell) {
+        return color(cell.getCellStyle().getFillForegroundColorColor()).orElse("#ffffff");
+    }
+
+    public static Optional<String> color(Color color) {
+        if(color instanceof HSSFColor) {
+            return Optional.of(color0((HSSFColor) color));
+        }
+        if(color instanceof ExtendedColor) {
+            return Optional.of(color0((ExtendedColor) color));
+        }
+        return Optional.empty();
+    }
+
+    private static String color0(HSSFColor color) {
+        short[] rgb = color.getTriplet();
+        return String.format("#%02x%02x%02x", rgb[0], rgb[1], rgb[2]);
+    }
+
+    private static String color0(ExtendedColor color) {
+        byte[] rgb = color.getRGB();
+        return String.format("#%02x%02x%02x", Byte.toUnsignedInt(rgb[0]), Byte.toUnsignedInt(rgb[1]), Byte.toUnsignedInt(rgb[2]));
+    }
+
+    public static int getColumnIndex(XLSReader reader, String columnName) {
+        if(columnName == null) {
+            return 0;
+        }
+        if (StringTraits.isNumber(columnName)) {
+            return Integer.parseInt(columnName);
+        }
+        if (columnName.length() == 1) {
+            char c = columnName.charAt(0);
+            if(c>='A' && c<='Z') {
+                return c-'A';
+            }
+        }
+
+        List<String> head = XLSUtils.text(reader.header(true));
+        for(int i=0, n=head.size(); i<n; i++) {
+            if(columnName.equalsIgnoreCase(head.get(i).trim())) {
+                return i;
+            }
+        }
+
+        return 0;
+    }
+
+    public static boolean isHeaderIndex(String text) {
+        if(StringTraits.isBlank(text)) {
+            return false;
+        }
+        if(StringTraits.isNumber(text)) {
+            return false;
+        }
+        if (text.length() == 1) {
+            char c = text.charAt(0);
+            return c < 'A' || c > 'Z';
+        }
+        return true;
+    }
+
+    public static class RowInfo extends ArrayList<CellInfo> {
+
+        public RowInfo(Row row) {
+            super(CQuery.from().iterable(row).map(CellInfo::new).list());
+        }
+    }
+
+    @Data
+    public static class CellInfo {
+
+        private final String back;
+        private final String fore;
+        private final CellType type;
+        private final int size;
+
+        public CellInfo(Cell cell) {
+            this.back = back(cell);
+            this.fore = fore(cell);
+            this.type = cell.getCellType();
+            this.size = text(cell).length();
+        }
+    }
+
+    @RequiredArgsConstructor
+    private static final class RowAsList extends VirtualList<String> {
+
+        private final Row row;
+
+        @Override
+        public int size() {
+            return row.getLastCellNum() - row.getFirstCellNum();
+        }
+
+        @Override
+        public String get(int index) {
+            return XLSUtils.text(row.getCell(row.getFirstCellNum() + index));
+        }
+    }
+}

+ 154 - 0
assira.xls/src/main/java/net/ranides/assira/xls/XLSWriter.java

@@ -0,0 +1,154 @@
+package net.ranides.assira.xls;
+
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+
+import net.ranides.assira.collection.iterators.IterableUtils;
+import net.ranides.assira.collection.iterators.IteratorUtils;
+import net.ranides.assira.collection.query.CQuery;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class XLSWriter implements AutoCloseable {
+
+    private final Path target;
+    private final SXSSFWorkbook wb;
+    private final Sheet sh;
+
+    private List<String> header;
+    private boolean opened;
+
+    public XLSWriter(String filename) {
+        this(Paths.get(filename));
+    }
+
+    public XLSWriter(Path filename) {
+        this.target = filename;
+        this.wb = new SXSSFWorkbook();
+        this.sh = wb.createSheet();
+        this.opened = true;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if(!opened) {
+            return;
+        }
+        try (OutputStream ostream = new FileOutputStream(target.toFile())) {
+            wb.write(ostream);
+        } finally {
+            wb.dispose();
+            opened = false;
+        }
+    }
+
+    public boolean isOpened() {
+        return opened;
+    }
+
+    public boolean hasHeader() {
+        return header != null;
+    }
+
+    public XLSWriter header(Row cells) {
+        return header(CQuery.from().iterable(cells).map(XLSUtils::text));
+    }
+
+    public XLSWriter header(Iterable<String> cells) {
+        if(hasHeader()) {
+            throw new IllegalStateException("Header is already defined");
+        }
+        this.header = IteratorUtils.collect(cells.iterator(), new ArrayList<>());
+        return appendRowAt(0, cells);
+    }
+
+
+    public XLSWriter appendRowAt(int rowIndex, Row cells) {
+        return appendCells(sh.createRow(rowIndex).getRowNum(), cells);
+    }
+
+    public XLSWriter appendRowAt(int rowIndex, Iterable<String> cells) {
+        return appendCells(sh.createRow(rowIndex).getRowNum(), cells);
+    }
+
+    public XLSWriter appendRowAt(int rowIndex, Map<String, String> cells) {
+        if(!hasHeader()) {
+            throw new IllegalStateException("Define header first");
+        }
+        return appendCells(sh.createRow(rowIndex).getRowNum(), cells);
+    }
+
+    public XLSWriter appendRow(Row cells) {
+        return appendRowAt(sh.getPhysicalNumberOfRows(), cells);
+    }
+
+    public XLSWriter appendRow(String[] cells) {
+        return appendRow(Arrays.asList(cells));
+    }
+
+    public XLSWriter appendRow(Iterable<String> cells) {
+        return appendRowAt(sh.getPhysicalNumberOfRows(), cells);
+    }
+
+    public XLSWriter appendRow(Map<String, String> cells) {
+        return appendRowAt(sh.getPhysicalNumberOfRows(), cells);
+    }
+
+    public XLSWriter appendCells(int rowIndex, Map<String, String> cells) {
+        if(!hasHeader()) {
+            throw new IllegalStateException("Define header first");
+        }
+        Row row = sh.getRow(rowIndex);
+        for(int index = 0, n = header.size(); index < n; index++){
+            row.createCell(index).setCellValue(cells.get(header.get(index)));
+        }
+        return this;
+    }
+
+    public XLSWriter appendCells(Row cells) {
+        return appendCells(sh.getLastRowNum(), cells);
+    }
+
+
+    public XLSWriter appendCells(int rowIndex, Row cells) {
+        return appendCells(rowIndex, IterableUtils.map(cells, cell -> XLSUtils.text(cell)));
+    }
+
+    public XLSWriter appendCells(Iterable<String> cells) {
+        return appendCells(sh.getLastRowNum(), cells);
+    }
+
+
+    public XLSWriter appendCells(int rowIndex, Iterable<String> cells) {
+        Row row = sh.getRow(rowIndex);
+        // please note, that getLastCellNum is 1-based, can't return 0
+        int last = Math.max(0, row.getLastCellNum());
+
+        for(String text : cells) {
+            row.createCell(last++).setCellValue(text);
+        }
+        return this;
+    }
+
+    public XLSWriter changeCell(int rowIndex, int cellIndex, String value) {
+        Row row = sh.getRow(rowIndex);
+        row.createCell(cellIndex).setCellValue(value);
+        return this;
+    }
+
+    public XLSWriter changeCell(int cellIndex, String value) {
+        Row row = sh.getRow(sh.getLastRowNum());
+        row.createCell(cellIndex).setCellValue(value);
+        return this;
+    }
+
+}

+ 6 - 0
pom.xml

@@ -79,6 +79,7 @@
                 <module>assira.core</module>
                 <module>assira.commons</module>
                 <module>assira.enterprise</module>
+                <module>assira.xls</module>
             </modules>
         </profile>
         <profile>
@@ -211,6 +212,11 @@
 
     <dependencyManagement>
         <dependencies>
+            <dependency>
+                <groupId>net.ranides</groupId>
+                <artifactId>assira.commons</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>net.ranides</groupId>
                 <artifactId>assira.core</artifactId>