From 0e2fa07a855c08f99f96a35a8ab4f255f154c445 Mon Sep 17 00:00:00 2001 From: jueyue Date: Sat, 25 Apr 2015 23:52:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=A0=E4=B8=AA=E5=B8=B8=E7=94=A8=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E7=9A=84=E6=94=AF=E6=8C=81,=E5=88=97?= =?UTF-8?q?=E7=9A=84=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../excel/entity/TemplateExportParams.java | 12 ++ .../template/ExcelExportOfTemplateUtil.java | 183 +++++++++++------- .../jeecgframework/poi/util/PoiElUtil.java | 19 +- .../poi/util/PoiPublicUtil.java | 11 +- .../poi/util/PoiSheetUtility.java | 90 +++++++++ .../poi/test/excel/doc/foreach.xlsx | Bin 9144 -> 9633 bytes .../excel/template/TemplateForEachTest.java | 4 + 7 files changed, 243 insertions(+), 76 deletions(-) create mode 100644 easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java diff --git a/easypoi-base/src/main/java/org/jeecgframework/poi/excel/entity/TemplateExportParams.java b/easypoi-base/src/main/java/org/jeecgframework/poi/excel/entity/TemplateExportParams.java index e9cc3cf..bc37195 100644 --- a/easypoi-base/src/main/java/org/jeecgframework/poi/excel/entity/TemplateExportParams.java +++ b/easypoi-base/src/main/java/org/jeecgframework/poi/excel/entity/TemplateExportParams.java @@ -47,6 +47,10 @@ public class TemplateExportParams extends ExcelBaseParams { * Excel 导出style */ private Class style = ExcelExportStylerDefaultImpl.class; + /** + * FOR EACH 用到的局部变量 + */ + private String tempParams = "t"; /** * 默认构造器 @@ -168,4 +172,12 @@ public class TemplateExportParams extends ExcelBaseParams { this.scanAllsheet = scanAllsheet; } + public String getTempParams() { + return tempParams; + } + + public void setTempParams(String tempParams) { + this.tempParams = tempParams; + } + } diff --git a/easypoi-base/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java b/easypoi-base/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java index 3b381d2..0f2953d 100644 --- a/easypoi-base/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java +++ b/easypoi-base/src/main/java/org/jeecgframework/poi/excel/export/template/ExcelExportOfTemplateUtil.java @@ -27,7 +27,11 @@ import org.jeecgframework.poi.excel.export.base.ExcelExportBase; import org.jeecgframework.poi.excel.export.styler.IExcelExportStyler; 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.PoiPublicUtil; +import org.jeecgframework.poi.util.PoiSheetUtility; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,34 +44,34 @@ import org.slf4j.LoggerFactory; */ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { - private static final Logger LOGGER = LoggerFactory - .getLogger(ExcelExportOfTemplateUtil.class); + private static final Logger LOGGER = LoggerFactory + .getLogger(ExcelExportOfTemplateUtil.class); - private static final String START_STR = "{{"; - private static final String END_STR = "}}"; - private static final String NUMBER_SYMBOL = "N:"; /** * 缓存TEMP 的for each创建的cell ,跳过这个cell的模板语法查找,提高效率 */ - private Set tempCreateCellSet = new HashSet(); + private Set tempCreateCellSet = new HashSet(); + /** + * 模板参数,全局都用到 + */ + private TemplateExportParams teplateParams; /** * 往Sheet 填充正常数据,根据表头信息 使用导入的部分逻辑,坐对象映射 * - * @param params + * @param teplateParams * @param pojoClass * @param dataSet * @param workbook */ - private void addDataToSheet(TemplateExportParams params, Class pojoClass, - Collection dataSet, Sheet sheet, Workbook workbook) - throws Exception { + private void addDataToSheet(Class pojoClass, Collection dataSet, Sheet sheet, + Workbook workbook) throws Exception { if (workbook instanceof XSSFWorkbook) { super.type = ExcelType.XSSF; } // 获取表头数据 - Map titlemap = getTitleMap(params, sheet); + Map titlemap = getTitleMap(sheet); Drawing patriarch = sheet.createDrawingPatriarch(); // 得到所有字段 Field[] fileds = PoiPublicUtil.getClassFields(pojoClass); @@ -77,17 +81,17 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { targetId = etarget.value(); } // 创建表格样式 - setExcelExportStyler((IExcelExportStyler) params.getStyle().getConstructor(Workbook.class) - .newInstance(workbook)); + setExcelExportStyler((IExcelExportStyler) teplateParams.getStyle() + .getConstructor(Workbook.class).newInstance(workbook)); // 获取实体对象的导出数据 List excelParams = new ArrayList(); getAllExcelField(null, targetId, fileds, excelParams, pojoClass, null); // 根据表头进行筛选排序 sortAndFilterExportField(excelParams, titlemap); short rowHeight = getRowHeight(excelParams); - int index = params.getHeadingRows() + params.getHeadingStartRow(), titleHeight = index; + int index = teplateParams.getHeadingRows() + teplateParams.getHeadingStartRow(), titleHeight = index; //下移数据,模拟插入 - sheet.shiftRows(params.getHeadingRows() + params.getHeadingStartRow(), + sheet.shiftRows(teplateParams.getHeadingRows() + teplateParams.getHeadingStartRow(), sheet.getLastRowNum(), getShiftRows(dataSet, excelParams), true, true); if (excelParams.size() == 0) { return; @@ -150,7 +154,8 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { Workbook wb = null; // step 2. 判断模板的Excel类型,解析模板 try { - wb = getCloneWorkBook(params); + this.teplateParams = params; + wb = getCloneWorkBook(); // step 3. 解析模板 for (int i = 0, le = params.isScanAllsheet() ? wb.getNumberOfSheets() : params .getSheetNum().length; i < le; i++) { @@ -166,8 +171,7 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { if (dataHanlder != null) { needHanlderList = Arrays.asList(dataHanlder.getNeedHandlerFields()); } - addDataToSheet(params, pojoClass, dataSet, wb.getSheetAt(params.getDataSheetNum()), - wb); + addDataToSheet(pojoClass, dataSet, wb.getSheetAt(params.getDataSheetNum()), wb); } } catch (Exception e) { LOGGER.error(e.getMessage(), e); @@ -179,46 +183,30 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { /** * 克隆excel防止操作原对象,workbook无法克隆,只能对excel进行克隆 * - * @param params + * @param teplateParams * @throws Exception * @Author JueYue * @date 2013-11-11 */ - private Workbook getCloneWorkBook(TemplateExportParams params) throws Exception { - return ExcelCache.getWorkbook(params.getTemplateUrl(), params.getSheetNum(), - params.isScanAllsheet()); - - } + private Workbook getCloneWorkBook() throws Exception { + return ExcelCache.getWorkbook(teplateParams.getTemplateUrl(), teplateParams.getSheetNum(), + teplateParams.isScanAllsheet()); - /** - * 获取参数值 - * - * @param params - * @param map - * @return - */ - private String getParamsValue(String params, Map map) throws Exception { - if (params.indexOf(".") != -1) { - String[] paramsArr = params.split("\\."); - return String - .valueOf(PoiPublicUtil.getValueDoWhile(map.get(paramsArr[0]), paramsArr, 1)); - } - return map.containsKey(params) ? map.get(params).toString() : ""; } /** * 获取表头数据,设置表头的序号 * - * @param params + * @param teplateParams * @param sheet * @return */ - private Map getTitleMap(TemplateExportParams params, Sheet sheet) { + private Map getTitleMap(Sheet sheet) { Row row = null; Iterator cellTitle; Map titlemap = new HashMap(); - for (int j = 0; j < params.getHeadingRows(); j++) { - row = sheet.getRow(j + params.getHeadingStartRow()); + for (int j = 0; j < teplateParams.getHeadingRows(); j++) { + row = sheet.getRow(j + teplateParams.getHeadingStartRow()); cellTitle = row.cellIterator(); int i = row.getFirstCellNum(); while (cellTitle.hasNext()) { @@ -235,10 +223,14 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { } private void parseTemplate(Sheet sheet, Map map) throws Exception { + deleteCell(sheet, map); 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() + "_" @@ -249,6 +241,40 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { } } + /** + * 先判断删除,省得影响效率 + * @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() == Cell.CELL_TYPE_STRING || cell.getCellType() == Cell.CELL_TYPE_NUMERIC)) { + cell.setCellType(Cell.CELL_TYPE_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值 * @@ -263,12 +289,11 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { String oldString; cell.setCellType(Cell.CELL_TYPE_STRING); oldString = cell.getStringCellValue(); - if (oldString != null && oldString.indexOf(START_STR) != -1 - && !oldString.contains("foreach||")) { + if (oldString != null && oldString.indexOf(START_STR) != -1 && !oldString.contains(FOREACH)) { // step 2. 判断是否含有解析函数 - String params; + String params = null; boolean isNumber = false; - if (oldString.contains(NUMBER_SYMBOL)) { + if (isNumber(oldString)) { isNumber = true; oldString = oldString.replace(NUMBER_SYMBOL, ""); } @@ -276,8 +301,8 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { params = oldString.substring(oldString.indexOf(START_STR) + 2, oldString.indexOf(END_STR)); - oldString = oldString.replace(START_STR + params + END_STR, - getParamsValue(params.trim(), map)); + oldString = oldString.replace(START_STR + params + END_STR, eval(params, map) + .toString()); } //如何是数值 类型,就按照数值类型进行设置 if (isNumber) { @@ -288,11 +313,15 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { } } //判断foreach 这种方法 - if (oldString != null - && (oldString.trim().startsWith("foreach||") || oldString.trim().startsWith( - "!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); } /** @@ -304,10 +333,12 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { */ private void addListDataToExcel(Cell cell, Map map, String name) throws Exception { - boolean isCreate = !name.startsWith("!"); - Collection datas = (Collection) map.get(name.substring(name.indexOf("||") + 2, - name.indexOf(START_STR))); - List columns = getAllDataColumns(cell, name); + boolean isCreate = !name.contains(FOREACH_NOT_CREATE); + name = name.replace(FOREACH_NOT_CREATE, EMPTY).replace(FOREACH, EMPTY) + .replace(START_STR, EMPTY); + String[] keys = name.replaceAll("\\s{1,}", " ").trim().split(" "); + Collection datas = (Collection) PoiPublicUtil.getParamsValue(keys[0], map); + List columns = getAllDataColumns(cell, name.replace(keys[0], EMPTY)); if (datas == null) { return; } @@ -317,7 +348,7 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { //处理当前行 if (its.hasNext()) { Object t = its.next(); - setForEeachCellValue(isCreate, cell.getRow(), cell.getColumnIndex(), t, columns); + setForEeachCellValue(isCreate, cell.getRow(), cell.getColumnIndex(), t, columns, map); } while (its.hasNext()) { Object t = its.next(); @@ -329,20 +360,32 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { row = cell.getRow().getSheet().createRow(rowIndex - 1); } } - setForEeachCellValue(isCreate, row, cell.getColumnIndex(), t, columns); + setForEeachCellValue(isCreate, row, cell.getColumnIndex(), t, columns, map); } } private void setForEeachCellValue(boolean isCreate, Row row, int columnIndex, Object t, - List columns) throws Exception { + 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++) { - String val = String.valueOf(PoiPublicUtil.getValueDoWhile(t, columns.get(i) - .split("\\."), 0)); - row.getCell(i + columnIndex).setCellValue(val); + boolean isNumber = false; + String tempStr = new String(columns.get(i)); + if (isNumber(tempStr)) { + isNumber = true; + tempStr = tempStr.replace(NUMBER_SYMBOL, ""); + } + map.put(teplateParams.getTempParams(), t); + String val = eval(tempStr, map).toString(); + if (isNumber) { + row.getCell(i + columnIndex).setCellValue(Double.parseDouble(val)); + row.getCell(i + columnIndex).setCellType(Cell.CELL_TYPE_NUMERIC); + } else { + row.getCell(i + columnIndex).setCellValue(val); + } tempCreateCellSet.add(row.getRowNum() + "_" + (i + columnIndex)); } @@ -357,20 +400,23 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { private List getAllDataColumns(Cell cell, String name) { List columns = new ArrayList(); if (name.contains(END_STR)) { - columns.add(name.substring(name.indexOf(START_STR) + 2, name.indexOf(END_STR)).trim()); + columns.add(name.replace(END_STR, EMPTY)); cell.setCellValue(""); return columns; } - columns.add(name.substring(name.indexOf(START_STR) + 2).trim()); + columns.add(name.trim()); int index = cell.getColumnIndex(); Cell tempCell; while (true) { tempCell = cell.getRow().getCell(++index); + if (tempCell == null) { + break; + } String cellStringString; - try {//不允许为空 + try {//允许为空,单表示已经完结了,因为可能被删除了 cellStringString = tempCell.getStringCellValue(); if (StringUtils.isBlank(cellStringString)) { - throw new ExcelExportException(); + break; } } catch (Exception e) { throw new ExcelExportException("for each 当中存在空字符串,请检查模板"); @@ -381,7 +427,12 @@ public final class ExcelExportOfTemplateUtil extends ExcelExportBase { columns.add(cellStringString.trim().replace(END_STR, "")); break; } else { - columns.add(cellStringString.trim()); + if (cellStringString.trim().contains(teplateParams.getTempParams())) { + columns.add(cellStringString.trim()); + } else { + //最后一行被删除了 + break; + } } } diff --git a/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiElUtil.java b/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiElUtil.java index 90543d9..a4873ef 100644 --- a/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiElUtil.java +++ b/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiElUtil.java @@ -11,13 +11,18 @@ import org.jeecgframework.poi.exception.excel.ExcelExportException; */ public final class PoiElUtil { - private static final String LENGTH = "le:"; - private static final String FORMAT_DATE = "fd:"; - private static final String FORMAT_NUMBER = "fn:"; - private static final String IF_DELETE = "!if:"; - private static final String EMPTY = ""; - private static final String LEFT_BRACKET = "("; - private static final String RIGHT_BRACKET = ")"; + public static final String LENGTH = "le:"; + public static final String FOREACH = "fe:"; + public static final String FOREACH_NOT_CREATE = "!fe:"; + public static final String START_STR = "{{"; + public static final String END_STR = "}}"; + public static final String NUMBER_SYMBOL = "n:"; + public static final String FORMAT_DATE = "fd:"; + public static final String FORMAT_NUMBER = "fn:"; + public static final String IF_DELETE = "!if:"; + public static final String EMPTY = ""; + public static final String LEFT_BRACKET = "("; + public static final String RIGHT_BRACKET = ")"; private PoiElUtil() { } diff --git a/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiPublicUtil.java b/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiPublicUtil.java index 0a6f632..f4aaf7a 100644 --- a/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiPublicUtil.java +++ b/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiPublicUtil.java @@ -37,6 +37,7 @@ import org.jeecgframework.poi.excel.entity.vo.PoiBaseConstants; import org.jeecgframework.poi.word.entity.WordImageEntity; import org.jeecgframework.poi.word.entity.params.ExcelListEntity; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; + /** * EASYPOI 的公共基础类 * @author JueYue @@ -362,12 +363,16 @@ public final class PoiPublicUtil { * @param map * @return */ - public static Object getParamsValue(String params, Map map) throws Exception { + @SuppressWarnings("rawtypes") + public static Object getParamsValue(String params, Object object) throws Exception { if (params.indexOf(".") != -1) { String[] paramsArr = params.split("\\."); - return getValueDoWhile(map.get(paramsArr[0]), paramsArr, 1); + return getValueDoWhile(object, paramsArr, 0); + } + if (object instanceof Map) { + return ((Map) object).get(params); } - return map.containsKey(params) ? map.get(params) : ""; + return getMethod(params, object.getClass()).invoke(object, new Object[] {}); } /** diff --git a/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java b/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java new file mode 100644 index 0000000..6cec624 --- /dev/null +++ b/easypoi-base/src/main/java/org/jeecgframework/poi/util/PoiSheetUtility.java @@ -0,0 +1,90 @@ +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 Cell.CELL_TYPE_BOOLEAN: { + cNew.setCellValue(cOld.getBooleanCellValue()); + break; + } + case Cell.CELL_TYPE_NUMERIC: { + cNew.setCellValue(cOld.getNumericCellValue()); + break; + } + case Cell.CELL_TYPE_STRING: { + cNew.setCellValue(cOld.getStringCellValue()); + break; + } + case Cell.CELL_TYPE_ERROR: { + cNew.setCellValue(cOld.getErrorCellValue()); + break; + } + case Cell.CELL_TYPE_FORMULA: { + cNew.setCellFormula(cOld.getCellFormula()); + break; + } + } + + } +} \ No newline at end of file diff --git a/easypoi-test/src/main/java/org/jeecgframework/poi/test/excel/doc/foreach.xlsx b/easypoi-test/src/main/java/org/jeecgframework/poi/test/excel/doc/foreach.xlsx index 09a895e2b1055fe765d576971ebfdead87190e07..bc13fc69c639e2240ae0e5e9f6b78e7a28b94dd9 100644 GIT binary patch delta 3264 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 KUU!842L1z~-sz|S delta 2794 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 mapList = new ArrayList>(); for (int i = 0; i < 4; i++) { Map testMap = new HashMap();