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

cn.afterturn.easypoi.excel.html.HtmlToExcelService Maven / Gradle / Ivy

The newest version!
package cn.afterturn.easypoi.excel.html;

import java.util.Map;
import java.util.List;

import cn.afterturn.easypoi.excel.html.entity.ExcelAttrConstant;
import cn.afterturn.easypoi.util.PoiMergeCellUtil;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jsoup.Jsoup;

import org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTDataValidationImpl;
import org.slf4j.Logger;

import java.util.HashMap;
import java.util.LinkedList;

import org.slf4j.LoggerFactory;

import cn.afterturn.easypoi.excel.export.styler.ExcelExportStylerDefaultImpl;
import cn.afterturn.easypoi.excel.html.css.CssParseService;
import cn.afterturn.easypoi.excel.html.css.ICssConvertToExcel;
import cn.afterturn.easypoi.excel.html.css.impl.AlignCssConvertImpl;
import cn.afterturn.easypoi.excel.html.css.impl.BackgroundCssConvertImpl;
import cn.afterturn.easypoi.excel.html.css.impl.BorderCssConverImpl;
import cn.afterturn.easypoi.excel.html.css.impl.HeightCssConverImpl;
import cn.afterturn.easypoi.excel.html.css.impl.TextCssConvertImpl;
import cn.afterturn.easypoi.excel.html.css.impl.WidthCssConverImpl;
import cn.afterturn.easypoi.excel.html.entity.ExcelCssConstant;
import cn.afterturn.easypoi.excel.html.entity.HtmlCssConstant;
import cn.afterturn.easypoi.excel.html.entity.style.CellStyleEntity;

import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import javax.management.RuntimeErrorException;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;

/**
 * 读取Table数据生成Excel
 * @author JueYue
 * 2017年3月19日
 */
public class HtmlToExcelService {
    private final Logger                          LOGGER         = LoggerFactory
        .getLogger(HtmlToExcelService.class);

    //样式
    private static final List STYLE_APPLIERS = new LinkedList();
    static {
        STYLE_APPLIERS.add(new AlignCssConvertImpl());
        STYLE_APPLIERS.add(new BackgroundCssConvertImpl());
        STYLE_APPLIERS.add(new BorderCssConverImpl());
        STYLE_APPLIERS.add(new TextCssConvertImpl());
    }

    //Cell 高宽
    private static final List SHEET_APPLIERS = new LinkedList();
    static {
        SHEET_APPLIERS.add(new WidthCssConverImpl());
        SHEET_APPLIERS.add(new HeightCssConverImpl());
    }
    private Sheet                  sheet;
    private Map    cellsOccupied = new HashMap();
    private Map cellStyles    = new HashMap();
    private CellStyle              defaultCellStyle;
    private int                    maxRow        = 0;
    private CssParseService cssParse      = new CssParseService();

    // --
    // private methods

    private void processTable(Element table) {
        int rowIndex = 0;
        if (maxRow > 0) {
            // blank row
            maxRow += 2;
            rowIndex = maxRow;
        }
        LOGGER.debug("Interate Table Rows.");
        String freezeCol = null;
        int freezeColIndex = -1;


        for (Element row : table.select("tr")) {
            LOGGER.debug("Parse Table Row [{}]. Row Index [{}].", row, rowIndex);

            // 获取当前行
            final Row orCreateRow = getOrCreateRow(rowIndex);

            // 冻结行属性
            String freezeRow = row.attr(ExcelCssConstant.FREEZE_ROW);
            // 隐藏行属性
            String zeroHeightRow = row.attr(ExcelCssConstant.ZEROH_EIGHT_ROW);
            if ("true".equals(freezeRow)) {
                sheet.createFreezePane(0, rowIndex + 1, 0, rowIndex + 1);
            }

            // 隐藏行
            if ("true".equals(zeroHeightRow)) {
                orCreateRow.setZeroHeight(true);
            }


            int colIndex = 0;
            LOGGER.debug("Interate Cols.");
            for (Element td : row.select("td, th")) {
                freezeCol = td.attr(ExcelCssConstant.FREEZE_COL);
                if ("true".equals(freezeCol)) {
                    if (colIndex > freezeColIndex) {
                        freezeColIndex = colIndex;
                    }
                }

                // skip occupied cell
                while (cellsOccupied.get(rowIndex + "_" + colIndex) != null) {
                    LOGGER.debug("Cell [{}][{}] Has Been Occupied, Skip.", rowIndex, colIndex);
                    ++colIndex;
                }
                LOGGER.debug("Parse Col [{}], Col Index [{}].", td, colIndex);
                int rowSpan = 0;
                String strRowSpan = td.attr("rowspan");
                if (StringUtils.isNotBlank(strRowSpan) && StringUtils.isNumeric(strRowSpan)) {
                    LOGGER.debug("Found Row Span [{}].", strRowSpan);
                    rowSpan = Integer.parseInt(strRowSpan);
                }
                int colSpan = 0;
                String strColSpan = td.attr("colspan");
                if (StringUtils.isNotBlank(strColSpan) && StringUtils.isNumeric(strColSpan)) {
                    LOGGER.debug("Found Col Span [{}].", strColSpan);
                    colSpan = Integer.parseInt(strColSpan);
                }
                // col span & row span
                if (colSpan > 1 && rowSpan > 1) {
                    spanRowAndCol(td, rowIndex, colIndex, rowSpan, colSpan);
                    colIndex += colSpan;
                }
                // col span only
                else if (colSpan > 1) {
                    spanCol(td, rowIndex, colIndex, colSpan);
                    colIndex += colSpan;
                }
                // row span only
                else if (rowSpan > 1) {
                    spanRow(td, rowIndex, colIndex, rowSpan);
                    ++colIndex;
                }
                // no span
                else {

                    // 创建下拉框
                    createDataValidation(rowIndex, colIndex, td);

                    final Cell cell = createCell(td, orCreateRow, colIndex);

                    // 创建单元个批注
                    createCellComment(td, cell);

                    cell.setCellValue(td.text());
                    ++colIndex;
                }
            }
            ++rowIndex;
        }
        if (freezeColIndex != -1) {
            sheet.createFreezePane(freezeColIndex + 1, 0, freezeColIndex + 1, 0);
        }
    }

    /**
     * 创建单元个批注
     *
     * @param td   模板单元格
     * @param cell 列
     */
    private void createCellComment(final Element td, final Cell cell) {
        // 批注
        final String comment = td.attr(ExcelAttrConstant.COMMENT_ATTR);
        if (StringUtils.isNotBlank(comment)) {
            //创建绘图对象
            Drawing drawingPatriarch = sheet.getDrawingPatriarch();
            if (drawingPatriarch == null) {
                drawingPatriarch = sheet.createDrawingPatriarch();
            }

            ClientAnchor clientAnchor;
            RichTextString richTextString;
            if (sheet.getWorkbook() instanceof XSSFWorkbook) {
                clientAnchor = new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
                richTextString = new XSSFRichTextString(comment);
            } else {
                clientAnchor = new HSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
                richTextString = new HSSFRichTextString(comment);
            }
            final Comment cellComment = drawingPatriarch.createCellComment(clientAnchor);
            cellComment.setString(richTextString);
            cell.setCellComment(cellComment);
        }
    }

    /**
     * 创建数据校验(下拉框值)
     *
     * @param rowIndex 行
     * @param colIndex 列
     * @param td       表格的列
     */
    private void createDataValidation(final int rowIndex, final int colIndex, final Element td) {
        final String attr = td.attr(ExcelAttrConstant.SELECT_ATTR);
        final String selectSheetAttr = td.attr(ExcelAttrConstant.SELECT_SHEET_ATTR);

        //CellRangeAddressList(firstRow, lastRow, firstCol, lastCol)设置行列范围
        CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, colIndex, colIndex);
        DataValidationHelper helper = sheet.getDataValidationHelper();

        if (StringUtils.isNotBlank(attr)) {
            String[] strs = attr.split(",");
            final Workbook workbook = sheet.getWorkbook();


            //设置下拉框数据
            DataValidationConstraint constraint = helper.createExplicitListConstraint(strs);
            DataValidation dataValidation = helper.createValidation(constraint, addressList);

            //处理Excel兼容性问题
            if (dataValidation instanceof XSSFDataValidation) {
                dataValidation.setSuppressDropDownArrow(true);
                dataValidation.setShowErrorBox(true);
            } else {
                dataValidation.setSuppressDropDownArrow(false);
            }
            sheet.addValidationData(dataValidation);


        }
        // 根据sheet页创建下拉校验
        else if (StringUtils.isNotBlank(selectSheetAttr)) {
            //            部门!$A$2:$A$96
            final String sheetName = selectSheetAttr.split("!")[0];

            // 公式名称
            final String formulaName = sheetName + "_Formula";

            Name name = sheet.getWorkbook().getName(formulaName);
            if (name == null) {
                name = sheet.getWorkbook().createName();
            }


            name.setNameName(formulaName);
            name.setRefersToFormula(selectSheetAttr);

            //设置下拉框数据
            DataValidationConstraint constraint = helper.createFormulaListConstraint(formulaName);
            DataValidation dataValidation = helper.createValidation(constraint, addressList);

            //处理Excel兼容性问题
            if (dataValidation instanceof XSSFDataValidation) {
                dataValidation.setSuppressDropDownArrow(true);
                dataValidation.setShowErrorBox(true);
            } else {
                dataValidation.setSuppressDropDownArrow(false);
            }
            sheet.addValidationData(dataValidation);
        }
    }

    /**
     * 创建sheet页
     *
     * @param html     模板
     * @param workbook 工作簿
     * @return 工作簿
     */
    public Workbook createSheet(String html, Workbook workbook) {
        Elements els = Jsoup.parseBodyFragment(html).select("table");
        Map sheets = new HashMap();
        Map maxrowMap = new HashMap();
        for (Element table : els) {
            String sheetName = table.attr(ExcelCssConstant.SHEET_NAME);
            String sheetHidden = table.attr(ExcelCssConstant.SHEET_HIDDEN);

            if (StringUtils.isBlank(sheetName)) {
                LOGGER.error("table必须存在name属性!");
                throw new RuntimeErrorException(null, "table必须存在name属性");
            }

            if (sheets.containsKey(sheetName)) {
                maxRow = maxrowMap.get(sheetName);
                //cellStyles = csStyleMap.get(sheetName);
                //cellsOccupied = cellsOccupiedMap.get(sheetName);
                sheet = sheets.get(sheetName);
            } else {
                maxRow = 0;
                cellStyles.clear();
                cellsOccupied.clear();
                sheet = workbook.createSheet(sheetName);
            }
            //生成一个默认样式
            defaultCellStyle = new ExcelExportStylerDefaultImpl(workbook).stringNoneStyle(workbook,
                    false);
            processTable(table);
            maxrowMap.put(sheetName, maxRow);
            sheets.put(sheetName, sheet);

            // 隐藏sheet页
            if (StringUtils.isNotBlank(sheetHidden) && "true".equals(sheetHidden)) {
                workbook.setSheetHidden(workbook.getSheetIndex(sheet), true);
            }

        }
        return workbook;
    }

    private void spanRow(Element td, int rowIndex, int colIndex, int rowSpan) {
        LOGGER.debug("Span Row , From Row [{}], Span [{}].", rowIndex, rowSpan);
        mergeRegion(rowIndex, rowIndex + rowSpan - 1, colIndex, colIndex);
        for (int i = 0; i < rowSpan; ++i) {
            Row row = getOrCreateRow(rowIndex + i);
            createCell(td, row, colIndex);
            cellsOccupied.put((rowIndex + i) + "_" + colIndex, true);
        }
        getOrCreateRow(rowIndex).getCell(colIndex).setCellValue(td.text());
    }

    private void spanCol(Element td, int rowIndex, int colIndex, int colSpan) {
        LOGGER.debug("Span Col, From Col [{}], Span [{}].", colIndex, colSpan);
        mergeRegion(rowIndex, rowIndex, colIndex, colIndex + colSpan - 1);
        Row row = getOrCreateRow(rowIndex);
        for (int i = 0; i < colSpan; ++i) {
            createCell(td, row, colIndex + i);
        }
        row.getCell(colIndex).setCellValue(td.text());
    }

    private void spanRowAndCol(Element td, int rowIndex, int colIndex, int rowSpan, int colSpan) {
        LOGGER.debug("Span Row And Col, From Row [{}], Span [{}].", rowIndex, rowSpan);
        LOGGER.debug("From Col [{}], Span [{}].", colIndex, colSpan);
        mergeRegion(rowIndex, rowIndex + rowSpan - 1, colIndex, colIndex + colSpan - 1);
        for (int i = 0; i < rowSpan; ++i) {
            Row row = getOrCreateRow(rowIndex + i);
            for (int j = 0; j < colSpan; ++j) {
                createCell(td, row, colIndex + j);
                cellsOccupied.put((rowIndex + i) + "_" + (colIndex + j), true);
            }
        }
        getOrCreateRow(rowIndex).getCell(colIndex).setCellValue(td.text());
    }

    private Cell createCell(Element td, Row row, int colIndex) {
        Cell cell = row.getCell(colIndex);
        if (cell == null) {
            LOGGER.debug("Create Cell [{}][{}].", row.getRowNum(), colIndex);
            cell = row.createCell(colIndex);
        }
        return applyStyle(td, cell);
    }

    private Cell applyStyle(Element td, Cell cell) {
        String style = td.attr(HtmlCssConstant.STYLE);
        CellStyle cellStyle = null;
        if (StringUtils.isNotBlank(style)) {
            CellStyleEntity styleEntity = cssParse.parseStyle(style.trim());
            cellStyle = cellStyles.get(styleEntity.toString());
            if (cellStyle == null) {
                LOGGER.debug("No Cell Style Found In Cache, Parse New Style.");
                cellStyle = cell.getRow().getSheet().getWorkbook().createCellStyle();
                cellStyle.cloneStyleFrom(defaultCellStyle);
                for (ICssConvertToExcel cssConvert : STYLE_APPLIERS) {
                    cssConvert.convertToExcel(cell, cellStyle, styleEntity);
                }
                cellStyles.put(styleEntity.toString(), cellStyle);
            }
            for (ICssConvertToExcel cssConvert : SHEET_APPLIERS) {
                cssConvert.convertToExcel(cell, cellStyle, styleEntity);
            }
            if (cellStyles.size() >= 4000) {
                LOGGER.info(
                        "Custom Cell Style Exceeds 4000, Could Not Create New Style, Use Default Style.");
                cellStyle = defaultCellStyle;
            }
        } else {
            LOGGER.debug("Style is null ,Use Default Cell Style.");
            cellStyle = defaultCellStyle;
        }

        cell.setCellStyle(cellStyle);
        return cell;
    }

    private Row getOrCreateRow(int rowIndex) {
        Row row = sheet.getRow(rowIndex);
        if (row == null) {
            LOGGER.debug("Create New Row [{}].", rowIndex);
            row = sheet.createRow(rowIndex);
            if (rowIndex > maxRow) {
                maxRow = rowIndex;
            }
        }
        return row;
    }

    private void mergeRegion(int firstRow, int lastRow, int firstCol, int lastCol) {
        LOGGER.debug("Merge Region, From Row [{}], To [{}].", firstRow, lastRow);
        LOGGER.debug("From Col [{}], To [{}].", firstCol, lastCol);
        PoiMergeCellUtil.addMergedRegion(sheet, firstRow, lastRow, firstCol, lastCol);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy