/* * Copyright 2017 the original author or authors. * * 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.github.liaochong.myexcel.core; import com.github.liaochong.myexcel.core.parser.ContentTypeEnum; import com.github.liaochong.myexcel.core.parser.HtmlTableParser; import com.github.liaochong.myexcel.core.parser.Td; import com.github.liaochong.myexcel.core.parser.Tr; import com.github.liaochong.myexcel.core.strategy.WidthStrategy; import com.github.liaochong.myexcel.core.style.BackgroundStyle; import com.github.liaochong.myexcel.core.style.BorderStyle; import com.github.liaochong.myexcel.core.style.CustomColor; import com.github.liaochong.myexcel.core.style.FontStyle; import com.github.liaochong.myexcel.core.style.LinkDefaultCellStyle; import com.github.liaochong.myexcel.core.style.TdDefaultCellStyle; import com.github.liaochong.myexcel.core.style.TextAlignStyle; import com.github.liaochong.myexcel.core.style.ThDefaultCellStyle; import com.github.liaochong.myexcel.core.style.WordBreakStyle; import com.github.liaochong.myexcel.utils.StringUtil; import com.github.liaochong.myexcel.utils.TdUtil; import lombok.NonNull; import org.apache.poi.common.usermodel.HyperlinkType; 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.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.CreationHelper; import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidationConstraint; import org.apache.poi.ss.usermodel.DataValidationHelper; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.Picture; import org.apache.poi.ss.usermodel.RichTextString; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFDataValidation; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.IOException; import java.nio.file.Files; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; /** * @author liaochong * @version 1.0 */ public abstract class AbstractExcelFactory implements ExcelFactory { protected Workbook workbook; /** * 是否为hssf */ protected boolean isHssf; /** * 每行的单元格最大高度map */ private Map<Integer, Short> maxTdHeightMap = new HashMap<>(); /** * 是否使用默认样式 */ private boolean useDefaultStyle; /** * 自定义颜色 */ private CustomColor customColor; /** * 单元格样式映射 */ private Map<Map<String, String>, CellStyle> cellStyleMap = new HashMap<>(); /** * 样式容器 */ private Map<HtmlTableParser.HtmlTag, CellStyle> defaultCellStyleMap; /** * 字体map */ private Map<String, Font> fontMap = new HashMap<>(); /** * 冻结区域 */ private FreezePane[] freezePanes; /** * 自动宽度策略,默认无宽度策略 */ protected WidthStrategy widthStrategy = WidthStrategy.NO_AUTO; /** * 暂存单元格,由后续行认领 */ private List<Td> stagingTds = new LinkedList<>(); private CreationHelper createHelper; private DataFormat format; @Override public ExcelFactory useDefaultStyle() { this.useDefaultStyle = true; return this; } @Override public ExcelFactory freezePanes(FreezePane... freezePanes) { this.freezePanes = freezePanes; return this; } @Override public ExcelFactory workbookType(WorkbookType workbookType) { if (Objects.nonNull(workbook)) { return this; } switch (workbookType) { case XLS: workbook = new HSSFWorkbook(); isHssf = true; break; case XLSX: workbook = new XSSFWorkbook(); break; case SXLSX: workbook = new SXSSFWorkbook(1); break; default: workbook = new XSSFWorkbook(); } return this; } @Override public ExcelFactory widthStrategy(@NonNull WidthStrategy widthStrategy) { this.widthStrategy = widthStrategy; return this; } protected String getRealSheetName(String sheetName) { if (sheetName == null) { sheetName = "sheet"; } Sheet sheet = workbook.getSheet(sheetName); int sort = 1; String realSheetName = sheetName; while (sheet != null) { realSheetName = sheetName + " (" + sort + ")"; sheet = workbook.getSheet(realSheetName); sort++; } return realSheetName; } /** * 创建行-row * * @param tr tr * @param sheet sheet */ protected void createRow(Tr tr, Sheet sheet) { Row row = sheet.createRow(tr.getIndex()); if (!tr.isVisibility()) { row.setZeroHeight(true); } stagingTds.stream().filter(blankTd -> Objects.equals(blankTd.getRow(), tr.getIndex())).forEach(td -> { if (tr.getTdList() == Collections.EMPTY_LIST) { tr.setTdList(new LinkedList<>()); } tr.getTdList().add(td); }); for (Td td : tr.getTdList()) { this.createCell(td, sheet, row); if (td.getRowSpan() == 0) { continue; } for (int i = td.getRow() + 1, rowBound = td.getRowBound(); i <= rowBound; i++) { for (int j = td.getCol(), colBound = td.getColBound(); j <= colBound; j++) { Td blankTd = new Td(i, j); blankTd.setTh(td.isTh()); blankTd.setStyle(td.getStyle()); stagingTds.add(blankTd); } } } // 移除暂存区空白单元格 stagingTds.removeIf(td -> Objects.equals(td.getRow(), tr.getIndex())); if (tr.getHeight() > 0) { row.setHeightInPoints(tr.getHeight()); } else { // 设置行高,最小12 if (maxTdHeightMap.get(row.getRowNum()) == null) { row.setHeightInPoints(row.getHeightInPoints() + 5); } else { row.setHeightInPoints((short) (maxTdHeightMap.get(row.getRowNum()) + 5)); maxTdHeightMap.remove(row.getRowNum()); } } } /** * 创建单元格 * * @param td td * @param sheet sheet * @param currentRow 当前行 */ protected void createCell(Td td, Sheet sheet, Row currentRow) { Cell cell; if (td.isFormula()) { cell = currentRow.createCell(td.getCol(), CellType.FORMULA); cell.setCellFormula(td.getContent()); } else { String content = td.getContent(); switch (td.getTdContentType()) { case STRING: cell = currentRow.createCell(td.getCol(), CellType.STRING); cell.setCellValue(content); break; case DOUBLE: cell = currentRow.createCell(td.getCol(), CellType.NUMERIC); if (null != content) { cell.setCellValue(Double.parseDouble(content)); } break; case DATE: cell = currentRow.createCell(td.getCol()); if (td.getDate() != null) { cell.setCellValue(td.getDate()); } else if (td.getLocalDateTime() != null) { cell.setCellValue(td.getLocalDateTime()); } else if (td.getLocalDate() != null) { cell.setCellValue(td.getLocalDate()); } break; case BOOLEAN: cell = currentRow.createCell(td.getCol(), CellType.BOOLEAN); if (null != content) { cell.setCellValue(Boolean.parseBoolean(content)); } break; case NUMBER_DROP_DOWN_LIST: cell = currentRow.createCell(td.getCol(), CellType.NUMERIC); String firstEle = setDropDownList(td, sheet, content); if (firstEle != null) { cell.setCellValue(Double.parseDouble(firstEle)); } break; case BOOLEAN_DROP_DOWN_LIST: cell = currentRow.createCell(td.getCol(), CellType.BOOLEAN); firstEle = setDropDownList(td, sheet, content); if (firstEle != null) { cell.setCellValue(Boolean.parseBoolean(firstEle)); } break; case DROP_DOWN_LIST: cell = currentRow.createCell(td.getCol(), CellType.STRING); firstEle = setDropDownList(td, sheet, content); if (firstEle != null) { cell.setCellValue(firstEle); } break; case LINK_URL: cell = setLink(td, currentRow, HyperlinkType.URL); break; case LINK_EMAIL: cell = setLink(td, currentRow, HyperlinkType.EMAIL); break; case IMAGE: cell = currentRow.createCell(td.getCol()); setImage(td, sheet); break; default: cell = currentRow.createCell(td.getCol(), CellType.STRING); cell.setCellValue(content); break; } } // 设置单元格样式 this.setCellStyle(currentRow, cell, td); if (td.getCol() != td.getColBound()) { for (int j = td.getCol() + 1, colBound = td.getColBound(); j <= colBound; j++) { cell = currentRow.createCell(j); this.setCellStyle(currentRow, cell, td); } } if (td.getColSpan() > 0 || td.getRowSpan() > 0) { sheet.addMergedRegion(new CellRangeAddress(td.getRow(), td.getRowBound(), td.getCol(), td.getColBound())); } } private void setImage(Td td, Sheet sheet) { if (td.getFile() == null) { return; } try { if (createHelper == null) { createHelper = workbook.getCreationHelper(); } byte[] bytes = Files.readAllBytes(td.getFile().toPath()); String fileName = td.getFile().getName(); int format; String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); switch (suffix) { case "jpg": case "jpeg": format = Workbook.PICTURE_TYPE_JPEG; break; case "png": format = Workbook.PICTURE_TYPE_PNG; break; case "dib": format = Workbook.PICTURE_TYPE_DIB; break; case "emf": format = Workbook.PICTURE_TYPE_EMF; break; case "pict": format = Workbook.PICTURE_TYPE_PICT; break; case "wmf": format = Workbook.PICTURE_TYPE_WMF; break; default: throw new IllegalArgumentException("Invalid image type"); } int pictureIdx = workbook.addPicture(bytes, format); Drawing drawing = sheet.createDrawingPatriarch(); ClientAnchor anchor = createHelper.createClientAnchor(); anchor.setCol1(td.getCol()); anchor.setRow1(td.getRow()); Picture pict = drawing.createPicture(anchor, pictureIdx); pict.resize(1, 1); } catch (IOException e) { throw new RuntimeException(e); } } private Cell setLink(Td td, Row currentRow, HyperlinkType hyperlinkType) { if (StringUtil.isBlank(td.getContent())) { return currentRow.createCell(td.getCol()); } if (createHelper == null) { createHelper = workbook.getCreationHelper(); } Cell cell = currentRow.createCell(td.getCol(), CellType.STRING); cell.setCellValue(td.getContent()); Hyperlink link = createHelper.createHyperlink(hyperlinkType); link.setAddress(td.getLink()); cell.setHyperlink(link); return cell; } private String setDropDownList(Td td, Sheet sheet, String content) { if (content.length() > 250) { throw new IllegalArgumentException("The total number of words in the drop-down list should not exceed 250."); } CellRangeAddressList addressList = new CellRangeAddressList( td.getRow(), td.getRowBound(), td.getCol(), td.getColBound()); DataValidationHelper dvHelper = sheet.getDataValidationHelper(); String[] list = content.split(","); DataValidationConstraint dvConstraint = dvHelper.createExplicitListConstraint(list); DataValidation validation = dvHelper.createValidation( dvConstraint, addressList); if (validation instanceof XSSFDataValidation) { validation.setSuppressDropDownArrow(true); validation.setShowErrorBox(true); } else { validation.setSuppressDropDownArrow(false); } sheet.addValidationData(validation); if (list.length > 0) { return list[0]; } return null; } /** * 设置单元格样式 * * @param cell 单元格 * @param td td单元格 */ private void setCellStyle(Row row, Cell cell, Td td) { if (useDefaultStyle) { if (td.isTh()) { cell.setCellStyle(defaultCellStyleMap.get(HtmlTableParser.HtmlTag.th)); } else { if (ContentTypeEnum.isLink(td.getTdContentType())) { cell.setCellStyle(defaultCellStyleMap.get(HtmlTableParser.HtmlTag.link)); } else { cell.setCellStyle(defaultCellStyleMap.get(HtmlTableParser.HtmlTag.td)); } } } else { this.doSetInnerSpan(cell, td); if (td.getStyle().isEmpty()) { return; } String fs = td.getStyle().get("font-size"); if (fs != null) { short fontSize = (short) TdUtil.getValue(fs); if (fontSize > maxTdHeightMap.getOrDefault(row.getRowNum(), FontStyle.DEFAULT_FONT_SIZE)) { maxTdHeightMap.put(row.getRowNum(), fontSize); } } if (cellStyleMap.containsKey(td.getStyle())) { cell.setCellStyle(cellStyleMap.get(td.getStyle())); return; } CellStyle cellStyle = workbook.createCellStyle(); // background-color BackgroundStyle.setBackgroundColor(cellStyle, td.getStyle(), customColor); // text-align TextAlignStyle.setTextAlign(cellStyle, td.getStyle()); // border BorderStyle.setBorder(cellStyle, td.getStyle()); // word-break WordBreakStyle.setWordBreak(cellStyle, td.getStyle()); // 内容格式 String formatStr = td.getStyle().get("format"); if (formatStr != null) { if (format == null) { format = workbook.createDataFormat(); } cellStyle.setDataFormat(format.getFormat(formatStr)); } // font if (td.getFonts() == null || td.getFonts().isEmpty()) { FontStyle.setFont(() -> workbook.createFont(), cellStyle, td.getStyle(), fontMap, customColor); } cell.setCellStyle(cellStyle); cellStyleMap.put(td.getStyle(), cellStyle); } } private void doSetInnerSpan(Cell cell, Td td) { if (td.getFonts() == null || td.getFonts().isEmpty()) { return; } RichTextString richText = isHssf ? new HSSFRichTextString(td.getContent()) : new XSSFRichTextString(td.getContent()); for (com.github.liaochong.myexcel.core.parser.Font font : td.getFonts()) { Font f = FontStyle.getFont(font.getStyle(), fontMap, () -> workbook.createFont(), customColor); richText.applyFont(font.getStartIndex(), font.getEndIndex(), f); } cell.setCellValue(richText); } /** * 空工作簿 * * @return Workbook */ protected Workbook emptyWorkbook() { if (workbook == null) { workbook = new XSSFWorkbook(); } workbook.createSheet(); return workbook; } /** * 初始化默认单元格样式 * * @param workbook workbook */ protected void initCellStyle(Workbook workbook) { if (useDefaultStyle) { defaultCellStyleMap = new EnumMap<>(HtmlTableParser.HtmlTag.class); defaultCellStyleMap.put(HtmlTableParser.HtmlTag.th, new ThDefaultCellStyle().supply(workbook)); defaultCellStyleMap.put(HtmlTableParser.HtmlTag.td, new TdDefaultCellStyle().supply(workbook)); defaultCellStyleMap.put(HtmlTableParser.HtmlTag.link, new LinkDefaultCellStyle().supply(workbook)); } else { if (workbook instanceof HSSFWorkbook) { HSSFPalette palette = ((HSSFWorkbook) workbook).getCustomPalette(); customColor = new CustomColor(true, palette); } else { customColor = new CustomColor(); } } } /** * 窗口冻结 * * @param tableIndex table index * @param sheet sheet */ protected void freezePane(int tableIndex, Sheet sheet) { if (freezePanes != null && freezePanes.length > tableIndex) { FreezePane freezePane = freezePanes[tableIndex]; if (freezePane == null) { throw new IllegalStateException("FreezePane is null"); } sheet.createFreezePane(freezePane.getColSplit(), freezePane.getRowSplit()); } } /** * 获取每列最大宽度 * * @param trList trList * @return colMaxWidthMap */ protected Map<Integer, Integer> getColMaxWidthMap(List<Tr> trList) { if (useDefaultStyle) { // 使用默认样式,需要重新修正加粗的标题自适应宽度 trList.parallelStream().forEach(tr -> { tr.getTdList().stream().filter(Td::isTh).forEach(th -> { int tdWidth = TdUtil.getStringWidth(th.getContent(), 0.25); tr.getColWidthMap().put(th.getCol(), tdWidth); }); }); } int mapMaxSize = trList.stream().mapToInt(tr -> tr.getColWidthMap().size()).max().orElse(16); Map<Integer, Integer> colMaxWidthMap = new HashMap<>(mapMaxSize); trList.forEach(tr -> { tr.getColWidthMap().forEach((k, v) -> { Integer width = colMaxWidthMap.get(k); if (Objects.isNull(width) || v > width) { colMaxWidthMap.put(k, v); } }); tr.setColWidthMap(null); }); return colMaxWidthMap; } /** * 设置每列宽度 * * @param colMaxWidthMap 列最大宽度Map * @param sheet sheet * @param maxColIndex 最大列索引 */ protected void setColWidth(Map<Integer, Integer> colMaxWidthMap, Sheet sheet, int maxColIndex) { if (WidthStrategy.isAutoWidth(widthStrategy)) { if (sheet instanceof SXSSFSheet) { throw new UnsupportedOperationException("SXSSF does not support automatic width at this time"); } for (int i = 0; i <= maxColIndex; i++) { sheet.autoSizeColumn(i); } } colMaxWidthMap.forEach((key, value) -> { int contentLength = value << 1; if (contentLength > 255) { contentLength = 255; } sheet.setColumnWidth(key, contentLength << 8); }); } /** * 清除缓存 */ protected void clearCache() { cellStyleMap = new HashMap<>(); fontMap = new HashMap<>(); maxTdHeightMap = new HashMap<>(); format = null; createHelper = null; } /** * 关闭工作簿 */ protected void closeWorkbook() { if (workbook == null) { return; } try { if (workbook instanceof SXSSFWorkbook) { ((SXSSFWorkbook) workbook).dispose(); } workbook.close(); } catch (IOException e1) { e1.printStackTrace(); } } }