All Downloads are FREE. Search and download functionalities are using the official Maven repository.

spreadsheet.xlsx.internal.XlsxBook Maven / Gradle / Ivy

/*
 * Copyright 2016 National Bank of Belgium
 *
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved
 * by the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 */
package spreadsheet.xlsx.internal;

import ec.util.spreadsheet.Book;
import ec.util.spreadsheet.Sheet;
import nbbrd.io.function.IOSupplier;
import nbbrd.io.xml.Sax;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import spreadsheet.xlsx.*;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.*;
import java.util.function.ObjIntConsumer;
import java.util.function.Supplier;
import java.util.stream.IntStream;

/**
 * @author Philippe Charles
 */
@lombok.RequiredArgsConstructor
public final class XlsxBook extends Book {

    @NonNull
    public static XlsxBook create(@NonNull XlsxPackage pkg, @NonNull XlsxReader reader) throws IOException {
        XlsxEntryParser mainEntryParser = null;

        try {
            mainEntryParser = reader.getEntryParser().create();

            WorkbookData data = parseWorkbook(pkg::getWorkbook, mainEntryParser);

            return new XlsxBook(pkg, data.sheets,
                    dateSystemOf(reader.getDateSystem(), data.date1904),
                    sharedStringsOf(pkg, mainEntryParser),
                    dateFormatsOf(pkg, mainEntryParser, reader.getNumberingFormat()),
                    mainEntryParser,
                    reader.getSheetBuilder());
        } catch (IOException ex) {
            closeAll(ex, mainEntryParser);
            throw ex;
        }
    }

    private static Supplier dateSystemOf(XlsxDateSystem.Factory dateSystem, boolean date1904) {
        return () -> dateSystem.of(date1904);
    }

    private static IOSupplier> sharedStringsOf(XlsxPackage pkg, XlsxEntryParser entryParser) {
        return () -> parseSharedStrings(pkg::getSharedStrings, entryParser);
    }

    private static IOSupplier dateFormatsOf(XlsxPackage pkg, XlsxEntryParser entryParser, XlsxNumberingFormat.Factory numberingFormat) {
        return () -> parseStyles(numberingFormat.of(), pkg::getStyles, entryParser);
    }

    private final XlsxPackage pkg;
    private final List sheets;
    private final Supplier dateSystem;
    private final IOSupplier> sharedStrings;
    private final IOSupplier dateFormats;
    private final XlsxEntryParser mainEntryParser;
    private final XlsxSheetBuilder.Factory mainSheetBuilderFactory;
    private XlsxSheetBuilder mainSheetBuilder = null;

    @Override
    public void close() throws IOException {
        closeAll(null, pkg, mainEntryParser, mainSheetBuilder);
    }

    @Override
    public int getSheetCount() {
        return sheets.size();
    }

    @Override
    public @NonNull Sheet getSheet(int index) throws IOException {
        if (mainSheetBuilder == null) {
            mainSheetBuilder = mainSheetBuilderFactory.create(dateSystem.get(), sharedStrings.getWithIO(), dateFormats.getWithIO());
        }
        return getSheet(index, mainSheetBuilder, mainEntryParser);
    }

    @Override
    public @NonNull String getSheetName(@NonNegative int index) {
        return sheets.get(index).getName();
    }

    @Override
    public void parallelForEach(@NonNull ObjIntConsumer action) throws IOException {
        XlsxDateSystem x = dateSystem.get();
        List y = sharedStrings.getWithIO();
        boolean[] z = dateFormats.getWithIO();

        try {
            IntStream.range(0, getSheetCount2())
                    .parallel()
                    .forEach(index -> {
                        try {
                            Sheet sheet = getSheet(index, DefaultSheetBuilder.of(x, y, z), new SaxEntryParser(Sax.createReader()));
                            action.accept(sheet, index);
                        } catch (IOException ex) {
                            throw new UncheckedIOException(ex);
                        }
                    });
        } catch (UncheckedIOException ex) {
            throw ex.getCause();
        }
    }

    private Sheet getSheet(int index, XlsxSheetBuilder sheetBuilder, XlsxEntryParser entryParser) throws IOException {
        SheetMeta meta = sheets.get(index);
        return parseSheet(meta.name, sheetBuilder, () -> pkg.getSheet(meta.relationId), entryParser);
    }

    static void closeAll(IOException initial, Closeable... closeables) throws IOException {
        for (Closeable o : closeables) {
            if (o != null) {
                try {
                    o.close();
                } catch (IOException ex) {
                    if (initial == null) {
                        initial = ex;
                    } else {
                        initial.addSuppressed(ex);
                    }
                }
            }
        }
        if (initial != null) {
            throw initial;
        }
    }

    @lombok.Value
    static class WorkbookData {

        List sheets;
        boolean date1904;
    }

    @lombok.Value
    static class SheetMeta {

        @NonNull
        String relationId;
        @NonNull
        String name;
    }

    static WorkbookData parseWorkbook(IOSupplier byteSource, XlsxEntryParser parser) throws IOException {
        WorkbookVisitorImpl result = new WorkbookVisitorImpl();
        try (InputStream stream = byteSource.getWithIO()) {
            parser.visitWorkbook(stream, result);
        }
        return result.build();
    }

    private static final class WorkbookVisitorImpl implements XlsxEntryParser.WorkbookVisitor {

        private final List sheets = new ArrayList<>();
        private boolean date1904 = false;

        @Override
        public void onSheet(String relationId, String name) {
            sheets.add(new SheetMeta(relationId, name));
        }

        @Override
        public void onDate1904(boolean date1904) {
            this.date1904 = date1904;
        }

        WorkbookData build() {
            return new WorkbookData(sheets, date1904);
        }
    }

    static List parseSharedStrings(IOSupplier byteSource, XlsxEntryParser parser) throws IOException {
        List result = new ArrayList<>();
        try (InputStream stream = byteSource.getWithIO()) {
            parser.visitSharedStrings(stream, o -> result.add(Objects.requireNonNull(o)));
        }
        return result;
    }

    static boolean[] parseStyles(XlsxNumberingFormat dateFormat, IOSupplier byteSource, XlsxEntryParser parser) throws IOException {
        StylesVisitorImpl result = new StylesVisitorImpl(dateFormat);
        try (InputStream stream = byteSource.getWithIO()) {
            parser.visitStyles(stream, result);
        }
        return result.build();
    }

    private static final class StylesVisitorImpl implements XlsxEntryParser.StylesVisitor {

        private final XlsxNumberingFormat dateFormat;
        private final List orderedListOfIds = new ArrayList<>();
        private final Map numberFormats = new HashMap<>();

        StylesVisitorImpl(XlsxNumberingFormat dateFormat) {
            this.dateFormat = dateFormat;
        }

        @Override
        public void onNumberFormat(int formatId, String formatCode) {
            numberFormats.put(formatId, formatCode);
        }

        @Override
        public void onCellFormat(int formatId) {
            orderedListOfIds.add(formatId);
        }

        public boolean[] build() {
            // Style order matters! -> accessed by index in sheets
            boolean[] result = new boolean[orderedListOfIds.size()];
            for (int i = 0; i < result.length; i++) {
                int numFmtId = orderedListOfIds.get(i);
                result[i] = dateFormat.isExcelDateFormat(numFmtId, numberFormats.getOrDefault(numFmtId, null));
            }
            return result;
        }
    }

    static Sheet parseSheet(String name, XlsxSheetBuilder sheetBuilder, IOSupplier byteSource, XlsxEntryParser parser) throws IOException {
        SheetVisitorImpl result = new SheetVisitorImpl(name, sheetBuilder);
        try (InputStream stream = byteSource.getWithIO()) {
            parser.visitSheet(stream, result);
        }
        return result.build();
    }

    private static final class SheetVisitorImpl implements XlsxEntryParser.SheetVisitor {

        private final String sheetName;
        private final XlsxSheetBuilder sheetBuilder;
        private boolean inData;

        SheetVisitorImpl(String sheetName, XlsxSheetBuilder sheetBuilder) {
            this.sheetName = sheetName;
            this.sheetBuilder = sheetBuilder;
            this.inData = false;
        }

        @Override
        public void onSheetData(String sheetBounds) {
            if (inData) {
                throw new IllegalStateException();
            }
            sheetBuilder.reset(sheetName, sheetBounds);
            inData = true;
        }

        @Override
        public void onCell(String ref, CharSequence value, XlsxDataType dataType, int styleIndex) {
            if (!inData) {
                throw new IllegalStateException();
            }
            sheetBuilder.put(ref, value, dataType, styleIndex);
        }

        public Sheet build() {
            return sheetBuilder.build();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy