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

com.arsframework.util.Excels Maven / Gradle / Ivy

package com.arsframework.util;

import java.io.*;
import java.util.Map;
import java.util.List;
import java.util.Date;
import java.util.Arrays;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.Iterator;
import java.util.Calendar;
import java.util.LinkedList;
import java.lang.reflect.Array;

import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;

import com.arsframework.annotation.Min;
import com.arsframework.annotation.Nonnull;

/**
 * Excel处理工具类
 *
 * @author yongqiang.wu
 */
public abstract class Excels {
    /**
     * 默认Sheet容量大小
     */
    public static final int DEFAULT_SHEET_VOLUME = 50000;

    /**
     * 公式转换器
     */
    private static final ThreadLocal evaluator = new ThreadLocal<>();

    /**
     * Excel格式枚举
     */
    public enum Type {
        /**
         * xls格式
         */
        XLS,

        /**
         * xlsx格式
         */
        XLSX;

        /**
         * 根据类型名称转换类型枚举
         *
         * @param name 类型名称
         * @return 类型枚举
         */
        public static Type parse(String name) {
            return name == null ? null : Type.valueOf(name.toUpperCase());
        }
    }

    /**
     * Excel单元格树
     */
    public static class Tree {
        /**
         * 单元格数量
         */
        public final int count;

        /**
         * 树宽度
         */
        public final int width;

        /**
         * 树高度
         */
        public final int height;

        /**
         * 行跨度
         */
        public final int rowspan;

        /**
         * 单元格值
         */
        public final Object value;

        /**
         * 单元格样式
         */
        public final CellStyle style;

        /**
         * 单元格子树数组
         */
        public final Tree[] children;

        public Tree(Object value, CellStyle style, @Min(1) int rowspan, @Nonnull Tree... children) {
            this.value = value;
            this.style = style;
            this.rowspan = rowspan;
            this.children = children;
            this.count = Arrays.stream(children).mapToInt(t -> t.count).sum() + 1;
            this.width = children.length == 0 ? 1 : Arrays.stream(children).mapToInt(t -> t.width).sum();
            this.height = children.length == 0 ? 1 : Arrays.stream(children).mapToInt(t -> t.height + 1).max().getAsInt();
        }

        @Override
        public String toString() {
            return this.value == null ? null : this.value.toString();
        }

        /**
         * 构建单元格树
         *
         * @param value    单元格值
         * @param children 单元格子树数组
         * @return 单元格树对象
         */
        public static Tree of(Object value, Tree... children) {
            return of(value, null, 1, children);
        }

        /**
         * 构建单元格树
         *
         * @param value    单元格值
         * @param rowspan  行跨度
         * @param children 单元格子树数组
         * @return 单元格树对象
         */
        public static Tree of(Object value, int rowspan, Tree... children) {
            return of(value, null, rowspan, children);
        }

        /**
         * 构建单元格树
         *
         * @param value    单元格值
         * @param style    单元格样式
         * @param children 单元格子树数组
         * @return 单元格树对象
         */
        public static Tree of(Object value, CellStyle style, Tree... children) {
            return of(value, style, 1, children);
        }

        /**
         * 构建单元格树
         *
         * @param value    单元格值
         * @param style    单元格样式
         * @param rowspan  行跨度
         * @param children 单元格子树数组
         * @return 单元格树对象
         */
        public static Tree of(Object value, CellStyle style, int rowspan, Tree... children) {
            return new Tree(value, style, rowspan, children);
        }
    }

    /**
     * Excel公式对象
     */
    public static class Formula {
        /**
         * 求和函数名称
         */
        public static final String SUM = "SUM";

        /**
         * 最大值函数名称
         */
        public static final String MAX = "MAX";

        /**
         * 最小值函数名称
         */
        public static final String MIN = "MIN";

        /**
         * 计数函数名称
         */
        public static final String COUNT = "COUNT";

        /**
         * 求平均值函数名称
         */
        public static final String AVERAGE = "AVERAGE";

        /**
         * 公式值
         */
        public final String value;

        @Nonnull
        public Formula(String value) {
            this.value = value;
        }

        @Override
        public int hashCode() {
            return Objects.hash(this.value);
        }

        @Override
        public boolean equals(Object other) {
            return other != null && other.getClass() == Formula.class && this.value.equals(((Formula) other).value);
        }

        @Override
        public String toString() {
            return this.value;
        }

        /**
         * 构建公式对象
         *
         * @param value 公式值
         * @return 公式对象
         */
        public static Formula of(String value) {
            return new Formula(value);
        }

        /**
         * 构建公式表达式
         *
         * @param function 公式函数名称
         * @param address  公式计算单元格范围地址
         * @return 公式对象
         */
        @Nonnull
        public static Formula build(String function, CellRangeAddress address) {
            String start = addressAdapter(address.getFirstRow(), address.getFirstColumn());
            String end = addressAdapter(address.getLastRow(), address.getLastColumn());
            return new Formula(new StringBuilder(function).append('(').append(start).append(':').append(end).append(')').toString());
        }

        /**
         * 构建求和公式
         *
         * @param address 计算单元格范围地址
         * @return 公式对象
         */
        public static Formula sum(CellRangeAddress address) {
            return build(SUM, address);
        }

        /**
         * 构建求最大值公式
         *
         * @param address 计算单元格范围地址
         * @return 公式对象
         */
        public static Formula max(CellRangeAddress address) {
            return build(MAX, address);
        }

        /**
         * 构建求最小值公式
         *
         * @param address 计算单元格范围地址
         * @return 公式对象
         */
        public static Formula min(CellRangeAddress address) {
            return build(MIN, address);
        }

        /**
         * 构建求数量公式
         *
         * @param address 计算单元格范围地址
         * @return 公式对象
         */
        public static Formula count(CellRangeAddress address) {
            return build(COUNT, address);
        }

        /**
         * 构建求平均值公式
         *
         * @param address 计算单元格范围地址
         * @return 公式对象
         */
        public static Formula average(CellRangeAddress address) {
            return build(AVERAGE, address);
        }
    }

    /**
     * Excel读接口
     */
    public interface Reader {
        /**
         * 读取Excel数据行并转换成对象实例
         *
         * @param row   数据行对象
         * @param count 当前记录数(从1开始)
         */
        void read(Row row, int count);
    }

    /**
     * Excel写接口
     *
     * @param  数据模型
     */
    public interface Writer {
        /**
         * 将对象实例写入到Excel数据行
         *
         * @param row    数据行对象
         * @param object 对象实例
         * @param count  当前记录数(从1开始)
         */
        void write(Row row, T object, int count);
    }

    /**
     * Excel读写器实现
     */
    public static class ReadWriter implements Reader {
        private int row; // 当前行下标
        private Sheet sheet; // 当前Excel表格
        protected final int index; // 写入数据开始行下标
        protected final Workbook workbook; // 写入数据Excel工作薄
        protected final Writer writer; // 数据行写入接口

        @Nonnull
        public ReadWriter(Workbook workbook) {
            this(workbook, 0);
        }

        @Nonnull
        public ReadWriter(Workbook workbook, int index) {
            this(workbook, index, (target, source, count) -> copy(source, target));
        }

        @Nonnull
        public ReadWriter(Workbook workbook, Writer writer) {
            this(workbook, 0, writer);
        }

        @Nonnull
        public ReadWriter(Workbook workbook, @Min(0) int index, Writer writer) {
            this.index = index;
            this.writer = writer;
            this.workbook = workbook;
        }

        @Override
        public void read(Row row, int count) {
            if (this.sheet == null || (this.row > this.index && this.row % DEFAULT_SHEET_VOLUME == 0)) {
                this.row = index;
                this.sheet = this.workbook.createSheet();
            }
            this.writer.write(this.sheet.createRow(this.row++), row, count);
        }
    }

    /**
     * 基于XML的Excel行实现
     */
    public static class XMLRow implements Row {
        /**
         * 行下标(从0开始)
         */
        private int index;

        /**
         * 样式
         */
        private CellStyle style;

        /**
         * 单元格列下标列表(从小到大排序)
         */
        private TreeSet columns = new TreeSet<>();

        /**
         * 单元格列下标/单元格映射表
         */
        private Map cells = new HashMap<>();

        @Override
        public Cell createCell(int i) {
            Cell cell = this.createCell(i, CellType.BLANK);
            cell.setCellValue(Strings.EMPTY_STRING);
            return cell;
        }

        @Override
        public Cell createCell(@Min(0) int i, CellType cellType) {
            Cell cell = new XMLCell(this, i);
            cell.setCellType(cellType);
            this.cells.put(i, cell);
            this.columns.add(i);
            return cell;
        }

        @Override
        public void removeCell(Cell cell) {
            if (cell != null) {
                this.cells.remove(cell.getColumnIndex());
            }
        }

        @Override
        public void setRowNum(@Min(0) int i) {
            this.index = i;
        }

        @Override
        public int getRowNum() {
            return this.index;
        }

        @Override
        public Cell getCell(int i) {
            return this.getCell(i, MissingCellPolicy.RETURN_NULL_AND_BLANK);
        }

        @Override
        public Cell getCell(int i, MissingCellPolicy missingCellPolicy) {
            return this.cells.get(i);
        }

        @Override
        public short getFirstCellNum() {
            return this.columns.isEmpty() ? -1 : this.columns.first().shortValue();
        }

        @Override
        public short getLastCellNum() {
            return this.columns.isEmpty() ? -1 : (short) (this.columns.last().shortValue() + 1);
        }

        @Override
        public int getPhysicalNumberOfCells() {
            return this.columns.size();
        }

        @Override
        public void setHeight(short i) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void shiftCellsRight(int i, int i1, int i2) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void shiftCellsLeft(int i, int i1, int i2) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setZeroHeight(boolean b) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean getZeroHeight() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setHeightInPoints(float v) {
            throw new UnsupportedOperationException();
        }

        @Override
        public short getHeight() {
            throw new UnsupportedOperationException();
        }

        @Override
        public float getHeightInPoints() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean isFormatted() {
            throw new UnsupportedOperationException();
        }

        @Override
        public CellStyle getRowStyle() {
            return this.style;
        }

        @Override
        public void setRowStyle(CellStyle cellStyle) {
            this.style = cellStyle;
        }

        @Override
        public Iterator cellIterator() {
            return new Iterator() {
                private Iterator columnIterator = columns.iterator();

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

                @Override
                public Cell next() {
                    return cells.get(columnIterator.next());
                }
            };
        }

        @Override
        public Sheet getSheet() {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getOutlineLevel() {
            throw new UnsupportedOperationException();
        }

        @Override
        public Iterator iterator() {
            return this.cellIterator();
        }

        @Override
        public String toString() {
            Integer last = -1;
            List cells = new LinkedList<>();
            for (Integer column : this.columns) {
                int differ = column - last;
                if (differ > 1) {
                    for (int i = 0, len = differ - 1; i < len; i++) {
                        cells.add(null);
                    }
                }
                cells.add(this.cells.get(column));
                last = column;
            }
            return cells.toString();
        }
    }

    /**
     * 基于XML的Excel单元格实现
     */
    public static class XMLCell implements Cell {
        private Row row;
        private int column;
        private Object value;
        private CellType type;
        private CellStyle style;
        private CellAddress address;

        public XMLCell(@Nonnull Row row, @Min(0) int column) {
            this.row = row;
            this.column = column;
            this.address = new CellAddress(row.getRowNum(), column);
        }

        @Override
        public int getColumnIndex() {
            return this.column;
        }

        @Override
        public int getRowIndex() {
            return this.row.getRowNum();
        }

        @Override
        public Sheet getSheet() {
            throw new UnsupportedOperationException();
        }

        @Override
        public Row getRow() {
            return this.row;
        }

        @Override
        public void setCellType(CellType cellType) {
            this.type = cellType;
        }

        @Override
        public CellType getCellType() {
            return this.type;
        }

        @Override
        public CellType getCellTypeEnum() {
            return this.type;
        }

        @Override
        public CellType getCachedFormulaResultType() {
            throw new UnsupportedOperationException();
        }

        @Override
        public CellType getCachedFormulaResultTypeEnum() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setCellValue(double v) {
            this.value = v;
        }

        @Override
        public void setCellValue(Date date) {
            this.value = date;
        }

        @Override
        public void setCellValue(Calendar calendar) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setCellValue(RichTextString richTextString) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setCellValue(String s) {
            this.value = s;
        }

        @Override
        public void setCellFormula(String s) throws FormulaParseException {
            throw new UnsupportedOperationException();
        }

        @Override
        public String getCellFormula() {
            throw new UnsupportedOperationException();
        }

        @Override
        public double getNumericCellValue() {
            return this.type == CellType.BLANK ? 0.0D : (double) this.value;
        }

        @Override
        public Date getDateCellValue() {
            return (Date) this.value;
        }

        @Override
        public RichTextString getRichStringCellValue() {
            throw new UnsupportedOperationException();
        }

        @Override
        public String getStringCellValue() {
            return (String) this.value;
        }

        @Override
        public void setCellValue(boolean b) {
            this.value = b;
        }

        @Override
        public void setCellErrorValue(byte b) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean getBooleanCellValue() {
            return this.type == CellType.BLANK ? false : (boolean) this.value;
        }

        @Override
        public byte getErrorCellValue() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setCellStyle(CellStyle cellStyle) {
            this.style = cellStyle;
        }

        @Override
        public CellStyle getCellStyle() {
            return this.style;
        }

        @Override
        public void setAsActiveCell() {
            throw new UnsupportedOperationException();
        }

        @Override
        public CellAddress getAddress() {
            return this.address;
        }

        @Override
        public void setCellComment(Comment comment) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Comment getCellComment() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void removeCellComment() {
            throw new UnsupportedOperationException();
        }

        @Override
        public Hyperlink getHyperlink() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setHyperlink(Hyperlink hyperlink) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void removeHyperlink() {
            throw new UnsupportedOperationException();
        }

        @Override
        public CellRangeAddress getArrayFormulaRange() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean isPartOfArrayFormulaGroup() {
            throw new UnsupportedOperationException();
        }

        @Override
        public String toString() {
            return this.value == null ? null : this.value.toString();
        }
    }

    /**
     * Excel2007数据读取处理器抽象实现
     */
    public static abstract class AbstractExcel2007Reader extends DefaultHandler implements Reader {
        private Row row; // 当前行
        private int count; // 迭代数量
        private int index; // 开始行下标
        private int column; // 当前列下标
        private String value; // 当前单元格值
        protected boolean datable; // 值是否为日期
        protected boolean related; // 数据是否关联
        protected OPCPackage pkg; // 文件包
        protected SharedStringsTable shared; // 共享字符串表

        public AbstractExcel2007Reader(OPCPackage pkg) {
            this(pkg, 0);
        }

        @Nonnull
        public AbstractExcel2007Reader(OPCPackage pkg, @Min(0) int index) {
            this.pkg = pkg;
            this.index = index;
            this.value = Strings.EMPTY_STRING;
        }

        /**
         * 构建行对象,解析行开始标签时调用
         *
         * @return 行对象实例
         */
        protected Row buildRow() {
            return new XMLRow();
        }

        /**
         * 构建单元格,解析单元格开始标签时调用
         *
         * @param row    行对象
         * @param column 列下标(从0开始)
         * @return 单元格对象
         */
        @Nonnull
        protected Cell buildCell(Row row, @Min(0) int column) {
            return row.createCell(column);
        }

        /**
         * 构建XML数据读取处理器
         *
         * @return XML数据读取处理器
         * @throws SAXException 构建异常
         */
        protected XMLReader buildXMLReader() throws SAXException {
            return XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        }

        /**
         * 根据单元格标签属性解析单元格地址,解析单元格开始标签时调用
         *
         * @param attributes 标签属性对象
         * @return 单元格地址
         */
        @Nonnull
        protected CellAddress analyseCellAddress(Attributes attributes) {
            return addressAdapter(attributes.getValue("r"));
        }

        /**
         * 初始化单元格类型,解析单元格开始标签时调用
         *
         * @param cell       单元格对象
         * @param attributes 标签属性对象
         */
        @Nonnull
        protected void initializeCellType(Cell cell, Attributes attributes) {
            String t = attributes.getValue("t");
            String s = attributes.getValue("s");
            this.datable = "1".equals(s) || "2".equals(s);
            if ((this.related = "s".equals(t)) || "inlineStr".equals(t)) {
                cell.setCellType(CellType.STRING);
            } else if ("b".equals(t)) {
                cell.setCellType(CellType.BOOLEAN);
            } else if (this.datable || Strings.isEmpty(t)) {
                cell.setCellType(CellType.NUMERIC);
            } else {
                cell.setCellType(CellType.STRING);
            }
        }

        /**
         * 初始化单元格值,解析单元格值结束标签时调用
         *
         * @param cell  单元格对象
         * @param value 原始值
         */
        @Nonnull
        protected void initializeCellValue(Cell cell, String value) {
            if (this.related) { // 如果为字符串,则从共享关联表中取数据
                cell.setCellValue(this.shared.getItemAt(Integer.parseInt(value)).getString());
            } else if (cell.getCellType() == CellType.BOOLEAN) {
                cell.setCellValue(Integer.parseInt(value) > 0);
            } else if (cell.getCellType() == CellType.NUMERIC) {
                double number = Double.parseDouble(value);
                if (this.datable) {
                    cell.setCellValue(HSSFDateUtil.getJavaDate(number));
                } else {
                    cell.setCellValue(number);
                }
            } else {
                cell.setCellValue(value);
            }
        }

        /**
         * Excel读操作
         *
         * @return 数据行总数
         */
        public int process() {
            try {
                XMLReader parser = this.buildXMLReader();
                parser.setContentHandler(this);
                XSSFReader reader = new XSSFReader(this.pkg);
                this.shared = reader.getSharedStringsTable();
                Iterator sheets = reader.getSheetsData();
                while (sheets.hasNext()) {
                    try (InputStream sheet = sheets.next()) {
                        parser.parse(new InputSource(sheet));
                    }
                }
            } catch (IOException | SAXException | OpenXML4JException e) {
                throw new RuntimeException(e);
            }
            return this.count;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            if (this.value.isEmpty()) {
                this.value = new String(ch, start, length);
            } else {
                this.value += new String(ch, start, length);
            }
        }

        @Override
        public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
            if ("row".equals(name)) { // 行开始标签
                this.row = null; // 清空当前行
                int number = Integer.parseInt(attributes.getValue("r")) - 1; // 当前行下标
                if (number >= this.index) {
                    this.row = this.buildRow();
                    this.row.setRowNum(number);
                }
            } else if (this.row != null && this.row.getRowNum() >= this.index) {
                this.value = Strings.EMPTY_STRING; // 清空过程数据
                if ("c".equals(name)) { // 单元格开始标签
                    // 解析单元格所在列下标
                    this.column = this.analyseCellAddress(attributes).getColumn();
                    // 构建单元格并初始化单元格值类型
                    this.initializeCellType(this.buildCell(this.row, this.column), attributes);
                }
            }
        }

        @Override
        public void endElement(String uri, String localName, String name) throws SAXException {
            if (this.row != null && this.row.getRowNum() >= this.index) {
                if ("row".equals(name) && !isEmpty(this.row)) { // 行解析完成
                    this.read(this.row, ++this.count);
                } else if (!this.value.isEmpty() && ("v".equals(name) || "t".equals(name))) { // 值解析完成
                    this.initializeCellValue(this.row.getCell(this.column), this.value);
                }
            }
        }
    }

    /**
     * Excel2007数据行记录器
     */
    public static class Excel2007Counter extends AbstractExcel2007Reader {
        public Excel2007Counter(OPCPackage pkg) {
            super(pkg);
        }

        public Excel2007Counter(OPCPackage pkg, int index) {
            super(pkg, index);
        }

        @Override
        protected void initializeCellType(Cell cell, Attributes attributes) {
            cell.setCellType(CellType.NUMERIC);
        }

        @Override
        protected void initializeCellValue(Cell cell, String value) {
            CellType type = cell.getCellType();
            if (!(type == CellType.BLANK || type == CellType.STRING)
                    || (type == CellType.STRING && !value.isEmpty() && !value.trim().isEmpty())) {
                cell.setCellValue(1); // 设置1表示此单元格值不为空
            }
        }

        @Override
        public void read(Row row, int count) {
        }
    }

    /**
     * 将Excel列字母字符串转换成下标
     *
     * @param column 列字母字符串
     * @return 列下标
     */
    @Nonnull
    public static int columnAdapter(String column) {
        if (!Strings.isLetter(column)) {
            throw new IllegalArgumentException("Invalid cell column: " + column);
        }
        int basic = 1, number = 0;
        for (int i = column.toUpperCase().length() - 1; i >= 0; i--) {
            number += (Character.toUpperCase(column.charAt(i)) - 'A' + 1) * basic;
            basic *= 26;
            if (number > Integer.MAX_VALUE) {
                return -1;
            }
        }
        return number - 1;
    }

    /**
     * 将Excel列下标适配成字母形式
     *
     * @param column 列下标(从0开始)
     * @return 字母字符串
     */
    public static String columnAdapter(@Min(0) int column) {
        StringBuilder letter = new StringBuilder();
        do {
            int mod = column % 26;
            letter.append((char) (mod + 'A'));
            column = (column - mod) / 26;
        } while (column-- > 0);
        return letter.reverse().toString();
    }

    /**
     * 将单元格对象转换成单元格地址字符串形式
     *
     * @param cell 单元格对象
     * @return 单元格地址字符串
     */
    @Nonnull
    public static String addressAdapter(Cell cell) {
        return addressAdapter(cell.getRowIndex(), cell.getColumnIndex());
    }

    /**
     * 将单元格地址对象转换成字符串形式
     *
     * @param address 单元格地址对象
     * @return 单元格地址字符串
     */
    @Nonnull
    public static String addressAdapter(CellAddress address) {
        return addressAdapter(address.getRow(), address.getColumn());
    }

    /**
     * 将单元格地址对象转换成字符串形式
     *
     * @param row    单元格行下标
     * @param column 单元格列下标
     * @return 单元格地址字符串
     */
    public static String addressAdapter(@Min(0) int row, @Min(0) int column) {
        return columnAdapter(column).concat(String.valueOf(row + 1));
    }

    /**
     * 将单元格地址字符串转换成地址对象
     *
     * @param address 单元格地址字符串
     * @return 单元格地址对象
     */
    @Nonnull
    public static CellAddress addressAdapter(String address) {
        for (int i = 0; i < address.length(); i++) {
            char c = address.charAt(i);
            if (c >= '1' && c <= '9') {
                return new CellAddress(Integer.parseInt(address.substring(i)) - 1, columnAdapter(address.substring(0, i)));
            }
        }
        throw new IllegalArgumentException("Invalid cell address: " + address);
    }

    /**
     * 构建Excel工作薄
     *
     * @return Excel工作薄
     */
    public static Workbook buildWorkbook() {
        return buildWorkbook(Type.XLSX);
    }

    /**
     * 构建Excel工作薄
     *
     * @param type Excel类型
     * @return Excel工作薄
     */
    @Nonnull
    public static Workbook buildWorkbook(Type type) {
        if (type == Type.XLS) {
            return new HSSFWorkbook();
        } else if (type == Type.XLSX) {
            return new SXSSFWorkbook();
        }
        throw new IllegalArgumentException("Not support excel type: " + type);
    }

    /**
     * 构建Excel工作薄
     *
     * @param file 文件对象
     * @return Excel工作薄
     * @throws IOException IO操作异常
     */
    @Nonnull
    public static Workbook buildWorkbook(File file) throws IOException {
        try (InputStream is = new FileInputStream(file)) {
            return buildWorkbook(is, Type.parse(Files.getSuffix(file.getName())));
        }
    }

    /**
     * 构建Excel工作薄
     *
     * @param path 文件路径
     * @return Excel工作薄
     * @throws IOException IO操作异常
     */
    @Nonnull
    public static Workbook buildWorkbook(String path) throws IOException {
        try (InputStream is = new FileInputStream(path)) {
            return buildWorkbook(is, Type.parse(Files.getSuffix(path)));
        }
    }

    /**
     * 构建Excel工作薄
     *
     * @param input 数据输入流
     * @param type  文件类型
     * @return Excel工作薄
     * @throws IOException IO操作异常
     */
    @Nonnull
    public static Workbook buildWorkbook(InputStream input, Type type) throws IOException {
        Workbook workbook;
        if (type == Type.XLS) {
            workbook = new HSSFWorkbook(input);
        } else if (type == Type.XLSX) {
            workbook = new XSSFWorkbook(input);
        } else {
            throw new IllegalArgumentException("Not support excel type: " + type);
        }
        evaluator.set(workbook.getCreationHelper().createFormulaEvaluator());
        return workbook;
    }

    /**
     * 构建Excel表
     *
     * @param workbook Excel工作薄
     * @param titles   表格标题数组
     * @return Excel表对象
     */
    public static Sheet buildSheet(Workbook workbook, String... titles) {
        return buildSheet(workbook, null, titles);
    }

    /**
     * 构建Excel表
     *
     * @param workbook Excel工作薄
     * @param name     表名称
     * @param titles   表标题数组
     * @return Excel表对象
     */
    public static Sheet buildSheet(@Nonnull Workbook workbook, String name, @Nonnull String... titles) {
        Sheet sheet = Strings.isEmpty(name) ? workbook.createSheet() : workbook.createSheet(name);
        if (titles.length > 0) {
            setTitles(sheet.createRow(0), titles);
        }
        return sheet;
    }

    /**
     * 构建默认读写器
     *
     * @param workbook Excel工作薄
     * @return Excel读接口
     */
    public static Reader buildReadWriter(Workbook workbook) {
        return new ReadWriter(workbook);
    }

    /**
     * 构建默认读写器
     *
     * @param workbook Excel工作薄
     * @param index    写文件开始行下标
     * @return Excel读接口
     */
    public static Reader buildReadWriter(Workbook workbook, int index) {
        return new ReadWriter(workbook, index);
    }

    /**
     * 构建指定读写器
     *
     * @param workbook Excel工作薄
     * @param writer   Excel写接口
     * @return Excel读接口
     */
    public static Reader buildReadWriter(Workbook workbook, Writer writer) {
        return new ReadWriter(workbook, writer);
    }

    /**
     * 构建指定读写器
     *
     * @param workbook Excel工作薄
     * @param index    写文件开始行下标
     * @param writer   Excel写接口
     * @return Excel读接口
     */
    public static Reader buildReadWriter(Workbook workbook, int index, Writer writer) {
        return new ReadWriter(workbook, index, writer);
    }

    /**
     * 保存Excel数据到本地文件
     *
     * @param workbook Excel工作薄
     * @param file     文件对象
     * @throws IOException IO操作异常
     */
    @Nonnull
    public static void save(Workbook workbook, File file) throws IOException {
        try (OutputStream output = new FileOutputStream(file)) {
            workbook.write(output);
        } finally {
            workbook.close();
        }
    }

    /**
     * 拷贝单元格
     *
     * @param source 原始单元格
     * @param target 目标单元格
     */
    @Nonnull
    public static void copy(Cell source, Cell target) {
        if (source.getCellType() == CellType.FORMULA) {
            target.setCellStyle(source.getCellStyle());
            target.setCellFormula(source.getCellFormula());
        } else {
            setValue(target, source.getCellStyle(), getValue(source));
        }
    }

    /**
     * 拷贝行
     *
     * @param source 原始行
     * @param target 目标行
     */
    @Nonnull
    public static void copy(Row source, Row target) {
        for (int i = source.getFirstCellNum(), len = source.getLastCellNum(); i < len; i++) {
            Cell cell = source.getCell(i);
            if (cell != null) {
                copy(cell, target.createCell(i));
            }
        }
    }

    /**
     * 拷贝表格
     *
     * @param source 原始表格
     * @param target 目标表格
     */
    @Nonnull
    public static void copy(Sheet source, Sheet target) {
        for (int r = source.getFirstRowNum(), rows = source.getLastRowNum(); r <= rows; r++) {
            Row row = source.getRow(r);
            if (row != null) {
                copy(row, target.createRow(r));
            }
        }
    }

    /**
     * 判断Excel数据行是否为空
     *
     * @param row Excel数据行对象
     * @return true/false
     */
    public static boolean isEmpty(Row row) {
        if (row != null) {
            for (int i = row.getFirstCellNum(), len = row.getLastCellNum(); i < len; i++) {
                Object value = getValue(row.getCell(i));
                if (value != null && (!(value instanceof CharSequence) || !Strings.isBlank((CharSequence) value))) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 获取Excel单元格值
     *
     * @param cell Excel单元格对象
     * @return 值
     */
    public static Object getValue(Cell cell) {
        if (cell == null) {
            return null;
        } else if (cell instanceof XMLCell) {
            Object value = ((XMLCell) cell).value;
            return value instanceof String ? Strings.trim((String) value) : value;
        }
        CellType type = cell.getCellType();
        if (type == CellType.BLANK) {
            return null;
        } else if (type == CellType.BOOLEAN) {
            return cell.getBooleanCellValue();
        } else if (type == CellType.NUMERIC) {
            return HSSFDateUtil.isCellDateFormatted(cell) ? cell.getDateCellValue() : cell.getNumericCellValue();
        } else if (type == CellType.FORMULA) {
            if (cell.getCachedFormulaResultType() == CellType.ERROR) {
                return FormulaError.forInt(cell.getErrorCellValue()).getString();
            }
            CellValue value = evaluator.get().evaluate(cell);
            if (value.getCellType() == CellType.NUMERIC) {
                return HSSFDateUtil.isCellDateFormatted(cell) ? HSSFDateUtil.getJavaDate(value.getNumberValue()) : value.getNumberValue();
            } else if (value.getCellType() == CellType.BOOLEAN) {
                return value.getBooleanValue();
            }
            return Strings.trim(value.getStringValue());
        } else if (type == CellType.ERROR) {
            return FormulaError.forInt(cell.getErrorCellValue()).getString();
        }
        return Strings.trim(cell.getStringCellValue());
    }

    /**
     * 获取Excel单元格值
     *
     * @param   数据类型
     * @param cell Excel单元格对象
     * @param type 值类型
     * @return 值
     */
    public static  T getValue(Cell cell, Class type) {
        return (T) Objects.toObject(type, getValue(cell));
    }

    /**
     * 获取单元格日期值
     *
     * @param cell 单元格对象
     * @return 日期对象
     */
    public static Date getDate(Cell cell) {
        return getDate(cell, Dates.ALL_DATE_FORMATS);
    }

    /**
     * 获取单元格日期值
     *
     * @param cell     单元格对象
     * @param patterns 日期格式数组
     * @return 日期对象
     */
    public static Date getDate(Cell cell, String... patterns) {
        return Objects.toDate(getValue(cell), patterns);
    }

    /**
     * 获取单元格文本值
     *
     * @param cell 单元格对象
     * @return 数据文本
     */
    public static String getString(Cell cell) {
        return Strings.toString(getValue(cell));
    }

    /**
     * 获取单元格数值
     *
     * @param cell 单元格对象
     * @return 数值
     */
    public static Double getNumber(Cell cell) {
        return Objects.toDouble(getValue(cell));
    }

    /**
     * 获取单元格Boolean值
     *
     * @param cell 单元格对象
     * @return true/false
     */
    public static Boolean getBoolean(Cell cell) {
        return Objects.toBoolean(getValue(cell));
    }

    /**
     * 获取Excel一行单元格的值,如果所有值都为空则返回空数组
     *
     * @param row Excel行对象
     * @return 值数组
     */
    public static Object[] getValues(Row row) {
        return getValues(row, Object.class);
    }

    /**
     * 获取Excel一行单元格的值,如果所有值都为空则返回空数组
     *
     * @param   数据类型
     * @param row  Excel行对象
     * @param type 数据类型
     * @return 值数组
     */
    public static  T[] getValues(Row row, @Nonnull Class type) {
        if (row == null) {
            return null;
        }
        int columns = row.getLastCellNum();
        T[] values = (T[]) Array.newInstance(type, columns);
        if (columns > 0) {
            for (int i = row.getFirstCellNum(); i < columns; i++) {
                values[i] = getValue(row.getCell(i), type);
            }
            for (; columns > 0 && values[columns - 1] == null; columns--) ; // 移除最右边连续为空的值
        }
        return columns == 0 ? values : Arrays.copyOf(values, columns);
    }

    /**
     * 设置Excel单元格值
     *
     * @param cell  Excel单元格对象
     * @param value 值
     */
    public static void setValue(@Nonnull Cell cell, Object value) {
        setValue(cell, null, value);
    }

    /**
     * 设置Excel单元格值
     *
     * @param cell  Excel单元格对象
     * @param style 单元格样式
     * @param value 值
     */
    public static void setValue(@Nonnull Cell cell, CellStyle style, Object value) {
        if (!Objects.isEmpty(value)) {
            cell.setCellStyle(style);
            if (value instanceof Number) {
                cell.setCellValue(((Number) value).doubleValue());
            } else if (value instanceof Boolean) {
                cell.setCellValue((Boolean) value);
            } else if (value instanceof Formula) {
                cell.setCellFormula(((Formula) value).value);
            } else if (value instanceof Iterable || value.getClass().isArray()) {
                cell.setCellValue(Strings.join(value, ","));
            } else {
                cell.setCellValue(Strings.toString(value));
            }
        }
    }

    /**
     * 设置Excel单元格值
     *
     * @param row    Excel行对象
     * @param values 单元格值数组
     */
    @Nonnull
    public static void setValues(Row row, Object... values) {
        setValues(row, null, values);
    }

    /**
     * 设置Excel单元格值
     *
     * @param row    Excel行对象
     * @param style  单元格样式
     * @param values 单元格值数组
     */
    public static void setValues(@Nonnull Row row, CellStyle style, @Nonnull Object... values) {
        if (values.length > 0) {
            for (int i = 0; i < values.length; i++) {
                setValue(row.createCell(i), style, values[i]);
            }
        }
    }

    /**
     * 添加Excel单元格值
     *
     * @param row    Excel行对象
     * @param values 单元格值数组
     */
    @Nonnull
    public static void appendValues(Row row, Object... values) {
        appendValues(row, null, values);
    }

    /**
     * 添加Excel单元格值
     *
     * @param row    Excel行对象
     * @param style  单元格样式
     * @param values 单元格值数组
     */
    public static void appendValues(@Nonnull Row row, CellStyle style, @Nonnull Object... values) {
        if (values.length > 0) {
            int i = row.getLastCellNum();
            for (Object value : values) {
                setValue(row.createCell(i++), style, value);
            }
        }
    }

    /**
     * 插入Excel单元格值
     *
     * @param row    Excel行对象
     * @param index  插入下标
     * @param values 单元格值数组
     */
    @Nonnull
    public static void insertValues(Row row, int index, Object... values) {
        insertValues(row, index, null, values);
    }

    /**
     * 插入Excel单元格值
     *
     * @param row    Excel行对象
     * @param index  插入下标
     * @param style  单元格样式
     * @param values 单元格值数组
     */
    public static void insertValues(@Nonnull Row row, int index, CellStyle style, @Nonnull Object... values) {
        if (values.length > 0) {
            int len = row.getLastCellNum();
            if (index < 0) {
                index = (Math.abs(index) > len ? index % len : index) + len;
            }
            // 移动单元格
            for (int i = len - 1; i >= index; i--) {
                copy(row.getCell(i), row.createCell(i + values.length));
            }
            // 设置插入值
            for (Object value : values) {
                setValue(row.createCell(index++), style, value);
            }
        }
    }

    /**
     * 设置单元格树
     *
     * @param sheet Excel表
     * @param trees 单元格树数组
     */
    public static void setValues(Sheet sheet, Tree... trees) {
        setValues(sheet, 0, trees);
    }

    /**
     * 设置单元格树
     *
     * @param sheet Excel表
     * @param index 开始行下标(从0开始)
     * @param trees 单元格树数组
     */
    public static void setValues(Sheet sheet, int index, Tree... trees) {
        setValues(sheet, index, 0, new HashMap<>(), trees);
    }

    /**
     * 设置单元格树
     *
     * @param sheet  Excel表
     * @param index  开始行下标(从0开始)
     * @param column 开始列下标(从0开始)
     * @param cache  行对象缓存
     * @param trees  单元格树数组
     */
    @Nonnull
    private static void setValues(Sheet sheet, @Min(0) int index, @Min(0) int column, Map cache, Tree... trees) {
        if (trees.length > 0) {
            Row row = cache.get(index);
            if (row == null) {
                row = sheet.createRow(index);
                cache.put(index, row);
            }
            for (Tree tree : trees) {
                setValue(row.createCell(column), tree.style, tree.value); // 设置单元格值
                if (tree.width > 1 || tree.rowspan > 1) { // 合并单元格
                    sheet.addMergedRegion(new CellRangeAddress(index, index + tree.rowspan - 1, column, column + tree.width - 1));
                }
                if (tree.children.length > 0) { // 遍历单元格子树
                    setValues(sheet, index + tree.rowspan, column, cache, tree.children);
                }
                column += tree.width;
            }
        }
    }

    /**
     * 设置Excel过滤器
     *
     * @param sheet Excel表格
     * @param index 行下标
     */
    @Nonnull
    public static void setFilters(Sheet sheet, @Min(0) int index) {
        Row row = sheet.getRow(index);
        if (row != null) {
            sheet.setAutoFilter(new CellRangeAddress(index, index, row.getFirstCellNum(), row.getLastCellNum() - 1));
        }
    }

    /**
     * 构建标题样式
     *
     * @param workbook Excel工作薄
     * @return 样式对象
     */
    @Nonnull
    public static CellStyle buildTitleStyle(Workbook workbook) {
        Font font = workbook.createFont();
        font.setBold(true);
        CellStyle style = workbook.createCellStyle();
        style.setFont(font);
        style.setAlignment(HorizontalAlignment.CENTER);
        return style;
    }

    /**
     * 设置Excel标题
     *
     * @param row    Excel行
     * @param titles 标题数组
     */
    @Nonnull
    public static void setTitles(Row row, String... titles) {
        if (titles.length > 0) {
            setValues(row, buildTitleStyle(row.getSheet().getWorkbook()), titles);
        }
    }

    /**
     * 统计Excel数据行数
     *
     * @param sheet Excel表格
     * @return 数量
     */
    public static int count(Sheet sheet) {
        return count(sheet, 0);
    }

    /**
     * 统计Excel数据行数
     *
     * @param workbook Excel工作薄
     * @return 数量
     */
    public static int count(Workbook workbook) {
        return count(workbook, 0);
    }

    /**
     * 统计Excel数据行数
     *
     * @param sheet Excel表格
     * @param index 开始数据行下标(从0开始)
     * @return 数量
     */
    @Nonnull
    public static int count(Sheet sheet, @Min(0) int index) {
        return sheet.getPhysicalNumberOfRows() - index;
    }

    /**
     * 统计Excel数据行数
     *
     * @param workbook Excel工作薄
     * @param index    开始数据行下标(从0开始)
     * @return 数量
     */
    @Nonnull
    public static int count(Workbook workbook, @Min(0) int index) {
        int count = 0;
        for (int i = 0, sheets = workbook.getNumberOfSheets(); i < sheets; i++) {
            count += count(workbook.getSheetAt(i), index);
        }
        return count;
    }

    /**
     * 统计Excel数据行数
     *
     * @param pkg 文件包
     * @return 数量
     */
    public static int count(OPCPackage pkg) {
        return count(pkg, 0);
    }

    /**
     * 统计Excel数据行数
     *
     * @param pkg   文件包
     * @param index 开始数据行下标(从0开始)
     * @return 数量
     */
    @Nonnull
    public static int count(OPCPackage pkg, @Min(0) int index) {
        return new Excel2007Counter(pkg, index).process();
    }

    /**
     * 通过解析XML的方式读取Excel2007数据
     *
     * @param pkg    文件包
     * @param reader Excel读接口
     * @return 读取数量
     */
    public static int read(OPCPackage pkg, Reader reader) {
        return read(pkg, 0, reader);
    }

    /**
     * 通过解析XML的方式读取Excel2007数据
     *
     * @param pkg    文件包
     * @param index  开始数据行下标(从0开始)
     * @param reader Excel读接口
     * @return 读取数量
     */
    public static int read(OPCPackage pkg, int index, Reader reader) {
        return new AbstractExcel2007Reader(pkg, index) {
            @Override
            public void read(Row row, int count) {
                reader.read(row, count);
            }
        }.process();
    }

    /**
     * 根据Excel行获取对象实例
     *
     * @param   数据类型
     * @param row  Excel行对象
     * @param type 对象类型
     * @return 对象实例
     */
    public static  M read(Row row, Class type) {
        return isEmpty(row) ? null : Objects.initialize(type, getValues(row));
    }

    /**
     * 从Excel中获取对象实例
     *
     * @param    数据类型
     * @param sheet Excel表格
     * @param type  对象类型
     * @return 对象实例列表
     */
    public static  List read(Sheet sheet, Class type) {
        return read(sheet, type, 0);
    }

    /**
     * 从Excel中获取对象实例
     *
     * @param       数据类型
     * @param workbook Excel工作薄
     * @param type     对象类型
     * @return 对象实例列表
     */
    public static  List read(Workbook workbook, Class type) {
        return read(workbook, type, 0);
    }

    /**
     * 从Excel中获取对象实例
     *
     * @param       数据类型
     * @param workbook Excel工作薄
     * @param type     对象类型
     * @param index    开始数据行下标(从0开始)
     * @return 对象实例列表
     */
    @Nonnull
    public static  List read(Workbook workbook, Class type, @Min(0) int index) {
        List objects = new LinkedList();
        for (int i = 0, sheets = workbook.getNumberOfSheets(); i < sheets; i++) {
            read(workbook.getSheetAt(i), type, index, objects);
        }
        return objects;
    }

    /**
     * 从Excel中获取对象实例
     *
     * @param    数据类型
     * @param sheet Excel表格
     * @param type  对象类型
     * @param index 开始数据行下标(从0开始)
     * @return 对象实例列表
     */
    @Nonnull
    public static  List read(Sheet sheet, Class type, @Min(0) int index) {
        List objects = new LinkedList();
        read(sheet, type, index, objects);
        return objects;
    }

    /**
     * 从Excel中获取对象实例
     *
     * @param        数据类型
     * @param sheet     Excel表格
     * @param type      对象类型
     * @param index     开始数据行下标(从0开始)
     * @param container 对象容器
     */
    @Nonnull
    private static  void read(Sheet sheet, Class type, @Min(0) int index, List container) {
        for (int r = index, rows = sheet.getLastRowNum(); r <= rows; r++) {
            M object = read(sheet.getRow(r), type);
            if (object != null) {
                container.add(object);
            }
        }
    }

    /**
     * 读Excel
     *
     * @param sheet  Excel表格
     * @param reader Excel读接口
     * @return 读取数量
     */
    public static int read(Sheet sheet, Reader reader) {
        return read(sheet, 0, reader);
    }

    /**
     * 读Excel
     *
     * @param workbook Excel工作薄
     * @param reader   Excel读接口
     * @return 读取数量
     */
    public static int read(Workbook workbook, Reader reader) {
        return read(workbook, 0, reader);
    }

    /**
     * 读Excel
     *
     * @param sheet  Excel表格
     * @param index  开始数据行下标(从0开始)
     * @param reader Excel读接口
     * @return 读取数量
     */
    @Nonnull
    public static int read(Sheet sheet, @Min(0) int index, Reader reader) {
        int[] count = {0};
        read(sheet, index, count, reader);
        return count[0];
    }

    /**
     * 读Excel
     *
     * @param workbook Excel工作薄
     * @param index    开始数据行下标(从0开始)
     * @param reader   Excel读接口
     * @return 读取数量
     */
    @Nonnull
    public static int read(Workbook workbook, @Min(0) int index, Reader reader) {
        int[] count = {0};
        for (int i = 0, sheets = workbook.getNumberOfSheets(); i < sheets; i++) {
            read(workbook.getSheetAt(i), index, count, reader);
        }
        return count[0];
    }

    /**
     * 读Excel
     *
     * @param sheet  Excel表格
     * @param index  开始数据行下标(从0开始)
     * @param count  当前记录数(从1开始)
     * @param reader Excel读接口
     */
    private static void read(Sheet sheet, int index, int[] count, Reader reader) {
        for (int r = index, rows = sheet.getLastRowNum(); r <= rows; r++) {
            Row row = sheet.getRow(r);
            if (!isEmpty(row)) {
                reader.read(row, ++count[0]);
            }
        }
    }

    /**
     * 设置对象实例到Excel行
     *
     * @param row    Excel行对象
     * @param object 对象实例
     */
    public static void write(@Nonnull Row row, Object object) {
        if (object != null) {
            Objects.access(object.getClass(), (field, i) -> setValue(row.createCell(i), Objects.getValue(object, field)));
        }
    }

    /**
     * 将对象实例设置到Excel中
     *
     * @param sheet   Excel表格
     * @param objects 对象实例列表
     */
    public static void write(Sheet sheet, List objects) {
        write(sheet, objects, 0);
    }

    /**
     * 将对象实例设置到Excel中
     *
     * @param workbook Excel工作薄
     * @param objects  对象实例列表
     */
    public static void write(Workbook workbook, List objects) {
        write(workbook, objects, 0);
    }

    /**
     * 将对象实例设置到Excel中
     *
     * @param sheet   Excel表格
     * @param objects 对象实例列表
     * @param index   开始数据行下标(从0开始)
     */
    @Nonnull
    public static void write(Sheet sheet, List objects, @Min(0) int index) {
        write(sheet, objects, index, (row, object, count) -> write(row, object));
    }

    /**
     * 将对象实例设置到Excel中
     *
     * @param workbook Excel工作薄
     * @param objects  对象实例列表
     * @param index    开始数据行下标(从0开始)
     */
    @Nonnull
    public static void write(Workbook workbook, List objects, @Min(0) int index) {
        write(workbook, objects, index, (row, object, count) -> write(row, object));
    }

    /**
     * 将对象实例写入到Excel中
     *
     * @param      数据类型
     * @param sheet   Excel表格
     * @param objects 对象实例列表
     * @param writer  Excel对象实例写入接口
     */
    public static  void write(Sheet sheet, List objects, Writer writer) {
        write(sheet, objects, 0, writer);
    }

    /**
     * 将对象实例写入到Excel中
     *
     * @param       数据类型
     * @param workbook Excel工作薄
     * @param objects  对象实例列表
     * @param writer   Excel对象实例写入接口
     */
    public static  void write(Workbook workbook, List objects, Writer writer) {
        write(workbook, objects, 0, writer);
    }

    /**
     * 将对象实例写入到Excel中
     *
     * @param      数据类型
     * @param sheet   Excel表格
     * @param objects 对象实例列表
     * @param index   开始数据行下标(从0开始)
     * @param writer  Excel对象实例写入接口
     */
    @Nonnull
    public static  void write(Sheet sheet, List objects, @Min(0) int index, Writer writer) {
        if (!objects.isEmpty()) {
            int c = 0;
            for (M object : objects) {
                writer.write(sheet.createRow(index++), object, ++c);
            }
        }
    }

    /**
     * 将对象实例写入到Excel中
     *
     * @param       数据类型
     * @param workbook Excel工作薄
     * @param objects  对象实例列表
     * @param index    开始数据行下标(从0开始)
     * @param writer   Excel对象实例写入接口
     */
    @Nonnull
    public static  void write(Workbook workbook, List objects, @Min(0) int index, Writer writer) {
        if (!objects.isEmpty()) {
            int c = 0, r = index;
            Sheet sheet = workbook.createSheet();
            for (M object : objects) {
                if (r > index && r % DEFAULT_SHEET_VOLUME == 0) {
                    r = index;
                    sheet = workbook.createSheet();
                }
                writer.write(sheet.createRow(r++), object, ++c);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy