From df96bfecff2c9c2dc917cb8a1fcef6940aaef3bf Mon Sep 17 00:00:00 2001 From: ls Date: Sun, 9 Feb 2025 19:40:50 +0800 Subject: [PATCH] update --- autopoi/autopoi-web/pom.xml | 43 + autopoi/autopoi/pom.xml | 73 ++ .../poi/excel/ExcelImportCheckUtil.java | 457 ++++++++ .../template/ExcelExportOfTemplateUtil.java | 1026 +++++++++++++++++ .../graph/builder/ExcelChartBuildService.java | 263 +++++ .../excel/html/helper/CellValueHelper.java | 135 +++ .../poi/excel/html/helper/StylerHelper.java | 259 +++++ .../poi/excel/imports/CellValueServer.java | 387 +++++++ .../poi/excel/imports/ExcelImportServer.java | 620 ++++++++++ .../poi/excel/imports/sax/SaxReadExcel.java | 91 ++ .../poi/excel/imports/sax/SheetHandler.java | 136 +++ .../jeecgframework/poi/util/ExcelUtil.java | 327 ++++++ .../poi/util/PoiSheetUtility.java | 104 ++ autopoi/pom.xml | 281 +++++ 14 files changed, 4202 insertions(+) create mode 100644 autopoi/autopoi-web/pom.xml create mode 100644 autopoi/autopoi/pom.xml create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/ExcelImportCheckUtil.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/graph/builder/ExcelChartBuildService.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/CellValueHelper.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/StylerHelper.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/CellValueServer.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/ExcelImportServer.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SaxReadExcel.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SheetHandler.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/ExcelUtil.java create mode 100644 autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java create mode 100644 autopoi/pom.xml diff --git a/autopoi/autopoi-web/pom.xml b/autopoi/autopoi-web/pom.xml new file mode 100644 index 0000000..a2aed2e --- /dev/null +++ b/autopoi/autopoi-web/pom.xml @@ -0,0 +1,43 @@ + + 4.0.0 + + org.jeecgframework.boot3 + autopoi-parent + 3.7.0 + + autopoi-web + + + + org.jeecgframework.boot3 + autopoi + ${autopoi.version} + + + + + org.springframework + spring-webmvc + true + + + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + true + + + + org.apache.logging.log4j + log4j-to-slf4j + 2.11.2 + true + test + + + \ No newline at end of file diff --git a/autopoi/autopoi/pom.xml b/autopoi/autopoi/pom.xml new file mode 100644 index 0000000..76f5581 --- /dev/null +++ b/autopoi/autopoi/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + + org.jeecgframework.boot3 + autopoi-parent + 3.7.0 + + autopoi + + + + org.apache.poi + poi + + + org.apache.poi + poi-ooxml + + + xml-apis + xml-apis + + + + + + org.apache.poi + poi-ooxml-full + + + + + + + + xerces + xercesImpl + + + + org.apache.poi + poi-scratchpad + + + + + com.google.guava + guava + + + + org.apache.commons + commons-lang3 + + + + + + org.slf4j + slf4j-api + true + + + + + org.springframework + spring-webmvc + true + + + + \ No newline at end of file diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/ExcelImportCheckUtil.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/ExcelImportCheckUtil.java new file mode 100644 index 0000000..6f13dab --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/ExcelImportCheckUtil.java @@ -0,0 +1,457 @@ +package org.jeecgframework.poi.excel; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.jeecgframework.core.util.ApplicationContextUtil; +import org.jeecgframework.dict.service.AutoPoiDictServiceI; +import org.jeecgframework.poi.excel.annotation.Excel; +import org.jeecgframework.poi.excel.annotation.ExcelCollection; +import org.jeecgframework.poi.excel.annotation.ExcelTarget; +import org.jeecgframework.poi.excel.annotation.ExcelVerify; +import org.jeecgframework.poi.excel.entity.ImportParams; +import org.jeecgframework.poi.excel.entity.params.ExcelCollectionParams; +import org.jeecgframework.poi.excel.entity.params.ExcelImportEntity; +import org.jeecgframework.poi.excel.entity.params.ExcelVerifyEntity; +import org.jeecgframework.poi.excel.imports.ExcelImportServer; +import org.jeecgframework.poi.exception.excel.ExcelImportException; +import org.jeecgframework.poi.exception.excel.enums.ExcelImportEnum; +import org.jeecgframework.poi.util.ExcelUtil; +import org.jeecgframework.poi.util.PoiPublicUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.util.*; + +/** + * EXCEL INCLUE CHECK + * 验证excel标题是否存在,当前默认有0.8(80%)即可通过验证 + */ +public class ExcelImportCheckUtil { + private final static Logger LOGGER = LoggerFactory.getLogger(ExcelImportCheckUtil.class); + + /**当有标题到达多少可以通过验证*/ + public static final Double defScreenRate = 0.8; + + /** + * check inclue filed match rate + * @param inputstream + * @param pojoClass + * @param params + * @return + */ + public static Boolean check(InputStream inputstream, Class pojoClass, ImportParams params) { + return check(inputstream,pojoClass,params,defScreenRate); + } + /** + * check inclue filed match rate + * @param inputstream + * @param pojoClass + * @param params + * @param screenRate field match rate (defalut:0.8) + * @return + */ + public static Boolean check(InputStream inputstream, Class pojoClass, ImportParams params, Double screenRate) { + Workbook book = null; + int errorNum = 0; + int successNum = 0; + if (!(inputstream.markSupported())) { + inputstream = new PushbackInputStream(inputstream, 8); + } + try { +// if (POIFSFileSystem.hasPOIFSHeader(inputstream)) { +// book = new HSSFWorkbook(inputstream); +// } else if (POIXMLDocument.hasOOXMLHeader(inputstream)) { +// book = new XSSFWorkbook(OPCPackage.open(inputstream)); +// } + book = WorkbookFactory.create(inputstream); + LOGGER.info(" >>> poi3升级到4兼容改造工作, pojoClass="+pojoClass); + } catch (Exception e) { + e.printStackTrace(); + } + for (int i = 0; i < params.getSheetNum(); i++) { + Row row = null; + //跳过表头和标题行 + Iterator rows; + try{ + rows= book.getSheetAt(i).rowIterator(); + }catch (Exception e){ + //为空说明读取不到,故不是excel + throw new RuntimeException("请导入正确格式的excel文件!"); + } + + + for (int j = 0; j < params.getTitleRows() + params.getHeadRows(); j++) { + try{ + row = rows.next(); + }catch (NoSuchElementException e){ + //为空说明标题不出在,excel格式错误 + throw new RuntimeException("请填写内容标题!"); + } + } + Sheet sheet = book.getSheetAt(i); + Map titlemap = null; + try { + titlemap = getTitleMap(sheet, params); + } catch (Exception e) { + e.printStackTrace(); + } + Set columnIndexSet = titlemap.keySet(); + Integer maxColumnIndex = Collections.max(columnIndexSet); + Integer minColumnIndex = Collections.min(columnIndexSet); + while (rows.hasNext() && (row == null || sheet.getLastRowNum() - row.getRowNum() > params.getLastOfInvalidRow())) { + row = rows.next(); + Map excelParams = new HashMap(); + List excelCollection = new ArrayList(); + String targetId = null; + if (!Map.class.equals(pojoClass)) { + Field fileds[] = PoiPublicUtil.getClassFields(pojoClass); + ExcelTarget etarget = pojoClass.getAnnotation(ExcelTarget.class); + if (etarget != null) { + targetId = etarget.value(); + } + try { + getAllExcelField(targetId, fileds, excelParams, excelCollection, pojoClass, null); + } catch (Exception e) { + e.printStackTrace(); + } + } + try { + int firstCellNum = row.getFirstCellNum(); + if (firstCellNum > minColumnIndex) { + firstCellNum = minColumnIndex; + } + int lastCellNum = row.getLastCellNum(); + if (lastCellNum < maxColumnIndex + 1) { + lastCellNum = maxColumnIndex + 1; + } + for (int j = firstCellNum, le = lastCellNum; j < le; j++) { + String titleString = (String) titlemap.get(j); + if (excelParams.containsKey(titleString) || Map.class.equals(pojoClass)) { + successNum+=1; + }else{ + if(excelCollection.size()>0){ + Iterator var33 = excelCollection.iterator(); + ExcelCollectionParams param = (ExcelCollectionParams)var33.next(); + if (param.getExcelParams().containsKey(titleString)) { + successNum+=1; + }else{ + errorNum+=1; + } + }else{ + errorNum+=1; + } + } + } + if(successNumerrorNum){ + if(errorNum>0){ + double newNumber = (double) successNum / (successNum + errorNum); + BigDecimal bg = new BigDecimal(newNumber); + double f1 = bg.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue(); + if(f1 getTitleMap(Sheet sheet, ImportParams params) throws Exception { + Map titlemap = new HashMap(); + Iterator cellTitle = null; + String collectionName = null; + Row headRow = null; + int headBegin = params.getTitleRows(); + int allRowNum = sheet.getPhysicalNumberOfRows(); + while(headRow == null && headBegin < allRowNum){ + headRow = sheet.getRow(headBegin++); + } + if(headRow==null){ + throw new Exception("不识别该文件"); + } + if (ExcelUtil.isMergedRegion(sheet, headRow.getRowNum(), 0)) { + params.setHeadRows(2); + }else{ + params.setHeadRows(1); + } + cellTitle = headRow.cellIterator(); + while (cellTitle.hasNext()) { + Cell cell = cellTitle.next(); + String value = getKeyValue(cell); + if (StringUtils.isNotEmpty(value)) { + titlemap.put(cell.getColumnIndex(), value);//加入表头列表 + } + } + + //多行表头 + for (int j = headBegin; j < headBegin + params.getHeadRows()-1; j++) { + headRow = sheet.getRow(j); + cellTitle = headRow.cellIterator(); + while (cellTitle.hasNext()) { + Cell cell = cellTitle.next(); + String value = getKeyValue(cell); + if (StringUtils.isNotEmpty(value)) { + int columnIndex = cell.getColumnIndex(); + //当前cell的上一行是否为合并单元格 + if(ExcelUtil.isMergedRegion(sheet, cell.getRowIndex()-1, columnIndex)){ + collectionName = ExcelUtil.getMergedRegionValue(sheet, cell.getRowIndex()-1, columnIndex); + if(params.isIgnoreHeader(collectionName)){ + titlemap.put(cell.getColumnIndex(), value); + }else{ + titlemap.put(cell.getColumnIndex(), collectionName + "_" + value); + } + }else{ + titlemap.put(cell.getColumnIndex(), value); + } + } + } + } + return titlemap; + } + /** + * 获取key的值,针对不同类型获取不同的值 + * + * @Author JEECG + * @date 20201023 + * @param cell + * @return + */ + private static String getKeyValue(Cell cell) { + if(cell==null){ + return null; + } + Object obj = null; + switch (cell.getCellType()) { + case STRING: + obj = cell.getStringCellValue(); + break; + case BOOLEAN: + obj = cell.getBooleanCellValue(); + break; + case NUMERIC: + obj = cell.getNumericCellValue(); + break; + case FORMULA: + obj = cell.getCellFormula(); + break; + } + return obj == null ? null : obj.toString().trim(); + } + + /** + * 获取需要导出的全部字段 + * + * + * + * @param targetId + * 目标ID + * @param fields + * @param excelCollection + * @throws Exception + */ + public static void getAllExcelField(String targetId, Field[] fields, Map excelParams, List excelCollection, Class pojoClass, List getMethods) throws Exception { + ExcelImportEntity excelEntity = null; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (PoiPublicUtil.isNotUserExcelUserThis(null, field, targetId)) { + continue; + } + if (PoiPublicUtil.isCollection(field.getType())) { + // 集合对象设置属性 + ExcelCollectionParams collection = new ExcelCollectionParams(); + collection.setName(field.getName()); + Map temp = new HashMap(); + ParameterizedType pt = (ParameterizedType)field.getGenericType(); + Class clz = (Class)pt.getActualTypeArguments()[0]; + collection.setType(clz); + getExcelFieldList(targetId, PoiPublicUtil.getClassFields(clz), clz, temp, (List)null); + collection.setExcelParams(temp); + collection.setExcelName(((ExcelCollection)field.getAnnotation(ExcelCollection.class)).name()); + additionalCollectionName(collection); + excelCollection.add(collection); + } else if (PoiPublicUtil.isJavaClass(field)) { + addEntityToMap(targetId, field, (ExcelImportEntity)excelEntity, pojoClass, getMethods, excelParams); + } else { + List newMethods = new ArrayList(); + if (getMethods != null) { + newMethods.addAll(getMethods); + } + newMethods.add(PoiPublicUtil.getMethod(field.getName(), pojoClass)); + getAllExcelField(targetId, PoiPublicUtil.getClassFields(field.getType()), excelParams, excelCollection, field.getType(), newMethods); + } + } + } + public static void getExcelFieldList(String targetId, Field[] fields, Class pojoClass, Map temp, List getMethods) throws Exception { + ExcelImportEntity excelEntity = null; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (!PoiPublicUtil.isNotUserExcelUserThis((List)null, field, targetId)) { + if (PoiPublicUtil.isJavaClass(field)) { + addEntityToMap(targetId, field, (ExcelImportEntity)excelEntity, pojoClass, getMethods, temp); + } else { + List newMethods = new ArrayList(); + if (getMethods != null) { + newMethods.addAll(getMethods); + } + + newMethods.add(PoiPublicUtil.getMethod(field.getName(), pojoClass, field.getType())); + getExcelFieldList(targetId, PoiPublicUtil.getClassFields(field.getType()), field.getType(), temp, newMethods); + } + } + } + } + /** + * 追加集合名称到前面 + * + * @param collection + */ + private static void additionalCollectionName(ExcelCollectionParams collection) { + Set keys = new HashSet(); + keys.addAll(collection.getExcelParams().keySet()); + Iterator var3 = keys.iterator(); + + while(var3.hasNext()) { + String key = (String)var3.next(); + collection.getExcelParams().put(collection.getExcelName() + "_" + key, collection.getExcelParams().get(key)); + collection.getExcelParams().remove(key); + } + } + /** + * 把这个注解解析放到类型对象中 + * + * @param targetId + * @param field + * @param excelEntity + * @param pojoClass + * @param getMethods + * @param temp + * @throws Exception + */ + public static void addEntityToMap(String targetId, Field field, ExcelImportEntity excelEntity, Class pojoClass, List getMethods, Map temp) throws Exception { + Excel excel = field.getAnnotation(Excel.class); + excelEntity = new ExcelImportEntity(); + excelEntity.setType(excel.type()); + excelEntity.setSaveUrl(excel.savePath()); + excelEntity.setSaveType(excel.imageType()); + excelEntity.setReplace(excel.replace()); + excelEntity.setDatabaseFormat(excel.databaseFormat()); + excelEntity.setVerify(getImportVerify(field)); + excelEntity.setSuffix(excel.suffix()); + excelEntity.setNumFormat(excel.numFormat()); + excelEntity.setGroupName(excel.groupName()); + //update-begin-author:taoYan date:20180202 for:TASK #2067 【bug excel 问题】excel导入字典文本翻译问题 + excelEntity.setMultiReplace(excel.multiReplace()); + if(StringUtils.isNotEmpty(excel.dicCode())){ + AutoPoiDictServiceI jeecgDictService = null; + try { + jeecgDictService = ApplicationContextUtil.getContext().getBean(AutoPoiDictServiceI.class); + } catch (Exception e) { + } + if(jeecgDictService!=null){ + String[] dictReplace = jeecgDictService.queryDict(excel.dictTable(), excel.dicCode(), excel.dicText()); + if(excelEntity.getReplace()!=null && dictReplace!=null && dictReplace.length!=0){ + excelEntity.setReplace(dictReplace); + } + } + } + //update-end-author:taoYan date:20180202 for:TASK #2067 【bug excel 问题】excel导入字典文本翻译问题 + getExcelField(targetId, field, excelEntity, excel, pojoClass); + if (getMethods != null) { + List newMethods = new ArrayList(); + newMethods.addAll(getMethods); + newMethods.add(excelEntity.getMethod()); + excelEntity.setMethods(newMethods); + } + temp.put(excelEntity.getName(), excelEntity); + + } + public static void getExcelField(String targetId, Field field, ExcelImportEntity excelEntity, Excel excel, Class pojoClass) throws Exception { + excelEntity.setName(getExcelName(excel.name(), targetId)); + String fieldname = field.getName(); + //update-begin-author:taoyan for:TASK #2798 【例子】导入扩展方法,支持自定义导入字段转换规则 + excelEntity.setMethod(PoiPublicUtil.getMethod(fieldname, pojoClass, field.getType(),excel.importConvert())); + //update-end-author:taoyan for:TASK #2798 【例子】导入扩展方法,支持自定义导入字段转换规则 + if (StringUtils.isNotEmpty(excel.importFormat())) { + excelEntity.setFormat(excel.importFormat()); + } else { + excelEntity.setFormat(excel.format()); + } + } + /** + * 判断在这个单元格显示的名称 + * + * @param exportName + * @param targetId + * @return + */ + public static String getExcelName(String exportName, String targetId) { + if (exportName.indexOf("_") < 0) { + return exportName; + } + String[] arr = exportName.split(","); + for (String str : arr) { + if (str.indexOf(targetId) != -1) { + return str.split("_")[0]; + } + } + return null; + } + /** + * 获取导入校验参数 + * + * @param field + * @return + */ + public static ExcelVerifyEntity getImportVerify(Field field) { + ExcelVerify verify = field.getAnnotation(ExcelVerify.class); + if (verify != null) { + ExcelVerifyEntity entity = new ExcelVerifyEntity(); + entity.setEmail(verify.isEmail()); + entity.setInterHandler(verify.interHandler()); + entity.setMaxLength(verify.maxLength()); + entity.setMinLength(verify.minLength()); + entity.setMobile(verify.isMobile()); + entity.setNotNull(verify.notNull()); + entity.setRegex(verify.regex()); + entity.setRegexTip(verify.regexTip()); + entity.setTel(verify.isTel()); + return entity; + } + return null; + } +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java new file mode 100644 index 0000000..011c065 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java @@ -0,0 +1,1026 @@ +/** + * Copyright 2013-2015 JEECG (jeecgos@163.com) + * + * 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 org.jeecgframework.poi.excel.export.template; + +import java.lang.reflect.Field; +import java.util.*; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.jeecgframework.poi.cache.ExcelCache; +import org.jeecgframework.poi.cache.ImageCache; +import org.jeecgframework.poi.entity.ImageEntity; +import org.jeecgframework.poi.excel.annotation.ExcelTarget; +import org.jeecgframework.poi.excel.entity.TemplateExportParams; +import org.jeecgframework.poi.excel.entity.enmus.ExcelType; +import org.jeecgframework.poi.excel.entity.params.ExcelExportEntity; +import org.jeecgframework.poi.excel.entity.params.ExcelForEachParams; +import org.jeecgframework.poi.excel.entity.params.ExcelTemplateParams; +import org.jeecgframework.poi.excel.export.base.ExcelExportBase; +import org.jeecgframework.poi.excel.export.styler.IExcelExportStyler; +import org.jeecgframework.poi.excel.html.helper.MergedRegionHelper; +import org.jeecgframework.poi.exception.excel.ExcelExportException; +import org.jeecgframework.poi.exception.excel.enums.ExcelExportEnum; + +import static org.jeecgframework.poi.util.PoiElUtil.*; + +import org.jeecgframework.poi.util.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Excel 导出根据模板导出 + * + * @author JEECG + * @date 2013-10-17 + * @version 1.0 + */ +public final class ExcelExportOfTemplateUtil extends ExcelExportBase { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelExportOfTemplateUtil.class); + + /** + * 缓存TEMP 的for each创建的cell ,跳过这个cell的模板语法查找,提高效率 + */ + private Set tempCreateCellSet = new HashSet(); + /** + * 模板参数,全局都用到 + */ + private TemplateExportParams teplateParams; + /** + * 单元格合并信息 + */ + private MergedRegionHelper mergedRegionHelper; + + + /** + * 往Sheet 填充正常数据,根据表头信息 使用导入的部分逻辑,坐对象映射 + * + * @param teplateParams + * @param pojoClass + * @param dataSet + * @param workbook + */ + private void addDataToSheet(Class pojoClass, Collection dataSet, Sheet sheet, Workbook workbook) throws Exception { + + if (workbook instanceof XSSFWorkbook) { + super.type = ExcelType.XSSF; + } + // 获取表头数据 + Map titlemap = getTitleMap(sheet); + Drawing patriarch = sheet.createDrawingPatriarch(); + // 得到所有字段 + Field[] fileds = PoiPublicUtil.getClassFields(pojoClass); + ExcelTarget etarget = pojoClass.getAnnotation(ExcelTarget.class); + String targetId = null; + if (etarget != null) { + targetId = etarget.value(); + } + // 获取实体对象的导出数据 + List excelParams = new ArrayList(); + getAllExcelField(null, targetId, fileds, excelParams, pojoClass, null); + // 根据表头进行筛选排序 + sortAndFilterExportField(excelParams, titlemap); + short rowHeight = getRowHeight(excelParams); + int index = teplateParams.getHeadingRows() + teplateParams.getHeadingStartRow(), titleHeight = index; + // 下移数据,模拟插入 + sheet.shiftRows(teplateParams.getHeadingRows() + teplateParams.getHeadingStartRow(), sheet.getLastRowNum(), getShiftRows(dataSet, excelParams), true, true); + if (excelParams.size() == 0) { + return; + } + Iterator its = dataSet.iterator(); + while (its.hasNext()) { + Object t = its.next(); + index += createCells(patriarch, index, t, excelParams, sheet, workbook, rowHeight); + } + // 合并同类项 + mergeCells(sheet, excelParams, titleHeight); + } + + /** + * 下移数据 + * + * @param its + * @param excelParams + * @return + */ + private int getShiftRows(Collection dataSet, List excelParams) throws Exception { + int size = 0; + Iterator its = dataSet.iterator(); + while (its.hasNext()) { + Object t = its.next(); + size += getOneObjectSize(t, excelParams); + } + return size; + } + + /** + * 获取单个对象的高度,主要是处理一堆多的情况 + * + * @param styles + * @param rowHeight + * @throws Exception + */ + public int getOneObjectSize(Object t, List excelParams) throws Exception { + ExcelExportEntity entity; + int maxHeight = 1; + for (int k = 0, paramSize = excelParams.size(); k < paramSize; k++) { + entity = excelParams.get(k); + if (entity.getList() != null) { + Collection list = (Collection) entity.getMethod().invoke(t, new Object[] {}); + if (list != null && list.size() > maxHeight) { + maxHeight = list.size(); + } + } + } + return maxHeight; + + } + + public Workbook createExcleByTemplate(TemplateExportParams params, Class pojoClass, Collection dataSet, Map map) { + // step 1. 判断模板的地址 + if (params == null || map == null || StringUtils.isEmpty(params.getTemplateUrl())) { + throw new ExcelExportException(ExcelExportEnum.PARAMETER_ERROR); + } + Workbook wb = null; + // step 2. 判断模板的Excel类型,解析模板 + try { + this.teplateParams = params; + wb = getCloneWorkBook(); + // 创建表格样式 + setExcelExportStyler((IExcelExportStyler) teplateParams.getStyle().getConstructor(Workbook.class).newInstance(wb)); + // step 3. 解析模板 + for (int i = 0, le = params.isScanAllsheet() ? wb.getNumberOfSheets() : params.getSheetNum().length; i < le; i++) { + if (params.getSheetName() != null && params.getSheetName().length > i && StringUtils.isNotEmpty(params.getSheetName()[i])) { + wb.setSheetName(i, params.getSheetName()[i]); + } + tempCreateCellSet.clear(); + parseTemplate(wb.getSheetAt(i), map, params.isColForEach()); + } + if (dataSet != null) { + // step 4. 正常的数据填充 + dataHanlder = params.getDataHanlder(); + if (dataHanlder != null) { + needHanlderList = Arrays.asList(dataHanlder.getNeedHandlerFields()); + } + addDataToSheet(pojoClass, dataSet, wb.getSheetAt(params.getDataSheetNum()), wb); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + return null; + } + return wb; + } + + /** + * 克隆excel防止操作原对象,workbook无法克隆,只能对excel进行克隆 + * + * @param teplateParams + * @throws Exception + * @Author JEECG + * @date 2013-11-11 + */ + private Workbook getCloneWorkBook() throws Exception { + //update-begin-author:wangshuai date:20200730 for:jar 包上传到服务器后 autopoi 读取不到excel模版文件 #1505 + return ExcelCache.getWorkbookByTemplate(teplateParams.getTemplateUrl(), teplateParams.getSheetNum(), teplateParams.isScanAllsheet()); + //update-end-author:wangshuai date:20200730 for:jar 包上传到服务器后 autopoi 读取不到excel模版文件 #1505 + } + + /** + * 获取表头数据,设置表头的序号 + * + * @param teplateParams + * @param sheet + * @return + */ + private Map getTitleMap(Sheet sheet) { + Row row = null; + Iterator cellTitle; + Map titlemap = new HashMap(); + for (int j = 0; j < teplateParams.getHeadingRows(); j++) { + row = sheet.getRow(j + teplateParams.getHeadingStartRow()); + cellTitle = row.cellIterator(); + int i = row.getFirstCellNum(); + while (cellTitle.hasNext()) { + Cell cell = cellTitle.next(); + String value = cell.getStringCellValue(); + if (!StringUtils.isEmpty(value)) { + titlemap.put(value, i); + } + i = i + 1; + } + } + return titlemap; + + } + + private void parseTemplate(Sheet sheet, Map map, boolean colForeach) throws Exception { + deleteCell(sheet, map); + //update-begin-author:liusq---date:20220527--for: 模板导出列循环核心代码 --- + mergedRegionHelper = new MergedRegionHelper(sheet); + if (colForeach) { + colForeach(sheet, map); + } + //update-end-author:liusq---date:20220527--for: 模板导出列循环核心代码 --- + Row row = null; + int index = 0; + while (index <= sheet.getLastRowNum()) { + row = sheet.getRow(index++); + if (row == null) { + continue; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { + if (row.getCell(i) != null && !tempCreateCellSet.contains(row.getRowNum() + "_" + row.getCell(i).getColumnIndex())) { + setValueForCellByMap(row.getCell(i), map); + } + } + } + } + + /** + * 先判断删除,省得影响效率 + * + * @param sheet + * @param map + * @throws Exception + */ + private void deleteCell(Sheet sheet, Map map) throws Exception { + Row row = null; + Cell cell = null; + int index = 0; + while (index <= sheet.getLastRowNum()) { + row = sheet.getRow(index++); + if (row == null) { + continue; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { + cell = row.getCell(i); + if (row.getCell(i) != null && (cell.getCellType() == CellType.STRING || cell.getCellType() == CellType.NUMERIC)) { + cell.setCellType(CellType.STRING); + String text = cell.getStringCellValue(); + if (text.contains(IF_DELETE)) { + if (Boolean.valueOf(eval(text.substring(text.indexOf(START_STR) + 2, text.indexOf(END_STR)).trim(), map).toString())) { + PoiSheetUtility.deleteColumn(sheet, i); + } + cell.setCellValue(""); + } + } + } + } + } + + /** + * 给每个Cell通过解析方式set值 + * + * @param cell + * @param map + */ + private void setValueForCellByMap(Cell cell, Map map) throws Exception { + CellType cellType = cell.getCellType(); + if (cellType != CellType.STRING && cellType != CellType.NUMERIC) { + return; + } + String oldString; + cell.setCellType(CellType.STRING); + oldString = cell.getStringCellValue(); + if (oldString != null && oldString.indexOf(START_STR) != -1 && !oldString.contains(FOREACH)) { + // step 2. 判断是否含有解析函数 + String params = null; + boolean isNumber = false; + if (isNumber(oldString)) { + isNumber = true; + oldString = oldString.replace(NUMBER_SYMBOL, ""); + } + while (oldString.indexOf(START_STR) != -1) { + params = oldString.substring(oldString.indexOf(START_STR) + 2, oldString.indexOf(END_STR)); + + oldString = oldString.replace(START_STR + params + END_STR, eval(params, map).toString()); + } + // 如何是数值 类型,就按照数值类型进行设置 + if (isNumber && StringUtils.isNotBlank(oldString)) { + cell.setCellValue(Double.parseDouble(oldString)); + cell.setCellType(CellType.NUMERIC); + } else { + cell.setCellValue(oldString); + } + } + // 判断foreach 这种方法 + if (oldString != null && oldString.contains(FOREACH)) { + addListDataToExcel(cell, map, oldString.trim()); + } + + } + + private boolean isNumber(String text) { + return text.startsWith(NUMBER_SYMBOL) || text.contains("{" + NUMBER_SYMBOL) || text.contains(" " + NUMBER_SYMBOL); + } + + /** + * 利用foreach循环输出数据 + * + * @param cell + * @param map + * @param oldString + * @throws Exception + */ + private void addListDataToExcel(Cell cell, Map map, String name) throws Exception { + boolean isCreate = !name.contains(FOREACH_NOT_CREATE); + boolean isShift = name.contains(FOREACH_AND_SHIFT); + name = name.replace(FOREACH_NOT_CREATE, EMPTY).replace(FOREACH_AND_SHIFT, EMPTY).replace(FOREACH, EMPTY).replace(START_STR, EMPTY); + String[] keys = name.replaceAll("\\s{1,}", " ").trim().split(" "); + Collection datas = (Collection) PoiPublicUtil.getParamsValue(keys[0], map); + //update-begin-author:liusq---date:20220609--for: [issues/3328]autopoi模板导出Excel功能,$fe: 遍历不好用 --- + Object[] columnsInfo = getAllDataColumns(cell, name.replace(keys[0], EMPTY), + mergedRegionHelper); + int rowspan = (Integer) columnsInfo[0], colspan = (Integer) columnsInfo[1]; + @SuppressWarnings("unchecked") + List columns = (List) columnsInfo[2]; + if (datas == null) { + return; + } + Iterator its = datas.iterator(); + Row row; + int rowIndex = cell.getRow().getRowNum() + 1; + //处理当前行 + int loopSize = 0; + if (its.hasNext()) { + Object t = its.next(); + cell.getRow().setHeight(columns.get(0).getHeight()); + loopSize = setForeachRowCellValue(isCreate, cell.getRow(), cell.getColumnIndex(), t, columns, map, + rowspan, colspan, mergedRegionHelper)[0]; + rowIndex += rowspan - 1 + loopSize - 1; + } + //修复不论后面有没有数据,都应该执行的是插入操作 + if (isShift && datas.size() * rowspan > 1 && cell.getRowIndex() + rowspan <= cell.getRow().getSheet().getLastRowNum()) { + int lastRowNum = cell.getRow().getSheet().getLastRowNum(); + int shiftRows = lastRowNum - cell.getRowIndex() - rowspan; + cell.getRow().getSheet().shiftRows(cell.getRowIndex() + rowspan, lastRowNum, (datas.size() - 1) * rowspan, true, true); + //update-begin-author:liusq---date:20221103--for: [issues/4142]exlce模板导出如果模板中有多个合并单元格的循环表格,第二个表格读取错误 --- + mergedRegionHelper.shiftRows(cell.getSheet(), cell.getRowIndex() + rowspan, (datas.size() - 1) * rowspan, shiftRows); + PoiExcelTempUtil.reset(cell.getSheet(), cell.getRowIndex() + rowspan + (datas.size() - 1) * rowspan, cell.getRow().getSheet().getLastRowNum()); + //update-end-author:liusq---date:20221103--for: [issues/4142]exlce模板导出如果模板中有多个合并单元格的循环表格,第二个表格读取错误 --- + } + while (its.hasNext()) { + Object t = its.next(); + row = createRow(rowIndex, cell.getSheet(), isCreate, rowspan); + row.setHeight(columns.get(0).getHeight()); + loopSize = setForeachRowCellValue(isCreate, row, cell.getColumnIndex(), t, columns, map, rowspan, + colspan, mergedRegionHelper)[0]; + rowIndex += rowspan + loopSize - 1; + } + //update-end-author:liusq---date:20220609--for: [issues/3328]autopoi模板导出Excel功能,$fe: 遍历不好用 --- + } + + private void setForEeachCellValue(boolean isCreate, Row row, int columnIndex, Object t, List columns, Map map) throws Exception { + for (int i = 0, max = columnIndex + columns.size(); i < max; i++) { + if (row.getCell(i) == null) + row.createCell(i); + } + for (int i = 0, max = columns.size(); i < max; i++) { + boolean isNumber = false; + String tempStr = new String(columns.get(i).getName()); + if (isNumber(tempStr)) { + isNumber = true; + tempStr = tempStr.replace(NUMBER_SYMBOL, ""); + } + map.put(teplateParams.getTempParams(), t); + String val = eval(tempStr, map).toString(); + if (isNumber && StringUtils.isNotEmpty(val)) { + row.getCell(i + columnIndex).setCellValue(Double.parseDouble(val)); + row.getCell(i + columnIndex).setCellType(CellType.NUMERIC); + } else { + row.getCell(i + columnIndex).setCellValue(val); + } + row.getCell(i + columnIndex).setCellStyle(columns.get(i).getCellStyle()); + tempCreateCellSet.add(row.getRowNum() + "_" + (i + columnIndex)); + } + + } + + /** + * 获取迭代的数据的值 + * + * @param cell + * @param name + * @return + */ + private List getAllDataColumns(Cell cell, String name) { + List columns = new ArrayList(); + cell.setCellValue(""); + if (name.contains(END_STR)) { + columns.add(new ExcelTemplateParams(name.replace(END_STR, EMPTY).trim(), cell.getCellStyle(), cell.getRow().getHeight())); + return columns; + } + columns.add(new ExcelTemplateParams(name.trim(), cell.getCellStyle(), cell.getRow().getHeight())); + int index = cell.getColumnIndex(); + //列数 + int lastCellNum = cell.getRow().getLastCellNum(); + Cell tempCell; + while (true) { + tempCell = cell.getRow().getCell(++index); + //--begin--date:2020/09/18---for:增加列数判断,防止提前跳出 + if (tempCell == null&&index>=lastCellNum) { + break; + } + String cellStringString; + try {// 允许为空,单表示已经完结了,因为可能被删除了 + cellStringString = tempCell.getStringCellValue(); + if (StringUtils.isBlank(cellStringString)&&index>=lastCellNum) { + break; + } + } catch (Exception e) { + throw new ExcelExportException("for each 当中存在空字符串,请检查模板"); + } + //--end--date:2020/09/18---for:增加列数判断,防止提前跳出 + // 把读取过的cell 置为空 + tempCell.setCellValue(""); + if (cellStringString.contains(END_STR)) { + columns.add(new ExcelTemplateParams(cellStringString.trim().replace(END_STR, ""), tempCell.getCellStyle(), tempCell.getRow().getHeight())); + break; + } else { + if (cellStringString.trim().contains(teplateParams.getTempParams())) { + columns.add(new ExcelTemplateParams(cellStringString.trim(), tempCell.getCellStyle(), tempCell.getRow().getHeight())); + }else if(cellStringString.trim().equals(EMPTY)){ + //可能是合并的单元格,允许空数据的设置 + columns.add(new ExcelTemplateParams(EMPTY, tempCell.getCellStyle(), tempCell.getRow().getHeight())); + } else { + // 最后一行被删除了 + break; + } + } + + } + return columns; + } + + /** + * 对导出序列进行排序和塞选 + * + * @param excelParams + * @param titlemap + * @return + */ + private void sortAndFilterExportField(List excelParams, Map titlemap) { + for (int i = excelParams.size() - 1; i >= 0; i--) { + if (excelParams.get(i).getList() != null && excelParams.get(i).getList().size() > 0) { + sortAndFilterExportField(excelParams.get(i).getList(), titlemap); + if (excelParams.get(i).getList().size() == 0) { + excelParams.remove(i); + } else { + excelParams.get(i).setOrderNum(i); + } + } else { + if (titlemap.containsKey(excelParams.get(i).getName())) { + excelParams.get(i).setOrderNum(i); + } else { + excelParams.remove(i); + } + } + } + sortAllParams(excelParams); + } + + //-----------------update-begin-author:liusq---date:20220527--for: 以下方法是模板导出列循环功能新增的方法 --- + /** + * 先进行列的循环,因为涉及很多数据 + * + * @param sheet + * @param map + */ + private void colForeach(Sheet sheet, Map map) throws Exception { + Row row = null; + Cell cell = null; + int index = 0; + while (index <= sheet.getLastRowNum()) { + row = sheet.getRow(index++); + if (row == null) { + continue; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { + cell = row.getCell(i); + if (row.getCell(i) != null && (cell.getCellType() == CellType.STRING + || cell.getCellType() == CellType.NUMERIC)) { + String text = PoiCellUtil.getCellValue(cell); + if (text.contains(FOREACH_COL) || text.contains(FOREACH_COL_VALUE)) { + foreachCol(cell, map, text); + } + } + } + } + } + + /** + * 循环列表 + * + * @param cell + * @param map + * @param name + * @throws Exception + */ + private void foreachCol(Cell cell, Map map, String name) throws Exception { + boolean isCreate = name.contains(FOREACH_COL_VALUE); + name = name.replace(FOREACH_COL_VALUE, EMPTY).replace(FOREACH_COL, EMPTY).replace(START_STR, + EMPTY); + String[] keys = name.replaceAll("\\s{1,}", " ").trim().split(" "); + Collection datas = (Collection) PoiPublicUtil.getParamsValue(keys[0], map); + Object[] columnsInfo = getAllDataColumns(cell, name.replace(keys[0], EMPTY), + mergedRegionHelper); + if (datas == null) { + return; + } + Iterator its = datas.iterator(); + int rowspan = (Integer) columnsInfo[0], colspan = (Integer) columnsInfo[1]; + @SuppressWarnings("unchecked") + List columns = (List) columnsInfo[2]; + while (its.hasNext()) { + Object t = its.next(); + setForeachRowCellValue(true, cell.getRow(), cell.getColumnIndex(), t, columns, map, + rowspan, colspan, mergedRegionHelper); + if (cell.getRow().getCell(cell.getColumnIndex() + colspan) == null) { + cell.getRow().createCell(cell.getColumnIndex() + colspan); + } + cell = cell.getRow().getCell(cell.getColumnIndex() + colspan); + } + if (isCreate) { + cell = cell.getRow().getCell(cell.getColumnIndex() - 1); + cell.setCellValue(cell.getStringCellValue() + END_STR); + } + } + /** + * 循环迭代创建,遍历row + * + * @param isCreate + * @param row + * @param columnIndex + * @param t + * @param columns + * @param map + * @param rowspan + * @param colspan + * @param mergedRegionHelper + * @return rowSize, cellSize + * @throws Exception + */ + private int[] setForeachRowCellValue(boolean isCreate, Row row, int columnIndex, Object t, + List columns, Map map, + int rowspan, int colspan, + MergedRegionHelper mergedRegionHelper) throws Exception { + createRowCellSetStyle(row, columnIndex, columns, rowspan, colspan); + //填写数据 + ExcelForEachParams params; + int loopSize = 1; + int loopCi = 1; + row = row.getSheet().getRow(row.getRowNum() - rowspan + 1); + for (int k = 0; k < rowspan; k++) { + int ci = columnIndex; + row.setHeight(getMaxHeight(k, colspan, columns)); + for (int i = 0; i < colspan && i < columns.size(); i++) { + boolean isNumber = false; + params = columns.get(colspan * k + i); + tempCreateCellSet.add(row.getRowNum() + "_" + (ci)); + if (params == null) { + continue; + } + if (StringUtils.isEmpty(params.getName()) + && StringUtils.isEmpty(params.getConstValue())) { + row.getCell(ci).setCellStyle(params.getCellStyle()); + ci = ci + params.getColspan(); + continue; + } + String val; + Object obj = null; + //是不是常量 + String tempStr = params.getName(); + if (StringUtils.isEmpty(params.getName())) { + val = params.getConstValue(); + } else { + if (isHasSymbol(tempStr, NUMBER_SYMBOL)) { + isNumber = true; + tempStr = tempStr.replaceFirst(NUMBER_SYMBOL, ""); + } + map.put(teplateParams.getTempParams(), t); + boolean isDict = false; + String dict = null; + if (isHasSymbol(tempStr, DICT_HANDLER)) { + isDict = true; + dict = tempStr.substring(tempStr.indexOf(DICT_HANDLER) + 5).split(";")[0]; + tempStr = tempStr.replaceFirst(DICT_HANDLER, ""); + tempStr = tempStr.replaceFirst(dict + ";", ""); + } + obj = eval(tempStr, map); + if (isDict && !(obj instanceof Collection)) { + obj = dictHandler.toName(dict, t, tempStr, obj); + } + val = obj.toString(); + } + if (obj != null && obj instanceof Collection) { + // 需要找到哪一级别是集合 ,方便后面的replace + String collectName = evalFindName(tempStr, map); + int[] loop = setForEachLoopRowCellValue(row, ci, (Collection) obj, columns, + params, map, rowspan, colspan, mergedRegionHelper, collectName); + loopSize = Math.max(loopSize, loop[0]); + i += loop[1] - 1; + ci = loop[2] - params.getColspan(); + } else if (obj != null && obj instanceof ImageEntity) { + ImageEntity img = (ImageEntity) obj; + row.getCell(ci).setCellValue(""); + if (img.getRowspan() > 1 || img.getColspan() > 1) { + img.setHeight(0); + row.getCell(ci).getSheet().addMergedRegion(new CellRangeAddress(row.getCell(ci).getRowIndex(), + row.getCell(ci).getRowIndex() + img.getRowspan() - 1, row.getCell(ci).getColumnIndex(), row.getCell(ci).getColumnIndex() + img.getColspan() - 1)); + } + createImageCell(row.getCell(ci), img.getHeight(), img.getRowspan(), img.getColspan(), img.getUrl(), img.getData()); + } else if (isNumber && StringUtils.isNotEmpty(val)) { + row.getCell(ci).setCellValue(Double.parseDouble(val)); + } else { + try { + row.getCell(ci).setCellValue(val); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } + if (params.getCellStyle() != null) { + row.getCell(ci).setCellStyle(params.getCellStyle()); + } + //如果合并单元格,就把这个单元格的样式和之前的保持一致 + setMergedRegionStyle(row, ci, params); + //合并对应单元格 + //update-begin-author:liusq---date:20221103--for: [issues/4142]exlce模板导出如果模板中有多个合并单元格的循环表格,第二个表格读取错误 --- + boolean isNeedMerge = (params.getRowspan() != 1 || params.getColspan() != 1) + && !mergedRegionHelper.isMergedRegion(row.getRowNum() + 1, ci); + //update-end-author:liusq---date:20221103--for: [issues/4142]exlce模板导出如果模板中有多个合并单元格的循环表格,第二个表格读取错误 --- + if (isNeedMerge) { + PoiMergeCellUtil.addMergedRegion(row.getSheet(), row.getRowNum(), + row.getRowNum() + params.getRowspan() - 1, ci, + ci + params.getColspan() - 1); + } + ci = ci + params.getColspan(); + } + loopCi = Math.max(loopCi, ci); + // 需要把需要合并的单元格合并了 --- 不是集合的栏位合并了 + if (loopSize > 1) { + handlerLoopMergedRegion(row, columnIndex, columns, loopSize); + } + row = row.getSheet().getRow(row.getRowNum() + 1); + } + return new int[]{loopSize, loopCi}; + } + /** + * 图片类型的Cell + */ + public void createImageCell(Cell cell, double height, int rowspan, int colspan, + String imagePath, byte[] data) throws Exception { + if (height > cell.getRow().getHeight()) { + cell.getRow().setHeight((short) height); + } + ClientAnchor anchor; + if (type.equals(ExcelType.HSSF)) { + anchor = new HSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + colspan), + cell.getRow().getRowNum() + rowspan); + } else { + anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + colspan), + cell.getRow().getRowNum() + rowspan); + } + if (StringUtils.isNotEmpty(imagePath)) { + data = ImageCache.getImage(imagePath); + } + if (data != null) { + PoiExcelGraphDataUtil.getDrawingPatriarch(cell.getSheet()).createPicture(anchor, + cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + /** + * 处理内循环 + * + * @param row + * @param columnIndex + * @param obj + * @param columns + * @param params + * @param map + * @param rowspan + * @param colspan + * @param mergedRegionHelper + * @param collectName + * @return [rowNums, columnsNums, ciIndex] + * @throws Exception + */ + private int[] setForEachLoopRowCellValue(Row row, int columnIndex, Collection obj, List columns, + ExcelForEachParams params, Map map, + int rowspan, int colspan, + MergedRegionHelper mergedRegionHelper, String collectName) throws Exception { + + //多个一起遍历 -去掉第一层 把所有的数据遍历一遍 + //STEP 1拿到所有的和当前一样项目的字段 + List temp = getLoopEachParams(columns, columnIndex, collectName); + Iterator its = obj.iterator(); + Row tempRow = row; + int nums = 0; + int ci = columnIndex; + while (its.hasNext()) { + Object data = its.next(); + map.put("loop_" + columnIndex, data); + int[] loopArr = setForeachRowCellValue(false, tempRow, columnIndex, data, temp, map, rowspan, + colspan, mergedRegionHelper); + nums += loopArr[0]; + ci = Math.max(ci, loopArr[1]); + map.remove("loop_" + columnIndex); + tempRow = createRow(tempRow.getRowNum() + loopArr[0], row.getSheet(), false, rowspan); + } + for (int i = 0; i < temp.size(); i++) { + temp.get(i).setName(temp.get(i).getTempName().pop()); + //都是集合 + temp.get(i).setCollectCell(true); + + } + return new int[]{nums, temp.size(), ci}; + } + /** + * 创建并返回第一个Row + * + * @param sheet + * @param rowIndex + * @param isCreate + * @param rows + * @return + */ + private Row createRow(int rowIndex, Sheet sheet, boolean isCreate, int rows) { + for (int i = 0; i < rows; i++) { + if (isCreate) { + sheet.createRow(rowIndex++); + } else if (sheet.getRow(rowIndex++) == null) { + sheet.createRow(rowIndex - 1); + } + } + return sheet.getRow(rowIndex - rows); + } + /** + * 根据 当前是集合的信息,把后面整个集合的迭代获取出来,并替换掉集合的前缀方便后面取数 + * + * @param columns + * @param columnIndex + * @param collectName + * @return + */ + private List getLoopEachParams(List columns, int columnIndex, String collectName) { + List temp = new ArrayList<>(); + for (int i = 0; i < columns.size(); i++) { + //先置为不是集合 + columns.get(i).setCollectCell(false); + if (columns.get(i) == null || columns.get(i).getName().contains(collectName)) { + temp.add(columns.get(i)); + if (columns.get(i).getTempName() == null) { + columns.get(i).setTempName(new Stack<>()); + } + columns.get(i).setCollectCell(true); + columns.get(i).getTempName().push(columns.get(i).getName()); + columns.get(i).setName(columns.get(i).getName().replace(collectName, "loop_" + columnIndex)); + } + } + return temp; + } + + /** + * 设置行样式 + * @param row + * @param columnIndex + * @param columns + * @param rowspan + * @param colspan + */ + private void createRowCellSetStyle(Row row, int columnIndex, List columns, + int rowspan, int colspan) { + //所有的cell创建一遍 + for (int i = 0; i < rowspan; i++) { + int size = columns.size(); + for (int j = columnIndex, max = columnIndex + colspan; j < max; j++) { + if (row.getCell(j) == null) { + row.createCell(j); + CellStyle style = row.getRowNum() % 2 == 0 + ? getStyles(false, + size <= j - columnIndex ? null : columns.get(j - columnIndex)) + : getStyles(true, + size <= j - columnIndex ? null : columns.get(j - columnIndex)); + //返回的styler不为空时才使用,否则使用Excel设置的,更加推荐Excel设置的样式 + if (style != null) { + row.getCell(j).setCellStyle(style); + } + } + + } + if (i < rowspan - 1) { + row = row.getSheet().getRow(row.getRowNum() + 1); + } + } + } + + /** + * 获取CellStyle + * @param isSingle + * @param excelForEachParams + * @return + */ + private CellStyle getStyles(boolean isSingle, ExcelForEachParams excelForEachParams) { + return excelExportStyler.getTemplateStyles(isSingle, excelForEachParams); + } + + /** + * 获取最大高度 + * @param k + * @param colspan + * @param columns + * @return + */ + private short getMaxHeight(int k, int colspan, List columns) { + short high = columns.get(0).getHeight(); + int n = k; + while (n > 0) { + if (columns.get(n * colspan).getHeight() == 0) { + n--; + } else { + high = columns.get(n * colspan).getHeight(); + break; + } + } + return high; + } + + private boolean isHasSymbol(String text, String symbol) { + return text.startsWith(symbol) || text.contains("{" + symbol) + || text.contains(" " + symbol); + } + /** + * 迭代把不是集合的数据都合并了 + * + * @param row + * @param columnIndex + * @param columns + * @param loopSize + */ + private void handlerLoopMergedRegion(Row row, int columnIndex, List columns, int loopSize) { + for (int i = 0; i < columns.size(); i++) { + if (!columns.get(i).isCollectCell()) { + PoiMergeCellUtil.addMergedRegion(row.getSheet(), row.getRowNum(), + row.getRowNum() + loopSize - 1, columnIndex, + columnIndex + columns.get(i).getColspan() - 1); + } + columnIndex = columnIndex + columns.get(i).getColspan(); + } + } + /** + * 设置合并单元格的样式 + * + * @param row + * @param ci + * @param params + */ + private void setMergedRegionStyle(Row row, int ci, ExcelForEachParams params) { + //第一行数据 + for (int i = 1; i < params.getColspan(); i++) { + if (params.getCellStyle() != null) { + row.getCell(ci + i).setCellStyle(params.getCellStyle()); + } + } + for (int i = 1; i < params.getRowspan(); i++) { + for (int j = 0; j < params.getColspan(); j++) { + if (params.getCellStyle() != null) { + row.getCell(ci + j).setCellStyle(params.getCellStyle()); + } + } + } + } + /** + * 获取迭代的数据的值 + * + * @param cell + * @param name + * @param mergedRegionHelper + * @return + */ + private Object[] getAllDataColumns(Cell cell, String name, + MergedRegionHelper mergedRegionHelper) { + List columns = new ArrayList(); + cell.setCellValue(""); + columns.add(getExcelTemplateParams(name.replace(END_STR, EMPTY), cell, mergedRegionHelper)); + int rowspan = 1, colspan = 1; + if (!name.contains(END_STR)) { + int index = cell.getColumnIndex(); + //保存col 的开始列 + int startIndex = cell.getColumnIndex(); + Row row = cell.getRow(); + while (index < row.getLastCellNum()) { + int colSpan = columns.get(columns.size() - 1) != null + ? columns.get(columns.size() - 1).getColspan() : 1; + index += colSpan; + + + for (int i = 1; i < colSpan; i++) { + //添加合并的单元格,这些单元可能不是空,但是没有值,所以也需要跳过 + columns.add(null); + continue; + } + cell = row.getCell(index); + //可能是合并的单元格 + if (cell == null) { + //读取是判断,跳过 + columns.add(null); + continue; + } + String cellStringString; + try {//不允许为空 便利单元格必须有结尾和值 + cellStringString = cell.getStringCellValue(); + if (StringUtils.isBlank(cellStringString) && colspan + startIndex <= index) { + throw new ExcelExportException("for each 当中存在空字符串,请检查模板"); + } else if (StringUtils.isBlank(cellStringString) + && colspan + startIndex > index) { + //读取是判断,跳过,数据为空,但是不是第一次读这一列,所以可以跳过 + columns.add(new ExcelForEachParams(null, cell.getCellStyle(), (short) 0)); + continue; + } + } catch (Exception e) { + throw new ExcelExportException(ExcelExportEnum.TEMPLATE_ERROR, e); + } + //把读取过的cell 置为空 + cell.setCellValue(""); + if (cellStringString.contains(END_STR)) { + columns.add(getExcelTemplateParams(cellStringString.replace(END_STR, EMPTY), + cell, mergedRegionHelper)); + //补全缺失的cell(合并单元格后面的) + int lastCellColspan = columns.get(columns.size() - 1).getColspan(); + for (int i = 1; i < lastCellColspan; i++) { + //添加合并的单元格,这些单元可能不是空,但是没有值,所以也需要跳过 + columns.add(null); + } + break; + } else if (cellStringString.contains(WRAP)) { + columns.add(getExcelTemplateParams(cellStringString.replace(WRAP, EMPTY), cell, + mergedRegionHelper)); + //发现换行符,执行换行操作 + colspan = index - startIndex + 1; + index = startIndex - columns.get(columns.size() - 1).getColspan(); + row = row.getSheet().getRow(row.getRowNum() + 1); + rowspan++; + } else { + columns.add(getExcelTemplateParams(cellStringString.replace(WRAP, EMPTY), cell, + mergedRegionHelper)); + } + } + } + colspan = 0; + for (int i = 0; i < columns.size(); i++) { + colspan += columns.get(i) != null ? columns.get(i).getColspan() : 0; + } + colspan = colspan / rowspan; + return new Object[]{rowspan, colspan, columns}; + } + /** + * 获取模板参数 + * + * @param name + * @param cell + * @param mergedRegionHelper + * @return + */ + private ExcelForEachParams getExcelTemplateParams(String name, Cell cell, + MergedRegionHelper mergedRegionHelper) { + name = name.trim(); + ExcelForEachParams params = new ExcelForEachParams(name, cell.getCellStyle(), + cell.getRow().getHeight()); + //判断是不是常量 + if (name.startsWith(CONST) && name.endsWith(CONST)) { + params.setName(null); + params.setConstValue(name.substring(1, name.length() - 1)); + } + //判断是不是空 + if (NULL.equals(name)) { + params.setName(null); + params.setConstValue(EMPTY); + } + //获取合并单元格的数据 + if (mergedRegionHelper.isMergedRegion(cell.getRowIndex() + 1, cell.getColumnIndex())) { + Integer[] colAndrow = mergedRegionHelper.getRowAndColSpan(cell.getRowIndex() + 1, + cell.getColumnIndex()); + params.setRowspan(colAndrow[0]); + params.setColspan(colAndrow[1]); + } + return params; + } + //-----------------update-end-author:liusq---date:20220527--for: 以上方法是模板导出列循环功能新增的方法 --- +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/graph/builder/ExcelChartBuildService.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/graph/builder/ExcelChartBuildService.java new file mode 100644 index 0000000..0346c55 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/graph/builder/ExcelChartBuildService.java @@ -0,0 +1,263 @@ +///** +// * +// */ +//package org.jeecgframework.poi.excel.graph.builder; +// +//import java.util.ArrayList; +//import java.util.List; +// +//import org.apache.commons.lang3.StringUtils; +//import org.apache.poi.ss.usermodel.Chart; +//import org.apache.poi.ss.usermodel.ClientAnchor; +//import org.apache.poi.ss.usermodel.Drawing; +//import org.apache.poi.ss.usermodel.Sheet; +//import org.apache.poi.ss.usermodel.Workbook; +//import org.apache.poi.ss.usermodel.charts.AxisCrosses; +//import org.apache.poi.ss.usermodel.charts.AxisPosition; +//import org.apache.poi.ss.usermodel.charts.ChartAxis; +//import org.apache.poi.ss.usermodel.charts.ChartDataSource; +//import org.apache.poi.ss.usermodel.charts.ChartLegend; +//import org.apache.poi.ss.usermodel.charts.DataSources; +//import org.apache.poi.ss.usermodel.charts.LegendPosition; +//import org.apache.poi.ss.usermodel.charts.LineChartData; +//import org.apache.poi.ss.usermodel.charts.ScatterChartData; +//import org.apache.poi.ss.usermodel.charts.ValueAxis; +//import org.apache.poi.ss.util.CellRangeAddress; +// +//import org.jeecgframework.poi.excel.graph.constant.ExcelGraphElementType; +//import org.jeecgframework.poi.excel.graph.constant.ExcelGraphType; +//import org.jeecgframework.poi.excel.graph.entity.ExcelGraph; +//import org.jeecgframework.poi.excel.graph.entity.ExcelGraphElement; +//import org.jeecgframework.poi.excel.graph.entity.ExcelTitleCell; +//import org.jeecgframework.poi.util.PoiCellUtil; +//import org.jeecgframework.poi.util.PoiExcelGraphDataUtil; +// +///** +// * @Description +// * @author liusq +// * @data 2022年1月4号 +// */ +//public class ExcelChartBuildService +//{ +// /** +// * +// * @param workbook +// * @param graphList +// * @param build 通过实时数据行来重新计算图形定义 +// * @param append +// */ +// public static void createExcelChart(Workbook workbook, List graphList, Boolean build, Boolean append) +// { +// if(workbook!=null&&graphList!=null){ +// //设定默认第一个sheet为数据项 +// Sheet dataSouce=workbook.getSheetAt(0); +// if(dataSouce!=null){ +// buildTitle(dataSouce,graphList); +// +// if(build){ +// PoiExcelGraphDataUtil.buildGraphData(dataSouce, graphList); +// } +// if(append){ +// buildExcelChart(dataSouce, dataSouce, graphList); +// }else{ +// Sheet sheet=workbook.createSheet("图形界面"); +// buildExcelChart(dataSouce, sheet, graphList); +// } +// } +// +// } +// } +// +// /** +// * 构建基础图形 +// * @param drawing +// * @param anchor +// * @param dataSourceSheet +// * @param graph +// */ +// private static void buildExcelChart(Drawing drawing,ClientAnchor anchor,Sheet dataSourceSheet,ExcelGraph graph){ +// Chart chart = null; +// // TODO 图表没有成功 +// //drawing.createChart(anchor); +// ChartLegend legend = chart.getOrCreateLegend(); +// legend.setPosition(LegendPosition.TOP_RIGHT); +// +// ChartAxis bottomAxis = chart.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM); +// ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT); +// leftAxis.setCrosses(AxisCrosses.AUTO_ZERO); +// ExcelGraphElement categoryElement=graph.getCategory(); +// +// ChartDataSource categoryChart; +// if(categoryElement!=null&& categoryElement.getElementType().equals(ExcelGraphElementType.STRING_TYPE)){ +// categoryChart=DataSources.fromStringCellRange(dataSourceSheet, new CellRangeAddress(categoryElement.getStartRowNum(),categoryElement.getEndRowNum(),categoryElement.getStartColNum(),categoryElement.getEndColNum())); +// }else{ +// categoryChart=DataSources.fromNumericCellRange(dataSourceSheet, new CellRangeAddress(categoryElement.getStartRowNum(),categoryElement.getEndRowNum(),categoryElement.getStartColNum(),categoryElement.getEndColNum())); +// } +// +// List valueList=graph.getValueList(); +// List> chartValueList= new ArrayList<>(); +// if(valueList!=null&&valueList.size()>0){ +// for(ExcelGraphElement ele:valueList){ +// ChartDataSource source=DataSources.fromNumericCellRange(dataSourceSheet, new CellRangeAddress(ele.getStartRowNum(),ele.getEndRowNum(),ele.getStartColNum(),ele.getEndColNum())); +// chartValueList.add(source); +// } +// } +// +// if(graph.getGraphType().equals(ExcelGraphType.LINE_CHART)){ +// LineChartData data = chart.getChartDataFactory().createLineChartData(); +// buildLineChartData(data, categoryChart, chartValueList, graph.getTitle()); +// chart.plot(data, bottomAxis, leftAxis); +// } +// else +// { +// ScatterChartData data=chart.getChartDataFactory().createScatterChartData(); +// buildScatterChartData(data, categoryChart, chartValueList,graph.getTitle()); +// chart.plot(data, bottomAxis, leftAxis); +// } +// } +// +// +// +// +// /** +// * 构建多个图形对象 +// * @param dataSourceSheet +// * @param tragetSheet +// * @param graphList +// */ +// private static void buildExcelChart(Sheet dataSourceSheet,Sheet tragetSheet,List graphList){ +// int len=graphList.size(); +// if(len==1) +// { +// buildExcelChart(dataSourceSheet, tragetSheet, graphList.get(0)); +// } +// else +// { +// int drawStart=0; +// int drawEnd=20; +// Drawing drawing = PoiExcelGraphDataUtil.getDrawingPatriarch(tragetSheet); +// for(int i=0;i0){ +// +// }else{ +// for(int i=0;i graphList){ +// if(graphList!=null&&graphList.size()>0){ +// for(ExcelGraph graph:graphList){ +// if(graph!=null) +// { +// buildTitle(sheet, graph); +// } +// } +// } +// } +// +// /** +// * +// * @param data +// * @param categoryChart +// * @param chartValueList +// * @param title +// */ +// private static void buildLineChartData(LineChartData data,ChartDataSource categoryChart,List> chartValueList,List title){ +// if(chartValueList.size()==title.size()) +// { +// int len=title.size(); +// for(int i=0;i source:chartValueList){ +// String temp_title=title.get(i); +// if(StringUtils.isNotBlank(temp_title)){ +// //data.addSerie(categoryChart, source).setTitle(_title); +// }else{ +// //data.addSerie(categoryChart, source); +// } +// } +// } +// } +// +// /** +// * +// * @param data +// * @param categoryChart +// * @param chartValueList +// * @param title +// */ +// private static void buildScatterChartData(ScatterChartData data,ChartDataSource categoryChart,List> chartValueList,List title){ +// if(chartValueList.size()==title.size()) +// { +// int len=title.size(); +// for(int i=0;i source:chartValueList){ +// String temp_title=title.get(i); +// if(StringUtils.isNotBlank(temp_title)){ +// data.addSerie(categoryChart, source).setTitle(temp_title); +// }else{ +// data.addSerie(categoryChart, source); +// } +// } +// } +// } +// +// +//} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/CellValueHelper.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/CellValueHelper.java new file mode 100644 index 0000000..51d4204 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/CellValueHelper.java @@ -0,0 +1,135 @@ +package org.jeecgframework.poi.excel.html.helper; + +import java.util.HashMap; +import java.util.Map; + +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.CellType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import com.google.common.xml.XmlEscapers; + +/** + * Cell值帮助类 + * + * @author JEECG + * @date 2015年5月9日 下午10:31:32 + */ +public class CellValueHelper { + /** + * Excel 格式 + */ + private boolean is07; + + private int cssRandom; + + private Map fontCache = new HashMap(); + + public CellValueHelper(Workbook wb, int cssRandom) { + this.cssRandom = cssRandom; + if (wb instanceof HSSFWorkbook) + is07 = false; + else if (wb instanceof XSSFWorkbook) { + is07 = true; + cacheFontInfo(wb); + } else + throw new IllegalArgumentException("unknown workbook type: " + wb.getClass().getSimpleName()); + } + + /** + * O7 版本坑爹bug + * + * @param wb + */ + private void cacheFontInfo(Workbook wb) { + for (int i = 0, le = wb.getNumberOfFonts(); i < le; i++) { + Font font = wb.getFontAt(i); + fontCache.put(font.getBold() + "_" + font.getItalic() + "_" + font.getFontName() + "_" + font.getFontHeightInPoints() + "_" + font.getColor(), font.getIndex() + ""); + } + + } + + public String getHtmlValue(Cell cell) { + if (CellType.BOOLEAN == cell.getCellType() || CellType.NUMERIC == cell.getCellType()) { + cell.setCellType( CellType.STRING); + return cell.getStringCellValue(); + } else if ( CellType.STRING == cell.getCellType()) { + if (cell.getRichStringCellValue().numFormattingRuns() == 0) { + return XmlEscapers.xmlContentEscaper().escape(cell.getStringCellValue()); + } else if (is07) { + return getXSSFRichString((XSSFRichTextString) cell.getRichStringCellValue()); + } else { + return getHSSFRichString((HSSFRichTextString) cell.getRichStringCellValue()); + } + } + return ""; + } + + /** + * 03版本复杂数据 + * + * @param rich + * @return + */ + private String getHSSFRichString(HSSFRichTextString rich) { + int nums = rich.numFormattingRuns(); + StringBuilder sb = new StringBuilder(); + String text = rich.toString(); + int currentIndex = 0; + sb.append(text.substring(0, rich.getIndexOfFormattingRun(0))); + for (int i = 0; i < nums; i++) { + sb.append(""); + currentIndex = rich.getIndexOfFormattingRun(i); + if (i < nums - 1) { + sb.append(XmlEscapers.xmlContentEscaper().escape(text.substring(currentIndex, rich.getIndexOfFormattingRun(i + 1)))); + } else { + sb.append(XmlEscapers.xmlContentEscaper().escape(text.substring(currentIndex, text.length()))); + } + sb.append(""); + } + return sb.toString(); + } + + /** + * 07版本复杂数据 + * + * @param rich + * @return + */ + private String getXSSFRichString(XSSFRichTextString rich) { + int nums = rich.numFormattingRuns(); + StringBuilder sb = new StringBuilder(); + String text = rich.toString(); + int currentIndex = 0, lastIndex = 0; + for (int i = 1; i <= nums; i++) { + sb.append(""); + currentIndex = rich.getIndexOfFormattingRun(i) == -1 ? text.length() : rich.getIndexOfFormattingRun(i); + sb.append(XmlEscapers.xmlContentEscaper().escape(text.substring(lastIndex, currentIndex))); + sb.append(""); + lastIndex = currentIndex; + } + return sb.toString(); + } + + private String getFontIndex(XSSFFont font) { + return fontCache.get(font.getBold() + "_" + font.getItalic() + "_" + font.getFontName() + "_" + font.getFontHeightInPoints() + "_" + font.getColor()); + } +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/StylerHelper.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/StylerHelper.java new file mode 100644 index 0000000..734126c --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/html/helper/StylerHelper.java @@ -0,0 +1,259 @@ +package org.jeecgframework.poi.excel.html.helper; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Formatter; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFPalette; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.ss.usermodel.*; +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.XSSFWorkbook; +import org.jeecgframework.poi.util.PoiPublicUtil; + +/** + * 样式帮助类 + * + * @author JEECG + * @date 2015年5月9日 下午4:04:24 + */ +public class StylerHelper { + + private static String DEFAULTS_CLASS_CSS = ".excelDefaults {background-color: white;color: black;text-decoration: none;direction: ltr;text-transform: none;text-indent: 0;letter-spacing: 0;word-spacing: 0;white-space: normal;unicode-bidi: normal;vertical-align: 0;text-shadow: none;padding: 0;margin: 0;border-collapse: collapse;white-space: pre-wrap;word-wrap: break-word;word-break: break-all;}.excelDefaults td {padding: 1px 5px;border: 1px solid silver;border-color: #000000;text-align: center;vertical-align: middle;font-size: 12pt;}.excelDefaults .colHeader {background-color: silver;font-weight: bold;border: 1px solid black;text-align: center;padding: 1px 5px;}.excelDefaults .rowHeader {background-color: silver;font-weight: bold;border: 1px solid black;text-align: right;padding: 1px 5px;}"; + + private static final String DEFAULTS_CLASS = "excelDefaults"; + + private static final Map ALIGN = PoiPublicUtil.mapFor(HorizontalAlignment.LEFT.getCode(), "left",HorizontalAlignment.CENTER.getCode(), "center",HorizontalAlignment.RIGHT.getCode(), "right", HorizontalAlignment.FILL.getCode(), "left",HorizontalAlignment.JUSTIFY.getCode(), "left",HorizontalAlignment.CENTER_SELECTION.getCode(), "center"); + + private static final Map VERTICAL_ALIGN = PoiPublicUtil.mapFor(VerticalAlignment.BOTTOM.getCode(), "bottom", VerticalAlignment.CENTER.getCode(), "middle",VerticalAlignment.TOP.getCode(), "top"); + + private Formatter out; + + private Sheet sheet; + + private HtmlHelper helper; + + private int sheetNum; + + private int cssRandom; + + public StylerHelper(Workbook wb, Formatter out, int sheetNum, int cssRandom) { + this.out = out; + this.sheetNum = sheetNum; + this.cssRandom = cssRandom; + if (wb instanceof HSSFWorkbook) + helper = new HSSFHtmlHelper((HSSFWorkbook) wb); + else if (wb instanceof XSSFWorkbook) + helper = new XSSFHtmlHelper((XSSFWorkbook) wb); + else + throw new IllegalArgumentException("unknown workbook type: " + wb.getClass().getSimpleName()); + printInlineStyle(wb); + } + + private void printInlineStyle(Workbook wb) { + out.format("%n"); + } + + private void prontFonts(Workbook wb) { + //update-begin---author:liusq Date:20220228 for:[I4I3ZY]issue AutoPOi Workbook对象转HTML字符串 数组下标越界异常---- + for (int i = 0, le = wb.getNumberOfFonts(); i < le; i++) { + Font font = wb.getFontAt(i); + out.format(".%s .%s {%n", DEFAULTS_CLASS, "font_" + i + "_" + cssRandom); + fontStyle(font); + out.format("}%n"); + } + //update-end---author:liusq Date:20220228 for:[I4I3ZY]issue AutoPOi Workbook对象转HTML字符串 数组下标越界异常整---- + } + + public void printStyles(Workbook wb) { + if (DEFAULTS_CLASS_CSS == null) { + DEFAULTS_CLASS_CSS = getDefaultsClassCss(); + } + out.format(DEFAULTS_CLASS_CSS); + Set seen = new HashSet(); + sheet = wb.getSheetAt(sheetNum); + Iterator rows = sheet.rowIterator(); + while (rows.hasNext()) { + Row row = rows.next(); + for (Cell cell : row) { + CellStyle style = cell.getCellStyle(); + if (!seen.contains(style)) { + printStyle(style); + seen.add(style); + } + } + } + } + + private String getDefaultsClassCss() { + BufferedReader in = null; + StringBuilder sb = new StringBuilder(); + Formatter formatter = new Formatter(sb); + try { + in = new BufferedReader(new InputStreamReader(StylerHelper.class.getResourceAsStream("excelStyle.css"))); + String line; + while ((line = in.readLine()) != null) { + formatter.format("%s%n", line); + } + return formatter.toString(); + } catch (IOException e) { + throw new IllegalStateException("Reading standard css", e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + throw new IllegalStateException("Reading standard css", e); + } + } + formatter.close(); + } + } + + private void printStyle(CellStyle style) { + out.format(".%s .%s {%n", DEFAULTS_CLASS, styleName(style)); + styleContents(style); + out.format("}%n"); + } + + private void styleContents(CellStyle style) { + if (style.getAlignment().getCode() != 2) { + styleOut("text-align", style.getAlignment().getCode(), ALIGN); + styleOut("vertical-align", style.getAlignment().getCode(), VERTICAL_ALIGN); + } + helper.colorStyles(style, out); + } + + private void fontStyle(Font font) { + if (font.getBold()) + out.format(" font-weight: bold;%n"); + if (font.getItalic()) + out.format(" font-style: italic;%n"); + out.format(" font-family: %s;%n", font.getFontName()); + + int fontheight = font.getFontHeightInPoints(); + if (fontheight == 9) { + fontheight = 10; + } + out.format(" font-size: %dpt;%n", fontheight); + helper.styleColor(out, "color", getColor(font)); + } + + private Color getColor(Font font) { + if (helper instanceof HSSFHtmlHelper) { + return ((HSSFWorkbook) sheet.getWorkbook()).getCustomPalette().getColor(font.getColor()); + } else { + return ((XSSFFont) font).getXSSFColor(); + } + } + + private String styleName(CellStyle style) { + if (style == null) + return ""; + return String.format("style_%02x_%s", style.getIndex(), cssRandom); + } + + private void styleOut(String attr, K key, Map mapping) { + String value = mapping.get(key); + if (value != null) { + out.format(" %s: %s;%n", attr, value); + } + } + + private interface HtmlHelper { + /** + * Outputs the appropriate CSS style for the given cell style. + * + * @param style + * The cell style. + * @param out + * The place to write the output. + */ + void colorStyles(CellStyle style, Formatter out); + + void styleColor(Formatter out, String attr, Color color); + } + + private class HSSFHtmlHelper implements HtmlHelper { + private final HSSFWorkbook wb; + private final HSSFPalette colors; + + //-------author:liusq------date:20210129-----for:-------poi3升级到4兼容改造工作【重要敏感修改点】-------- + private HSSFColor HSSF_AUTO = new HSSFColor(0x40, -1, java.awt.Color.black); + //-------author:liusq------date:20210129-----for:-------poi3升级到4兼容改造工作【重要敏感修改点】-------- + + public HSSFHtmlHelper(HSSFWorkbook wb) { + this.wb = wb; + colors = wb.getCustomPalette(); + } + + public void colorStyles(CellStyle style, Formatter out) { + HSSFCellStyle cs = (HSSFCellStyle) style; + out.format(" /* fill pattern = %d */%n", cs.getFillPattern()); + styleColor(out, "background-color", cs.getFillForegroundColor()); + styleColor(out, "color", colors.getColor(cs.getFont(wb).getColor())); + } + + private void styleColor(Formatter out, String attr, short index) { + HSSFColor color = colors.getColor(index); + if (index == HSSF_AUTO.getIndex() || color == null) { + out.format(" /* %s: index = %d */%n", attr, index); + } else { + short[] rgb = color.getTriplet(); + out.format(" %s: #%02x%02x%02x; /* index = %d */%n", attr, rgb[0], rgb[1], rgb[2], index); + } + } + + public void styleColor(Formatter out, String attr, Color color) { + if (color == null) { + return; + } + HSSFColor hSSFColor = (HSSFColor) color; + short[] rgb = hSSFColor.getTriplet(); + out.format(" %s: #%02x%02x%02x; %n", attr, rgb[0], rgb[1], rgb[2]); + } + } + + /** + * Implementation of {@link HtmlHelper} for XSSF files. + * + * @author Ken Arnold, Industrious Media LLC + */ + private class XSSFHtmlHelper implements HtmlHelper { + + public XSSFHtmlHelper(XSSFWorkbook wb) { + } + + public void colorStyles(CellStyle style, Formatter out) { + XSSFCellStyle cs = (XSSFCellStyle) style; + styleColor(out, "background-color", cs.getFillForegroundXSSFColor()); + styleColor(out, "color", cs.getFont().getXSSFColor()); + } + + public void styleColor(Formatter out, String attr, Color color) { + XSSFColor xSSFColor = (XSSFColor) color; + if (color == null || xSSFColor.isAuto()) + return; + + byte[] rgb = xSSFColor.getRGB(); + if (rgb == null) { + return; + } + out.format(" %s: #%02x%02x%02x;%n", attr, rgb[0], rgb[1], rgb[2]); + } + } + +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/CellValueServer.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/CellValueServer.java new file mode 100644 index 0000000..23e1910 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/CellValueServer.java @@ -0,0 +1,387 @@ +/** + * Copyright 2013-2015 JEECG (jeecgos@163.com) + * + * 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 org.jeecgframework.poi.excel.imports; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.jeecgframework.poi.excel.entity.params.ExcelImportEntity; +import org.jeecgframework.poi.excel.entity.sax.SaxReadCellEntity; +import org.jeecgframework.poi.exception.excel.ExcelImportException; +import org.jeecgframework.poi.exception.excel.enums.ExcelImportEnum; +import org.jeecgframework.poi.handler.inter.IExcelDataHandler; +import org.jeecgframework.poi.util.ExcelUtil; +import org.jeecgframework.poi.util.PoiPublicUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Time; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Cell 取值服务 判断类型处理数据 1.判断Excel中的类型 2.根据replace替换值 3.handler处理数据 4.判断返回类型转化数据返回 + * + * @author JEECG + * @date 2014年6月26日 下午10:42:28 + */ +public class CellValueServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(CellValueServer.class); + + private List hanlderList = null; + + /** + * 获取单元格内的值 + * + * @param xclass + * @param cell + * @param entity + * @return + */ + private Object getCellValue(String xclass, Cell cell, ExcelImportEntity entity) { + if (cell == null) { + return ""; + } + Object result = null; + // 日期格式比较特殊,和cell格式不一致 + if ("class java.util.Date".equals(xclass) || ("class java.sql.Time").equals(xclass)) { + if ( CellType.NUMERIC == cell.getCellType()) { + // 日期格式 + result = cell.getDateCellValue(); + } else { + cell.setCellType( CellType.STRING); + result = getDateData(entity, cell.getStringCellValue()); + } + if (("class java.sql.Time").equals(xclass)) { + result = new Time(((Date) result).getTime()); + } + } else if ( CellType.NUMERIC == cell.getCellType()) { + result = cell.getNumericCellValue(); + } else if ( CellType.BOOLEAN == cell.getCellType()) { + result = cell.getBooleanCellValue(); + } else if ( CellType.FORMULA == cell.getCellType() && PoiPublicUtil.isNumber(xclass)) { + //如果单元格是表达式 且 字段是数字类型 + double cellValue = cell.getNumericCellValue(); + //---author:liusq---date:20221102-----for: [issues/3369]Excel导入 带公式的时候精度丢失--- + //setScale方法的第一个参数设置小数点保留位数,第二个参数设置进位方法、此处是四舍五入 + BigDecimal bigDecimal= new BigDecimal(cellValue).setScale(4, RoundingMode.HALF_UP); + //stripTrailingZeros方法去除末尾的0,toPlainString避免输出科学计数法的字符串 + result = bigDecimal.stripTrailingZeros().toPlainString(); + //---author:liusq---date:20221102-----for:[issues/3369] Excel导入 带公式的时候精度丢失--- + } else { + //设置单元格类型 + cell.setCellType(CellType.STRING); + result = cell.getStringCellValue(); + } + return result; + } + + /** + * 获取日期类型数据 + * + * @Author JEECG + * @date 2013年11月26日 + * @param entity + * @param value + * @return + */ + private Date getDateData(ExcelImportEntity entity, String value) { + if (StringUtils.isNotEmpty(entity.getFormat()) && StringUtils.isNotEmpty(value)) { + SimpleDateFormat format = new SimpleDateFormat(entity.getFormat()); + try { + return format.parse(value); + } catch (ParseException e) { + LOGGER.error("时间格式化失败,格式化:{},值:{}", entity.getFormat(), value); + throw new ExcelImportException(ExcelImportEnum.GET_VALUE_ERROR); + } + } + return null; + } + + /** + * 获取cell的值 + * + * @param object + * @param excelParams + * @param cell + * @param titleString + */ + public Object getValue(IExcelDataHandler dataHanlder, Object object, Cell cell, Map excelParams, String titleString) throws Exception { + ExcelImportEntity entity = excelParams.get(titleString); + String xclass = "class java.lang.Object"; + if (!(object instanceof Map)) { + Method setMethod = entity.getMethods() != null && entity.getMethods().size() > 0 ? entity.getMethods().get(entity.getMethods().size() - 1) : entity.getMethod(); + Type[] ts = setMethod.getGenericParameterTypes(); + xclass = ts[0].toString(); + } + Object result = getCellValue(xclass, cell, entity); + if (entity != null) { + result = hanlderSuffix(entity.getSuffix(), result); + //update-begin-author:taoYan date:20180807 for:多值替换 + result = replaceValue(entity.getReplace(), result,entity.isMultiReplace()); + //update-end-author:taoYan date:20180807 for:多值替换 + } + result = hanlderValue(dataHanlder, object, result, titleString); + return getValueByType(xclass, result, entity); + } + + /** + * 获取cell值 + * + * @param dataHanlder + * @param object + * @param cellEntity + * @param excelParams + * @param titleString + * @return + */ + public Object getValue(IExcelDataHandler dataHanlder, Object object, SaxReadCellEntity cellEntity, Map excelParams, String titleString) { + ExcelImportEntity entity = excelParams.get(titleString); + Method setMethod = entity.getMethods() != null && entity.getMethods().size() > 0 ? entity.getMethods().get(entity.getMethods().size() - 1) : entity.getMethod(); + Type[] ts = setMethod.getGenericParameterTypes(); + String xclass = ts[0].toString(); + Object result = cellEntity.getValue(); + result = hanlderSuffix(entity.getSuffix(), result); + //update-begin-auhtor:taoyan date:20180807 for:多值替换 + result = replaceValue(entity.getReplace(), result,entity.isMultiReplace()); + //update-end-auhtor:taoyan date:20180807 for:多值替换 + result = hanlderValue(dataHanlder, object, result, titleString); + return getValueByType(xclass, result, entity); + } + + /** + * 把后缀删除掉 + * + * @param result + * @param suffix + * @return + */ + private Object hanlderSuffix(String suffix, Object result) { + if (StringUtils.isNotEmpty(suffix) && result != null && result.toString().endsWith(suffix)) { + String temp = result.toString(); + return temp.substring(0, temp.length() - suffix.length()); + } + return result; + } + + /** + * 根据返回类型获取返回值 + * + * @param xclass + * @param result + * @param entity + * @return + */ + private Object getValueByType(String xclass, Object result, ExcelImportEntity entity) { + try { + //update-begin-author:scott date:20180711 for:TASK #2950 【bug】excel 导入报错,空指针问题 + if(result==null || "".equals(String.valueOf(result))){ + return null; + } + //update-end-author:scott date:20180711 for:TASK #2950 【bug】excel 导入报错,空指针问题 + if ("class java.util.Date".equals(xclass)) { + return result; + } + if ("class java.lang.Boolean".equals(xclass) || "boolean".equals(xclass)) { + //update-begin-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + Boolean temp = Boolean.valueOf(String.valueOf(result)); + //if(StringUtils.isNotEmpty(entity.getNumFormat())){ + // return Boolean.valueOf(new DecimalFormat(entity.getNumFormat()).format(temp)); + //}else{ + return temp; + //} + //update-end-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + } + if ("class java.lang.Double".equals(xclass) || "double".equals(xclass)) { + //update-begin-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + Double temp = Double.valueOf(String.valueOf(result)); + //if(StringUtils.isNotEmpty(entity.getNumFormat())){ + // return Double.valueOf(new DecimalFormat(entity.getNumFormat()).format(temp)); + //}else{ + return temp; + //} + //update-end-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + } + if ("class java.lang.Long".equals(xclass) || "long".equals(xclass)) { + //update-begin-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + Long temp = Long.valueOf(ExcelUtil.remove0Suffix(String.valueOf(result))); + //if(StringUtils.isNotEmpty(entity.getNumFormat())){ + // return Long.valueOf(new DecimalFormat(entity.getNumFormat()).format(temp)); + //}else{ + return temp; + //} + //update-end-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + } + if ("class java.lang.Float".equals(xclass) || "float".equals(xclass)) { + //update-begin-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + Float temp = Float.valueOf(String.valueOf(result)); + //if(StringUtils.isNotEmpty(entity.getNumFormat())){ + // return Float.valueOf(new DecimalFormat(entity.getNumFormat()).format(temp)); + //}else{ + return temp; + //} + //update-end-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + } + if ("class java.lang.Integer".equals(xclass) || "int".equals(xclass)) { + //update-begin-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + Integer temp = Integer.valueOf(ExcelUtil.remove0Suffix(String.valueOf(result))); + //if(StringUtils.isNotEmpty(entity.getNumFormat())){ + // return Integer.valueOf(new DecimalFormat(entity.getNumFormat()).format(temp)); + //}else{ + return temp; + //} + //update-end-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + } + if ("class java.math.BigDecimal".equals(xclass)) { + //update-begin-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + BigDecimal temp = new BigDecimal(String.valueOf(result)); + //if(StringUtils.isNotEmpty(entity.getNumFormat())){ + // return new BigDecimal(new DecimalFormat(entity.getNumFormat()).format(temp)); + //}else{ + return temp; + //} + //update-end-author:taoYan date:20200319 for:Excel注解的numFormat方法似乎未实现 #970 + } + if ("class java.lang.String".equals(xclass)) { + // 针对String 类型,但是Excel获取的数据却不是String,比如Double类型,防止科学计数法 + if (result instanceof String) { + //---update-begin-----autor:scott------date:20191016-------for:excel导入数字类型,去掉后缀.0------ + return ExcelUtil.remove0Suffix(result); + //---update-end-----autor:scott------date:20191016-------for:excel导入数字类型,去掉后缀.0------ + } + // double类型防止科学计数法 + if (result instanceof Double) { + return PoiPublicUtil.doubleToString((Double) result); + } + //---update-begin-----autor:scott------date:20191016-------for:excel导入数字类型,去掉后缀.0------ + return ExcelUtil.remove0Suffix(String.valueOf(result)); + //---update-end-----autor:scott------date:20191016-------for:excel导入数字类型,去掉后缀.0------ + } + return result; + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + throw new ExcelImportException(ExcelImportEnum.GET_VALUE_ERROR); + } + } + + /** + * 调用处理接口处理值 + * + * @param dataHanlder + * @param object + * @param result + * @param titleString + * @return + */ + private Object hanlderValue(IExcelDataHandler dataHanlder, Object object, Object result, String titleString) { + if (dataHanlder == null || dataHanlder.getNeedHandlerFields() == null || dataHanlder.getNeedHandlerFields().length == 0) { + return result; + } + if (hanlderList == null) { + hanlderList = Arrays.asList(dataHanlder.getNeedHandlerFields()); + } + if (hanlderList.contains(titleString)) { + return dataHanlder.importHandler(object, titleString, result); + } + return result; + } + + //update-begin-author:taoyan date:20180807 for:导入多值替换-- + /** + * 导入支持多值替换 + * @param replace 数据库中字典查询出来的数组 + * @param result excel单元格获取的值 + * @param multiReplace 是否支持多值替换 + * @author taoYan + * @since 2018年8月7日 + */ + private Object replaceValue(String[] replace, Object result,boolean multiReplace) { + if(result == null){ + return ""; + } + if(replace == null || replace.length<=0){ + return result; + } + String temp = String.valueOf(result); + String backValue = ""; + if(temp.indexOf(",")>0 && multiReplace){ + //原值中带有逗号,认为他是多值的 + String multiReplaces[] = temp.split(","); + for (String str : multiReplaces) { + backValue = backValue.concat(replaceSingleValue(replace, str)+","); + } + if(backValue.equals("")){ + backValue = temp; + }else{ + backValue = backValue.substring(0, backValue.length()-1); + } + }else{ + backValue = replaceSingleValue(replace, temp); + } + //update-begin-author:liusq date:20210204 for:字典替换失败提示日志 + if(replace.length>0 && backValue.equals(temp)){ + LOGGER.warn("====================字典替换失败,字典值:{},要转换的导入值:{}====================", replace, temp); + } + //update-end-author:liusq date:20210204 for:字典替换失败提示日志 + return backValue; + } + /** + * 单值替换 ,若没找到则原值返回 + */ + private String replaceSingleValue(String[] replace, String temp){ + String[] tempArr; + for (int i = 0; i < replace.length; i++) { + //update-begin---author:scott Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析--- + //tempArr = replace[i].split("_"); + tempArr = getValueArr(replace[i]); + if (temp.equals(tempArr[0]) || temp.replace("_","---").equals(tempArr[0])) { + //update-begin---author:wangshuai ---date:20220422 for:导入字典替换需要将---替换成_,不然数据库会存--- ------------ + if(tempArr[1].contains("---")){ + return tempArr[1].replace("---","_"); + } + //update-end---author:wangshuai ---date:20220422 for:导入字典替换需要将---替换成_,不然数据库会存--- -------------- + return tempArr[1]; + } + //update-end---author:scott Date:20211220 for:[issues/I4MBB3]@Excel dicText字段的值有下划线时,导入功能不能正确解析--- + } + return temp; + } + //update-end-author:taoyan date:20180807 for:导入多值替换-- + + /** + * 字典文本中含多个下划线横岗,取最后一个(解决空值情况) + * + * @param val + * @return + */ + public String[] getValueArr(String val) { + int i = val.lastIndexOf("_");//最后一个分隔符的位置 + String[] c = new String[2]; + c[0] = val.substring(0, i); //label + c[1] = val.substring(i + 1); //key + return c; + } + +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/ExcelImportServer.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/ExcelImportServer.java new file mode 100644 index 0000000..7bdb7a7 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/ExcelImportServer.java @@ -0,0 +1,620 @@ +/** + * Copyright 2013-2015 JEECG (jeecgos@163.com) + * + * 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 org.jeecgframework.poi.excel.imports; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.poifs.filesystem.FileMagic; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.formula.functions.T; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.jeecgframework.core.util.ApplicationContextUtil; +import org.jeecgframework.poi.excel.annotation.ExcelTarget; +import org.jeecgframework.poi.excel.entity.ImportParams; +import org.jeecgframework.poi.excel.entity.params.ExcelCollectionParams; +import org.jeecgframework.poi.excel.entity.params.ExcelImportEntity; +import org.jeecgframework.poi.excel.entity.result.ExcelImportResult; +import org.jeecgframework.poi.excel.entity.result.ExcelVerifyHanlderResult; +import org.jeecgframework.poi.excel.imports.base.ImportBaseService; +import org.jeecgframework.poi.excel.imports.base.ImportFileServiceI; +import org.jeecgframework.poi.excel.imports.verifys.VerifyHandlerServer; +import org.jeecgframework.poi.exception.excel.ExcelImportException; +import org.jeecgframework.poi.exception.excel.enums.ExcelImportEnum; +import org.jeecgframework.poi.util.ExcelUtil; +import org.jeecgframework.poi.util.PoiPublicUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Excel 导入服务 + * + * @author JEECG + * @date 2014年6月26日 下午9:20:51 + */ +@SuppressWarnings({ "rawtypes", "unchecked", "hiding" }) +public class ExcelImportServer extends ImportBaseService { + + private final static Logger LOGGER = LoggerFactory.getLogger(ExcelImportServer.class); + + private CellValueServer cellValueServer; + + private VerifyHandlerServer verifyHandlerServer; + + private boolean verfiyFail = false; + //仅允许字母数字字符的正则表达式 + private static final Pattern lettersAndNumbersPattern = Pattern.compile("^[a-zA-Z0-9]+$") ; + /** + * 异常数据styler + */ + private CellStyle errorCellStyle; + + public ExcelImportServer() { + this.cellValueServer = new CellValueServer(); + this.verifyHandlerServer = new VerifyHandlerServer(); + } + + /*** + * 向List里面继续添加元素 + * + * @param object + * @param param + * @param row + * @param titlemap + * @param targetId + * @param pictures + * @param params + */ + private void addListContinue(Object object, ExcelCollectionParams param, Row row, Map titlemap, String targetId, Map pictures, ImportParams params) throws Exception { + Collection collection = (Collection) PoiPublicUtil.getMethod(param.getName(), object.getClass()).invoke(object, new Object[] {}); + Object entity = PoiPublicUtil.createObject(param.getType(), targetId); + String picId; + boolean isUsed = false;// 是否需要加上这个对象 + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) { + Cell cell = row.getCell(i); + String titleString = (String) titlemap.get(i); + if (param.getExcelParams().containsKey(titleString)) { + if (param.getExcelParams().get(titleString).getType() == 2) { + picId = row.getRowNum() + "_" + i; + saveImage(object, picId, param.getExcelParams(), titleString, pictures, params); + } else { + saveFieldValue(params, entity, cell, param.getExcelParams(), titleString, row); + } + isUsed = true; + } + } + if (isUsed) { + collection.add(entity); + } + } + + /** + * 获取key的值,针对不同类型获取不同的值 + * + * @Author JEECG + * @date 2013-11-21 + * @param cell + * @return + */ + private String getKeyValue(Cell cell) { + if(cell==null){ + return null; + } + Object obj = null; + switch (cell.getCellType()) { + case STRING: + obj = cell.getStringCellValue(); + break; + case BOOLEAN: + obj = cell.getBooleanCellValue(); + break; + case NUMERIC: + obj = cell.getNumericCellValue(); + break; + case FORMULA: + obj = cell.getCellFormula(); + break; + } + return obj == null ? null : obj.toString().trim(); + } + + /** + * 获取保存的真实路径 + * + * @param excelImportEntity + * @param object + * @return + * @throws Exception + */ + private String getSaveUrl(ExcelImportEntity excelImportEntity, Object object) throws Exception { + String url = ""; + if (excelImportEntity.getSaveUrl().equals("upload")) { + if (excelImportEntity.getMethods() != null && excelImportEntity.getMethods().size() > 0) { + object = getFieldBySomeMethod(excelImportEntity.getMethods(), object); + } + url = object.getClass().getName().split("\\.")[object.getClass().getName().split("\\.").length - 1]; + return excelImportEntity.getSaveUrl() + "/" + url.substring(0, url.lastIndexOf("Entity")); + } + return excelImportEntity.getSaveUrl(); + } + //update-begin--Author:xuelin Date:20171205 for:TASK #2098 【excel问题】 Online 一对多导入失败-------------------- + private List importExcel(Collection result, Sheet sheet, Class pojoClass, ImportParams params, Map pictures) throws Exception { + List collection = new ArrayList(); + Map excelParams = new HashMap(); + List excelCollection = new ArrayList(); + String targetId = null; + if (!Map.class.equals(pojoClass)) { + Field fileds[] = PoiPublicUtil.getClassFields(pojoClass); + ExcelTarget etarget = pojoClass.getAnnotation(ExcelTarget.class); + if (etarget != null) { + targetId = etarget.value(); + } + getAllExcelField(targetId, fileds, excelParams, excelCollection, pojoClass, null); + } + ignoreHeaderHandler(excelParams, params); + Iterator rows = sheet.rowIterator(); + Map titlemap = getTitleMap(sheet, rows, params, excelCollection); + //update-begin-author:liusq date:20220310 for:[issues/I4PU45]@excel里面新增属性fixedIndex + Set keys = excelParams.keySet(); + for (String key : keys) { + if (key.startsWith("FIXED_")) { + String[] arr = key.split("_"); + titlemap.put(Integer.parseInt(arr[1]), key); + } + } + //update-end-author:liusq date:20220310 for:[issues/I4PU45]@excel里面新增属性fixedIndex + Set columnIndexSet = titlemap.keySet(); + Integer maxColumnIndex = Collections.max(columnIndexSet); + Integer minColumnIndex = Collections.min(columnIndexSet); + Row row = null; + //跳过表头和标题行 + for (int j = 0; j < params.getTitleRows() + params.getHeadRows(); j++) { + row = rows.next(); + } + Object object = null; + String picId; + while (rows.hasNext() && (row == null || sheet.getLastRowNum() - row.getRowNum() > params.getLastOfInvalidRow())) { + row = rows.next(); + //update-begin--Author:xuelin Date:20171017 for:TASK #2373 【bug】表改造问题,导致 3.7.1批量导入用户bug-导入不成功-------------------- + // 判断是集合元素还是不是集合元素,如果是就继续加入这个集合,不是就创建新的对象 + //update-begin--Author:xuelin Date:20171206 for:TASK #2451 【excel导出bug】online 一对多导入成功, 但是现在代码生成后的一对多online导入有问题了 + Cell keyIndexCell = row.getCell(params.getKeyIndex()); + if (excelCollection.size()>0 && StringUtils.isEmpty(getKeyValue(keyIndexCell)) && object != null && !Map.class.equals(pojoClass)) { + //update-end--Author:xuelin Date:20171206 for:TASK #2451 【excel导出bug】online 一对多导入成功, 但是现在代码生成后的一对多online导入有问题了 + for (ExcelCollectionParams param : excelCollection) { + addListContinue(object, param, row, titlemap, targetId, pictures, params); + } + + } else { + object = PoiPublicUtil.createObject(pojoClass, targetId); + try { + //update-begin-author:taoyan date:20200303 for:导入图片 + int firstCellNum = row.getFirstCellNum(); + if(firstCellNum>minColumnIndex){ + firstCellNum = minColumnIndex; + } + int lastCellNum = row.getLastCellNum(); + if(lastCellNum excelParams,ImportParams params){ + List ignoreList = new ArrayList<>(); + for(String key:excelParams.keySet()){ + String temp = excelParams.get(key).getGroupName(); + if(temp!=null && temp.length()>0){ + ignoreList.add(temp); + } + } + params.setIgnoreHeaderList(ignoreList); + } + + /** + * 获取表格字段列名对应信息 + * + * @param rows + * @param params + * @param excelCollection + * @return + */ + private Map getTitleMap(Sheet sheet, Iterator rows, ImportParams params, List excelCollection) throws Exception { + Map titlemap = new HashMap(); + Iterator cellTitle = null; + String collectionName = null; + ExcelCollectionParams collectionParams = null; + Row headRow = null; + int headBegin = params.getTitleRows(); + //update_begin-author:taoyan date:2020622 for:当文件行数小于代码里设置的TitleRows时headRow一直为空就会出现死循环 + int allRowNum = sheet.getPhysicalNumberOfRows(); + //找到首行表头,每个sheet都必须至少有一行表头 + while(headRow == null && headBegin < allRowNum){ + headRow = sheet.getRow(headBegin++); + } + if(headRow==null){ + throw new Exception("不识别该文件"); + } + //update-end-author:taoyan date:2020622 for:当文件行数小于代码里设置的TitleRows时headRow一直为空就会出现死循环 + + //设置表头行数 + if (ExcelUtil.isMergedRegion(sheet, headRow.getRowNum(), 0)) { + params.setHeadRows(2); + }else{ + params.setHeadRows(1); + } + cellTitle = headRow.cellIterator(); + while (cellTitle.hasNext()) { + Cell cell = cellTitle.next(); + String value = getKeyValue(cell); + if (StringUtils.isNotEmpty(value)) { + titlemap.put(cell.getColumnIndex(), value);//加入表头列表 + } + } + + //多行表头 + for (int j = headBegin; j < headBegin + params.getHeadRows()-1; j++) { + headRow = sheet.getRow(j); + cellTitle = headRow.cellIterator(); + while (cellTitle.hasNext()) { + Cell cell = cellTitle.next(); + String value = getKeyValue(cell); + if (StringUtils.isNotEmpty(value)) { + int columnIndex = cell.getColumnIndex(); + //当前cell的上一行是否为合并单元格 + if(ExcelUtil.isMergedRegion(sheet, cell.getRowIndex()-1, columnIndex)){ + collectionName = ExcelUtil.getMergedRegionValue(sheet, cell.getRowIndex()-1, columnIndex); + if(params.isIgnoreHeader(collectionName)){ + titlemap.put(cell.getColumnIndex(), value); + }else{ + titlemap.put(cell.getColumnIndex(), collectionName + "_" + value); + } + }else{ + //update-begin-author:taoyan date:20220112 for: JT640 【online】导入 无论一对一还是一对多 如果子表只有一个字段 则子表无数据 + // 上一行不是合并的情况下另有一种特殊的场景: 如果当前单元格和上面的单元格同一列 即子表字段只有一个 所以标题没有出现跨列 + String prefixTitle = titlemap.get(cell.getColumnIndex()); + if(prefixTitle!=null && !"".equals(prefixTitle)){ + titlemap.put(cell.getColumnIndex(), prefixTitle + "_" +value); + }else{ + titlemap.put(cell.getColumnIndex(), value); + } + //update-end-author:taoyan date:20220112 for: JT640 【online】导入 无论一对一还是一对多 如果子表只有一个字段 则子表无数据 + } + /*int i = cell.getColumnIndex(); + // 用以支持重名导入 + if (titlemap.containsKey(i)) { + collectionName = titlemap.get(i); + collectionParams = getCollectionParams(excelCollection, collectionName); + titlemap.put(i, collectionName + "_" + value); + } else if (StringUtils.isNotEmpty(collectionName) && collectionParams.getExcelParams().containsKey(collectionName + "_" + value)) { + titlemap.put(i, collectionName + "_" + value); + } else { + collectionName = null; + collectionParams = null; + } + if (StringUtils.isEmpty(collectionName)) { + titlemap.put(i, value); + }*/ + } + } + } + return titlemap; + } + //update-end--Author:xuelin Date:20171205 for:TASK #2098 【excel问题】 Online 一对多导入失败-------------------- + /** + * 获取这个名称对应的集合信息 + * + * @param excelCollection + * @param collectionName + * @return + */ + private ExcelCollectionParams getCollectionParams(List excelCollection, String collectionName) { + for (ExcelCollectionParams excelCollectionParams : excelCollection) { + if (collectionName.equals(excelCollectionParams.getExcelName())) { + return excelCollectionParams; + } + } + return null; + } + + /** + * Excel 导入 field 字段类型 Integer,Long,Double,Date,String,Boolean + * + * @param inputstream + * @param pojoClass + * @param params + * @return + * @throws Exception + */ + public ExcelImportResult importExcelByIs(InputStream inputstream, Class pojoClass, ImportParams params) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Excel import start ,class is {}", pojoClass); + } + List result = new ArrayList(); + Workbook book = null; + boolean isXSSFWorkbook = false; + if (!(inputstream.markSupported())) { + inputstream = new PushbackInputStream(inputstream, 8); + } + //begin-------author:liusq------date:20210129-----for:-------poi3升级到4兼容改造工作【重要敏感修改点】-------- + //------poi4.x begin---- +// FileMagic fm = FileMagic.valueOf(FileMagic.prepareToCheckMagic(inputstream)); +// if(FileMagic.OLE2 == fm){ +// isXSSFWorkbook=false; +// } + book = WorkbookFactory.create(inputstream); + if(book instanceof XSSFWorkbook){ + isXSSFWorkbook=true; + } + LOGGER.info(" >>> poi3升级到4.0兼容改造工作, isXSSFWorkbook = " +isXSSFWorkbook); + //end-------author:liusq------date:20210129-----for:-------poi3升级到4兼容改造工作【重要敏感修改点】-------- + + //begin-------author:liusq------date:20210313-----for:-------多sheet导入改造点-------- + //获取导入文本的sheet数 + //update-begin-author:taoyan date:20211210 for:https://gitee.com/jeecg/jeecg-boot/issues/I45C32 导入空白sheet报错 + if(params.getSheetNum()==0){ + int sheetNum = book.getNumberOfSheets(); + if(sheetNum>0){ + params.setSheetNum(sheetNum); + } + } + //update-end-author:taoyan date:20211210 for:https://gitee.com/jeecg/jeecg-boot/issues/I45C32 导入空白sheet报错 + //end-------author:liusq------date:20210313-----for:-------多sheet导入改造点-------- + createErrorCellStyle(book); + Map pictures; + // 获取指定的sheet名称 + String sheetName = params.getSheetName(); + + //update-begin-author:liusq date:20220609 for:issues/I57UPC excel导入 ImportParams 中没有startSheetIndex参数 + for (int i = params.getStartSheetIndex(); i < params.getStartSheetIndex() + + params.getSheetNum(); i++) { + //update-end-author:liusq date:20220609 for:issues/I57UPC excel导入 ImportParams 中没有startSheetIndex参数 + + //update-begin-author:taoyan date:2023-3-4 for: 导入数据支持指定sheet名称 + if(sheetName!=null && !"".equals(sheetName)){ + Sheet tempSheet = book.getSheetAt(i); + if(!sheetName.equals(tempSheet.getSheetName())){ + continue; + } + } + //update-end-author:taoyan date:2023-3-4 for: 导入数据支持指定sheet名称 + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" start to read excel by is ,startTime is {}", System.currentTimeMillis()); + } + if (isXSSFWorkbook) { + pictures = PoiPublicUtil.getSheetPictrues07((XSSFSheet) book.getSheetAt(i), (XSSFWorkbook) book); + } else { + pictures = PoiPublicUtil.getSheetPictrues03((HSSFSheet) book.getSheetAt(i), (HSSFWorkbook) book); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" end to read excel by is ,endTime is {}", new Date().getTime()); + } + result.addAll(importExcel(result, book.getSheetAt(i), pojoClass, params, pictures)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" end to read excel list by pos ,endTime is {}", new Date().getTime()); + } + } + if (params.isNeedSave()) { + saveThisExcel(params, pojoClass, isXSSFWorkbook, book); + } + return new ExcelImportResult(result, verfiyFail, book); + } + /** + * + * @param is + * @return + * @throws IOException + */ + public static byte[] getBytes(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int len; + byte[] data = new byte[100000]; + while ((len = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, len); + } + + buffer.flush(); + return buffer.toByteArray(); + } + + /** + * 保存字段值(获取值,校验值,追加错误信息) + * + * @param params + * @param object + * @param cell + * @param excelParams + * @param titleString + * @param row + * @throws Exception + */ + private void saveFieldValue(ImportParams params, Object object, Cell cell, Map excelParams, String titleString, Row row) throws Exception { + Object value = cellValueServer.getValue(params.getDataHanlder(), object, cell, excelParams, titleString); + if (object instanceof Map) { + if (params.getDataHanlder() != null) { + params.getDataHanlder().setMapValue((Map) object, titleString, value); + } else { + ((Map) object).put(titleString, value); + } + } else { + ExcelVerifyHanlderResult verifyResult = verifyHandlerServer.verifyData(object, value, titleString, excelParams.get(titleString).getVerify(), params.getVerifyHanlder()); + if (verifyResult.isSuccess()) { + setValues(excelParams.get(titleString), object, value); + } else { + Cell errorCell = row.createCell(row.getLastCellNum()); + errorCell.setCellValue(verifyResult.getMsg()); + errorCell.setCellStyle(errorCellStyle); + verfiyFail = true; + throw new ExcelImportException(ExcelImportEnum.VERIFY_ERROR); + } + } + } + + /** + * + * @param object + * @param picId + * @param excelParams + * @param titleString + * @param pictures + * @param params + * @throws Exception + */ + private void saveImage(Object object, String picId, Map excelParams, String titleString, Map pictures, ImportParams params) throws Exception { + if (pictures == null || pictures.get(picId)==null) { + return; + } + PictureData image = pictures.get(picId); + byte[] data = image.getData(); + String fileName = "pic" + Math.round(Math.random() * 100000000000L); + fileName += "." + PoiPublicUtil.getFileExtendName(data); + //update-beign-author:taoyan date:20200302 for:【多任务】online 专项集中问题 LOWCOD-159 + int saveType = excelParams.get(titleString).getSaveType(); + if ( saveType == 1) { + String path = PoiPublicUtil.getWebRootPath(getSaveUrl(excelParams.get(titleString), object)); + File savefile = new File(path); + if (!savefile.exists()) { + savefile.mkdirs(); + } + savefile = new File(path + "/" + fileName); + FileOutputStream fos = new FileOutputStream(savefile); + fos.write(data); + fos.close(); + setValues(excelParams.get(titleString), object, getSaveUrl(excelParams.get(titleString), object) + "/" + fileName); + } else if(saveType==2) { + setValues(excelParams.get(titleString), object, data); + } else { + ImportFileServiceI importFileService = null; + try { + importFileService = ApplicationContextUtil.getContext().getBean(ImportFileServiceI.class); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + if(importFileService!=null){ + //update-beign-author:liusq date:20230411 for:【issue/4415】autopoi-web 导入图片字段时无法指定保存路径 + String saveUrl = excelParams.get(titleString).getSaveUrl(); + String dbPath; + if(StringUtils.isNotBlank(saveUrl)){ + LOGGER.debug("图片保存路径saveUrl = "+saveUrl); + Matcher matcher = lettersAndNumbersPattern.matcher(saveUrl); + if(!matcher.matches()){ + LOGGER.warn("图片保存路径格式错误,只能设置字母和数字的组合!"); + dbPath = importFileService.doUpload(data); + }else{ + dbPath = importFileService.doUpload(data,saveUrl); + } + }else{ + dbPath = importFileService.doUpload(data); + } + //update-end-author:liusq date:20230411 for:【issue/4415】autopoi-web 导入图片字段时无法指定保存路径 + setValues(excelParams.get(titleString), object, dbPath); + } + } + //update-end-author:taoyan date:20200302 for:【多任务】online 专项集中问题 LOWCOD-159 + } + + private void createErrorCellStyle(Workbook workbook) { + errorCellStyle = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setColor(Font.COLOR_RED); + errorCellStyle.setFont(font); + } + +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SaxReadExcel.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SaxReadExcel.java new file mode 100644 index 0000000..3f16c69 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SaxReadExcel.java @@ -0,0 +1,91 @@ +/** + * Copyright 2013-2015 JEECG (jeecgos@163.com) + * + * 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 org.jeecgframework.poi.excel.imports.sax; + +import java.io.InputStream; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.xssf.eventusermodel.XSSFReader; +import org.apache.poi.xssf.model.SharedStringsTable; +import org.jeecgframework.poi.excel.entity.ImportParams; +import org.jeecgframework.poi.excel.imports.sax.parse.ISaxRowRead; +import org.jeecgframework.poi.excel.imports.sax.parse.SaxRowRead; +import org.jeecgframework.poi.exception.excel.ExcelImportException; +import org.jeecgframework.poi.handler.inter.IExcelReadRowHanlder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * 基于SAX Excel大数据读取,读取Excel 07版本,不支持图片读取 + * + * @author JEECG + * @date 2014年12月29日 下午9:41:38 + * @version 1.0 + */ +@SuppressWarnings("rawtypes") +public class SaxReadExcel { + + private static final Logger LOGGER = LoggerFactory.getLogger(SaxReadExcel.class); + + public List readExcel(InputStream inputstream, Class pojoClass, ImportParams params, ISaxRowRead rowRead, IExcelReadRowHanlder hanlder) { + try { + OPCPackage opcPackage = OPCPackage.open(inputstream); + return readExcel(opcPackage, pojoClass, params, rowRead, hanlder); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + throw new ExcelImportException(e.getMessage()); + } + } + + private List readExcel(OPCPackage opcPackage, Class pojoClass, ImportParams params, ISaxRowRead rowRead, IExcelReadRowHanlder hanlder) { + try { + XSSFReader xssfReader = new XSSFReader(opcPackage); + SharedStringsTable sst = (SharedStringsTable) xssfReader.getSharedStringsTable(); + if (rowRead == null) { + rowRead = new SaxRowRead(pojoClass, params, hanlder); + } + XMLReader parser = fetchSheetParser(sst, rowRead); + Iterator sheets = xssfReader.getSheetsData(); + int sheetIndex = 0; + while (sheets.hasNext() && sheetIndex < params.getSheetNum()) { + sheetIndex++; + InputStream sheet = sheets.next(); + InputSource sheetSource = new InputSource(sheet); + parser.parse(sheetSource); + sheet.close(); + } + return rowRead.getList(); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + throw new ExcelImportException("SAX导入数据失败"); + } + } + + private XMLReader fetchSheetParser(SharedStringsTable sst, ISaxRowRead rowRead) throws SAXException { + XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); + ContentHandler handler = new SheetHandler(sst, rowRead); + parser.setContentHandler(handler); + return parser; + } + +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SheetHandler.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SheetHandler.java new file mode 100644 index 0000000..893a99e --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/excel/imports/sax/SheetHandler.java @@ -0,0 +1,136 @@ +/** + * Copyright 2013-2015 JEECG (jeecgos@163.com) + * + * 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 org.jeecgframework.poi.excel.imports.sax; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.usermodel.XSSFRichTextString; +import org.jeecgframework.poi.excel.entity.enmus.CellValueType; +import org.jeecgframework.poi.excel.entity.sax.SaxReadCellEntity; +import org.jeecgframework.poi.excel.imports.sax.parse.ISaxRowRead; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import com.google.common.collect.Lists; + +/** + * 回调接口 + * + * @author JEECG + * @date 2014年12月29日 下午9:50:09 + */ +public class SheetHandler extends DefaultHandler { + + private SharedStringsTable sst; + private String lastContents; + + // 当前行 + private int curRow = 0; + // 当前列 + private int curCol = 0; + + private CellValueType type; + + private ISaxRowRead read; + + // 存储行记录的容器 + private List rowlist = Lists.newArrayList(); + + public SheetHandler(SharedStringsTable sst, ISaxRowRead rowRead) { + this.sst = sst; + this.read = rowRead; + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { + // 置空 + lastContents = ""; + // c => 单元格 + if ("c".equals(name)) { + // 如果下一个元素是 SST 的索引,则将nextIsString标记为true + String cellType = attributes.getValue("t"); + if ("s".equals(cellType)) { + type = CellValueType.String; + return; + } + // 日期格式 + cellType = attributes.getValue("s"); + if ("1".equals(cellType)) { + type = CellValueType.Date; + } else if ("2".equals(cellType)) { + type = CellValueType.Number; + } + } else if ("t".equals(name)) {// 当元素为t时 + type = CellValueType.TElement; + } + + } + + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + + // 根据SST的索引值的到单元格的真正要存储的字符串 + // 这时characters()方法可能会被调用多次 + if (CellValueType.String.equals(type)) { + try { + int idx = Integer.parseInt(lastContents); + lastContents = sst.getItemAt(idx).getString(); + } catch (Exception e) { + + } + } + // t元素也包含字符串 + if (CellValueType.TElement.equals(type)) { + String value = lastContents.trim(); + rowlist.add(curCol, new SaxReadCellEntity(CellValueType.String, value)); + curCol++; + type = CellValueType.None; + // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引 + // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符 + } else if ("v".equals(name)) { + String value = lastContents.trim(); + value = value.equals("") ? " " : value; + if (CellValueType.Date.equals(type)) { + Date date = DateUtil.getJavaDate(Double.valueOf(value)); + rowlist.add(curCol, new SaxReadCellEntity(CellValueType.Date, date)); + } else if (CellValueType.Number.equals(type)) { + BigDecimal bd = new BigDecimal(value); + rowlist.add(curCol, new SaxReadCellEntity(CellValueType.Number, bd)); + } else if (CellValueType.String.equals(type)) { + rowlist.add(curCol, new SaxReadCellEntity(CellValueType.String, value)); + } + curCol++; + } else if (name.equals("row")) {// 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法 + read.parse(curRow, rowlist); + rowlist.clear(); + curRow++; + curCol = 0; + } + + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + // 得到单元格内容的值 + lastContents += new String(ch, start, length); + } + +} diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/ExcelUtil.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/ExcelUtil.java new file mode 100644 index 0000000..49826f2 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/ExcelUtil.java @@ -0,0 +1,327 @@ +package org.jeecgframework.poi.util; +/** + * @author Link Xue + * @version 20171025 + * POI对EXCEL操作工具 + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.util.CellRangeAddress; + +public class ExcelUtil { + + public static void main(String[] args){ + //读取excel数据 + ArrayList> result = ExcelUtil.readExcelToObj("D:\\上传表.xlsx"); + for(Map map:result){ + System.out.println(map); + } + + } + /** + * 读取excel数据 + * @param path + */ + public static ArrayList> readExcelToObj(String path) { + + Workbook wb = null; + ArrayList> result = null; + try { + wb = WorkbookFactory.create(new File(path)); + result = readExcel(wb, 0, 2, 0); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 读取excel文件 + * @param wb + * @param sheetIndex sheet页下标:从0开始 + * @param startReadLine 开始读取的行:从0开始 + * @param tailLine 去除最后读取的行 + */ + public static ArrayList> readExcel(Workbook wb,int sheetIndex, int startReadLine, int tailLine) { + Sheet sheet = wb.getSheetAt(sheetIndex); + Row row = null; + ArrayList> result = new ArrayList>(); + for(int i=startReadLine; i map = new HashMap(); + for(Cell c : row) { + String returnStr = ""; + + boolean isMerge = isMergedRegion(sheet, i, c.getColumnIndex()); + //判断是否具有合并单元格 + if(isMerge) { + String rs = getMergedRegionValue(sheet, row.getRowNum(), c.getColumnIndex()); +// System.out.print(rs + "------ "); + returnStr = rs; + }else { +// System.out.print(c.getRichStringCellValue()+"++++ "); + returnStr = c.getRichStringCellValue().getString(); + } + if(c.getColumnIndex()==0){ + map.put("id",returnStr); + }else if(c.getColumnIndex()==1){ + map.put("base",returnStr); + }else if(c.getColumnIndex()==2){ + map.put("siteName",returnStr); + }else if(c.getColumnIndex()==3){ + map.put("articleName",returnStr); + }else if(c.getColumnIndex()==4){ + map.put("mediaName",returnStr); + }else if(c.getColumnIndex()==5){ + map.put("mediaUrl",returnStr); + }else if(c.getColumnIndex()==6){ + map.put("newsSource",returnStr); + }else if(c.getColumnIndex()==7){ + map.put("isRecord",returnStr); + }else if(c.getColumnIndex()==8){ + map.put("recordTime",returnStr); + }else if(c.getColumnIndex()==9){ + map.put("remark",returnStr); + } + + } + result.add(map); +// System.out.println(); + + } + return result; + + } + + /** + * 获取合并单元格的值 + * @param sheet + * @param row + * @param column + * @return + */ + public static String getMergedRegionValue(Sheet sheet ,int row , int column){ + int sheetMergeCount = sheet.getNumMergedRegions(); + + for(int i = 0 ; i < sheetMergeCount ; i++){ + CellRangeAddress ca = sheet.getMergedRegion(i); + int firstColumn = ca.getFirstColumn(); + int lastColumn = ca.getLastColumn(); + int firstRow = ca.getFirstRow(); + int lastRow = ca.getLastRow(); + + if(row >= firstRow && row <= lastRow){ + + if(column >= firstColumn && column <= lastColumn){ + Row fRow = sheet.getRow(firstRow); + Cell fCell = fRow.getCell(firstColumn); + return getCellValue(fCell) ; + } + } + } + + return null ; + } + + /** + * 判断合并了行 + * @param sheet + * @param row + * @param column + * @return + */ + public static boolean isMergedRow(Sheet sheet,int row ,int column) { + int sheetMergeCount = sheet.getNumMergedRegions(); + for (int i = 0; i < sheetMergeCount; i++) { + CellRangeAddress range = sheet.getMergedRegion(i); + int firstColumn = range.getFirstColumn(); + int lastColumn = range.getLastColumn(); + int firstRow = range.getFirstRow(); + int lastRow = range.getLastRow(); + if(row == firstRow && row == lastRow){ + if(column >= firstColumn && column <= lastColumn){ + return true; + } + } + } + return false; + } + + /** + * 判断指定的单元格是否是合并单元格 + * @param sheet + * @param row 行下标 + * @param column 列下标 + * @return + */ + public static boolean isMergedRegion(Sheet sheet,int row ,int column) { + int sheetMergeCount = sheet.getNumMergedRegions(); + for (int i = 0; i < sheetMergeCount; i++) { + CellRangeAddress range = sheet.getMergedRegion(i); + int firstColumn = range.getFirstColumn(); + int lastColumn = range.getLastColumn(); + int firstRow = range.getFirstRow(); + int lastRow = range.getLastRow(); + if(row >= firstRow && row <= lastRow){ + if(column >= firstColumn && column <= lastColumn){ + return true; + } + } + } + return false; + } + + /** + * 判断sheet页中是否含有合并单元格 + * @param sheet + * @return + */ + public static boolean hasMerged(Sheet sheet) { + return sheet.getNumMergedRegions() > 0 ? true : false; + } + + /** + * 合并单元格 + * @param sheet + * @param firstRow 开始行 + * @param lastRow 结束行 + * @param firstCol 开始列 + * @param lastCol 结束列 + */ + public static void mergeRegion(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) { + //update-begin-author:wangshuai date:20201118 for:一对多导出needMerge 子表数据对应数量小于2时报错 github#1840、gitee I1YH6B + try{ + sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol)); + }catch (IllegalArgumentException e){ + e.fillInStackTrace(); + } + //update-end-author:wangshuai date:20201118 for:一对多导出needMerge 子表数据对应数量小于2时报错 github#1840、gitee I1YH6B + } + + /** + * 获取单元格的值 + * @param cell + * @return + */ + public static String getCellValue(Cell cell){ + + if(cell == null) { + return ""; + } + + if(cell.getCellType() == CellType.STRING){ + + return cell.getStringCellValue(); + + }else if(cell.getCellType() == CellType.BOOLEAN){ + + return String.valueOf(cell.getBooleanCellValue()); + + }else if(cell.getCellType() == CellType.FORMULA){ + + return cell.getCellFormula() ; + + }else if(cell.getCellType() == CellType.NUMERIC){ + + return String.valueOf(cell.getNumericCellValue()); + + } + return ""; + } + + /** + * 数字值,去掉.0后缀 + * @param cell + * @return + */ + public static String remove0Suffix(Object value){ + if(value!=null) { + // update-begin-author:taoyan date:20210526 for:对于特殊的字符串 V1.0 也进行了去.0操作 这是不合理的 + String val = value.toString(); + if(val.endsWith(".0") && isNumberString(val)) { + val = val.replace(".0", ""); + } + // update-end-author:taoyan date:20210526 for:对于特殊的字符串 V1.0 也进行了去.0操作 这是不合理的 + return val; + } + return null; + } + + /** + * 判断给定的字符串是不是只有数字 + * @param str + * @return + */ + private static boolean isNumberString(String str){ + String regex = "^[0-9]+\\.0+$"; + Pattern pattern = Pattern.compile(regex); + Matcher m = pattern.matcher(str); + if (m.find()) { + return true; + } + return false; + } + + /** + * 从excel读取内容 + */ + public static void readContent(String fileName) { + boolean isE2007 = false; //判断是否是excel2007格式 + if(fileName.endsWith("xlsx")) + isE2007 = true; + try { + InputStream input = new FileInputStream(fileName); //建立输入流 + Workbook wb = null; + //根据文件格式(2003或者2007)来初始化 + if(isE2007) + wb = new XSSFWorkbook(input); + else + wb = new HSSFWorkbook(input); + Sheet sheet = wb.getSheetAt(0); //获得第一个表单 + Iterator rows = sheet.rowIterator(); //获得第一个表单的迭代器 + while (rows.hasNext()) { + Row row = rows.next(); //获得行数据 + System.out.println("Row #" + row.getRowNum()); //获得行号从0开始 + Iterator cells = row.cellIterator(); //获得第一行的迭代器 + while (cells.hasNext()) { + Cell cell = cells.next(); + System.out.println("Cell #" + cell.getColumnIndex()); + switch (cell.getCellType()) { //根据cell中的类型来输出数据 + case NUMERIC: + System.out.println(cell.getNumericCellValue()); + break; + case STRING: + System.out.println(cell.getStringCellValue()); + break; + case BOOLEAN: + System.out.println(cell.getBooleanCellValue()); + break; + case FORMULA: + System.out.println(cell.getCellFormula()); + break; + default: + System.out.println("unsuported sell type======="+cell.getCellType()); + break; + } + } + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java new file mode 100644 index 0000000..732a066 --- /dev/null +++ b/autopoi/autopoi/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java @@ -0,0 +1,104 @@ +/** + * Copyright 2013-2015 JEECG (jeecgos@163.com) + * + * 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 org.jeecgframework.poi.util; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * 国外高手的,不过也不好,慎用,效率不行 Helper functions to aid in the management of sheets + */ +public class PoiSheetUtility extends Object { + + /** + * Given a sheet, this method deletes a column from a sheet and moves all + * the columns to the right of it to the left one cell. + * + * Note, this method will not update any formula references. + * + * @param sheet + * @param column + */ + public static void deleteColumn(Sheet sheet, int columnToDelete) { + int maxColumn = 0; + for (int r = 0; r < sheet.getLastRowNum() + 1; r++) { + Row row = sheet.getRow(r); + + // if no row exists here; then nothing to do; next! + if (row == null) + continue; + + // if the row doesn't have this many columns then we are good; next! + int lastColumn = row.getLastCellNum(); + if (lastColumn > maxColumn) + maxColumn = lastColumn; + + if (lastColumn < columnToDelete) + continue; + + for (int x = columnToDelete + 1; x < lastColumn + 1; x++) { + Cell oldCell = row.getCell(x - 1); + if (oldCell != null) + row.removeCell(oldCell); + + Cell nextCell = row.getCell(x); + if (nextCell != null) { + Cell newCell = row.createCell(x - 1, nextCell.getCellType()); + cloneCell(newCell, nextCell); + } + } + } + + // Adjust the column widths + for (int c = 0; c < maxColumn; c++) { + sheet.setColumnWidth(c, sheet.getColumnWidth(c + 1)); + } + } + + /* + * Takes an existing Cell and merges all the styles and forumla into the new + * one + */ + private static void cloneCell(Cell cNew, Cell cOld) { + cNew.setCellComment(cOld.getCellComment()); + cNew.setCellStyle(cOld.getCellStyle()); + + switch (cNew.getCellType()) { + case BOOLEAN: { + cNew.setCellValue(cOld.getBooleanCellValue()); + break; + } + case NUMERIC: { + cNew.setCellValue(cOld.getNumericCellValue()); + break; + } + case STRING: { + cNew.setCellValue(cOld.getStringCellValue()); + break; + } + case ERROR: { + cNew.setCellValue(cOld.getErrorCellValue()); + break; + } + case FORMULA: { + cNew.setCellFormula(cOld.getCellFormula()); + break; + } + } + + } +} \ No newline at end of file diff --git a/autopoi/pom.xml b/autopoi/pom.xml new file mode 100644 index 0000000..5da2d0a --- /dev/null +++ b/autopoi/pom.xml @@ -0,0 +1,281 @@ + + 4.0.0 + org.jeecgframework.boot3 + autopoi-parent + + 3.7.0 + pom + + autopoi-parent + http://www.jeecg.com + + + autopoi + autopoi-web + + + office 工具类 基于 poi + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + scm:git:https://github.com/zhangdaiscott/autopoi.git + scm:git:https://github.com/zhangdaiscott/autopoi.git + https://github.com/zhangdaiscott/autopoi + + + + jeecg + jeecgos@163.com + + + + + 3.7.0 + 5.2.2 + 2.9.1 + 29.0-jre + 3.10 + 1.7.30 + 6.0.13 + + + + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + + + + + org.apache.poi + poi-ooxml-full + ${poi.version} + + + + xerces + xercesImpl + ${xerces.version} + true + + + org.apache.poi + poi-scratchpad + ${poi.version} + + + + + + + com.google.guava + guava + ${guava.version} + + + + org.apache.commons + commons-lang3 + ${commons-lang.version} + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + provided + + + + + org.springframework + spring-webmvc + ${spring.version} + true + + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + true + + + + + org.jeecgframework.boot3 + autopoi + ${autopoi.version} + + + + + + + jeecg + + + + maven-compiler-plugin + + 17 + 17 + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + + + package + + jar-no-fork + + + + + + + + + jeecg + jeecg Repository + http://maven.jeecg.com:8090/nexus/content/repositories/jeecg + + + jeecg-snapshots + jeecg Snapshot Repository + http://maven.jeecg.com:8090/nexus/content/repositories/snapshots/ + + + + + release + + + + maven-compiler-plugin + + 17 + 17 + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + + + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + package + + jar + + + UTF-8 + -Xdoclint:none + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + oss + https://oss.sonatype.org/ + true + + + + + + + oss + https://oss.sonatype.org/content/repositories/snapshots/ + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + UTF-8 + + + + + + \ No newline at end of file