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

com.dua3.meja.model.poi.PoiWorkbook Maven / Gradle / Ivy

There is a newer version: 6-rc
Show newest version
/*
 * Copyright 2015 Axel Howind ([email protected]).
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.dua3.meja.model.poi;

import com.dua3.meja.io.FileTypeWorkbook;
import com.dua3.meja.io.WorkbookWriter;
import com.dua3.meja.model.AbstractWorkbook;
import com.dua3.meja.model.CellStyle;
import com.dua3.meja.model.Sheet;
import com.dua3.meja.model.poi.PoiCellStyle.PoiHssfCellStyle;
import com.dua3.meja.model.poi.PoiCellStyle.PoiXssfCellStyle;
import com.dua3.meja.model.poi.io.FileTypeXls;
import com.dua3.meja.model.poi.io.FileTypeXlsx;
import com.dua3.utility.data.Color;
import com.dua3.utility.data.DataUtil;
import com.dua3.utility.io.FileType;
import com.dua3.utility.options.Arguments;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFPalette;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.ss.formula.eval.NotImplementedException;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jspecify.annotations.Nullable;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.DoubleConsumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * @author axel
 */
public abstract class PoiWorkbook extends AbstractWorkbook {

    private static final Logger LOGGER = LogManager.getLogger(PoiWorkbook.class);
    /**
     *
     */
    protected final Workbook poiWorkbook;
    /**
     *
     */
    protected final FormulaEvaluator evaluator;
    /**
     *
     */
    protected final List sheets = new ArrayList<>();
    /**
     *
     */
    protected final Map cellStyles = new HashMap<>();

    /**
     * Construct a new instance.
     *
     * @param poiWorkbook the POI workbook instance
     * @param uri         the URI of this workbook
     */
    protected PoiWorkbook(Workbook poiWorkbook, @Nullable URI uri) {
        super(uri);
        this.poiWorkbook = poiWorkbook;
        this.evaluator = poiWorkbook.getCreationHelper().createFormulaEvaluator();

        // init cell TextAttributes map
        for (short i = 0; i < poiWorkbook.getNumCellStyles(); i++) {
            cellStyles.put("style#" + i, i);
        }
    }

    @Override
    public void close() throws IOException {
        poiWorkbook.close();
    }

    @Override
    public PoiCellStyle copyCellStyle(String styleName, CellStyle style) {
        PoiCellStyle cellStyle = getCellStyle(styleName);
        cellStyle.poiCellStyle.cloneStyleFrom(((PoiCellStyle) style).poiCellStyle);
        return cellStyle;
    }

    /**
     * Create a new font.
     *
     * @param font the font to create a POI version of
     * @return an instance of {@link PoiFont}
     */
    protected abstract PoiFont createFont(com.dua3.utility.text.Font font);

    /**
     * Convert {@link String} to {@link RichTextString}.
     *
     * @param s the {@link String} to convert
     * @return {@link RichTextString} with the same text as {@code s}
     */
    public abstract RichTextString createRichTextString(String s);

    /**
     * Create instance of {@link PoiSheet} for the given POI sheet.
     *
     * @param poiSheet the POI sheet
     * @return instance of {@link PoiSheet}
     */
    protected PoiSheet createSheet(org.apache.poi.ss.usermodel.Sheet poiSheet) {
        PoiSheet sheet = new PoiSheet(this, poiSheet);
        sheets.add(sheet);
        return sheet;
    }

    @Override
    public Sheet createSheet(String sheetName) {
        org.apache.poi.ss.usermodel.Sheet poiSheet = poiWorkbook.createSheet(sheetName);
        PoiSheet sheet = new PoiSheet(this, poiSheet);
        sheets.add(sheet);
        sheetAdded(sheets.size() - 1);
        return sheet;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        return obj instanceof PoiWorkbook other && Objects.equals(poiWorkbook, other.poiWorkbook);
    }

    @Override
    public PoiCellStyle getCellStyle(String name) {
        Short index = cellStyles.get(name);
        org.apache.poi.ss.usermodel.CellStyle poiCellStyle;
        if (index == null) {
            poiCellStyle = poiWorkbook.createCellStyle();
            index = poiCellStyle.getIndex();
            cellStyles.put(name, index);
        } else {
            poiCellStyle = poiWorkbook.getCellStyleAt(index);
        }
        return getPoiCellStyle(poiCellStyle);
    }

    String getCellStyleName(PoiCellStyle cellStyle) {
        final short styleIndex = cellStyle.poiCellStyle.getIndex();
        for (Entry entry : cellStyles.entrySet()) {
            if (entry.getValue() == styleIndex) {
                return entry.getKey();
            }
        }
        throw new IllegalArgumentException("CellStyle is not from this workbook.");
    }

    @Override
    public List getCellStyleNames() {
        return new ArrayList<>(cellStyles.keySet());
    }

    /**
     * Get the color from the given POI color. If the POI color is null, return the default color.
     *
     * @param poiColor     the POI color
     * @param defaultColor the default color
     * @return the color
     */
    public abstract Color getColor(org.apache.poi.ss.usermodel.Color poiColor, Color defaultColor);

    /**
     * Get font color.
     *
     * @param poiFont   instance of POI font
     * @param dfltColor color return if none is set
     * @return the color for the given font
     */
    public abstract Color getColor(Font poiFont, Color dfltColor);

    @Override
    public Optional getCurrentSheet() {
        int currentSheetIdx = getCurrentSheetIndex();
        if (currentSheetIdx < sheets.size()) {
            return Optional.of(getSheet(currentSheetIdx));
        } else {
            return Optional.empty();
        }
    }

    @Override
    public void setCurrentSheet(int idx) {
        Objects.checkIndex(idx, sheets.size());

        int oldIdx = getCurrentSheetIndex();
        if (idx != oldIdx) {
            poiWorkbook.setActiveSheet(idx);
            activeSheetChanged(oldIdx, idx);
        }
    }

    @Override
    public int getCurrentSheetIndex() {
        return poiWorkbook.getActiveSheetIndex();
    }

    static DataFormatter getDataFormatter(Locale locale) {
        return new DataFormatter(locale);
    }

    @Override
    public abstract PoiCellStyle getDefaultCellStyle();

    /**
     * Get instance of {@link PoiFont}.
     *
     * @param poiFont the POI font instance
     * @return instance of {@link PoiFont} for the given font
     */
    public PoiFont getFont(@Nullable Font poiFont) {
        return poiFont == null ? getDefaultCellStyle().getPoiFont() : new PoiFont(this, poiFont);
    }

    /**
     * Get {@link PoiCellStyle} from {@link org.apache.poi.ss.usermodel.CellStyle}.
     *
     * @param cellStyle POI cell style
     * @return instance of {@link PoiCellStyle}
     */
    public abstract PoiCellStyle getPoiCellStyle(org.apache.poi.ss.usermodel.CellStyle cellStyle);

    /**
     * Get POI color.
     *
     * @param color the color
     * @return POI color
     */
    public abstract org.apache.poi.ss.usermodel.Color getPoiColor(Color color);

    PoiFont getPoiFont(com.dua3.utility.text.Font font) {
        // try to find existing font
        for (int i = 0; i < poiWorkbook.getNumberOfFonts(); i++) {
            Font poiFont = poiWorkbook.getFontAt(i);

            if (poiFont.getFontName().equalsIgnoreCase(font.getFamily())
                    && poiFont.getFontHeightInPoints() == font.getSizeInPoints()
                    && poiFont.getBold() == font.isBold()
                    && poiFont.getItalic() == font.isItalic()
                    && (poiFont.getUnderline() != Font.U_NONE) == font.isUnderline()
                    && poiFont.getStrikeout() == font.isStrikeThrough()
                    && getColor(poiFont, Color.BLACK).equals(font.getColor())
                    && poiFont.getTypeOffset() == Font.SS_NONE) {
                return new PoiFont(this, poiFont);
            }
        }

        // if not found, create it
        return createFont(font);
    }

    Workbook getPoiWorkbook() {
        return poiWorkbook;
    }

    @Override
    public PoiSheet getSheet(int sheetNr) {
        return sheets.get(sheetNr);
    }

    @Override
    public int getSheetCount() {
        assert poiWorkbook.getNumberOfSheets() == sheets.size();
        return sheets.size();
    }

    /**
     * Return the standard file type for this implementation.
     *
     * @return the file type matching the underlying POI implementation
     */
    protected abstract FileType getStandardFileType();

    @Override
    public boolean hasCellStyle(String name) {
        return cellStyles.containsKey(name);
    }

    @Override
    public int hashCode() {
        return poiWorkbook.hashCode();
    }

    /**
     *
     */
    protected final void init() {
        for (int i = 0; i < poiWorkbook.getNumberOfSheets(); i++) {
            createSheet(poiWorkbook.getSheetAt(i));
        }
    }

    /**
     * Test if formula evaluation is supported.
     *
     * @return true if formula evaluation is supported
     */
    public abstract boolean isFormulaEvaluationSupported();

    /**
     * Try to evaluate all formula cells.
     */
    public void evaluateAllFormulaCells() {
        if (isFormulaEvaluationSupported()) {
            try {
                evaluator.evaluateAll();
            } catch (NotImplementedException e) {
                LOGGER.warn("unsupported function in formula; flagging workbook as needing recalculation", e);
                setForceFormulaRecalculation(true);
            }
        }
    }

    /**
     * Returns the flag indicating whether force formula recalculation is enabled.
     *
     * @return true if force formula recalculation is enabled, false otherwise.
     */
    public boolean getForceFormulaRecalculation() {
        return poiWorkbook.getForceFormulaRecalculation();
    }

    /**
     * Sets the flag indicating whether force formula recalculation is enabled.
     *
     * @param flag the flag to set. Pass true to enable force formula recalculation, false to disable it.
     */
    public void setForceFormulaRecalculation(boolean flag) {
        poiWorkbook.setForceFormulaRecalculation(flag);
        LOGGER.debug("setForceFormulaRecalculation({})", flag);
    }

    @Override
    public Iterator iterator() {
        return new Iterator<>() {
            private final Iterator iter = sheets.iterator();

            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public Sheet next() {
                return iter.next();
            }

            @Override
            public void remove() {
                iter.remove();
            }
        };
    }

    @Override
    public void removeSheet(int sheetNr) {
        sheets.remove(sheetNr);
        poiWorkbook.removeSheetAt(sheetNr);
        sheetRemoved(sheetNr);
    }

    @Override
    public void write(FileType type, OutputStream out, Arguments options, DoubleConsumer updateProgress) throws IOException {
        //noinspection ObjectEquality
        if (type == getStandardFileType()) {
            // if the workbook is to be saved in the same format, write it out
            // directly so that
            // features not yet supported by Meja don't get lost in the process
            updateProgress.accept(WorkbookWriter.PROGRESS_INDETERMINATE);
            poiWorkbook.write(out);
            updateProgress.accept(1.0);
        } else if (type instanceof FileTypeWorkbook fileTypeWorkbook) {
            WorkbookWriter writer = fileTypeWorkbook.getWorkbookWriter();
            writer.setOptions(options);
            writer.write(this, out, updateProgress);
        } else {
            throw new IllegalStateException("could not write workbook");
        }
    }

    /**
     * Creates a hyperlink based on the target URI.
     *
     * @param target the target URI for the hyperlink.
     * @return a Hyperlink object representing the created hyperlink.
     * @throws IllegalArgumentException if the target URI's protocol is not supported.
     */
    public Hyperlink createHyperLink(URI target) {
        HyperlinkType type;
        String address = target.toString();
        type = switch (Optional.ofNullable(target.getScheme()).map(s -> s.toLowerCase(Locale.ROOT)).orElse("")) {
            case "http", "https" -> HyperlinkType.URL;
            case "file" -> HyperlinkType.FILE;
            case "mailto" -> HyperlinkType.EMAIL;
            case "" -> HyperlinkType.FILE; // workbook-relative link
            default -> throw new IllegalArgumentException("unsupported protocol: " + target.getScheme());
        };
        Hyperlink link = poiWorkbook.getCreationHelper().createHyperlink(type);
        link.setAddress(address);
        return link;
    }

    @Override
    public Stream cellStyles() {
        Iterator iter = DataUtil.map(cellStyles.keySet().iterator(), this::getCellStyle);
        Spliterator spliterator = Spliterators.spliterator(iter, cellStyles.size(), 0);
        return StreamSupport.stream(spliterator, false);
    }

    /**
     * Concrete implementation of {@link PoiWorkbook} for HSSF-workbooks (the old
     * Excel format).
     */
    public static class PoiHssfWorkbook extends PoiWorkbook {

        private final PoiHssfCellStyle defaultCellStyle;

        /**
         * Construct instance from existing POI workbook.
         *
         * @param poiWorkbook the POI workbook instance
         * @param uri         the URI of the workbook
         */
        public PoiHssfWorkbook(HSSFWorkbook poiWorkbook, @Nullable URI uri) {
            super(poiWorkbook, uri);
            this.defaultCellStyle = new PoiHssfCellStyle(this, poiWorkbook.getCellStyleAt(0));
            cellStyles.put("", (short) 0);
            init();
        }

        @Override
        public PoiFont createFont(com.dua3.utility.text.Font font) {
            Font poiFont = poiWorkbook.createFont();
            poiFont.setFontName(font.getFamily());
            poiFont.setFontHeight(((short) Math.round(20 * font.getSizeInPoints())));
            poiFont.setColor(getPoiColor(font.getColor()).getIndex());
            poiFont.setBold(font.isBold());
            poiFont.setItalic(font.isItalic());
            poiFont.setUnderline(font.isUnderline() ? Font.U_SINGLE
                    : Font.U_NONE);
            poiFont.setStrikeout(font.isStrikeThrough());
            return new PoiFont(this, poiFont);
        }

        @Override
        public HSSFRichTextString createRichTextString(String s) {
            return new HSSFRichTextString(s);
        }

        @Override
        public Color getColor(Font poiFont, Color dfltColor) {
            return getColor(((HSSFFont) poiFont).getHSSFColor(((HSSFWorkbook) getPoiWorkbook())), Color.BLACK);
        }

        @Override
        public Color getColor(org.apache.poi.ss.usermodel.@Nullable Color poiColor, Color defaultColor) {
            if (poiColor == null || poiColor.equals(HSSFColorPredefined.AUTOMATIC.getColor())) {
                return defaultColor;
            }

            short[] triplet = ((HSSFColor) poiColor).getTriplet();

            if (triplet == null) {
                return defaultColor;
            }

            int a = 0xff;
            int r = triplet[0];
            int g = triplet[1];
            int b = triplet[2];
            return Color.rgba(r, g, b, a);
        }

        Color getColor(short idx) {
            return getColor(((HSSFWorkbook) poiWorkbook).getCustomPalette().getColor(idx), Color.BLACK);
        }

        @Override
        public PoiCellStyle getDefaultCellStyle() {
            return defaultCellStyle;
        }

        /**
         * Retrieves the font at the given index.
         *
         * @param idx the index of the font to retrieve
         * @return the PoiFont instance representing the font at the given index
         */
        public PoiFont getFont(int idx) {
            return getFont(poiWorkbook.getFontAt(idx));
        }

        @Override
        public PoiCellStyle getPoiCellStyle(org.apache.poi.ss.usermodel.CellStyle poiStyle) {
            return new PoiHssfCellStyle(this, (HSSFCellStyle) poiStyle);
        }

        @Override
        public HSSFColor getPoiColor(Color color) {
            HSSFPalette palette = ((HSSFWorkbook) poiWorkbook).getCustomPalette();
            int argb = color.argb();
            int r = (argb >> 16) & 0xff;
            int g = (argb >> 8) & 0xff;
            int b = argb & 0xff;
            return palette.findSimilarColor(r, g, b);
        }

        @Override
        protected FileType getStandardFileType() {
            return FileTypeXls.instance();
        }

        @Override
        public boolean isFormulaEvaluationSupported() {
            return true;
        }
    }

    /**
     *
     */
    public static class PoiXssfWorkbook extends PoiWorkbook {

        private final PoiXssfCellStyle defaultCellStyle;

        /**
         * Construct instance from existing POI workbook.
         *
         * @param poiWorkbook the POI workbook instance
         * @param uri         the URI of the workbook
         */
        public PoiXssfWorkbook(Workbook poiWorkbook, @Nullable URI uri) {
            super(poiWorkbook, uri);
            assert poiWorkbook instanceof XSSFWorkbook || poiWorkbook instanceof SXSSFWorkbook;
            this.defaultCellStyle = new PoiXssfCellStyle(this, (XSSFCellStyle) poiWorkbook.getCellStyleAt(0));
            init();
        }

        @Override
        public PoiFont createFont(com.dua3.utility.text.Font font) {
            XSSFFont poiFont = (XSSFFont) poiWorkbook.createFont();
            poiFont.setFontName(font.getFamily());
            poiFont.setFontHeight(((short) Math.round(20 * font.getSizeInPoints())));
            poiFont.setColor(getPoiColor(font.getColor()));
            poiFont.setBold(font.isBold());
            poiFont.setItalic(font.isItalic());
            poiFont.setUnderline(font.isUnderline() ? Font.U_SINGLE
                    : Font.U_NONE);
            poiFont.setStrikeout(font.isStrikeThrough());
            return new PoiFont(this, poiFont);
        }

        @Override
        public XSSFRichTextString createRichTextString(String s) {
            return new XSSFRichTextString(s);
        }

        @Override
        public Color getColor(Font poiFont, Color dfltColor) {
            return getColor(((XSSFFont) poiFont).getXSSFColor(), Color.BLACK);
        }

        @Override
        public Color getColor(org.apache.poi.ss.usermodel.@Nullable Color poiColor, Color defaultColor) {
            XSSFColor xssfColor = (XSSFColor) poiColor;
            if (poiColor == null || xssfColor.isAuto()) {
                return defaultColor;
            }

            // try to get RGB values
            byte[] rgb = xssfColor.hasAlpha() ? xssfColor.getARGB()
                    : xssfColor.hasTint() ? xssfColor.getRGBWithTint() : xssfColor.getRGB();

            if (rgb != null) {
                if (rgb.length == 4) {
                    int a = rgb[0] & 0xFF;
                    int r = rgb[1] & 0xFF;
                    int g = rgb[2] & 0xFF;
                    int b = rgb[3] & 0xFF;
                    return Color.rgba(r, g, b, a);
                } else {
                    int r = rgb[0] & 0xFF;
                    int g = rgb[1] & 0xFF;
                    int b = rgb[2] & 0xFF;
                    return Color.rgb(r, g, b);
                }
            }

            // what should be done now?
            return defaultColor;
        }

        @Override
        public PoiCellStyle getDefaultCellStyle() {
            return defaultCellStyle;
        }

        @Override
        public PoiCellStyle getPoiCellStyle(org.apache.poi.ss.usermodel.CellStyle poiStyle) {
            return new PoiXssfCellStyle(this, (XSSFCellStyle) poiStyle);
        }

        @Override
        public XSSFColor getPoiColor(Color color) {
            return new XSSFColor(color.toByteArrayRGB());
        }

        @Override
        protected FileType getStandardFileType() {
            return FileTypeXlsx.instance();
        }

        @Override
        public boolean isFormulaEvaluationSupported() {
            return !(poiWorkbook instanceof SXSSFWorkbook);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy