From 01d54cedc3d5c9cc751dc7ba0786cc4cc878f768 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 22 Oct 2019 22:49:29 +0800 Subject: [PATCH 01/30] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=B5=81=E5=88=9B=E5=BB=BA=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metadata/holder/WriteWorkbookHolder.java | 2 +- .../easyexcel/test/temp/simple/Wirte.java | 16 ++++++++++++++++ update.md | 4 ++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index d802085..7614489 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java @@ -146,7 +146,7 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { byte[] templateFileByte = null; if (writeWorkbook.getTemplateFile() != null) { templateFileByte = FileUtils.readFileToByteArray(writeWorkbook.getTemplateFile()); - } else if (writeWorkbook.getTemplateInputStream() == null) { + } else if (writeWorkbook.getTemplateInputStream() != null) { try { templateFileByte = IoUtils.toByteArray(writeWorkbook.getTemplateInputStream()); } finally { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index 27bea33..49240d1 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -5,12 +5,15 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.easyexcel.test.core.large.LargeData; +import com.alibaba.easyexcel.test.core.simple.SimpleData; import com.alibaba.easyexcel.test.demo.write.DemoData; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; @@ -18,6 +21,8 @@ import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.fastjson.JSON; +import net.sf.cglib.beans.BeanMap; + /** * 测试poi * @@ -27,6 +32,17 @@ import com.alibaba.fastjson.JSON; public class Wirte { private static final Logger LOGGER = LoggerFactory.getLogger(Wirte.class); + @Test + public void simpleWrite1() { + LargeData ss = new LargeData(); + ss.setStr23("ttt"); + Map map = BeanMap.create(ss); + System.out.println(map.containsKey("str23")); + System.out.println(map.containsKey("str22")); + System.out.println(map.get("str23")); + System.out.println(map.get("str22")); + } + @Test public void simpleWrite() { // 写法1 diff --git a/update.md b/update.md index 2ae7d28..9bbd87a 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,7 @@ +# 2.1.0-beta2 +* 修改模板通过流创建报错的bug +* + # 2.1.0-beta1 * 新增支持导入、导出支持公式 * 新增支持读取单元格类型、写入指定单元格类型 From 4f4d8acc7f3fe07ad909fd7d604a51d20609b72f Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 22 Oct 2019 23:01:55 +0800 Subject: [PATCH 02/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A9=BA=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9C=AA=E6=9B=BF=E6=8D=A2=E6=8E=89=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../executor/ExcelWriteFillExecutor.java | 22 ++++++++++++++++--- update.md | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 217f21e..555f29b 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -279,7 +279,12 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (cell == null) { continue; } - prepareData(cell.getStringCellValue(), analysisCellList, collectionAnalysisCellList, i, j); + boolean needFill = + prepareData(cell.getStringCellValue(), analysisCellList, collectionAnalysisCellList, i, j); + // Prevent empty data from not being replaced + if (needFill) { + cell.setCellValue(StringUtils.EMPTY); + } } } templateAnalysisCache.put(sheetNo, analysisCellList); @@ -287,10 +292,19 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { return analysisCache.get(sheetNo); } - private void prepareData(String value, List analysisCellList, + /** + * + * @param value + * @param analysisCellList + * @param collectionAnalysisCellList + * @param rowIndex + * @param columnIndex + * @return Is a cell to be filled + */ + private boolean prepareData(String value, List analysisCellList, List collectionAnalysisCellList, int rowIndex, int columnIndex) { if (StringUtils.isEmpty(value)) { - return; + return false; } AnalysisCell analysisCell = null; int startIndex = 0; @@ -365,7 +379,9 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } else { collectionAnalysisCellList.add(analysisCell); } + return true; } + return false; } private String convertPrepareData(String prepareData) { diff --git a/update.md b/update.md index 9bbd87a..f1f5cf8 100644 --- a/update.md +++ b/update.md @@ -1,6 +1,6 @@ # 2.1.0-beta2 * 修改模板通过流创建报错的bug -* +* 修复空数据未替换掉的bug # 2.1.0-beta1 * 新增支持导入、导出支持公式 From 13d546a713e8560d3c0348e586ec20e239f90e34 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 22 Oct 2019 23:08:15 +0800 Subject: [PATCH 03/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A9=BA=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=BC=9A=E7=A9=BA=E4=B8=80=E8=A1=8C=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../metadata/holder/WriteSheetHolder.java | 5 +++ .../easyexcel/test/temp/poi/PoiTest.java | 40 +++++-------------- update.md | 1 + 4 files changed, 17 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index 68fe674..90838f3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.0-beta1 + 2.1.0-beta2 jar easyexcel diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java index 1fb19fb..90030f4 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java @@ -156,6 +156,11 @@ public class WriteSheetHolder extends AbstractWriteHolder { int newRowIndex = 0; switch (writeLastRowTypeEnum) { case TEMPLATE_EMPTY: + newRowIndex = Math.max(sheet.getLastRowNum(), cachedSheet.getLastRowNum()); + if (newRowIndex != 0 || cachedSheet.getRow(0) != null) { + newRowIndex++; + } + break; case HAS_DATA: newRowIndex = Math.max(sheet.getLastRowNum(), cachedSheet.getLastRowNum()); newRowIndex++; diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java index ee7be1e..367ee58 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java @@ -96,43 +96,23 @@ public class PoiTest { @Test public void cp() throws IOException, InvalidFormatException { String file = "d://test/tt.xlsx"; - XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); - XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); - XSSFRow row = xssfSheet.getRow(2); - xssfSheet.removeRow(row); -// Row r2= xssfSheet.createRow(2); -// r2.createCell(1); - SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook); - - - SXSSFSheet sxssfSheet = sxssfWorkbook.getSheetAt(0); - sxssfSheet.createRow(2); - - - FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); - sxssfWorkbook.write(fileout); - sxssfWorkbook.dispose(); - sxssfWorkbook.close(); - - xssfWorkbook.close(); + SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file)); + SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + SXSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); } @Test public void lastRowNum233443() throws IOException, InvalidFormatException { - String file = "d://test/tt.xlsx"; + String file = "d://test/em0.xlsx"; XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); - SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook); XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); - XSSFRow row = xssfSheet.getRow(2); - xssfSheet.removeRow(row); - new CellCopyPolicy().createBuilder().build(); + System.out.println(xssfSheet.getLastRowNum()); + System.out.println(xssfSheet.getRow(0)); - FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); - sxssfWorkbook.write(fileout); - sxssfWorkbook.dispose(); - sxssfWorkbook.close(); - - xssfWorkbook.close(); } @Test diff --git a/update.md b/update.md index f1f5cf8..d2c0cbb 100644 --- a/update.md +++ b/update.md @@ -1,6 +1,7 @@ # 2.1.0-beta2 * 修改模板通过流创建报错的bug * 修复空数据未替换掉的bug +* 修复空模板会空一行的bug # 2.1.0-beta1 * 新增支持导入、导出支持公式 From f3700661bc2ca08621e47c8c5322d9a90fe0a7e5 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 22 Oct 2019 23:12:55 +0800 Subject: [PATCH 04/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A9=BA=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=BC=9A=E7=A9=BA=E4=B8=80=E8=A1=8C=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 555f29b..ad38a05 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -293,6 +293,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } /** + * To prepare data * * @param value * @param analysisCellList From 60f7ab19298305b2bf288e2a160e823e391597d8 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 22 Oct 2019 23:16:42 +0800 Subject: [PATCH 05/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A9=BA=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E4=BC=9A=E7=A9=BA=E4=B8=80=E8=A1=8C=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../executor/ExcelWriteFillExecutor.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index ad38a05..388fc77 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -337,15 +337,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } } if (analysisCell == null) { - analysisCell = new AnalysisCell(); - analysisCell.setRowIndex(rowIndex); - analysisCell.setColumnIndex(columnIndex); - analysisCell.setOnlyOneVariable(Boolean.TRUE); - List variableList = new ArrayList(); - analysisCell.setVariableList(variableList); - List prepareDataList = new ArrayList(); - analysisCell.setPrepareDataList(prepareDataList); - analysisCell.setCellType(WriteTemplateAnalysisCellTypeEnum.COMMON); + analysisCell = initAnalysisCell(rowIndex, columnIndex); } String variable = value.substring(prefixIndex + 1, suffixIndex); if (StringUtils.isEmpty(variable)) { @@ -385,6 +377,19 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { return false; } + private AnalysisCell initAnalysisCell(Integer rowIndex, Integer columnIndex) { + AnalysisCell analysisCell = new AnalysisCell(); + analysisCell.setRowIndex(rowIndex); + analysisCell.setColumnIndex(columnIndex); + analysisCell.setOnlyOneVariable(Boolean.TRUE); + List variableList = new ArrayList(); + analysisCell.setVariableList(variableList); + List prepareDataList = new ArrayList(); + analysisCell.setPrepareDataList(prepareDataList); + analysisCell.setCellType(WriteTemplateAnalysisCellTypeEnum.COMMON); + return analysisCell; + } + private String convertPrepareData(String prepareData) { prepareData = prepareData.replaceAll(ESCAPE_FILL_PREFIX, FILL_PREFIX); prepareData = prepareData.replaceAll(ESCAPE_FILL_SUFFIX, FILL_SUFFIX); From 2c8918bed3975e1eeb4745a1665234fe1b282ee0 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 23 Oct 2019 18:43:07 +0800 Subject: [PATCH 06/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A1=AB=E5=85=85?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99=E6=9C=89=E6=95=B0=E5=AD=97=E4=BC=9A?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../excel/analysis/ExcelAnalyserImpl.java | 20 ++-- .../excel/analysis/v07/XlsxSaxAnalyser.java | 2 - .../excel/context/WriteContextImpl.java | 25 ++--- .../metadata/property/ExcelHeadProperty.java | 48 ++++++--- .../metadata/holder/AbstractReadHolder.java | 2 +- .../property/ExcelReadHeadProperty.java | 5 +- .../com/alibaba/excel/util/WorkBookUtil.java | 17 +++- .../write/builder/ExcelWriterBuilder.java | 43 ++++++++ .../builder/ExcelWriterSheetBuilder.java | 33 ++++++ .../builder/ExcelWriterTableBuilder.java | 34 +++++++ .../write/executor/ExcelWriteAddExecutor.java | 8 +- .../executor/ExcelWriteFillExecutor.java | 36 ++++--- .../write/metadata/WriteBasicParameter.java | 49 +++++++++ .../excel/write/metadata/WriteWorkbook.java | 16 ++- .../metadata/holder/AbstractWriteHolder.java | 94 +++++++++++++++++- .../write/metadata/holder/WriteHolder.java | 9 ++ .../metadata/holder/WriteWorkbookHolder.java | 19 ++++ .../property/ExcelWriteHeadProperty.java | 5 +- .../LongestMatchColumnWidthStyleStrategy.java | 2 +- .../easyexcel/test/core/fill/FillData.java | 1 + .../easyexcel/test/demo/write/WriteTest.java | 31 ++++++ .../easyexcel/test/temp/simple/Wirte.java | 4 +- src/test/resources/fill/simple.xls | Bin 19456 -> 19456 bytes src/test/resources/fill/simple.xlsx | Bin 10138 -> 10199 bytes update.md | 8 ++ 26 files changed, 450 insertions(+), 63 deletions(-) diff --git a/pom.xml b/pom.xml index 90838f3..514464b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.0-beta2 + 2.1.0-beta3 jar easyexcel diff --git a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java index 6e1f435..72a27e7 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -129,26 +129,28 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { } ReadWorkbookHolder readWorkbookHolder = analysisContext.readWorkbookHolder(); + Throwable throwable = null; + try { if (readWorkbookHolder.getReadCache() != null) { readWorkbookHolder.getReadCache().destroy(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } try { if (readWorkbookHolder.getOpcPackage() != null) { readWorkbookHolder.getOpcPackage().revert(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } try { if (readWorkbookHolder.getPoifsFileSystem() != null) { readWorkbookHolder.getPoifsFileSystem().close(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } try { if (analysisContext.readWorkbookHolder().getAutoCloseStream() @@ -156,17 +158,21 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { readWorkbookHolder.getInputStream().close(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } try { if (readWorkbookHolder.getTempFile() != null) { FileUtils.delete(readWorkbookHolder.getTempFile()); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } clearEncrypt03(); + + if (throwable != null) { + throw new ExcelAnalysisException("Can not close IO", throwable); + } } private void clearEncrypt03() { @@ -177,10 +183,6 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { Biff8EncryptionKey.setCurrentUserPassword(null); } - private void throwCanNotCloseIo(Throwable t) { - throw new ExcelAnalysisException("Can not close IO", t); - } - @Override public ExcelReadExecutor excelExecutor() { return excelReadExecutor; diff --git a/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java index dd468b4..a30b426 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java @@ -15,7 +15,6 @@ import javax.xml.parsers.SAXParserFactory; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.openxml4j.opc.PackagePart; -import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFRelation; @@ -35,7 +34,6 @@ import com.alibaba.excel.read.metadata.holder.ReadWorkbookHolder; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.FileUtils; import com.alibaba.excel.util.SheetUtils; -import com.alibaba.excel.util.StringUtils; /** * diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index ffacd52..f136ea0 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -179,6 +179,7 @@ public class WriteContextImpl implements WriteContext { Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i); WriteHandlerUtils.afterRowCreate(this, row, relativeRowIndex, Boolean.TRUE); addOneRowOfHeadDataToExcel(row, excelWriteHeadProperty.getHeadMap(), relativeRowIndex); + WriteHandlerUtils.afterRowDispose(this, row, relativeRowIndex, Boolean.FALSE); } } @@ -197,8 +198,7 @@ public class WriteContextImpl implements WriteContext { Cell cell = row.createCell(columnIndex); WriteHandlerUtils.afterCellCreate(this, cell, head, relativeRowIndex, Boolean.TRUE); cell.setCellValue(head.getHeadNameList().get(relativeRowIndex)); - CellData cellData = null; - WriteHandlerUtils.afterCellDispose(this, cellData, cell, head, relativeRowIndex, Boolean.TRUE); + WriteHandlerUtils.afterCellDispose(this, (CellData)null, cell, head, relativeRowIndex, Boolean.TRUE); } } @@ -261,12 +261,13 @@ public class WriteContextImpl implements WriteContext { if (writeWorkbookHolder == null) { return; } + Throwable throwable = null; boolean isOutputStreamEncrypt = false; try { isOutputStreamEncrypt = doOutputStreamEncrypt07(); } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } if (!isOutputStreamEncrypt) { @@ -274,7 +275,7 @@ public class WriteContextImpl implements WriteContext { writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); writeWorkbookHolder.getWorkbook().close(); } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } } @@ -284,7 +285,7 @@ public class WriteContextImpl implements WriteContext { ((SXSSFWorkbook)workbook).dispose(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } try { @@ -292,14 +293,14 @@ public class WriteContextImpl implements WriteContext { writeWorkbookHolder.getOutputStream().close(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } if (!isOutputStreamEncrypt) { try { doFileEncrypt07(); } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } } @@ -308,20 +309,20 @@ public class WriteContextImpl implements WriteContext { writeWorkbookHolder.getTempTemplateInputStream().close(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } clearEncrypt03(); + if (throwable != null) { + throw new ExcelGenerateException("Can not close IO", throwable); + } + if (LOGGER.isDebugEnabled()) { LOGGER.debug("Finished write."); } } - private void throwCanNotCloseIo(Throwable t) { - throw new ExcelGenerateException("Can not close IO", t); - } - @Override public Sheet getCurrentSheet() { return writeSheetHolder.getSheet(); diff --git a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java index 6921161..eed5699 100644 --- a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java +++ b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java @@ -17,14 +17,15 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.format.DateTimeFormat; import com.alibaba.excel.annotation.format.NumberFormat; -import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.converters.AutoConverter; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.enums.HeadKindEnum; import com.alibaba.excel.exception.ExcelCommonException; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.metadata.Holder; import com.alibaba.excel.util.StringUtils; +import com.alibaba.excel.write.metadata.holder.AbstractWriteHolder; /** * Define the header attribute of excel @@ -46,7 +47,6 @@ public class ExcelHeadProperty { * The number of rows in the line with the most rows */ private int headRowNumber; - /** * Configuration header information */ @@ -64,7 +64,7 @@ public class ExcelHeadProperty { */ private Map ignoreMap; - public ExcelHeadProperty(Class headClazz, List> head, Boolean convertAllFiled) { + public ExcelHeadProperty(Holder holder, Class headClazz, List> head, Boolean convertAllFiled) { this.headClazz = headClazz; headMap = new TreeMap(); contentPropertyMap = new TreeMap(); @@ -73,14 +73,21 @@ public class ExcelHeadProperty { headKind = HeadKindEnum.NONE; headRowNumber = 0; if (head != null && !head.isEmpty()) { + int headIndex = 0; for (int i = 0; i < head.size(); i++) { - headMap.put(i, new Head(i, null, head.get(i), Boolean.FALSE, Boolean.TRUE)); - contentPropertyMap.put(i, null); + if (holder instanceof AbstractWriteHolder) { + if (((AbstractWriteHolder)holder).ignore(null, i)) { + continue; + } + } + headMap.put(headIndex, new Head(headIndex, null, head.get(i), Boolean.FALSE, Boolean.TRUE)); + contentPropertyMap.put(headIndex, null); + headIndex++; } headKind = HeadKindEnum.STRING; } else { // convert headClazz to head - initColumnProperties(convertAllFiled); + initColumnProperties(holder, convertAllFiled); } initHeadRowNumber(); if (LOGGER.isDebugEnabled()) { @@ -108,7 +115,7 @@ public class ExcelHeadProperty { } } - private void initColumnProperties(Boolean convertAllFiled) { + private void initColumnProperties(Holder holder, Boolean convertAllFiled) { if (headClazz == null) { return; } @@ -161,20 +168,36 @@ public class ExcelHeadProperty { int index = 0; for (Field field : defaultFieldList) { while (customFiledMap.containsKey(index)) { - initOneColumnProperty(index, customFiledMap.get(index), Boolean.TRUE); customFiledMap.remove(index); + if (!initOneColumnProperty(holder, index, customFiledMap.get(index), Boolean.TRUE)) { + index++; + } + } + if (!initOneColumnProperty(holder, index, field, Boolean.FALSE)) { index++; } - initOneColumnProperty(index, field, Boolean.FALSE); - index++; } for (Map.Entry entry : customFiledMap.entrySet()) { - initOneColumnProperty(entry.getKey(), entry.getValue(), Boolean.TRUE); + initOneColumnProperty(holder, entry.getKey(), entry.getValue(), Boolean.TRUE); } headKind = HeadKindEnum.CLASS; } - private void initOneColumnProperty(int index, Field field, Boolean forceIndex) { + /** + * Initialization column property + * + * @param holder + * @param index + * @param field + * @param forceIndex + * @return Ignore current field + */ + private boolean initOneColumnProperty(Holder holder, int index, Field field, Boolean forceIndex) { + if (holder instanceof AbstractWriteHolder) { + if (((AbstractWriteHolder)holder).ignore(field.getName(), index)) { + return true; + } + } ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); List tmpHeadList = new ArrayList(); boolean notForceName = excelProperty == null || excelProperty.value().length <= 0 @@ -206,6 +229,7 @@ public class ExcelHeadProperty { headMap.put(index, head); contentPropertyMap.put(index, excelContentProperty); fieldNameContentPropertyMap.put(field.getName(), excelContentProperty); + return false; } public Class getHeadClazz() { diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 4ca8d63..a748f32 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -70,7 +70,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH } // Initialization property - this.excelReadHeadProperty = new ExcelReadHeadProperty(getClazz(), getHead(), convertAllFiled); + this.excelReadHeadProperty = new ExcelReadHeadProperty(this, getClazz(), getHead(), convertAllFiled); if (readBasicParameter.getHeadRowNumber() == null) { if (parentAbstractReadHolder == null) { if (excelReadHeadProperty.hasHead()) { diff --git a/src/main/java/com/alibaba/excel/read/metadata/property/ExcelReadHeadProperty.java b/src/main/java/com/alibaba/excel/read/metadata/property/ExcelReadHeadProperty.java index fdc5562..1b7ae17 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/property/ExcelReadHeadProperty.java +++ b/src/main/java/com/alibaba/excel/read/metadata/property/ExcelReadHeadProperty.java @@ -2,6 +2,7 @@ package com.alibaba.excel.read.metadata.property; import java.util.List; +import com.alibaba.excel.metadata.Holder; import com.alibaba.excel.metadata.property.ExcelHeadProperty; /** @@ -11,7 +12,7 @@ import com.alibaba.excel.metadata.property.ExcelHeadProperty; */ public class ExcelReadHeadProperty extends ExcelHeadProperty { - public ExcelReadHeadProperty(Class headClazz, List> head, Boolean convertAllFiled) { - super(headClazz, head, convertAllFiled); + public ExcelReadHeadProperty(Holder holder, Class headClazz, List> head, Boolean convertAllFiled) { + super(holder, headClazz, head, convertAllFiled); } } diff --git a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java index bcec523..186708c 100644 --- a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java +++ b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java @@ -31,12 +31,21 @@ public class WorkBookUtil { if (writeWorkbookHolder.getTempTemplateInputStream() != null) { XSSFWorkbook xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTempTemplateInputStream()); writeWorkbookHolder.setCachedWorkbook(xssfWorkbook); - writeWorkbookHolder.setWorkbook(new SXSSFWorkbook(xssfWorkbook, ROW_ACCESS_WINDOW_SIZE)); + if (writeWorkbookHolder.getInMemory()) { + writeWorkbookHolder.setWorkbook(xssfWorkbook); + } else { + writeWorkbookHolder.setWorkbook(new SXSSFWorkbook(xssfWorkbook, ROW_ACCESS_WINDOW_SIZE)); + } return; } - SXSSFWorkbook sxssWorkbook = new SXSSFWorkbook(ROW_ACCESS_WINDOW_SIZE); - writeWorkbookHolder.setCachedWorkbook(sxssWorkbook); - writeWorkbookHolder.setWorkbook(sxssWorkbook); + Workbook workbook = null; + if (writeWorkbookHolder.getInMemory()) { + workbook = new XSSFWorkbook(); + } else { + workbook = new SXSSFWorkbook(ROW_ACCESS_WINDOW_SIZE); + } + writeWorkbookHolder.setCachedWorkbook(workbook); + writeWorkbookHolder.setWorkbook(workbook); return; } HSSFWorkbook hssfWorkbook; diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java index 82de038..8af1e62 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import com.alibaba.excel.ExcelWriter; @@ -103,6 +104,48 @@ public class ExcelWriterBuilder { return this; } + /** + * Write excel in memory. Default false,the cache file is created and finally written to excel. + *

+ * Comment and RichTextString are only supported in memory mode. + */ + public ExcelWriterBuilder inMemory(Boolean inMemory) { + writeWorkbook.setInMemory(inMemory); + return this; + } + + /** + * Ignore the custom columns. + */ + public ExcelWriterBuilder excludeColumnIndexes(Collection excludeColumnIndexes) { + writeWorkbook.setExcludeColumnIndexes(excludeColumnIndexes); + return this; + } + + /** + * Ignore the custom columns. + */ + public ExcelWriterBuilder excludeColumnFiledNames(Collection excludeColumnFiledNames) { + writeWorkbook.setExcludeColumnFiledNames(excludeColumnFiledNames); + return this; + } + + /** + * Only output the custom columns. + */ + public ExcelWriterBuilder includeColumnIndexes(Collection includeColumnIndexes) { + writeWorkbook.setIncludeColumnIndexes(includeColumnIndexes); + return this; + } + + /** + * Only output the custom columns. + */ + public ExcelWriterBuilder includeColumnFiledNames(Collection includeColumnFiledNames) { + writeWorkbook.setIncludeColumnFiledNames(includeColumnFiledNames); + return this; + } + /** * The default is all excel objects.if true , you can use {@link com.alibaba.excel.annotation.ExcelIgnore} ignore a * field. if false , you must use {@link com.alibaba.excel.annotation.ExcelProperty} to use a filed. diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java index 26acff5..50f230a 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java @@ -1,6 +1,7 @@ package com.alibaba.excel.write.builder; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import com.alibaba.excel.ExcelWriter; @@ -135,6 +136,38 @@ public class ExcelWriterSheetBuilder { return this; } + /** + * Ignore the custom columns. + */ + public ExcelWriterSheetBuilder excludeColumnIndexes(Collection excludeColumnIndexes) { + writeSheet.setExcludeColumnIndexes(excludeColumnIndexes); + return this; + } + + /** + * Ignore the custom columns. + */ + public ExcelWriterSheetBuilder excludeColumnFiledNames(Collection excludeColumnFiledNames) { + writeSheet.setExcludeColumnFiledNames(excludeColumnFiledNames); + return this; + } + + /** + * Only output the custom columns. + */ + public ExcelWriterSheetBuilder includeColumnIndexes(Collection includeColumnIndexes) { + writeSheet.setIncludeColumnIndexes(includeColumnIndexes); + return this; + } + + /** + * Only output the custom columns. + */ + public ExcelWriterSheetBuilder includeColumnFiledNames(Collection includeColumnFiledNames) { + writeSheet.setIncludeColumnFiledNames(includeColumnFiledNames); + return this; + } + public WriteSheet build() { return writeSheet; } diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java index eca1fe0..40ca58b 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java @@ -1,6 +1,7 @@ package com.alibaba.excel.write.builder; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import com.alibaba.excel.ExcelWriter; @@ -128,6 +129,39 @@ public class ExcelWriterTableBuilder { return this; } + + /** + * Ignore the custom columns. + */ + public ExcelWriterTableBuilder excludeColumnIndexes(Collection excludeColumnIndexes) { + writeTable.setExcludeColumnIndexes(excludeColumnIndexes); + return this; + } + + /** + * Ignore the custom columns. + */ + public ExcelWriterTableBuilder excludeColumnFiledNames(Collection excludeColumnFiledNames) { + writeTable.setExcludeColumnFiledNames(excludeColumnFiledNames); + return this; + } + + /** + * Only output the custom columns. + */ + public ExcelWriterTableBuilder includeColumnIndexes(Collection includeColumnIndexes) { + writeTable.setIncludeColumnIndexes(includeColumnIndexes); + return this; + } + + /** + * Only output the custom columns. + */ + public ExcelWriterTableBuilder includeColumnFiledNames(Collection includeColumnFiledNames) { + writeSheet.setIncludeColumnFiledNames(includeColumnFiledNames); + return this; + } + public WriteTable build() { return writeTable; } diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java index 8843e2c..0fa0cae 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java @@ -98,6 +98,9 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { private void doAddBasicTypeToExcel(List oneRowData, Head head, Row row, int relativeRowIndex, int dataIndex, int cellIndex) { + if (writeContext.currentWriteHolder().ignore(null, cellIndex)) { + return; + } WriteHandlerUtils.beforeCellCreate(writeContext, row, head, cellIndex, relativeRowIndex, Boolean.FALSE); Cell cell = WorkBookUtil.createCell(row, cellIndex); WriteHandlerUtils.afterCellCreate(writeContext, cell, head, relativeRowIndex, Boolean.FALSE); @@ -121,6 +124,9 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { cellIndex = entry.getKey(); ExcelContentProperty excelContentProperty = entry.getValue(); String name = excelContentProperty.getField().getName(); + if (writeContext.currentWriteHolder().ignore(name, cellIndex)) { + continue; + } if (!beanMap.containsKey(name)) { continue; } @@ -147,7 +153,7 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { for (Field field : fieldList) { String filedName = field.getName(); boolean uselessData = !beanMap.containsKey(filedName) || beanMapHandledSet.contains(filedName) - || ignoreMap.containsKey(filedName); + || ignoreMap.containsKey(filedName) || writeContext.currentWriteHolder().ignore(filedName, cellIndex); if (uselessData) { continue; } diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 388fc77..75a8810 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -9,6 +9,7 @@ import java.util.Map; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; @@ -141,6 +142,9 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { Cell cell = getOneCell(analysisCell, fillConfig); if (analysisCell.getOnlyOneVariable()) { String variable = analysisCell.getVariableList().get(0); + if (writeContext.currentWriteHolder().ignore(variable, analysisCell.getColumnIndex())) { + continue; + } if (!dataMap.containsKey(variable)) { continue; } @@ -154,6 +158,9 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { List cellDataList = new ArrayList(); for (String variable : analysisCell.getVariableList()) { cellValueBuild.append(analysisCell.getPrepareDataList().get(index++)); + if (writeContext.currentWriteHolder().ignore(variable, analysisCell.getColumnIndex())) { + continue; + } if (!dataMap.containsKey(variable)) { continue; } @@ -279,11 +286,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (cell == null) { continue; } - boolean needFill = - prepareData(cell.getStringCellValue(), analysisCellList, collectionAnalysisCellList, i, j); + String preparedData = prepareData(cell, analysisCellList, collectionAnalysisCellList, i, j); // Prevent empty data from not being replaced - if (needFill) { - cell.setCellValue(StringUtils.EMPTY); + if (preparedData != null) { + cell.setCellValue(preparedData); } } } @@ -295,18 +301,23 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { /** * To prepare data * - * @param value + * @param cell * @param analysisCellList * @param collectionAnalysisCellList * @param rowIndex * @param columnIndex - * @return Is a cell to be filled + * @return Returns the data that the cell needs to replace */ - private boolean prepareData(String value, List analysisCellList, + private String prepareData(Cell cell, List analysisCellList, List collectionAnalysisCellList, int rowIndex, int columnIndex) { + if (!CellType.STRING.equals(cell.getCellTypeEnum())) { + return null; + } + String value = cell.getStringCellValue(); if (StringUtils.isEmpty(value)) { - return false; + return null; } + StringBuilder preparedData = new StringBuilder(); AnalysisCell analysisCell = null; int startIndex = 0; int length = value.length(); @@ -354,8 +365,9 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (lastPrepareDataIndex == prefixIndex) { analysisCell.getPrepareDataList().add(StringUtils.EMPTY); } else { - analysisCell.getPrepareDataList() - .add(convertPrepareData(value.substring(lastPrepareDataIndex, prefixIndex))); + String data = convertPrepareData(value.substring(lastPrepareDataIndex, prefixIndex)); + preparedData.append(data); + analysisCell.getPrepareDataList().add(data); analysisCell.setOnlyOneVariable(Boolean.FALSE); } lastPrepareDataIndex = suffixIndex + 1; @@ -372,9 +384,9 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } else { collectionAnalysisCellList.add(analysisCell); } - return true; + return preparedData.toString(); } - return false; + return null; } private AnalysisCell initAnalysisCell(Integer rowIndex, Integer columnIndex) { diff --git a/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java b/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java index 2689cd9..61115af 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java +++ b/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java @@ -1,6 +1,7 @@ package com.alibaba.excel.write.metadata; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import com.alibaba.excel.metadata.BasicParameter; @@ -28,6 +29,22 @@ public class WriteBasicParameter extends BasicParameter { * Use the default style.Default is true. */ private Boolean useDefaultStyle; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnIndexes; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnFiledNames; + /** + * Only output the custom columns. + */ + private Collection includeColumnIndexes; + /** + * Only output the custom columns. + */ + private Collection includeColumnFiledNames; public Integer getRelativeHeadRowIndex() { return relativeHeadRowIndex; @@ -60,4 +77,36 @@ public class WriteBasicParameter extends BasicParameter { public void setUseDefaultStyle(Boolean useDefaultStyle) { this.useDefaultStyle = useDefaultStyle; } + + public Collection getExcludeColumnIndexes() { + return excludeColumnIndexes; + } + + public void setExcludeColumnIndexes(Collection excludeColumnIndexes) { + this.excludeColumnIndexes = excludeColumnIndexes; + } + + public Collection getExcludeColumnFiledNames() { + return excludeColumnFiledNames; + } + + public void setExcludeColumnFiledNames(Collection excludeColumnFiledNames) { + this.excludeColumnFiledNames = excludeColumnFiledNames; + } + + public Collection getIncludeColumnIndexes() { + return includeColumnIndexes; + } + + public void setIncludeColumnIndexes(Collection includeColumnIndexes) { + this.includeColumnIndexes = includeColumnIndexes; + } + + public Collection getIncludeColumnFiledNames() { + return includeColumnFiledNames; + } + + public void setIncludeColumnFiledNames(Collection includeColumnFiledNames) { + this.includeColumnFiledNames = includeColumnFiledNames; + } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java index 7d9ebcd..738299a 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java +++ b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java @@ -43,7 +43,7 @@ public class WriteWorkbook extends WriteBasicParameter { */ private File templateFile; /** - * Default trueuseDefaultStyle + * Default true. */ private Boolean autoCloseStream; /** @@ -57,6 +57,12 @@ public class WriteWorkbook extends WriteBasicParameter { * */ private String password; + /** + * Write excel in memory. Default false,the cache file is created and finally written to excel. + *

+ * Comment and RichTextString are only supported in memory mode. + */ + private Boolean inMemory; /** * The default is all excel objects.Default is true. *

@@ -155,4 +161,12 @@ public class WriteWorkbook extends WriteBasicParameter { public void setPassword(String password) { this.password = password; } + + public Boolean getInMemory() { + return inMemory; + } + + public void setInMemory(Boolean inMemory) { + this.inMemory = inMemory; + } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java index 72f5f9b..b28f267 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java @@ -1,6 +1,7 @@ package com.alibaba.excel.write.metadata.holder; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -65,6 +66,22 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ * Use the default style.Default is true. */ private Boolean useDefaultStyle; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnIndexes; + /** + * Ignore the custom columns. + */ + private Collection excludeColumnFiledNames; + /** + * Only output the custom columns. + */ + private Collection includeColumnIndexes; + /** + * Only output the custom columns. + */ + private Collection includeColumnFiledNames; public AbstractWriteHolder(WriteBasicParameter writeBasicParameter, AbstractWriteHolder parentAbstractWriteHolder, Boolean convertAllFiled) { @@ -110,8 +127,29 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ this.useDefaultStyle = writeBasicParameter.getUseDefaultStyle(); } + if (writeBasicParameter.getExcludeColumnFiledNames() == null && parentAbstractWriteHolder != null) { + this.excludeColumnFiledNames = parentAbstractWriteHolder.getExcludeColumnFiledNames(); + } else { + this.excludeColumnFiledNames = writeBasicParameter.getExcludeColumnFiledNames(); + } + if (writeBasicParameter.getExcludeColumnIndexes() == null && parentAbstractWriteHolder != null) { + this.excludeColumnIndexes = parentAbstractWriteHolder.getExcludeColumnIndexes(); + } else { + this.excludeColumnIndexes = writeBasicParameter.getExcludeColumnIndexes(); + } + if (writeBasicParameter.getIncludeColumnFiledNames() == null && parentAbstractWriteHolder != null) { + this.includeColumnFiledNames = parentAbstractWriteHolder.getIncludeColumnFiledNames(); + } else { + this.includeColumnFiledNames = writeBasicParameter.getIncludeColumnFiledNames(); + } + if (writeBasicParameter.getIncludeColumnIndexes() == null && parentAbstractWriteHolder != null) { + this.includeColumnIndexes = parentAbstractWriteHolder.getIncludeColumnIndexes(); + } else { + this.includeColumnIndexes = writeBasicParameter.getIncludeColumnIndexes(); + } + // Initialization property - this.excelWriteHeadProperty = new ExcelWriteHeadProperty(getClazz(), getHead(), convertAllFiled); + this.excelWriteHeadProperty = new ExcelWriteHeadProperty(this, getClazz(), getHead(), convertAllFiled); // Compatible with old code compatibleOldCode(writeBasicParameter); @@ -148,7 +186,6 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ getConverterMap().put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); } } - } /** @@ -343,6 +380,27 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ return result; } + @Override + public boolean ignore(String fieldName, Integer columnIndex) { + if (fieldName != null) { + if (includeColumnFiledNames != null && !includeColumnFiledNames.contains(fieldName)) { + return true; + } + if (excludeColumnFiledNames != null && excludeColumnFiledNames.contains(fieldName)) { + return true; + } + } + if (columnIndex != null) { + if (includeColumnIndexes != null && !includeColumnIndexes.contains(columnIndex)) { + return true; + } + if (excludeColumnIndexes != null && excludeColumnIndexes.contains(columnIndex)) { + return true; + } + } + return false; + } + public Boolean getNeedHead() { return needHead; } @@ -383,6 +441,38 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ this.useDefaultStyle = useDefaultStyle; } + public Collection getExcludeColumnIndexes() { + return excludeColumnIndexes; + } + + public void setExcludeColumnIndexes(Collection excludeColumnIndexes) { + this.excludeColumnIndexes = excludeColumnIndexes; + } + + public Collection getExcludeColumnFiledNames() { + return excludeColumnFiledNames; + } + + public void setExcludeColumnFiledNames(Collection excludeColumnFiledNames) { + this.excludeColumnFiledNames = excludeColumnFiledNames; + } + + public Collection getIncludeColumnIndexes() { + return includeColumnIndexes; + } + + public void setIncludeColumnIndexes(Collection includeColumnIndexes) { + this.includeColumnIndexes = includeColumnIndexes; + } + + public Collection getIncludeColumnFiledNames() { + return includeColumnFiledNames; + } + + public void setIncludeColumnFiledNames(Collection includeColumnFiledNames) { + this.includeColumnFiledNames = includeColumnFiledNames; + } + @Override public ExcelWriteHeadProperty excelWriteHeadProperty() { return getExcelWriteHeadProperty(); diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java index 53c11db..8f2ae7e 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java @@ -28,6 +28,15 @@ public interface WriteHolder extends ConfigurationHolder { */ Map, List> writeHandlerMap(); + /** + * Is to determine if a field needs to be ignored + * + * @param fieldName + * @param columnIndex + * @return + */ + boolean ignore(String fieldName, Integer columnIndex); + /** * Whether a header is required for the currently operated cell * diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index 7614489..4de6ade 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java @@ -97,6 +97,12 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { * Whether the encryption */ private String password; + /** + * Write excel in memory. Default false,the cache file is created and finally written to excel. + *

+ * Comment and RichTextString are only supported in memory mode. + */ + private Boolean inMemory; public WriteWorkbookHolder(WriteWorkbook writeWorkbook) { super(writeWorkbook, null, writeWorkbook.getConvertAllFiled()); @@ -137,6 +143,11 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { } this.hasBeenInitializedSheet = new HashMap(); this.password = writeWorkbook.getPassword(); + if (writeWorkbook.getInMemory() == null) { + this.inMemory = Boolean.FALSE; + } else { + this.inMemory = writeWorkbook.getInMemory(); + } } private void copyTemplate() throws IOException { @@ -262,6 +273,14 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.password = password; } + public Boolean getInMemory() { + return inMemory; + } + + public void setInMemory(Boolean inMemory) { + this.inMemory = inMemory; + } + @Override public HolderEnum holderType() { return HolderEnum.WORKBOOK; diff --git a/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java b/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java index fbd33c4..b1ba891 100644 --- a/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java +++ b/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java @@ -17,6 +17,7 @@ import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.HeadKindEnum; import com.alibaba.excel.metadata.CellRange; import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.metadata.Holder; import com.alibaba.excel.metadata.property.ColumnWidthProperty; import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.metadata.property.ExcelHeadProperty; @@ -31,8 +32,8 @@ public class ExcelWriteHeadProperty extends ExcelHeadProperty { private RowHeightProperty headRowHeightProperty; private RowHeightProperty contentRowHeightProperty; - public ExcelWriteHeadProperty(Class headClazz, List> head, Boolean convertAllFiled) { - super(headClazz, head, convertAllFiled); + public ExcelWriteHeadProperty(Holder holder, Class headClazz, List> head, Boolean convertAllFiled) { + super(holder, headClazz, head, convertAllFiled); if (getHeadKind() != HeadKindEnum.CLASS) { return; } diff --git a/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java b/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java index 006927f..463fea0 100644 --- a/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java +++ b/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java @@ -21,7 +21,7 @@ import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; */ public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy { - private static final int MAX_COLUMN_WIDTH = 256; + private static final int MAX_COLUMN_WIDTH = 255; private static final Map> CACHE = new HashMap>(8); diff --git a/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java index 13dfa17..dcdf23f 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java @@ -15,4 +15,5 @@ public class FillData { @NumberFormat("#") @ExcelProperty(converter = DoubleStringConverter.class) private double number; + private String empty; } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index 2582f10..ff30ae2 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -4,7 +4,9 @@ import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.IndexedColors; @@ -62,6 +64,35 @@ public class WriteTest { excelWriter.finish(); } + /** + * 根据参数只导出指定列 + *

+ * 1. 创建excel对应的实体对象 参照{@link DemoData} + *

+ * 2. 根据自己或者排除自己需要的列 + *

+ * 3. 直接写即可 + */ + @Test + public void excludeOrIncludeWrite() { + String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; + + // 根据用户传入字段 假设我们要忽略 date + Set excludeColumnFiledNames = new HashSet(); + excludeColumnFiledNames.add("date"); + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板") + .doWrite(data()); + + fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; + // 根据用户传入字段 假设我们只要导出 date + Set includeColumnFiledNames = new HashSet(); + includeColumnFiledNames.add("date"); + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板") + .doWrite(data()); + } + /** * 指定写入的列 *

diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index 49240d1..698dc06 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -19,6 +19,7 @@ import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy; import com.alibaba.fastjson.JSON; import net.sf.cglib.beans.BeanMap; @@ -49,7 +50,8 @@ public class Wirte { String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 - EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); + EasyExcel.write(fileName, DemoData.class) + .registerWriteHandler(new SimpleRowHeightStyleStrategy((short)150, (short)120)).sheet("模板").doWrite(data()); } private List> head() { diff --git a/src/test/resources/fill/simple.xls b/src/test/resources/fill/simple.xls index ea66e944309bec381e00ac115b08e64aad53a331..317ef6debac1d619c71ab6d6bc9f67952906e411 100644 GIT binary patch delta 806 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 vVW delta 4474 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 diff --git a/update.md b/update.md index d2c0cbb..1eb3717 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,11 @@ +# 2.1.0-beta3 +* 支持强行指定在内存处理,以支持备注、RichTextString等的写入 +* 修复关闭流失败,可能会不删除临时文件的问题 +* 支持根据参数自定义导出列 +* 修改最长匹配策略的最大长度 [Issue #734](https://github.com/alibaba/easyexcel/issues/734) +* 修复策略头未生效的bug [Issue #735](https://github.com/alibaba/easyexcel/issues/735) +* 修复填充的时候有数字会异常 + # 2.1.0-beta2 * 修改模板通过流创建报错的bug * 修复空数据未替换掉的bug From 4c4599cce60a73b55ea16c6bb612ce16841f9583 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 23 Oct 2019 18:51:10 +0800 Subject: [PATCH 07/30] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E8=87=AA=E5=AE=9A=E4=B9=89=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alibaba/excel/metadata/property/ExcelHeadProperty.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java index eed5699..9d6a544 100644 --- a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java +++ b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java @@ -168,8 +168,9 @@ public class ExcelHeadProperty { int index = 0; for (Field field : defaultFieldList) { while (customFiledMap.containsKey(index)) { + Field customFiled = customFiledMap.get(index); customFiledMap.remove(index); - if (!initOneColumnProperty(holder, index, customFiledMap.get(index), Boolean.TRUE)) { + if (!initOneColumnProperty(holder, index, customFiled, Boolean.TRUE)) { index++; } } From da2182dec2aa86ed854f57997a4c4bbad463b050 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 23 Oct 2019 19:10:08 +0800 Subject: [PATCH 08/30] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=BB=E9=94=99?= =?UTF-8?q?=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../easyexcel/test/demo/write/WriteTest.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index ff30ae2..f9a69db 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -50,13 +50,13 @@ public class WriteTest { public void simpleWrite() { // 写法1 String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); // 写法2 fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读 + // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); excelWriter.write(data(), writeSheet); @@ -80,7 +80,7 @@ public class WriteTest { // 根据用户传入字段 假设我们要忽略 date Set excludeColumnFiledNames = new HashSet(); excludeColumnFiledNames.add("date"); - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板") .doWrite(data()); @@ -88,7 +88,7 @@ public class WriteTest { // 根据用户传入字段 假设我们只要导出 date Set includeColumnFiledNames = new HashSet(); includeColumnFiledNames.add("date"); - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板") .doWrite(data()); } @@ -105,7 +105,7 @@ public class WriteTest { @Test public void indexWrite() { String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data()); } @@ -121,7 +121,7 @@ public class WriteTest { @Test public void complexHeadWrite() { String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data()); } @@ -138,7 +138,7 @@ public class WriteTest { public void repeatedWrite() { // 方法1 如果写到同一个sheet String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读 + // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); // 这里注意 如果同一个sheet只要创建一次 WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); @@ -194,7 +194,7 @@ public class WriteTest { @Test public void converterWrite() { String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data()); } @@ -236,7 +236,7 @@ public class WriteTest { *

* 2. 使用{@link ExcelProperty}注解指定写入的列 *

- * 3. 使用withTemplate 读取模板 + * 3. 使用withTemplate 写取模板 *

* 4. 直接写即可 */ @@ -244,7 +244,7 @@ public class WriteTest { public void templateWrite() { String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data()); } @@ -260,7 +260,7 @@ public class WriteTest { @Test public void widthAndHeightWrite() { String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data()); } @@ -297,7 +297,7 @@ public class WriteTest { HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板") .doWrite(data()); } @@ -316,7 +316,7 @@ public class WriteTest { String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写 LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data()); } @@ -331,7 +331,7 @@ public class WriteTest { public void tableWrite() { String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例 - // 这里 需要指定写用哪个class去读 + // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build(); @@ -385,7 +385,7 @@ public class WriteTest { public void longestMatchColumnWidthWrite() { String fileName = TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, LongestMatchColumnWidthData.class) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong()); } @@ -404,7 +404,7 @@ public class WriteTest { @Test public void customHandlerWrite() { String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler()) .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data()); } @@ -416,7 +416,7 @@ public class WriteTest { public void noModleWrite() { // 写法1 String fileName = TestFileUtil.getPath() + "noModleWrite" + System.currentTimeMillis() + ".xlsx"; - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList()); } From b1e12aa4185a00a113626b9d0113cd2c055919ae Mon Sep 17 00:00:00 2001 From: zhouqinglai Date: Thu, 24 Oct 2019 13:07:34 +0800 Subject: [PATCH 09/30] =?UTF-8?q?[fix]=20=E8=A7=A3=E5=86=B3=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=AD=98=E5=9C=A8=E7=9A=84=E7=A9=BA=E6=8C=87=E9=92=88?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/alibaba/excel/context/WriteContextImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index f136ea0..6e691fd 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -429,7 +429,9 @@ public class WriteContextImpl implements WriteContext { OutputStream outputStream = encryptor.getDataStream(fileSystem); opcPackage.save(outputStream); } finally { - opcPackage.close(); + if (opcPackage != null) { + opcPackage.close(); + } } return fileSystem; } From 5b6957952bb1ac21cdc5131887a65ec501a4bcd9 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 24 Oct 2019 21:53:05 +0800 Subject: [PATCH 10/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E5=A4=B4=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=E4=BC=9A=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E6=95=B0=E6=8D=AE=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 +++--- .../excel/context/WriteContextImpl.java | 15 +++------- .../com/alibaba/excel/metadata/CellData.java | 6 ++++ .../metadata/holder/AbstractReadHolder.java | 5 ++-- .../executor/AbstractExcelWriteExecutor.java | 7 +++-- .../executor/ExcelWriteFillExecutor.java | 28 +++++++++++-------- .../LongestMatchColumnWidthStyleStrategy.java | 7 ++++- .../easyexcel/test/temp/simple/Wirte.java | 12 ++------ update.md | 5 ++++ 9 files changed, 50 insertions(+), 44 deletions(-) diff --git a/pom.xml b/pom.xml index 514464b..a618349 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.0-beta3 + 2.1.0-beta4 jar easyexcel @@ -132,11 +132,11 @@ - + org.apache.maven.plugins maven-pmd-plugin - 3.12.0 + 3.8 true true @@ -157,7 +157,6 @@ - pmd-check-verify validate @@ -170,7 +169,7 @@ com.alibaba.p3c p3c-pmd - 2.0.0 + 1.3.6 diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index f136ea0..ec49ea3 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -1,27 +1,17 @@ package com.alibaba.excel.context; -import com.alibaba.excel.support.ExcelTypeEnum; -import com.alibaba.excel.util.FileUtils; -import com.alibaba.excel.util.StringUtils; - import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.InputStream; import java.io.OutputStream; -import java.security.GeneralSecurityException; import java.util.Map; import java.util.UUID; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; -import org.apache.poi.poifs.crypt.Decryptor; import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.crypt.EncryptionMode; import org.apache.poi.poifs.crypt.Encryptor; -import org.apache.poi.poifs.filesystem.DocumentOutputStream; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; @@ -36,6 +26,9 @@ import com.alibaba.excel.enums.WriteTypeEnum; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.support.ExcelTypeEnum; +import com.alibaba.excel.util.FileUtils; +import com.alibaba.excel.util.StringUtils; import com.alibaba.excel.util.WorkBookUtil; import com.alibaba.excel.util.WriteHandlerUtils; import com.alibaba.excel.write.metadata.WriteSheet; @@ -179,7 +172,7 @@ public class WriteContextImpl implements WriteContext { Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i); WriteHandlerUtils.afterRowCreate(this, row, relativeRowIndex, Boolean.TRUE); addOneRowOfHeadDataToExcel(row, excelWriteHeadProperty.getHeadMap(), relativeRowIndex); - WriteHandlerUtils.afterRowDispose(this, row, relativeRowIndex, Boolean.FALSE); + WriteHandlerUtils.afterRowDispose(this, row, relativeRowIndex, Boolean.TRUE); } } diff --git a/src/main/java/com/alibaba/excel/metadata/CellData.java b/src/main/java/com/alibaba/excel/metadata/CellData.java index a3a40c6..a6b7af5 100644 --- a/src/main/java/com/alibaba/excel/metadata/CellData.java +++ b/src/main/java/com/alibaba/excel/metadata/CellData.java @@ -202,6 +202,9 @@ public class CellData { * Ensure that the object does not appear null */ public void checkEmpty() { + if (type == null) { + type = CellDataTypeEnum.EMPTY; + } switch (type) { case STRING: case ERROR: @@ -225,6 +228,9 @@ public class CellData { @Override public String toString() { + if (type == null) { + return "empty"; + } switch (type) { case NUMBER: return numberValue.toString(); diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index a748f32..1b9fbb9 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -158,7 +158,6 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH if (currentheadRowNumber == rowIndex + 1) { buildHead(analysisContext, cellDataMap); } - // Now is header for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { try { @@ -206,8 +205,8 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH tmpContentPropertyMap.put(entry.getKey(), contentPropertyMapData.get(entry.getKey())); continue; } - String headName = headData.getHeadNameList().get(0); - + List headNameList = headData.getHeadNameList(); + String headName = headNameList.get(headNameList.size() - 1); for (Map.Entry stringEntry : dataMap.entrySet()) { String headString = stringEntry.getValue(); Integer stringKey = stringEntry.getKey(); diff --git a/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java b/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java index 9adcb62..a174629 100644 --- a/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java @@ -31,7 +31,7 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { protected CellData converterAndSet(WriteHolder currentWriteHolder, Class clazz, Cell cell, Object value, ExcelContentProperty excelContentProperty) { if (value == null) { - return new CellData(); + return new CellData(CellDataTypeEnum.EMPTY); } if (value instanceof String && currentWriteHolder.globalConfiguration().getAutoTrim()) { value = ((String)value).trim(); @@ -40,6 +40,9 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { if (cellData.getFormula() != null && cellData.getFormula()) { cell.setCellFormula(cellData.getFormulaValue()); } + if (cellData.getType() == null) { + cellData.setType(CellDataTypeEnum.EMPTY); + } switch (cellData.getType()) { case STRING: cell.setCellValue(cellData.getStringValue()); @@ -64,7 +67,7 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { protected CellData convert(WriteHolder currentWriteHolder, Class clazz, Cell cell, Object value, ExcelContentProperty excelContentProperty) { if (value == null) { - return new CellData(); + return new CellData(CellDataTypeEnum.EMPTY); } // This means that the user has defined the data. if (value instanceof CellData) { diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 75a8810..d7cb630 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -14,6 +14,7 @@ import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import com.alibaba.excel.context.WriteContext; +import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.WriteDirectionEnum; import com.alibaba.excel.enums.WriteTemplateAnalysisCellTypeEnum; import com.alibaba.excel.exception.ExcelGenerateException; @@ -168,18 +169,21 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { CellData cellData = convert(writeSheetHolder, value == null ? null : value.getClass(), cell, value, fieldNameContentPropertyMap.get(variable)); cellDataList.add(cellData); - switch (cellData.getType()) { - case STRING: - cellValueBuild.append(cellData.getStringValue()); - break; - case BOOLEAN: - cellValueBuild.append(cellData.getBooleanValue()); - break; - case NUMBER: - cellValueBuild.append(cellData.getNumberValue()); - break; - default: - break; + CellDataTypeEnum type = cellData.getType(); + if (type != null) { + switch (type) { + case STRING: + cellValueBuild.append(cellData.getStringValue()); + break; + case BOOLEAN: + cellValueBuild.append(cellData.getBooleanValue()); + break; + case NUMBER: + cellValueBuild.append(cellData.getNumberValue()); + break; + default: + break; + } } } cellValueBuild.append(analysisCell.getPrepareDataList().get(index)); diff --git a/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java b/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java index 463fea0..5140ae6 100644 --- a/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java +++ b/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java @@ -6,6 +6,7 @@ import java.util.Map; import org.apache.poi.ss.usermodel.Cell; +import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.util.CollectionUtils; @@ -56,7 +57,11 @@ public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthSty return cell.getStringCellValue().getBytes().length; } CellData cellData = cellDataList.get(0); - switch (cellData.getType()) { + CellDataTypeEnum type = cellData.getType(); + if (type == null) { + return -1; + } + switch (type) { case STRING: return cellData.getStringValue().getBytes().length; case BOOLEAN: diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index 698dc06..b19ac81 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -1,7 +1,5 @@ package com.alibaba.easyexcel.test.temp.simple; -import java.io.FileInputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -13,14 +11,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.easyexcel.test.core.large.LargeData; -import com.alibaba.easyexcel.test.core.simple.SimpleData; import com.alibaba.easyexcel.test.demo.write.DemoData; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.write.metadata.WriteSheet; -import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy; -import com.alibaba.fastjson.JSON; import net.sf.cglib.beans.BeanMap; @@ -50,8 +43,7 @@ public class Wirte { String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 - EasyExcel.write(fileName, DemoData.class) - .registerWriteHandler(new SimpleRowHeightStyleStrategy((short)150, (short)120)).sheet("模板").doWrite(data()); + EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); } private List> head() { @@ -74,7 +66,7 @@ public class Wirte { DemoData data = new DemoData(); data.setString("字符串" + i); data.setDate(new Date()); - data.setDoubleData(0.56); + data.setDoubleData(null); list.add(data); } return list; diff --git a/update.md b/update.md index 1eb3717..36b2381 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,8 @@ +# 2.1.0-beta4 +* 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747) +* 修改afterRowDispose错误 [Issue #751](https://github.com/alibaba/easyexcel/issues/751) +* 修复多个头的情况下会读取数据为空 + # 2.1.0-beta3 * 支持强行指定在内存处理,以支持备注、RichTextString等的写入 * 修复关闭流失败,可能会不删除临时文件的问题 From ddabe77ddc8359be5e9a2896b70dfdd091e5e449 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 24 Oct 2019 21:57:41 +0800 Subject: [PATCH 11/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E5=A4=B4=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=E4=BC=9A=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E6=95=B0=E6=8D=AE=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/excel/read/metadata/holder/AbstractReadHolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 1b9fbb9..1d79f9e 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -144,7 +144,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH try { readListenerException.onException(e, analysisContext); } catch (Exception exception) { - throw new ExcelAnalysisException("Listen error!", exception); + throw new ExcelAnalysisException(exception.getMessage(), exception); } } break; From 38e105f382109ed0aa032aba6d2690304779cd95 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 24 Oct 2019 21:58:04 +0800 Subject: [PATCH 12/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E5=A4=B4=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=E4=BC=9A=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E6=95=B0=E6=8D=AE=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/excel/read/metadata/holder/AbstractReadHolder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 1d79f9e..157f90e 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -167,7 +167,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH try { readListenerException.onException(e, analysisContext); } catch (Exception exception) { - throw new ExcelAnalysisException("Listen error!", exception); + throw new ExcelAnalysisException(exception.getMessage(), exception); } } break; From 6ecdb90abdabc19a299399ced7db544d8049bd47 Mon Sep 17 00:00:00 2001 From: zl Date: Fri, 25 Oct 2019 15:07:37 +0800 Subject: [PATCH 13/30] =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=97=B6=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E5=8D=95=E5=85=83=E6=A0=BC=E5=A2=9E=E5=8A=A0=E8=A1=8C?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../excel/write/merge/LoopMergeStrategy.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java index 85694a8..b087804 100644 --- a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java @@ -13,24 +13,39 @@ import com.alibaba.excel.metadata.Head; */ public class LoopMergeStrategy extends AbstractMergeStrategy { private int eachRow; + private int columnCount; private int columnIndex; public LoopMergeStrategy(int eachRow, int columnIndex) { + this(eachRow, 1, columnIndex); + } + + public LoopMergeStrategy(int eachRow, int columnCount, int columnIndex) { if (eachRow < 1) { throw new IllegalArgumentException("EachRows must be greater than 1"); } + if (columnCount < 1) { + throw new IllegalArgumentException("ColumnCount must be greater than 1"); + } + if (columnCount == 1 && eachRow == 1) { + throw new IllegalArgumentException("ColumnCount or eachRows must be greater than 1"); + } if (columnIndex < 0) { throw new IllegalArgumentException("ColumnIndex must be greater than 0"); } this.eachRow = eachRow; + this.columnCount = columnCount; this.columnIndex = columnIndex; } @Override protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { if (head.getColumnIndex() == columnIndex && relativeRowIndex % eachRow == 0) { - CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), - cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex()); + CellRangeAddress cellRangeAddress = new CellRangeAddress( + cell.getRowIndex(), + cell.getRowIndex() + eachRow - 1, + cell.getColumnIndex(), + cell.getColumnIndex() + columnCount - 1); sheet.addMergedRegion(cellRangeAddress); } } From 00581c77ee8d996a8bcf20a7f622d339125e1ef2 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Mon, 28 Oct 2019 18:48:04 +0800 Subject: [PATCH 14/30] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/readme/quickstart/fill/complexFill.png | Bin 5119 -> 0 bytes .../quickstart/fill/complexFillTemplate.png | Bin 1938 -> 0 bytes .../quickstart/fill/complexFillWithTable.png | Bin 4917 -> 0 bytes .../fill/complexFillWithTableTemplate.png | Bin 1621 -> 0 bytes img/readme/quickstart/fill/horizontalFill.png | Bin 4608 -> 0 bytes .../quickstart/fill/horizontalFillTemplate.png | Bin 1553 -> 0 bytes img/readme/quickstart/fill/listFill.png | Bin 1690 -> 0 bytes img/readme/quickstart/fill/listFillTemplate.png | Bin 811 -> 0 bytes img/readme/quickstart/fill/simpleFill.png | Bin 1680 -> 0 bytes .../quickstart/fill/simpleFillTemplate.png | Bin 1841 -> 0 bytes img/readme/quickstart/read/demo.png | Bin 2746 -> 0 bytes .../quickstart/write/complexHeadWrite.png | Bin 4379 -> 0 bytes img/readme/quickstart/write/converterWrite.png | Bin 5563 -> 0 bytes .../quickstart/write/customHandlerWrite.png | Bin 4344 -> 0 bytes .../quickstart/write/dynamicHeadWrite.png | Bin 4649 -> 0 bytes img/readme/quickstart/write/imageWrite.png | Bin 9060 -> 0 bytes img/readme/quickstart/write/indexWrite.png | Bin 5075 -> 0 bytes .../write/longestMatchColumnWidthWrite.png | Bin 6702 -> 0 bytes img/readme/quickstart/write/mergeWrite.png | Bin 4045 -> 0 bytes img/readme/quickstart/write/repeatedWrite.png | Bin 5576 -> 0 bytes img/readme/quickstart/write/simpleWrite.png | Bin 3528 -> 0 bytes img/readme/quickstart/write/styleWrite.png | Bin 4610 -> 0 bytes img/readme/quickstart/write/tableWrite.png | Bin 6807 -> 0 bytes img/readme/quickstart/write/templateWrite.png | Bin 6844 -> 0 bytes .../quickstart/write/widthAndHeightWrite.png | Bin 4805 -> 0 bytes img/readme/wechat.png | Bin 67852 -> 0 bytes .../excel/analysis/ExcelAnalyserImpl.java | 3 --- update.md | 3 +++ 28 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 img/readme/quickstart/fill/complexFill.png delete mode 100644 img/readme/quickstart/fill/complexFillTemplate.png delete mode 100644 img/readme/quickstart/fill/complexFillWithTable.png delete mode 100644 img/readme/quickstart/fill/complexFillWithTableTemplate.png delete mode 100644 img/readme/quickstart/fill/horizontalFill.png delete mode 100644 img/readme/quickstart/fill/horizontalFillTemplate.png delete mode 100644 img/readme/quickstart/fill/listFill.png delete mode 100644 img/readme/quickstart/fill/listFillTemplate.png delete mode 100644 img/readme/quickstart/fill/simpleFill.png delete mode 100644 img/readme/quickstart/fill/simpleFillTemplate.png delete mode 100644 img/readme/quickstart/read/demo.png delete mode 100644 img/readme/quickstart/write/complexHeadWrite.png delete mode 100644 img/readme/quickstart/write/converterWrite.png delete mode 100644 img/readme/quickstart/write/customHandlerWrite.png delete mode 100644 img/readme/quickstart/write/dynamicHeadWrite.png delete mode 100644 img/readme/quickstart/write/imageWrite.png delete mode 100644 img/readme/quickstart/write/indexWrite.png delete mode 100644 img/readme/quickstart/write/longestMatchColumnWidthWrite.png delete mode 100644 img/readme/quickstart/write/mergeWrite.png delete mode 100644 img/readme/quickstart/write/repeatedWrite.png delete mode 100644 img/readme/quickstart/write/simpleWrite.png delete mode 100644 img/readme/quickstart/write/styleWrite.png delete mode 100644 img/readme/quickstart/write/tableWrite.png delete mode 100644 img/readme/quickstart/write/templateWrite.png delete mode 100644 img/readme/quickstart/write/widthAndHeightWrite.png delete mode 100644 img/readme/wechat.png diff --git a/img/readme/quickstart/fill/complexFill.png b/img/readme/quickstart/fill/complexFill.png deleted file mode 100644 index d37bb0f9be8c8907f226d5111ab9b7313411e240..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5119 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 b%*ZR6B}ogslCpSkrPB`f7h diff --git a/img/readme/quickstart/fill/complexFillTemplate.png b/img/readme/quickstart/fill/complexFillTemplate.png deleted file mode 100644 index 14e26b95176f0516826fb5670cf972fc8c6d9102..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1938 zcmV;D2W|L?P)MuYxVX4?cXxN4!or<*oxJ7 z<=5BO$H&LQ!otjqw&R&>$1~ZEXR;m7WILY8c07~qcqXzl zhXIx|f^26!vK`MoB0JYGz;Z^A?W{+(HdVh=(4+#$ZT*>*d|9_j5A->CLsb*_3dTmbsotTfNo!=KEC9^4MU z?ZkEpLH2>&*i_H98U%x#8b&HI%+Q0`8F+333auSUR}EQ0(ADk_e5Lf+{H5)vhy312 z-9wge;Lp)eoRHH>X6Rl@ry!933e4GjCf{OvkN7glp=EYzq+yL87(uqP9zni^CDL$) z9~eQlvmTM1DN>N_Bq7`J-6OKIMhdc>M1*d#-!B$*y)W%8CqCITmmLIQD2O&> zTxg8Z|NmKwjL;ODZVC=`PV9r2xH*~I%FVKK2}TLqQRX~$z===a<|mLE?x?}UjM$DG^SJ|OM-~M5My(1$Vh6UP z0}w}gYjbvF_{iXZE1Mvb3co8b`w-irX;*YGLTr?sA^hL~zpaMtC}TUyja1u-U|vB= zxArP%s`%Eg9Yk_Fq+q9FpnU*e&wlM>VTq$=huF?ecDUnAQ*}FK9jH zj7iZBQ0nkaYx)@G)pxd**+H?g0ma^UDmDMob~dt8uWU$!tnD~E|2%h63t5YN#CF>M z->FwNf=XrMrXkmRzjjvHAvHVnhefI@+q=CpXl@$>qA)>khH0mha}K@a(*FN{*fL%Q z8zfd}m&`ie7c3+tkstX4BCI9Wj^v#FZxp}{4+czRETTN(H}>yIPfTvp)#(ggPN-Fz34Dy6zg-BPVnwWTlKnZ~Jt*GXYnxmjLDP5~cofM{ z8H@hB53W0HPvpWClbkCWkBmp5Oq6jzrwPf{H^%&VMb1@w)Qm@=Oq6lZXY0d#*XV$p zD_8o+cofP+8OLJrR#>I$6qe1}$4qt~;B6 z&QZ%24ABWBF`bkeIu~0upr-?+mJMkBDqq>MjeP_tv}|u2os@H0lth^F-qUmd)J1+} zHniFF*?>tp0LlWC@?vqHZR`X2ReS(#pA9%g$H&;b%ETT|0U}IVGo~tH{oB+S8#OlvSE{ z5mcw6MyH?U)W_Lu=y_wKF3Yh|zvtR=^v&rwohdz~j~AYm?+JW(-LdS$=~(vRbPW6W Y2kuj%@nx5kApigX07*qoM6N<$f}~B-(EtDd diff --git a/img/readme/quickstart/fill/complexFillWithTable.png b/img/readme/quickstart/fill/complexFillWithTable.png deleted file mode 100644 index c107520113287c13bdffb801826fecd563c21be1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4917 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 J_V47;NC1cPavWqeHeVMY1 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 diff --git a/img/readme/quickstart/fill/horizontalFillTemplate.png b/img/readme/quickstart/fill/horizontalFillTemplate.png deleted file mode 100644 index 462b86ebb56544c8d5cece22dffedd80aa55be18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1553 zcmV+s2JZQZP)Lz`(%h)v$Mpu;{{_)v$`f!oqiVot<}gu+`|?+}z5_%Duh4k9VE0ig)Pf)rzpy zcXx`4cYU3()t$oCcZ!OgcZ!x%A432D1x`ssK~#9!?3d|ofC6APEhQ67>#=hJ~S9<%zXyZ{eT6^sm#z4B*4^>h<8 z>&j;zaa(|g&3NVRAba8}pp3^>-hhR-TlhF?@UU@t`!xmGlVtO*{Jjd;wv4OWzFgBh zw(>*|d*$=Gqbb0`)}?Yc$wW`whLrKx%2#mEZ5SBY8U<8_cRI|ta*_r8iGkFma@^Kr zPn36=1r^mr_n3Mb}o8jL^|Vmw+|E4w_^i?OHQORfCHp55p+2*W@CM!GHU|H$JM z$H9v#6>tj0zUHT4(<*(-LKbeUT*~rxN{Oe8EiEMbm6sg}r3}U>qjjn5rd)yh>?L`< zoXYOXh7~NS?5zwtd3oWaaw%)!m|bnz%e!#O3XFrss$<=%ElaCKdTjzJZ`WlpcGzPW z$TBKNo69IUuKZX&dTVe2gj6w`fM@@QoRm3HZIS7u@=lNW4RQdu%-#gJnE4@_m}yPsHVu$)r{)&ll2 zVCwg@H}Z9`K3!QsaeOZ~A~#+Z>G>M2v+Cf%$^awEXLoXT0T`8s>#SF;7{ghlc9Z6E zSH?C|m6W3P`Lms=4CspCrwpJstTdLP z*<&!*DC_-*nX61=B}V)BAmbNvSWZbJN07w#v~{EsXgq=mac`vLC(iPWb9v2c@uA-nXxUmGPI2rlPnvjG(K1A! zzE`k}jynS1w+!1T0_>BYG*<%Y;OmwFee&=XW7=HFT9);nc=O$F!d%IEmK#i&E1~62 z_kHkTW@lDK8S&gFxImR)0J zq-9?*a(?=a%9ZHUwU${i8o82yWdP-H{v3zQYmAFr$@cpgEmqmFWUfTyu?n2a`Z#e| zF=#nv2eb4__O%?yl?2up9$;t7fn15V9Fe)T<*r;w+_L1fY;q;E{K{mx5?T)BO5(i| z;2h}o#d)LDspEHd%dK2VyjP;VMO4lU%3x#5?OaKGe_1X2^MVW&BakbJSw6^P8LHyr zb6L_@Zskhiy%GT{Mk`km@0CD+(klV%8slJp%hg;7_c8v+xAXaP@*oBFN}eqDo}lGT zEaU&rtn6gjriMM}*hHu06Se8q?H88sn55UqW6_<69z+6x;1tcEad4|A5Uk{O*7zzZBv*}_yttTncwu1y!?2;DAv|#R?%l4g zE@~6CzrSClQgwHCYcv|E7Sm#wP$<+&g+i$oQ4SAdm~t4KoSZZm419pYpUihtgM>mKpCa z)+N$L0@4M^V&{wO8ke@P{{9BjDlU{2SmQ8t>c{C|()!%^y?Lybii}98Oa0sL0s9*7 z1#o}7iyYRLJUp5?&f|aovhzfcyiO&}6&|g8dE~Z`Fj>!JpKRJu($k&PtS^-sUclNH@ih+utj5x*D|lt`O%>hQr{JDXN8A2c~7K)P9gHg%CcsRkNGIkaW>m$;R`FGtoumyn(NL)F^$^W zh`cB9imS<6?0R&GWqkR_&*xu?cfAUA-;ZXTo*7?`Tb8W&Q`6e6oby`GrNt3$)OL7m z;qaYBa7A{{gNNZiZy3%rnrnIRIo)x9)C{`V)*8K1VCPtYS@LK?fq0M2_;cVW#aRZv znZ}7%ATe%yUF0FB@3*78we#8N)7Rm==`+qXl-Yx^;{okbZDM+uDBBRu`mfWf%OR$q zX5boVAm_tP*UF9|F`7gcaB!{Y-PTR=p2*j_fcpa!gs+>4CjSAJAdd3`zy4FvrTY$0 zf}=32;7Dg_aigmAE7uc2szpmIhi)-HuD;A|I#%Uh`Hi84L2^eMK%N4bZ`-@r&(S)< zk>H^8V$H7Iu=!KiuKE&TPI_b(InV-}2(30lH-GnrefQ@f3LOFg(I~LP zMFf)D>s3H)J`!_UpCg(0x)OA`vKh~U0Yt$-&X-V8U?h%@Y}*3+vIJrJ=}pmSiQ)C+ zm4YxtodZ~&Ku<^5ri*gjUOFm#oEC%PWtm&xgm>)Nn%isIL1rsB3c8To=iIp7Fh@}t zi&k?-$`7bnob(4B1s3yEIoJ)1EXCWY1ot+m<4rIMG@H2T=J*cz2d!1W36*&iuMp4> zmtF`?t%PUg>)Gi3x6IT#2G4$rgohzz-UUGqG+W+!y025 zn{_84e@a=hRmxl+w1kezf|KyM>Z*GIxgGck*Utu^_CaMU=<+rrIXN#B$zMEX#xLvq zceh&@gDZZhyHIsj_Y?Gg>}*s31C=e5<~Qs91!<5)np?!&5OVVCWa mtfGG`c6PIQW);lbDOut^U1zK@>5K8dg%Az1XcwqEi~a&aF(dQ< diff --git a/img/readme/quickstart/fill/listFillTemplate.png b/img/readme/quickstart/fill/listFillTemplate.png deleted file mode 100644 index 010c8c1665a547af3f340b0dc620230ab6a63a18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 811 zcmV+`1JwM9P)VLA!oa}5)v$`f=;*N3 z=;h_*)#&Kr;^N%g+{((z!otG6y}fseu;{{_u!?t`cX!|4-`UyO=;+mou+`|*uy=Qg zig%rNcb%Pgl!|wOox;_3ii)3{SE&F10%%D@K~!ko?U#vG!Y~j;+hI{$DBuPvqM)Gv z|AP{eGNE%0$)gR2K%UT|*qh5RP17uoEJ-+ISoonn2<71H&L&#;=~9IO4$iiPKkD;P z4$iiPpX>8b4$cOJ`_o?T%q;iLRd=0RKzL?^Z#)Nj`}-c|u5`5(|>VXuL% ze&5~oXko(rDNy1T3;PVzUk(Sak4+0NN5O;#y?nlM;_o0~_4Lmj3wsRo`F^{8emSAo zNw*jU6P~Lx&fOC)Xb;;ZaIqobLAUEuk$X(oT>_hJ3%|HKLJty)@Y~OjcY89%gy$=u zCC0Y!y-ztj=FKADm9B6L%Q43SXJMj{MlHi=OF!6&!frKjqkqHJR^}}_x)2Of}vM^U1tgw!L0n`~5 z)~;NNjlJ5#P$K@W!VBA2{ta&l{Qw&a7Zx=k7)m9Hpz7knTUQdg7?gXTgm0i8uAbiq zoOCQ4a#|w`PCOpr6=vaJ)0(_O9V$%3y%qYxLG^`=@-c)(VGiiw()SoWOtxc<3hRSQ z=kk+Z@ba>~Nh_MfSK%Zq5>gmAoB4kQ6yr!PB8-st3FBUz6BW*qaBRXv=89@|yX$0^ zu;dsBbC|E+*YEiY3x(>PB;URBMnD~$j0{KoT`Wtn3Rj#+3Ogz+IZ`;Q3fJsm2GMSX zRhkr5X;N6Wgi6B|&T0q~iGdDj3CU5r5G}!T!FFRFYU4jkNX~|`9GhezAqC254E*>$( z*j*cI-ABVUwpkxTxW4T@Nz3lNXf|Bk!cDom>+|^wKIfNnUeEJ-yna0AhcHZti4n{Q z000xzsi1HG_(ZLn&l*BL5^0b`*EnHcFn$j3Wf9Y^P{7q5{U%l(IzG)1_uX0m5MG=DV55psVORr#{*STsZ^rNQb|-2`pnEs zi(F2n(&*GY9*wF}fi#^1uonF)l76#1 zB5!3YCh{}1ZzNLDnvXEP5Hsiak9Fd@Vj9w$-{`hDUa{zGt>55qeI4DzX1?a)&2pLw z5GK8xG~;MW{3_d~h@i0QR!f!mqXrMV4z=Pp3?co!lV>D5P_|1?I45u4@@{7xcQLlz zKKhGUg+KUTc2mcMUwmXO|6{>xbL_7oN^o^|Fuz)Z7g@Z^Nw3_@E3TWiO6Qkt3coyz zyC=tGzFt0Wj8}}rAFC>k_+C`IAFg!`rF2*=VJshRKIW?LcucL^87t6t&-py2ux@_n z7T{wIyurhH2q)k?p8w7GoTgK|X&yOn4)2>uVHRB8?8e>3TiOFZvq^%c{F=nswm7qz z+qN3i?wju_A0WMu>;+-&jzKS+NN{{;_UHx2?_3Ah-tGz*y-;?NRk5GFtnkjWntHc+ zRil?5$hSU4pg%q1$6~cp<3&%XKozO9P|1EJs&2x+F)|{g#)!yt^OL?fKgmn}N}4lA zn0;a|;B2FPM4cJQ|FnYoH#xGE z_o8EFxewQ%#y~GH6789!%p}AkGfdfk&a)OyhVX_+M6|aaJ_8+1d4ubp!{>o+BtGd+;%&Le@5!*`{hJhyGyeXeAg=V_kf3i#;ZHF|;k zthBnDo&!zs<1)^KM;hePNm2^Gk8y25GYMI=p^^lyB(|-JK2ocw9E>SDlJUD7QhaB)Tu2v#368 zv$OLtcfpp^q~Iu8Lm8pU_o6|>BSIZpyHX0Q=27O|7B9QSg9AerrN0~Z)k}N=qSGC= zZV-CVl`hUM4-|sCc#UWOELe|4@h!5ZH%Ellsw0L^{2!ZjXtndFGZmocT;{+oyS}@s zYShX#f=gEV!KAq>Thj%i@x7t{oSiZvH4sYwx+XW^kExpM z2r23Muu$mj=xKhO=>&F!B+9@YD1V0d;zR+g-GAfFJhd$eX-BH>Dkw_w_Mb&NOdwkV)fW}!97ky?a0 zeOr+}+zXS5iXV7+8e1~R!NV^V9N*@I${zN`aTw)Qde*Qh64bLx{=AX7pFvJQyw?&_ zoDpx`;ZRXZ!R!3UO4xPmo&y&Ze7zJ4VvT>fZLEdbw(IF3CxIO$T1<(Oo^WAAdzgel z_Cac3aP?9xHPeXnsDQ&Y7d|gOOd8YTWN{&%-vU)c^4GHBc&K@zvKw)wHdTL(O}@S*^oEY{QwZnf%*?yHyF#WycXxNfop*Pg!q3mo$;rv3wnEa<(n7YS!szIo!sy!C z+Q7iT#OUb4zP`TI=;-Lxuy=~E=)#@Vu!{Tp`*(Mpop*OuR#vdp=$xFK zVPRqI?d|5~=G@%ez1G%`cb!76jIfG#ZEbD6zFX+%)rzpycXx_^e}9U1eL|*VjLz1b zu+`SabEVeSbH>)4!qwK+y<5J$t*x!r&Wv}8ik)|gN=iyeNlA>ZLR+RnxxXab000Ic zNkl}b$EP)qvHe`ZO>0I`9AOAojP^P14U3 z5@3yh(h2U7ex90ffR!cl$tM^EVz*=K_p#ysfn2q!naY=~ZPJQ9_H8~Ifuxn&#Uv!;&TaupxFAk$npmPIE@Zm^b^p(JnpB5i~=I7)6;wdYZYDpTtX{4+rxfZ zl%oRu1Tfg&&Rk8)?I$a8L;4xsbdbl_em?y8NbFJox*x;s3l{5@9%DZ}uxhw?{^}7g z-Ot_aTjHksX_>F?Ru}oSSUI?acAGUsaD8~RfJecjufGQ;fP-;IRa(FP?m5?fB*-z@hF_wNd z&a$6@2gg|Y**MaVWA%zbMh!yYM!jOfi~>Scj+)Bh!R!afKG^Jz^z#teheG{qDgC8? zt}t+Gy*cLD89mz{zuGq!QJ45(eJ#Lw}OFamPg0^ip7nD#4w z{-IZfp27HRBZBDhSIUuZBQ=&E%!f&msH6YW4HM-4Gx-S~zZ)oL$utI|58t>X?fLZl zkpE>%x1i@A@`JTh{;@`rpEQra5kLLB@hCq48^X~1L#ja=6OFzO`_JzfzfYKOogXH$ zcz*i97IMu1HHI0a|4s8hhM$4{50gZE|A%av;d`P%82Pc2BX4NGutsJ)|FByaJU!El zA|Tn+kC{JYkW)^hSN(=h$_JM_|7*xvNy(s8{yCEJq1vxU$s2t5d;H+mGC%N$a#wtw zRlnHr12~MwiNZ5IvR+*`aLP}Fr7#-`yw3n2UsbPP)B~pTqd@H8XT(x3KXS_1T{4TX z6x{e6X#?31=0K@mR05WUpXs;x0Z+}%4zLsym&y=+j_VhTpJ0CaiA-*TEQRDo)0cHD zH$T@e7C+(qU{l$IMQ-B<)6M)G*DpSTA7D0#D+9J3$jcA)EJ~<{`j`J>etY=QZCp-x zNS3Z!s#lY+zQym0O*~Tlq7<;O`VBBS(m>~0WjoL_fqLxcUA>2*Tep$l3b2%WnIG8~ z?!EPk>P15GkJX0$Ql9){^>A diff --git a/img/readme/quickstart/read/demo.png b/img/readme/quickstart/read/demo.png deleted file mode 100644 index 8bd14139d10fd49f9bc24ad512f2fc49e6bda23e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2746 zcmV;r3PtsaP)1?AGC=gAkKia-K+u^I}ZVvY&;(-u=b+} zx6z=sVZoR%`V2vW{9vR{n+}+z?=b#8PSV1x>L5>>q%cS8`WzN-Qkd2IWvMKfr7z6U z+n7(AUqYDkB@;sy{r>J+=m#*1#mle-vn)$L2=mv8$53`sn6FG`3Zna8svSR4`~b|d zHa`gWRk3*9q=s34Cxp3poJ50(JU3wJ3G-$=tiQJfV=LhUh`D77qtDh8fgc8D=>-!P zm9O(se9znpJd}}OmH{wJz+ACaJ{b5U&aDh4cfADB@yL+*0n9_88n6URrpn>v3wuZl zQyP5)?_qv0QslX5uq^!xgs+pS5a!}VQsz_s#iORl&oif9m;F$tropoGPi%f}adp-= zpew&1>q)pDjPxVPO;N%GxHV;qSsw#%??nMFUwkHSm~lTU1g*=-6lF3jC- zwOWF^}2EpE&mfKWz;TSg) zT?%tNv|-w~YWt6-8)k#peupkTe+V$)r>i3lieVDIEP_n*P?&=bnAEU605XkX5v)Bx z5NpFkzx}?eqdZ?x7Z|v8QcUz%nQ0dTc-os*zM}2OO!#wkq)26sw6pv&(G$YN77c7% zmzX%1j*hX)`~avK5ynK13KKQ)DY~=F@f+rziA*79M|6u+=D0l}785-lOc+=?@z&OT z3vxZpktcRX5Qz8yYC1$XCVG6BWDmf;lYb1CaFoeRz_dF?go%2xCr3>5BvT{AZQtpz zB|M-A9l5EIt7Al%xY~AFFwtYeq^99X-T@N_aF~brMaS5gNpRdSCVHyOdl0x^@W@4)@a>gLZZ_ z@n8~a*LT<^2}~k8E;(T080xvJ%-f%~I|98NCbhEPIs<2PmEdxa;8uH)9UA+ETBvDe zs?y#!n4HqCFl`%D0ZMyPnAC3f2jfEe5D;5snC%g=H0bmO*?iID(%=i zoj4qtnJ<+@X~)rIN;~#W3Nz@QxpYURNAqVzRclL08_T6-Kf%z6&=zw?Iy9( zPUf^QN$>_FUDIw7D(&dUDedQfn#+`SsM6_`b~>@rPO7x$sTyUX+w=;iD($MY=b4!b zQQsPv~#79duC>+w7c(_IsHKmFhiAg zGD(&8@9_ZgP}-5Ls!ki|OEL_e5~wI)=}?Ha*6iw9_~OIP9P7 z`gR_aji{!c_EmpoB(rN(od#V_yOOlOk_~%YnT;-|eI9t)$Ej|8ry=dr5IpVE)P1KS z?b8s_WA@5vho>E+9SAPAm>}&S?J-v-!~|)NDdwXO2&8?-5>Jr!-RfnP=`l2oc_S*V zH=-V&gun|^<7qb~?fkibw7Z&JPs1Lbc98aIK00MiJB?#b+KV%3FS2Lzwo1^5O5{x1 zNk)wtZ6j*2ZA4AQGapxZd{284F)tY!QL~6yRT(_(MZA7I4PdgXVDMbUXzLkP~E~U`_|G_T42qKJycDvb4#y4H0@Q@Rxc+Tonjj@c+4snHC5 zBWf1W(O^!P_BJq0dk1qarycOfX@@gRK=GY9ZQ9|G(+)~N-FOO^#-?3s*uCpB`|)gG+MD(co)t_W3C!`Fc3i+5Gwcy=M74pbFzvv_%p5fA z5z4SFVE%hfdr$CHV2+#i3z!PS{wgrXUZ@$R#yFdHJkqqEM?NYIdu#ic_SSE9(~kRg zUeQ4XN7Ekf;mqVRC(QHbX<&YSm9h|~O?u4lDyQ~^FwK^^6loGBVFrX*xy;RA?%PV3 zMlkEPayPeuDO)+unN~3C!b7`NKk`pM!ZhmV4-l%Z&MsL1cK`qY07*qoM6N<$f}1>I AVE_OC diff --git a/img/readme/quickstart/write/complexHeadWrite.png b/img/readme/quickstart/write/complexHeadWrite.png deleted file mode 100644 index 995ffc3ce37ae2ff14cdee4f058518924f523824..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4379 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 diff --git a/img/readme/quickstart/write/converterWrite.png b/img/readme/quickstart/write/converterWrite.png deleted file mode 100644 index 5c9f28983f9198475cdcae48098750ce06ae4e39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5563 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 diff --git a/img/readme/quickstart/write/customHandlerWrite.png b/img/readme/quickstart/write/customHandlerWrite.png deleted file mode 100644 index 41916c3fb1da3a4f9991d84e26cddfe6629c0219..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4344 zcmYjVc|4R|*dEI})C?)g*e6CQ#aLfbJdZ{CA>ltO+8!rFX+%FcEVzyd4b2 zC$KYxc^(GeL*0bI_@!(t?M?pv{mWvpI2;adzq`Ae&1UE2<(MXUS5Ww!R_sB z7Kh%;8?CLayr{Xkd24HHVPS#EWP)f|dwY9tZ}0T;DeW)RWQ$vA3 zaQcP%;NV~muc4fp^73*BqH}Wm=pZojLN$rah9H(Bg~yZwv7P`tRXjO(b1g^<1Yo>v zVj(*q@hwEBYtfR>Af*2=1dsPCU|0snYO&b`Y<3a|LXP-b>_Q@ocW<5uG+0A%G>EWF zqG^F>G>q3H011O&JZNwg+mBLnDX#zkciO~rmJm@3B0{|GgCGn5@pwl^Je~+?P199C zI(?`#Uq8 z@p6Jmef9cK(D(ZbyyeQ@({~b*O4S#;`swzYEBoCN)&H$=)j1hw*vi-v>2551-3b|u zyIjZO_L7;Y(%;mp;u`6pks2rm-OOdJ+??08amCwF$RK+uw`%UUs;#B8lbxnkFTKgO zwK^$5?sju!W9Utu37-|7u`?JTDF(*4G>DxXd5KPMBF2Z$ugr+b9@P5Yq0PI>YZ zJNUtEWNJL0?U0mi-wfgrvFUUSDuReSZTU30R5cBq;Q#QW5Bwdxbg=m2VpyDz+JgrN z5d!xv0)qzYk5G*RiQvH{s+Mrm0inV@i^(UnZQG#lc=v1Z%(_1jV%!<>M^V&l(s9wY zX-|`;eb*CS`YGNx5|BXIEiQ-UbOTSMJv_9h+c!3j-OB!qdfJ}T44oljnazMF!y=U) z;|kS|&Rad-TAMTQwlltgln$~mL=*;j2tJz_e{=3Ue8)85(Fct^k6E89c306vR$<8lk69&a(Hv%FjmlY5_APGsd4&! z4_+vgAiI9jZ7@)JFRY0%8YEABMRrBLQAOqJN#uu(3kCM7bFvL;4mGH3Ra}p!=^VGQ ziDBMJ@EmfU@6f#z*<+RZW3`HA(S{T>1qgPSJ*Ew4)4&Y5FpWb+P7`mkF8K>VV6ljm zlcs4WTZEjkL`(5ay&(CRo$XKL>#3;@Lk)LWEOp&Ln(ifTkTp>lQ=y0TO>8^l1woZ5 z36{X@Wsbk|s3K2#FEX&5^gbo4OfSi#BihZkrxg3zY-1foe|Zk8KZiIW@Ipa7=Aq{% zp7{%`H#gUIk4Bj&NY0_9<>bIr1jfuzu@n@qJ3uz#u71SECaWpz!6L55A0D@m@h`ZC zs5bl19>`2;&vk`<1{QC=^=j91I==VS)VPObY;o~joFSwX)bnFW^k**tQ;wPgdpf;r z;aU#(G+X>lp~ij7i26Use*{ZHM`*$w56lq>Wp1(Iaj808PdDsq)2(9o@(#wgU&kce z!}W;j349QS0*WNa87NcC#z|&yldz^vT@1T1SV9wEYkT?yVe}SJb1YgpsdzL-w6(-@ zRzqw6C#V!EJnhgY*%~gn*6kV}rN&^6s9IZW1s-cq+2B15>X!Dk{`hmSb4U1D1=xSD zxHz-s%_9`#G)Bg8yIqWAOd8dn>s;IAe%*;49MTp}EYDcFGj-`C?^*s4VDjhMBC<0g zZm*=N!d2@k_g<{H({%nrE$yeUj9>mQey17Jh6lx$x*TFOVhlZ`F#Pw;WN&7*NOd@2 z!{vS;SGHnBBVHl;gukF#()|Ssbvr{9KO}+jK1Ci3yi+U-Sg~z}nMk2$dM5n6GRg%$ zph>Lvi}qKQ62xP1iE~0tkLKOl$NrtIJUhk_y9smK&o^ij%Z!K%g7;|Hrb%+|D)rY1 z3jyN4q^GTX@~rc!Sy0j@+M31JqSh&i&y2ZlQzeI=nkrML1UI*!13fR(vc}%n%$F#x zqaD?mM02%`%2R^nPhHVFeWJ-6u8nI1yKA{;Rmt;ySo9fBiEGBImWDZN>yMn{IjmOC zA1?ZKjH$afaet0o+s%NT=#hA0yW(97MLKxi!RS0an909c6p1S?n>+PX@Oh(mrMT{$ z8Wdv0zWB-!1xym@Jn4XQ?1jvqPM7_Rn9cP6GvJ`W(m>qb48GyR+QhJ(J1w@e3E$@**lTnU$FjcWY=SZb<{2N=l_N%E^k&!F z)hKSqY?{RI)n{zArnxqP4q0cWXJNP|WKcQ`$MF@Kx{J}p3-KzdmTg+9HLgb0ODB6$Rx@>hHMyu}HC18GA(lAddzYpiJ@T)Va z$dm{^cuufbn97wi^Ql_2Z3P|Ak@-B*S>V&9Tk?9Y|4XDpUUh}f)ccD$Hop7TC>NzK zEv{VYxRL8xG~6;GeyS{{5fuBR4w+96B#*?GsuXpE@Z@D7yNOc;0;xAl=#x6N-%FLT}gvXNYEKJt?h`J0nf`E5H_L7a+9oSoaRe=OL( zrtcPKVTu#yn0lo;ZmW{|N71IdP7IZr{!ZCK`$W;6=V7_!C=rxM*4!#THpH)xlrW1Vpv{_%8fD(FM-g>nm7>;>;sPwhfRc zro)FguTo%dj0IP~1E&c4u&bt>EsgYBR}xYcHyq(3AJz3pBbi2_hLxcwgX|9p1l!E@ zmy!Kfz*A!;Q;AI-j9a$azy8}yHy}=tT)X;Cu#;}x78HA*d=BnH<$HXBy#LiV*{JZO zTs6n{faIYs14&S_`O#F_>o-(lwYbKng!t(eAy38oo*1tR(=pTU%}XCf6bwRZA10N4 zhGAV>Xt>-0gMiN)OXE5sFt`Mx=9|s9twDglvbzSNzW93WLoHgBW2Pt)*I?jz<1?$! zn$#9JS-CfW%F*qbXl-QWSo{e(wRJXkL1gK7-In-MO}D;x-iHfkU(ir1wEP5|kh&)7 zH4XRXZw5~Ctmaf<60RW;85E6V{r6p%f}$Nb`s`yI(G_br~kP-8<5n-JTs=*58TBW4~ z0W#mB(L))%8`xaON2hP{s4<#ASPI{^cNsm5-FH=$R=PMwuXvTpt;XhmMup(O?(8?9qVqR zNgc&;B$Pymg8N-xO-&o+zw<#NLEY;Q0}W7pp0m!8$RfGYWeWopNrAV`tU^EcNfY?$ zpZoCN-!xG+H%1gj>Vq-$NZ3HwWDF3F)^4%xZkyF`^h0}e!_hGc zEy{v^=F*P<8|r73@I+xXyv?W)f2HkfHHPq!(=3y#*)5WbgtpnVa3bUDV8(jUP^--1 ze9)(!X>ZlJ-~L04;lpFzjQXjcSL{S!`U$80%{FkUq-w|$G(+MSV{e=gm^6DvI@Z5N zQ#!v?_j^yFsq}-i5a_x}*;NO|Gk9j0HfPj~B+_z^Y1{Na zE#E>er~kWVFVOVC&E{T%^SG;YEK?`f(1j}2>f;UQT#`TK;!TZvs;zFNtu8`QQf@md z(Rwyw+)R%9`m;Afbotq+p4~^4B==uEOL}&HbeM2WUNTyT)(i@>sg`8lgLmM@dKB%Z z$61$))yUKt)0K7e+Xpw_4a>|+TureIuT|w#sfFg1AQF_Umh1a&-V8C^t8x4o<7WNV zT({HI{oZoDV{KuUYj)0$ZWU0daHr@Zpr7Z#`hl0=qFEx?^R#&gnfetF>nvh_vRXl z{G3rrZ!8PQ!TAMa)TOlx<5vFb9eM(%u2S<01Fi5BqIBIiLUF-qGFD_q5pBzCRhs3E z`HC(uOc2Qm`u%Aq&NED~lK%>$D5bzVSE&?tXa~yKGP@z$18C||N&^(M9T3mf!&+;- z8LrZ^p(OcDCup3F_wmf`C_^&N3b9;3-)_%(vste-1y89f z19%=!@%52yT2mhSMJ-**h{IDP;9N6BuGwkI(_K5{ZjrJ_zBE#U-d{9*@9%^0!4&+n z<9!&3<7O>{Eu+O&>gkT&Y?n#drtI|X{(QfAPgR*=*(TS)?f%jig~xfdTNVGWmeKup za*Q3|!G{#}?FWpRXFT_KfAFo9GOE&2Uy5wB?DRDQ(oKG>SS=7mvdG%iO^lA; z#h+qSamM4~oniGwFK{DSSpzX;_Sa>U&>yMLl*jFVX;RR7bd!3S+n?~sSMG?EWiK_~ z*~l(G)3fAg2qVfZ-m_$iP|OSEPb8dgF6%5wUmqiQEM1sbh^0hO>W;BSsz~9V=3Gva f=PIpHv7|+Qxh~@_x`Lm}&ew#wi8a39G#LIr(6mye diff --git a/img/readme/quickstart/write/dynamicHeadWrite.png b/img/readme/quickstart/write/dynamicHeadWrite.png deleted file mode 100644 index 3b25ad72b894131a3fadf7707bf7eb6a1cd0d52e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4649 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 diff --git a/img/readme/quickstart/write/imageWrite.png b/img/readme/quickstart/write/imageWrite.png deleted file mode 100644 index c6a0c67a91dc5a7022a8cd04ff727258f34794d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9060 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 diff --git a/img/readme/quickstart/write/indexWrite.png b/img/readme/quickstart/write/indexWrite.png deleted file mode 100644 index 8dbec177459e85b61bada41ffedbc8b43f425323..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5075 zcma)AX&_YX+aHD*%UC1Fo}z4NMj||98H6EZZ(noN8Mc@NV+4W;ClHuyHXC6~=JVMECZA0< zh9CIFfeARt0w@>CB|!o~UteEsZSCOTV0U*no|OCO(WA!3MzS#i4#MM;lks>M4@Sy` z1f*Pu$>uYeWHJFpBCwgvj*bpKn@J!Lm}KMr{{Gh1*8BJGbGh6*ckcA|_7VtWIFp1= zUJo%w7!%+`LR(u~Q&SUx48suMso~*a*kr@9*=!hbc2mXFcEh>b7dXnz_3t*n`YjnM_4`xEfw`1tS9R`6Uc<_zKe*aFfB(MvzMkl1 zVfbzn#?1P#fr7jKhzInC5-~0}{c-&gTb>_Ljky^)sX9Lw{$kkPC)9;{5OuT$_%>qt z#;&(<=kvkNBt2GOWmFF}_xPbZ<(bj$K&|JK9Hic4zwf)f;Y$}ne6H@-j8vh@zV_W% z74Sv!UH+SD1^;dSik+a;A3)ui9Wg^mH?4YTWj0~Sc8hl7o3EJ_}eF`Ds|M%LG1*I$|Ae5YzsSUz2uvL3H4U@fIhO(2$%-g_6pg0zotsLB{g@007brDdF5 zUDgs3n`(|?7`SSXyP*A|wCL)pICCRvkadp-b)B-|>513|MGR$W`xWWr^1;fc>c}VsWQSd6O8r4Ef#j9%a)v1FBC&EWtd}TdG+v}-Dd6!B ztKAcKPtTCN`VSy(7Dzi6c~E|0S>Q``#sXvhMA%zPFEdH!Bl(7m>Z@sHLQI63&f?R3 zmR%tMM(#M`H(J|qk&d2&sve=m3tKzdSQiw#?ueCt_cEy{C3rrtc&g8vC&_{_N}VNrIS`mTNv`{iG?FBg_8bg#@#-VJC8XDkDJe%4*ER<0BFx|BU<-EoAp2KYn#!S}v!uz1kq&!2^Xr>tQr?Zh> z-3@K-njLcadcCJ&y`&n?Qcxai(sWFXYz(Z1%GvFrWHEKVc+9Luh?PBk(f%6oC&lYZXjiUGw)xeFhguR7Vo8#voxgPsl`A9Ptz@3p5d{sT^iIb&SI&IDJb6|c zwW<_9%%=!yPg{i}`K$IR77a=mUof0(27Id^(TZ9h+34*cZ~F6RntkZhv*~@dUaKeG zZ8+Oxf@&uP9%;EO9PR33sQ*nGUL-Vi(#{OE`Bjum`mGtEc)h7E3WyB*++MFsn z5fmrV>?Ks!+V`yjA-)iyVf_sxbuR8%Q@Z|V>c`uXef1iE?_Foy3rRy1BNu>CHhE)o zRs9aeDkG0SyLLXh++proWNWkg!>YAZ{U3>~6We-lSG>i#Fs+K=aFxt(f!$nE-Cy9Z zQcW@J+unnoQqG|`vE#I=ld-QudQLzwF0{{6R2?6d5+?c@qFsvi+O>1HJorX=X6!i4 zUeVu<7A@opEg3gGgs)yJD;YBGf9`#kxZM9~*U?SsGY2Y`^bLo6YVtFP)jMbn;H~Y; z-cAZi!A9H5(&buyPG_&Zaz)*E^0eae{3{QOQ-3>|N9Vms_!4&Z={FNu2CC~Jk=r#a z*3>?&~ntRC49_$zv+npuUwi@q{;m*r;nZ@e8jP*VEWYn6XGCzOm96!B6GC%vw^;TzW z@t!khGZ#MSQA_bt5*QnosPTvw+z^AQ=g}*@V7i&e^B0pZF3`(WNalu`CaIy@B{6y{ z=HQ$qXPinU`3ip~(8yeDU8QE-ITo{@DqQTutR|JhhQ-CZloVBCOrP$AXA zARz6zz;D$sUf1K^>6HfCd~oDa?%+UJmcoGdkAY2v>-_RC|12oxh`lm5NiBU>rNIJ` z48_y~*d>j;Z-Bl#Hgf3$^eo0#7S$^O%cy{57?zwaWAVMpPNJQ=HS*|Tk_=Xh4e&C? zceOgT0_>Taqz~?bF#(!R`AIqFvGovf7}5x91;b;utTWMIrsnQ@2uCKIY3ee&M|FFo zD9UNihVrbn{3L(y&5SSx`&NXtBJQ$uru@Oz$%}Kd&c9A

#GJHk`?RS=uczJQ%J| zP_fXToQ!YIq7N3JW~EVL8`Fyr$(^_|LW%DVzQ9%;<1Yq$8AN8dQLtr?mlFy^XWo|R zoS8Z7lqOQn(Yg7BGypx`nxzo6!d^i**VMSkse?V<=f-oysYA?UQb(BQ~n6BwsTlqo@eMxI_H)w-6E@;k~$L{MHSf` zPAoxn6#;Hu<&NjY=$+mt(T+JIKcI$K^fb#X5yTfg?$*U>{h=y_)je~(iimM8R0G1( z6(U?T3I_+QwZX-D`P>xNuD1?pBGIy8zJ`GOG>8yOmRzZ>s;ux7MWyY|x9pY&%^VVf zu!mjCb_9qN?JIOw#^i|GY#J~6p5_`a1=JWnIi@U&+LNb?J$$CrI3U@{)Dqy|0uP|= z(2fjUx2|7(xC&&~a(AV)TlOXMD|Ur8fb9<@4-fPV+3T<1L)ge{++a+e>(#i29ID9i z`6K!om{*eWletO8<%T(Ii5e#D>Q)6dk}o)+D9pqtGF5R@jCQE}~~1eF-T$n7h`;p~w0^ zA~Z{m$eLKiqXV{ry4Jki?M=CFg=N8slqgZ>I(lYr(k?;_#rw4^_Yc1OjW|WXRo2At z1>~m)Shw~pUw!jW=&T5efW(}mCD`uQAo}v?Mq-JSUHeLNt$Bv9o>;xJ;2L$n*2v+>{oE-o_%$Dgo1K@awna9u8!a9VaENQIl+3fPe zlvxSX*!3(7x8R$0xA#uoV_8&yo2DvpVBgGqG#fpZ0`V?G@rC9hjKh>cx=}b1evf)) zt-DU9r~+xA4Dze~c*)g_=hG#O5t%uN8mkKjDI#6Fuz0%BHVpTTsPpPskoalh0+fg4 zaRH(#-G~M`)v*6>y>ghYGwB3?m^MhOwWC7fKta?uSZNW&dkFbKd|@yS_=e-jJ_*2J zxL;|}tp*u)yIn1KrZzkx-%EyD^n23t1C;j{6oq6&QW}11_|c7`FeV$YMNYv)oEbgS zAmdUCKWWwG34dFK{xXu`EoXw^#$tIzAt{tp6g@N8ReiGEJK<#bsar&tZZkm4dT?(( zLj;)LtwX|Md0MKhAA3|ut#GN%Pdd-R0uxPnaFlx!j#oCEdxGK=b1);Etxcc*`tELa z*s~N$o<(69P-{@uiyC|4a{BX&vuhQe-W@_WbFc~(5Fb}u;#ssVI4@TQsDu1wYd*M| z@=Q?y9pbFGE!q*r>Yc*JdH_+AT5H+-;q9JIVQ?;7ohXA$8AZF(q&tO%I|$wcNqY6m zTj()MfH>kdA<)XIJGEibXjTe^Qh|gq&F4b`F#N!7$saT-^pyuLT+rKsQUS?F-1H{cujce%O}_5v^_a#%C(%HO)BXce%&MECELiYb(@NX7}u=#=i#`u6ps zRBU**^#8vVvN2r=C!kIxCqD1X!uySMX{P7G-4Z9uK}ZUGbzFkE->6ydHm2M?nHKCE%}dH2CRsHYA$X!TJm`wIfPG~H-g zwCgzj#_t0nj`PrYNck^mR+L-p(vJ?|dlY6|$Z1E@q-)RtUBX}*H2N`|a^pYJ4KF)* z#f(UZ)wyVpkro}5zx0vfr&N1NL$QIqv(e`IOiq z8Qp2X3A8*x0p2Xz?KcYADZ@CzY}0@+#%=f73RqKmwFc{_3^xiA`iN}#c{#7On>QttTBbvKqRtPi zW)0?b>iK1ujSF!VD(h|r@y8OzAbVhk){azd5yu*@c>1x^B^PrffbjG0)rt90dr{zFWzJax5lpXx8b~0z zjYzqseU2*&{?%5ik&gy~&#$VouICm@OQSeVW NgsYP$p6dY8{tHioFaQ7m diff --git a/img/readme/quickstart/write/longestMatchColumnWidthWrite.png b/img/readme/quickstart/write/longestMatchColumnWidthWrite.png deleted file mode 100644 index c8a06049062bb982f35903212ff1834a918ab3a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6702 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 diff --git a/img/readme/quickstart/write/mergeWrite.png b/img/readme/quickstart/write/mergeWrite.png deleted file mode 100644 index b631e59bd0e04b221c39593bb5570d7d1c399b40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4045 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 diff --git a/img/readme/quickstart/write/repeatedWrite.png b/img/readme/quickstart/write/repeatedWrite.png deleted file mode 100644 index fb204a4bbafb476425d72363985f791b46db020d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5576 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 diff --git a/img/readme/quickstart/write/simpleWrite.png b/img/readme/quickstart/write/simpleWrite.png deleted file mode 100644 index e5924b196bccb240a109aa574aef47d350917132..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3528 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 z

=3{XTgNunZe+rvj7i2Pnh4?Q>lW2BW5?2C(<_^))p$<>uzz$+|N)H%BBAQ7kMq zHAEH`l!k@|v8D!>%V1#@GQ7i=u3`CSD3(H?`6p=y!<`foDtOhO@ zMXdS!`7;G83VdoP*j$`OE)Xmi$Ec0Qin7#3V`>?UZ{NOYV4~@CdRbYSh6WBmDl9An zV2G3&A`y#4i2@r$MNt$Yv4%oKp-?!0305?^mVt?`rO{|86gEFUzq`A;y}e!Z?=}=b zMHB^W1Xch56fmJusdaUA6%`eLVQ_G;v$GSUfoo}L`IjjLB`Vs{(b3!6+tbs7#R4uU zjD`k28Mz~9UdM6Iipa-yM$mM zT6k7Cv4$v$B?1(rUrJ9+O#!^IfH#&HjllshH30j+X<-3XSb)S(ED23b(m@!U2Bt9c z0SZfzPDo%uVVepajb2)t##5cMvl9}e3!9p7(Y3jNcDUSVTy#_AE8q&Jfn!0T9A8QU zSU}nUUP4o0o|WA%78c%D+8VbFpARo3GoSTJa5Tk)a@)PUb?3sR&rA1)Yzty(Ov@us z-xWG@=M^(PKukC-$h`B9bD{OoKbAizWt*F0O+DK060>^V^c!tB$(f;b<^Ks}+fV7H z4fk7i*cv;x^+XhZzs-+qb1voX(3oEcGj+jLyy8)ri&YFTadDS6aWS#xJkGB4R`0Ay z-L)sN5^E*FoMr#}Rj)2P=dnT0E zwx^ph!|;~KNV(VOaCb@RI`wtEc)*L0`-y=!Whw-RSmwHnAbpRF0+kpkG2t8;VdGv+ zTT|U_1WpOQ+uczHHSWIzRb5a@s=oyKYJYJoa~_aHxXsQ;S`eicFrAho>6mB&nL03c&3T*edJYt_y0kJE+=hOq(#7 z;=38eSysrs)8LWMV%C?f8%m{q28DucAN+2Lgr*OE`b|>MyqpwxeoMxV)%%WgF01{9 zr7(7s@s71kGrZgJZ7A=v85I-Z>|1NKr0*A3(hMGQXPG?B7yqBil-s_X_De@@rkTitn#>(uh%M`Fy$BvhKRYRvK-% zPRglt&a|4O_tR)*epvaB%6vvQu@qX$)2~&o{-d8`Urp!SN^AD}fzAy(iuG`Osmsc< z6}JmLf&B?9_?+r}Ki(AD8!kiL+vhMDJG8Lh-E$2i10g?=K6i^GmCf?}uT`p1!eoH8pv!SjDS;u}Tp(hL&`h z-Lb57`l7+|NI=Kv2d*iy8eKWrRAOwQ-R@%UzBxHJ3Z=Oq0bcibRtKNpY4n0!|f^1lyv)-yL+z!<;G#ibyPdrb_S5scepl4`M>KPKYw(659c{Ecw9?6#s2xwXXF>+{eUl}nCXLF;k=dbk zeIM3`AB+!{fA!SP754f~D3kkIewp9DdgbQ}=cvfjf~wwpiN(50X0n3`&%Ck89$x3Z z|2UHe-^7>fV8>(7!oFBu9vjum1G&~o?ZOMn>7{Ac>~HixyB}cD*Q@#ALKm;c!>6|U z3#0R%RmBfA->Mwn=c8(dpam`NZ9H{D=1n7E%#s)GU3bPxOP}vLcuiR~7-N5!E$&Nd&m+r6{Wm!p4rV+%be6OG1M z(s9#D%isHTpm}HmGepja#FVe8#0bX9X06*RY#Bv#t=k;;W9G^Y70f3WKPAZ0O8>m# zq65_}>Vq3sr+&5miL;_GUt5)6Hui%jDl#JV&wVJ)e>$)@T+m$I>WqS&U~ZCd;c?v13rBt?OS?lyPqDH9BYhHeLJ;AkF3k^-7 z5#Ul&#OUT+bdIJMi}Vy1ExM6`XOmv={2hN_%hlrfLUhH>P)#yPghQJ<7Jkidqnnrz z)iSh(JICA??X45wAbxHnms8(@TAoBlRnP3FpZJt4nYe*o2)1izW1^+&HnShw4;tKc zDt1MFY13VQ@ecfrAG!O9IU@B_)i?LcV7=k5PWh$*%@Ft*GXBn+3^j z3`Bn!4o3g`%-8w%Az`r4q5#PUlk7B z*2S$-Lco(f@tgz1w5alDcsXHtF6ld&l5pyJ6EE?h>N+*=VktqiWTwn5S+8p)l=Ef; z_({su7ES!auF>1R&|8LHH78?{q(Z$t(sXX>mmrxK>pb&KXVNXl_LGYZU0V4ItA_tn z@k`H>O)KRU;O2bNbxNz!(Vy^HV~#5!Yz3OxGIqhejLTbi zTeez~R~;nwHM$u6|M3F%ar^V%p*jg8#Up{zM>CJW-(Qf1Pdrrpfxzp7unraPd zJsEp~^ir>iv}e4T9oEYpaIJSPFTfr7Ugj~Y*OcvaW$xg|{a*(nIQf9R;SYQAFKfZC z{wsrde}*c&t+;=5D^pWvc*d4}_Sk%=6J2!N-?IKM)qkyn_FYA! zf|Rm*OD8fp;W@o3p4fkcGE#>RCUMOtmsad#h}FBFEd!WOTTkqcL>k2XW(p2&J`RtL zauQkO>C<<7jF!^5`($j6yBKuf>dFP$7blILdqh;d-v6RVHl+Kr-~LVn2TADo-A1#` zuabOTo@e))(l`U7Jd!$s%Fj0Xea^9RHaFu}##voN(h|TZkNWm1KdZzUt_;yR5yT;3 z|CLpY<+YJN1`yJ7=W(HHKWm#Ia_<+f{kh^6+#FPEZIDhG5=`uN=pJYub(~QPKQ%uwzmloPF>g7LSn0;kPE-c1kezLNO{y z7&GUa$R8k4ogh3{eqK&rnib`;bxA>5*^yds3!bZL=2uAy zq&eYD(GzQ}@rukps(2)Fhw!vJ;16o?+uTZon25m6Gi>GNSL>11LsCQ*+)=70mAEwd zsQhp&8XsFyY6D&4_kBk0k>Gg!)V$@$9nY6k)4QA%i&^oA&pb@m2Ic20+~P~Rmcj4p zNNZ?Q^@C%LQp$qbSpV&rj#Eyt%ARKxY>JSWPem3gpzSm2`T}cpW3UWl7-D8(ahbeo z{z=iMb4O{~E@^p)o6sKF2?BQ=&}Ybsi;LuSJB4V(Ozj|SmQNd$z-C4meIewi3rZ+1 z0}Y9-`Ib$QaW$}T&=Bbup#fIJ2{})gHdS8dn^Zgub<{^j<&du14=ztQg zc=|Li5|}PPK>y#~&92?9Wn#Ey{Rr2cw(O`m@;I(JRKPxhmC%keBP2TrA;lGJpdA7p zDv|EZP7yK$lD0iz&nzNr$QnXhAfE#f5ms;oAy7-H&$w(IEPz7_8HwO=mg#3BP#Ich zAv8-kg!U+7-X|Dgl;-F=2IF5*nf}?2&zynr@2X7q(x=GVZ6mOC6@5sLuZ0g&G4Zgb zVhImDPf*#Zz^;)MgyaGdQ)F}J!}myq=}vh{r5S`as(FT>Qh}I}@M#X+$pIx?XxxX% zyA^b6>$V|gf>$b*7Mng}Fsc)u7HKI09#f^Rk`ACDg;|(=pgG7iK+BCl^7VmI4#lJrfC~Ibffa16C^f zz!=W14W!tBbZ=gVBx{;GD@;2&l|X)wscrf670eX*=V-IUaV$f;(kO$#y=xnXzeE}| zjYJI4Wa$g!ivu7qr;ne7PwyjTo6)eWr1QJB7sq?^i;+ulba{}khK3qHVU(ZH_9k6p z%6~|~MwOiiXvk=m_q8Y0UpkBEhDnEgX1O5nnZ??zH^ysm^w`B8A8vE&1!wYn5!bET zx%*)4HD*uX_YEKTO!pKV)PRWID+|@M`JM zo*u2o(=WyOF>;Xeaz_a&6W>*>3Tj~i-(cQZ(4Hx>k^#HpQ>_#W+)$L?P!fISI~f>X zBd2Z49asre$LS^Zgt{G?JfEV1jn31*D;or=^K=ejj}3IaxGnCU-o0?~Bs@SqoW8z| z<_n#AK9zi$mEYif&*cve8&{8U1TiT-Vzr$3HNb+z)`i@6^NTe0 zY1PA(y)ly?ZlM(l%#1MA{MIwp8Dm~^$4Ca5YRd zLu|%cQZ_%LI;aw=TfGW?b`*eyjnH#EfqkTSB+Y9!x4!BF+eu`iFD!*t7jOxtrLf z1=h@09*`7ZQxbkzE8T5Y?_%h!C-DaI(fL8bDX%+kv~Y>vL7i0ay}fFmbJTDZ=Z1i*v4y> z-uMVU`asIEDYAK_KXuY1yro(xH59I~?j~V(CdzE4$T2%2Oi#+WS-@9Fph-Jz&n3L& zk!fqY^9zVJ(&X#~D!aWQo3zf1Fmub}rGc5^C^9F83$A1xlRjJ?d%`UY9xx`AIq4-^ z+HNKazxqb!`;cb$=(2ieJo^g7ep+ewhS_)U)rY)Cf%LreqN7GEp0RsYcvzAgN>5n0 z47eTV?Uog5St;UeSf3ivwFS%PH&#%y`QrHxp|fOQ$HJ~k;!cZ{|F$+ZAZS~M$R=B(TkCUn?rBn zAMul_?o6x7#qmfLLP`QhJQw*%A;x1_$25e4Ph2tN2Ma`0Uj=9SmVSNgwR)!_twSS3Q zB$df$?iRitoc0SJH@z?X1Zi#*37QmBx4Y3=^upS7adFQAIXN)t`o@Gb&p%;`5yl~% zj3TMhk~tQ<&r=R85r)*c>eakWFTIxM!6vG>sjN8e zAS?&3F0IyEBlS_CF2}twW$$&`-5P8g$_ECuQ==05WA;ILpBlpd>r_{ar+a_BPy}Ah z3ow{1kUrhEptxJ&5^h`wdDcGjKhWs91J3FSdN6FgqDXK|zPdD3uZP5-LhpSR7-@)n zi;FRWOne?lRMM8VD#r6p{k|)1{)qlFW4EzumKmD&~FA-sF^VWb&_{wt4p)=(t&W6J~(4* zH*7{}YUlir^==x|$pAUOa{It$<*Wx^$g69nTwCo#mD} zFS}q%;T&tk>yt6|rv}&FL5|wU^Ryxsnv#w-DCW=u8%w?N4uk5ww}k{$@%33VNV*g{pvE)T`Lmd zZw?2>G=v_z{O(oI7u_zH_?xaUbSPrs@LngZ%^WY#q)74c^lZ*CR-d~@=#!Eqh}tfO zp~(m*dG7#I1YB+hu*O!ZyMUE6(V(2`vIMH+X%DUCtQoR;WK^8<-yp+9>FQ$tzE*<~ z(axVDIU#GGpfnN`H2<`@dkPU?8i&veLmLDP-&7;k+}*OJ}68I#wBWdEN&WqMdW zloLW5D9Dj>W+!Yc(E8JOK=bZe0@;8{TAi>($ z&I9Cl-bYz?Bj@P`xvAFZ9KLza&#BS*+ey{w9Nrdq-Nw)y?0%z`ttPX44mL5OY&{zC zgf|S77{sD_>#FzMagRhxif4JJsyiZp|7dPJNQ0F#^yBN6smXd;yM1&T(C`RhSwV_p zQ)+*^sY!CL`NvuErWH=iZwN$?VRa;m`49Q)IoB_&wrw#&WdadUi88zr+a9ggQ)`_| zeVe~YT`N$9$;4YKv)CT%vTMvs|1c2SO7$th1mj<1}GO)=70y-b+Ju`W4c7zdKN-{E7_7N43dln{&j zW9tji=exT@GuX8?))tC5p-4HfPhrnNk>E-~1XZgAmnW2$4FN0LU?_dI`~(#rvI{uuD@0ploKUu|E&O{@#Cpg9RvuH<8^W zddqfE!pu4p@h-Yle0QQabemiW9FJ*(m^6f|<^;zHVS?gKPe)ie>*{}UNd!4nPa~RG}w&G}cGy;JT zx3{x$K_CQ({4oLw@-<&QCZ6yYENA?2>-W5a9 zn}Nq75DYxR$~%?A<1v{`1fF4K6;@qcT~}9!z)~6;8`*3op26n(Gbvd9#b#1kTU*1d z{`KJdR905Le*Kz)MZA6cwx*`0y}iAqrG>-cG&D4nmzR56g*7)fFG_B7hzbLYaWy!qQxn8^$_1Izc|=Sg9dhJ}S$g)x{E zHXjxr1%F`qfcOXzVX2%_KBr-&cnUK$EER#LAov*n1;1rd^+B0@pM$&CTpS*x%Qc zA0o3KW%&_fMMJ!9k++V+h~LgpQq4hv@#luUKEx5#nmMbY^3yGAt_eW!X z#*%RZWt=xQ!Xq+SjnYOkT9k`4R(SbD^a*76y6(oA zn!9?C_1IHQ^q{reRuMx{Q5!(^ACnm$tPz~vBtzEskDZl04vgC{dzeLJxt_;f?~Fv$!e?qfH%)FPe<8$~0u z11@{8C|7Shy$G`^HxrY}P;ubhqC0?sccyFd%%T0a%Ks|fv)KKqxqq$Rz5G}8V%nVd z=5u|l)$rqOf!(89x9xB%+jppvcE_cyo4i9%VoTMne??rbQ29q-(&iho^e{8&Nw^1S!1(`uiS`n|o5HI~IkwYD|dJdm$-)au3liOVq{CMw+E zEG^fNG)?D}pZ(~x6n*-$+Y8J-N2$y;gMJ$!&G-ZZ$tw3pXRaSl1?63xC?yqtDZ745m)B#)Ih- zSm9ggNSWVT?>em@h!T!0iOdFy!NF^fm>Q%oh4~>d6S(XYhtcY|f1r@0oAk2i z)KAG+%1^yApLLt2Kof1qz9MQ;bK<~2ynFP3*ViQy0c*mtA)V6pV?Ee*&S{0KBx-)J zbQnGTa>VBZPET-KdJp<>jA2W=mr%M*rO?dT*4+*SluNa|ojgOb>T49<*S88Nn`17# zDRay*pn6vWWzk`zETx7|fsZnDpBTr5`F`GVtv)(>B(N2Y2U(0shRtqE9KkdrVhjK-ltL;8>Z}knzmwQEjh~{(5my> zS>hEUeeGV4+&g&itLSEIM_u!2~< z3Wr9)gW;n_L!EVFRlWja8%eoGmT|`hu<`2Qp;s|$HXk|Yp#fV7@%O4Jwr%XdvrneW!Cz3$^E2gfF)UcgKBO(%W)r7yS=uzB<{THTuaMkb6f!X zrpOyjBB_wHL%u(Xx-?2UG3j8YI-$b}w1=D_0?km>OL$JlLtta}07_YcaPEaQYx%9? zL*5)AY8&`%k{dy7lqhTz)daIzD?HdPD(t?#Y7sQD!<5w~r}Fp5fp6!MWkmy?%Mouy zMxvDGi(F;{tM2JVd}L{Z3QxSM8!?of;ck$T(2Bm;t0$H4XWE}|rA=!)H%Lr$vBd^M zw|q)PAezwBb^{3`CcKm`iUHKC+@MTdM7qMB{0_Ktmo%XC+c7gS>d*2OzngC6xKHQO zsCm9W6^6HA!vD04Y-84oTj3rbQ;60O-Tgi}M%G}KGM;@(C&huDtgZU_!?`r1VzXS> zgCF77C0$7#Bvzi^6Xs^a^$s-7Z!~r*p!Cy_knJ;&hljy=y>wVQsG?;#F39Jnn8hC4 zns;vGzPX|^&|y8C`B#7JYc-pYYs=jRpjsxpBfzq}vYUOB zFJf^j?Df@Mc9(*s=+~M@jtEQ9lUJO3IDj&n*OR0xoC4>)1I7xF-{YHi0nx(hV8=7e zhqE7l8Gx_0(0ktNOv&TGb~u4JU1_f>Uj_l_F+gBC8|RE3E|({??Zj!`Pz60AQxa6_ zZmI+$r~whT+R#$x%B>)03lVOzY*mJQxT&EJ6|#gN2k4!HKB%Px;h~d8_zUGC$TL*E z_cv;*FY;w-CNA@=6#Y+P(L;6cnL>C$wID<`H_WJB^n$iGW z>nkXkuZc3`vpFzqLW^n#1h~gLgO^+lK(GVOGq@YA-%@9idKf}eMUV%ifTf}&Vha2^ zWDmFWdUNH~KQ6xxiQ-L?I-C5kQ$KZS$+>WOoC2WS6H$H`Dw!5T_U@sX@24eyPw6{j zDMkOheBLQ+yfxgI1p*QK83URU#Hq)mV%sAaH!m=^byx ze!pW}q7i9Q%tDi2c^9SVCZb76UrhBubI%9Ru?DWn^8BN$!zg-PZ5Sg3TXZ zsCCFl$K4pV59_=-cR=cM2Q210_jT+NJ?Bn~LGV)zIb~YZ3m_jB#7(E-rt#2C5sN`v z_Y84INWWF3?-=f8%{}bmZqA| zHr`(LUTH6Xat^*Rab_JV>fEbo4(=3qFNlT!I}=+I>5{`}wHPYhywn_o#$PNt3@xQ) zf5O7Odu(58|{eP9{BFSlYY%Q?SwMQXt#G}+l1T~4p?}1*pC2{QV@5Gby z3%Do>1{k?<4BC#o*KP{#*94zqs$gRYf;3>Sa6o`R=^urjeq9zJ1X6%MUS)mJC`oj>%YAGhR)gMH7i-eQ1QWo<2g#)5c}mNqK7&fqw7 z4O>SWao^>6;^3KQsevoAaDuDSyBxWLQ5&ZUH?*~AC-6bR(Tob$RX3NF-OU9lQKS=1 z;Ayop=c(q_33JlG)#P0^Uw47ewOkXdaGA-E$=@pCsmYyIxTR(v6lIKLjk6OXyez(6 z=CZ{KXD5mk$U$F3j5=db9C@to(~gRqf*`ED$b~BAd&kcYU|tOJaBxrhcQiNDH6fcg zDRs#uOtbb1`)k@AQwOdeNlF_W5;+RtNxS=Sh&Mipz?*GY^M*Y}{P*hqavB1$KL&ZB zW9!^~VPQt)1fPRi2`>A{zE0!5pW%=RP|;|ge2$IO9`YLTm6{CNuvG!2$l>Q z8O8CD+9j;>`?%S8R#p`)ujls;j=D4Sp0qoSwz-9Z+|%{q>d|&I(wGU$y~}prCH(t< zElS|d9|ttmC~5hih|0NFCoupdSSO94zYP*o-+Z^-KVmLZm4qsnst_f1uA8i!H@UMHpF$k5C$6k`{L?%-l8b5yJPJC_MJI|GwyXi?fy@;tNJ}!(O*_!`z`be z`OCf9BlBe%vH>K$Zd)_`CMiUOkuYlr_N=4AbDGUpAB`C;QIL6s>P zGa47N*gCnP2_)??Z$pbWD6B$slgfqi6`saz%cQ1#;~WAZfaMf??#68#Jr}Tsj#uV$9_>0SXjEXKUMXF$p{l!p0GTy;5uVm`>5aUNN6f?pu z*LdM)$J$IU=zeOEnEE;RCXUm+@7`ZW6JG0|keb``w>36uZ9nD(PfC*fSC4!U-n~?w z%2D66UTv6FNC_}RQ^KEGZ}ArVxH0mJLt@lmcuGyvD05Mc{DZ|cZ`a_8>dvgi`-c*g zlmu6%-gTe9R-mlyxgMg(FxJ0yaNh=^Z-V$&Z69X$4X2=&iXuL7Uxe{a0%}j>D8=N7 zYL3=2Q4BR#z5XM|G`+n_S$?`=bt+xRgvJg%J$6m@@|Eno8d1~%qG%0>nyvsNBC>j5-;k9*7E8f8BeEb+=`vXH`$(((i#S*F@XB0(X?VBZn|a ziqziX#qBcEJkN#jIib()#>6%sl?RKB-eiGm2nS1K2s?I<(baXOlX;g!%n*m^w`fgk z?t6Kyg8{925wDx5MH+Ub1o z(jjXkQo%H$DrofNf(maWzeYO7-(zc(;c6qy`ZfTCC!Kex`$Z7d^%f)Cdogi;cz%E8 z;{E!qng_jJ5ROO?7Q=C*i5g{l&aPkCEgRDQX5tUBzS{*zz(ASfXp(k=Y=)b=xlD#O z$zZink2P`Dl2ULoTrbY{t>mHDNK&!msJ6yMqE+bS(cv4_T?0_=_2!2^8#VupJj(tj zLRW}#&FHlO%Y8InKp|KB+k2n4^^4ZgiEm@qgwr>?^muEO8@{r-6^k)ZiUtbfQpEp~ z_9={S;pjnWPm3F>^9{_D$*t$mt%@ij_IQrf$4 zBUj+ry6mt*sf=Mmo7&nBmm$(FR9i9r<3*h=@zY0(L?)s$WrdL&^1}yspZO-%DnAF4 zE#JGdry>SV7^Y88tv>$aBPdYODD5BABRQe}u&W{?r#E_OHuKLGrLrg4V0k|7dL1%e zK*p`4y~HNuAJS8D#m^Gw@BO4-1;~e?bJeAui5CorS8atIG=C)JWvpM%Th$jXGxQTp zmo3DfEIcTKU80e@oOa77m<#~vZA#c3$99ow+$VcZ-aJUw)T70S_;MD0l^E%}PfGDC z+CTE!VM(mn()-p*n~>iD@%9>u7n-6`c1XfS<5IbU^&W-cLC@P>-TW*C$SJn?lfoj$ zMUYEh0*?HRkstQkY`;V3j(zm5SXf<2JD<(yv&IFW=K`i`AMRB{Tg;%5ZCf5#;og|z zcvmlO-bthCDWj<}XidTI(HwPG$il7wo+f>}BuR5Dhw0(gxK8$kib^s|gx{n|qsRc( zHTD$0xuOSDX@V-GOIGHDKm6`0k-grvK@t9vy|urGcZ6y6So2wb=q#b?^UZF0sB|dsByBSW456 zV}NmY3?DHNDGgkMP|BhxatG~Ti=O%o>QYUkK_6p#Y{B%KHoL;zE};-=GWhtI_L|7d z?z+(nzk=WAM4>LBN5YR%z7ludGDl9n)6WXIHkzmou69iB=pSyq9S^ITfNgs30{4ZR zfFhDy!aLvlZ2(RRAgfUti6ThPguC2bYtOEAHECwT(=_g)Hjsai8o;F?aMJ}1y>AN> zcVYpYApgX7)0+1xCI&X%z+@ZhK=d^on;9Vi9RK9e+HEKWh=bg#+C-W1EZA?SaQ<$} zeMKc8f~I35PVQYF>TLq4oic>%J|}3a61|pn<NC0<4BB&39X#+PO&R0>I9s19={eJqGqOhhk4ugW>fo2qzL69z4qte1biTnAEr9khg~^osNOhqYuRE%b27 ztkz#V(f!~h00&q5m3^-!Q1fuIg}U5#DW0QUp}KL6n~9fK?3j^jWz(3NNc@}qIPn@SE*&1F^ok7vWNhxS@RToH?`ucsc1 z;)rL3ae47wlJq|!{Q2lT*e@1-Ed{hebRyN+*G=(R+erNhr(BP2w+kcDu zuBZJwh^~%X%T)ACuyYxyh~L652JW~2PI4zjGLp~X!aI+-1lBht`euK(<5e4;CungW e>RSyTL9BnxHW9%`$MAnZA?&T4tST)?)c*&Ru}0HvT{kmVT`~A;I2p2nf+5NI05J=v^ z9_9`LNuYsvXr~15ZF&PF1_FsaM!=D_&pr$ag+dO8!{_sX6pzP)mP6@udR|^0i9~{f zKpYMU1cH+|91sWsgTb1bnuv5E*$*ZZa;Y2+1W&~SKO{H=1lq2Klc-cG2!iKusK6a6 z9s(oMArJ@<(TOm+P}teoSzBA%+S)4Ql7)2Q@bEAm0vZ??V6)j=p%CUr=92vYFrknR zAi-cn5|u+D;qh<)15Sj&s;jH1BmkELhr>yD2vA;MUr*)=84Ly-j_>d9Z*OmZ^5h8w zxD1p)Kx1QLAvHV*96*5g_V&W@02mwufhT2|0-+y_I668?rE++@8V;2TG}+qP!sCGq zoJs|e!*hVyAfrX| zqDd!@0CnEi_okZz#hKZACZt}zay^xGfiFFM?yslPzV^dqW^T=DpSz_$8||wz`FVF- zd8uYSYzKAm`oP$tT=yG}SW(7#a6upN3&BS9Q6_8{O@N?oJu3 zJKkM)T=#k|W!q>mUW1mxQV&E1LGsg#lM2XiLq-EVhM zDaXjyV$RGDTPfxVOpCu0H9I071Ye9gmDHhdL^Ekh8O35S28z_LI$#b2UPlW=r&B6_ zk0I8!O6drRO|~f8(Q$iGuyAf`-KFDj&sXobi8K>=J7YmXDz9B?IbyjAgF$bK`N$hP z2xU^fGs=xa_GkObE}E4{StvBp=)o9mnT=?^MteNDM)v7FkFLV4g3f zl_6P2=5a!8a)K=0`pzVF!Nx0PW;6#SnJNKZ@}70@+8TYjqSM=FlRk*kkNvv$h~Jdg z;OmUSwEG^ELAta1Pt!NTABn#%dSBj;(0cU#$esUW3=Vr&Ove0jMrZ&t-F$myXTMDn zju7kAJIMsvR@3tt21k0f3ikU%Iw1GfDt`4-zDhIs(SV$<0n+6L3PJKC0rIC$1VB3m zIuE&IM-GuGxmSa^+g2PjoEw_X^%gknF!ZzAj-$NGRuW}%QC{B}rwb_xW$uq(;WQL4 z)W1Z2(kcqKb1NI^nZ2@d5p(-wP;RH&t%ig8N@x$=BURad9&5O+!D4jQ$=YGi`VLjPuI_#k9o}qca!YGO%mQu&;$R}SLa*!Plu|#I*KOV zd!b#SW^Rak>E=3i;Fp(Ov&CAd1y5Gz_-NV0mUgN_$KNeh??iqvMOPRHe^goSTYlrh zp@qApU>M}EW=%BcsM5#6twv{F~3Bj%l^FSVWE?Ijr_Z!+kIa~rnuLx-A1K) zhi-}Ue6rgw=vE(dz5%Y2#LUEk;5tq(mjy5TSDHT>3fE1QybQm@89z?kRs zI2Ys&mF7R+Wgb_XakWy;sGCALTsNWRtXuhvaU=lsH1P6ErH4f5S%cS_AoMgw>|VIk zArdOZow3SA)^yli-I72nHLF%5PCD$VubFGVCr6dFQQI4CUucuwnj1eR4O)IuUOSxc z)BZ9a#hrEF&<*EoREBnfQH!Hl0{1*hoos~QL4b-o$%3jut6#0yVTeJE zf`&dX)5S+U;(^=kM~8_J^nLBmrfF$7FEPiZx!w=x0n!)PZgB|)zr{7Su|rI~E+x-# zN{C3ol&=M>trWAgfn~`g)`)GBNL$uF8W!%Nu$Q zT-m=zniZ=K4JaGxERNo$yckF{S*!e-;)&1W9fcknwPT#>DFrDA@1+%}Iv3SE8U0$_ zF8f6A;g|yA##9KbEwW~%+GscT+b=?ZZNJCg;(Hd*j;|{;tD-KhqafZO)lBtNy|ce% zO^tf|i<6#D}#WkUko=$!L^4j9{ z;jS*ubn4+8)3|x`SI3n7@}T&%`%~?FX0nBPP{Cy1Y)MFAgKT#wE$j54>HLBOJ%Vc@ zMovXFU?*laZTx4=D~>NupS*p;+!Xzr*~RsA!C8MSH<*;AV?30H6w7IFs5sb>f%A^v z^z>5VG(}3@7(Ct`r?tnnd`4Y4)a%$WC*>4Wa9G)N+Q+?|AeB;~XZe=CK5H2_^5Wh#sv{k`EI$QyKyn{Y{e=2O{$^`TJ z-a+$(bQ`10f804-s%e)(>sR`lcZx}r=pCCz_r8k-lzoU3)h-$B2c_Q3zkt5JM_^hQ zInI4(EyYOJU5>e&8OFK3xaQD?{8m@!+Mo3M;rOickQ$ZZ^kA{|bg#;oq9}$* zr{}4As`o>lX(7&x#?~Z-B-8}D#ym)N-Z*9u)f;MEKZ-REHry0*g*Nu}lh(8koOAKg zj|E~sUduqVbtQOb4nsBz(z>g>E+N{cO4G(xa zsQcG}p0Eq9Z)m)+Ymkt>O&{M6jpaiyi9~N;&*EL*bX|{r+-8fCikNfTeny6|;#(?c zYGbHtKk`13?&9C0?e*j0KCtXXfdae1_@M5WV`m31&-DLD-Z2;$_VpL%U=SZxqNeDa zz1Qt&R{m=hB{_R4G8x-k4tzEaD|8oYtAh!=QoLhRZC9_mT;dRR=?e9n1 z>MGxooi9by*W@0t&?NKqbDZxQ!=d(a!?+W-FjX;OCrp`3;#3oIthh5+J7Oo($ntJ) zS$p4$$qO4Fjq7K}@tPIS9F%I%CbzhkWwjsQC}frHk2pV*qMfxl>G<%u^5tMh`Qk4l zojM)OYC#yaq(4JXZecW@52`@H4L{t{`zQRqMRkV>j;n`;*cnEKb}_=MZ=&~s+)SJd zk2*E0Td7Uk;SCZG@6p}$Tb}c+_bO_7zpb8Ng;31u-WRkT@B$k>9(mPwTsv~%lB6f- zXiCH5KTr9dI`&H)3bkB^j2c{8Z9bh{5z5MO&~h?ByT>S|TY6$#GzKa=eY=CSe2r-g;mOJQ0y(~S&y6$P3DE5K!H0`7NoR%s&uerIH?KmhF)Lr zooXfPI+>sUq)VKu2$f0~y+G?|HDsh(7oc0@g{|D(Js)%X=UK@ROVG@<3cVNVb3=}W zq;J2DGwYlj(%!Hjr{z;f5B=aXJk?yj_Rpt(wAr@h;kiP^rq&s7`nnW5v2!7rz(h zWL`-a=Z~JN7bxSLcDRq%TJ(BNI1va|Dk&oOFdTRiSCdUi6=4buXoeRm*vXI=PvZD5 zR_C|~tbrY&u}X;#+_Ss=za_K9?Dg8EHGwn&n=DfNu?siHJo-M}{S$heenYh1sL#AK z=LCA(-i9ErvaKUhIoyU&!j9a-qSPjfpoEXsIEye%W-%LqJ)JCa9DM~)@v&3}sKn#I zg~qL95$0zwAZ8twkkB$;!G0%Y!OvYPuzrlur!ikP1M(>CuUg$$ZxfUjv)Rso%q?N8 zla_#t|A)|hB1K)p2vRK%Er7H2>!D9IU;m4{<=ED0qU^H98csbewTlGvF=X8^{ z@NH|7N#BPh6y8$e=N@+-pITjQn*Op;+a_8PE3AIwtPEj*Q(E&4;ai3bR_@X?&Y=Bosj7_bWNSmNBT5j}To!Xnn@ z|9PtKcfgY%Kwz6~&Fyl?eoqqOlSLxKZ5d6_)8b0}7ay(zmP)cHnF3#2=&I|sEsq80 zTU}@PFFNy%0j=Ml$EQR^kyrxZwFGe7#iFRFS(UOss}$G}^vzh59m*vBi>Y;Iz}{0y zR}^~CSel{6@94Md>UWsiHl&LB{<3BR-T(DNolMZcx*eWysue)SXHcAjuxnOp;s1|rP z=_0msqfado7)VDBw;_71_LeOVv)L!c-O@@9TU7FGln6LN-uSM<_9S%fw4A@|LD;B=AP6N%d!sRr&@uvcC#FQy@s5QV z$jjttYSP#=_Lp6PQ`x(-(E=K~PQnmD5ECdD`ymLiojPf9G`4cnOvE9NDxyKGCyo7N z`*3Z4(94!Ux*^^0JaYVJo8ZbpO$4D#v*wWqDj`^%jpfrFSptog6r7wqay&x(eE`Fq z&{ZT`DuPy3vipgu@e8~iNbHYo9#M2c&2^1A)PGKF7k_xbKS;vWBF|zGv>$Ub1Y04> z7?Tw!@29bsO&Gjy$nnqd%>OWS{Vzi^Bk{KpRco1HnK*EU({7b8KAj)(Ggf)fvP6_I zP42vjWu0bPtp#1q%lHdxB|z8v(%3_1S!d>X38Q&ukys=)8o|oK6u$QD|5j!oVBg*5 z<$)T%&cq?HvrI4&8(b{X#ZlY{tVN)809dfP-ij*2f#U*q(hc#SB+oK)|G7P4SS~6R zb&TRx2u2%dILnrG(+)LGLwzMw+`^4d4olP6i8#3-JsP`p3g9$v>+hY9tJ>yUM<@Jm z=uRgv1a#)RVJ<+?g_X4uCO~gQZ!@S6b!Xr*adNKigdp2(@D?1_ugXVMx2L5U%DM^2 z$XKB-gjH;W-UL*C!11a=Vkfl!L=bEk=tkuDL6>P8*3DEpp$)y+8V;1u*q}JF8{tgE zkKAj$QyqfA8<>?;fDDZyWn_M?=a0JRAL^+jOy#zIGnt3dCVBnCulSDp^3;va`|Z-- zYHxK1JDlFX_#sg9 diff --git a/img/readme/wechat.png b/img/readme/wechat.png deleted file mode 100644 index b8057604d16ad255000c635210c80a825754e8fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67852 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 Date: Mon, 28 Oct 2019 19:25:29 +0800 Subject: [PATCH 15/30] =?UTF-8?q?=E4=BF=AE=E6=94=B9map=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java | 3 ++- .../excel/analysis/v07/handlers/DefaultCellHandler.java | 3 ++- .../alibaba/excel/read/listener/ModelBuildEventListener.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java index 21977a4..a489cce 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -3,6 +3,7 @@ package com.alibaba.excel.analysis.v03; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -77,7 +78,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { public XlsSaxAnalyser(AnalysisContext context, POIFSFileSystem poifsFileSystem) { this.analysisContext = context; - this.records = new TreeMap(); + this.records = new LinkedHashMap(); this.poifsFileSystem = poifsFileSystem; analysisContext.readWorkbookHolder().setPoifsFileSystem(poifsFileSystem); } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 0d44a95..002999d 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java @@ -9,6 +9,7 @@ import static com.alibaba.excel.constant.ExcelXmlConstants.CELL_VALUE_TYPE_TAG; import java.math.BigDecimal; import java.util.Deque; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; import java.util.TreeMap; @@ -37,7 +38,7 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder private final AnalysisContext analysisContext; private Deque currentTagDeque = new LinkedList(); private int curCol; - private Map curRowContent = new TreeMap(); + private Map curRowContent = new LinkedHashMap(); private CellData currentCellData; private StringBuilder dataStringBuilder; private StringBuilder formulaStringBuilder; diff --git a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java index a368024..1cfe182 100644 --- a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java +++ b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java @@ -2,6 +2,7 @@ package com.alibaba.excel.read.listener; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -44,7 +45,7 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener AnalysisContext context) { int index = 0; if (context.readWorkbookHolder().getDefaultReturnMap()) { - Map map = new HashMap(cellDataMap.size() * 4 / 3 + 1); + Map map = new LinkedHashMap(cellDataMap.size() * 4 / 3 + 1); for (Map.Entry entry : cellDataMap.entrySet()) { Integer key = entry.getKey(); CellData cellData = entry.getValue(); From 6e6896d4a7add91f70349212e09f8a94a52949ae Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Mon, 28 Oct 2019 19:26:03 +0800 Subject: [PATCH 16/30] =?UTF-8?q?=E4=BF=AE=E6=94=B9map=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update.md b/update.md index f389bbf..edc64f4 100644 --- a/update.md +++ b/update.md @@ -1,5 +1,5 @@ # 2.1.0-bea5 - +* 修改map返回为LinkedHashMap # 2.1.0-beta4 * 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747) From 2205ade4bebf6d06c638377429d3c097249ed8a6 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Fri, 1 Nov 2019 19:53:06 +0800 Subject: [PATCH 17/30] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E8=BF=94=E5=9B=9E=E5=AF=B9=E8=B1=A1=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=B3=9B=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++-- .../java/com/alibaba/excel/ExcelReader.java | 21 +------- .../java/com/alibaba/excel/ExcelWriter.java | 20 ++++++- .../excel/analysis/ExcelAnalyserImpl.java | 8 +++ .../excel/analysis/v03/XlsSaxAnalyser.java | 2 +- .../v03/handlers/BofRecordHandler.java | 2 +- .../v07/handlers/DefaultCellHandler.java | 3 +- .../alibaba/excel/context/WriteContext.java | 5 +- .../excel/context/WriteContextImpl.java | 33 +++++++++--- .../converters/DefaultConverterLoader.java | 2 + .../converters/url/UrlImageConverter.java | 52 +++++++++++++++++++ .../read/builder/ExcelReaderSheetBuilder.java | 4 +- .../metadata/holder/AbstractReadHolder.java | 3 ++ .../alibaba/excel/util/ConverterUtils.java | 11 +++- .../com/alibaba/excel/util/DateUtils.java | 3 ++ .../com/alibaba/excel/write/ExcelBuilder.java | 5 +- .../alibaba/excel/write/ExcelBuilderImpl.java | 24 +++++---- .../write/builder/ExcelWriterBuilder.java | 8 +++ .../excel/write/metadata/WriteWorkbook.java | 12 +++++ .../metadata/holder/WriteWorkbookHolder.java | 22 +++++++- .../easyexcel/test/demo/fill/FillTest.java | 11 ++++ .../easyexcel/test/demo/read/DemoDAO.java | 15 ++++++ .../test/demo/read/DemoDataListener.java | 34 ++++++++++++ .../easyexcel/test/demo/read/ReadTest.java | 10 ++-- .../easyexcel/test/demo/web/UploadDAO.java | 20 +++++++ .../test/demo/web/UploadDataListener.java | 38 +++++++++++++- .../easyexcel/test/demo/web/WebTest.java | 43 +++++++++++++-- .../easyexcel/test/demo/write/ImageData.java | 7 +++ .../easyexcel/test/demo/write/WriteTest.java | 5 +- .../easyexcel/test/temp/read/HDListener.java | 42 +++++++++++++++ .../test/temp/read/HeadReadData.java | 18 +++++++ .../test/temp/read/HeadReadTest.java | 25 +++++++++ update.md | 9 +++- 33 files changed, 459 insertions(+), 67 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java diff --git a/README.md b/README.md index fd5c5a2..1251ebd 100644 --- a/README.md +++ b/README.md @@ -62,16 +62,19 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java) ```java /** - * 文件下载 + * 文件下载(失败了会返回一个有部分数据的Excel) *

1. 创建excel对应的实体对象 参照{@link DownloadData} *

2. 设置返回的 参数 *

3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { + // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); - response.setHeader("Content-disposition", "attachment;filename=demo.xlsx"); + // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 + String fileName = URLEncoder.encode("测试", "UTF-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); } @@ -84,7 +87,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { - EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead(); + EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; } ``` diff --git a/src/main/java/com/alibaba/excel/ExcelReader.java b/src/main/java/com/alibaba/excel/ExcelReader.java index 0887e32..07a373b 100644 --- a/src/main/java/com/alibaba/excel/ExcelReader.java +++ b/src/main/java/com/alibaba/excel/ExcelReader.java @@ -14,7 +14,6 @@ import com.alibaba.excel.analysis.ExcelReadExecutor; import com.alibaba.excel.cache.MapCache; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; -import com.alibaba.excel.exception.ExcelAnalysisException; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.parameter.AnalysisParam; import com.alibaba.excel.read.listener.ReadListener; @@ -35,8 +34,6 @@ public class ExcelReader { */ private ExcelAnalyser excelAnalyser; - private boolean finished = false; - /** * Create new reader * @@ -160,7 +157,6 @@ public class ExcelReader { * Parse all sheet content by default */ public void readAll() { - checkFinished(); excelAnalyser.analysis(null, Boolean.TRUE); } @@ -181,7 +177,6 @@ public class ExcelReader { * @return */ public ExcelReader read(List readSheetList) { - checkFinished(); excelAnalyser.analysis(readSheetList, Boolean.FALSE); return this; } @@ -231,7 +226,6 @@ public class ExcelReader { * @return */ public AnalysisContext analysisContext() { - checkFinished(); return excelAnalyser.analysisContext(); } @@ -241,7 +235,6 @@ public class ExcelReader { * @return */ public ExcelReadExecutor excelExecutor() { - checkFinished(); return excelAnalyser.excelExecutor(); } @@ -281,10 +274,6 @@ public class ExcelReader { * Complete the entire read file.Release the cache and close stream. */ public void finish() { - if (finished) { - return; - } - finished = true; excelAnalyser.finish(); } @@ -294,19 +283,11 @@ public class ExcelReader { */ @Override protected void finalize() { - if (finished) { - return; - } try { - excelAnalyser.finish(); + finish(); } catch (Throwable e) { LOGGER.warn("Destroy object failed", e); } } - private void checkFinished() { - if (finished) { - throw new ExcelAnalysisException("Can not use a finished reader."); - } - } } diff --git a/src/main/java/com/alibaba/excel/ExcelWriter.java b/src/main/java/com/alibaba/excel/ExcelWriter.java index 65a99fe..a0b4795 100644 --- a/src/main/java/com/alibaba/excel/ExcelWriter.java +++ b/src/main/java/com/alibaba/excel/ExcelWriter.java @@ -5,6 +5,9 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.metadata.Table; @@ -31,6 +34,8 @@ import com.alibaba.excel.write.metadata.fill.FillConfig; * @author jipengfei */ public class ExcelWriter { + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelWriter.class); + private ExcelBuilder excelBuilder; /** @@ -320,7 +325,20 @@ public class ExcelWriter { * Close IO */ public void finish() { - excelBuilder.finish(); + excelBuilder.finish(false); + } + + /** + * Prevents calls to {@link #finish} from freeing the cache + * + */ + @Override + protected void finalize() { + try { + finish(); + } catch (Throwable e) { + LOGGER.warn("Destroy object failed", e); + } } /** diff --git a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java index 4002b84..f3ef950 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -34,6 +34,10 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { private AnalysisContext analysisContext; private ExcelReadExecutor excelReadExecutor; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; public ExcelAnalyserImpl(ReadWorkbook readWorkbook) { try { @@ -121,6 +125,10 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { @Override public void finish() { + if (finished) { + return; + } + finished = true; if (analysisContext == null || analysisContext.readWorkbookHolder() == null) { return; } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java index a489cce..76a869a 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -121,7 +121,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { private void init() { lastRowNumber = 0; lastColumnNumber = 0; - records = new TreeMap(); + records = new LinkedHashMap(); buildXlsRecordHandlers(); } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java index 3a86294..083edbc 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java @@ -73,7 +73,7 @@ public class BofRecordHandler extends AbstractXlsRecordHandler { readSheet = SheetUtils.match(readSheet, readSheetList, readAll, context.readWorkbookHolder().getGlobalConfiguration()); if (readSheet != null) { - if (readSheet.getSheetNo() != 0) { + if (readSheet.getSheetNo() != 0 && context.readSheetHolder() != null) { // Prompt for the end of the previous form read context.readSheetHolder().notifyAfterAllAnalysed(context); } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 002999d..e0b9c41 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java @@ -12,7 +12,6 @@ import java.util.Deque; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; -import java.util.TreeMap; import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.xssf.model.StylesTable; @@ -55,7 +54,7 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder @Override public void clearResult() { - curRowContent = new TreeMap(); + curRowContent = new LinkedHashMap(); } @Override diff --git a/src/main/java/com/alibaba/excel/context/WriteContext.java b/src/main/java/com/alibaba/excel/context/WriteContext.java index c6e85c9..0a59e1d 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContext.java +++ b/src/main/java/com/alibaba/excel/context/WriteContext.java @@ -66,9 +66,10 @@ public interface WriteContext { /** * close + * + * @param onException */ - void finish(); - + void finish(boolean onException); /** * Current sheet diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index ec49ea3..3f2b51a 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -65,6 +65,10 @@ public class WriteContextImpl implements WriteContext { * Configuration of currently operated cell */ private WriteHolder currentWriteHolder; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; public WriteContextImpl(WriteWorkbook writeWorkbook) { if (writeWorkbook == null) { @@ -249,23 +253,36 @@ public class WriteContextImpl implements WriteContext { } @Override - public void finish() { + public void finish(boolean onException) { + if (finished) { + return; + } + finished = true; WriteHandlerUtils.afterWorkbookDispose(this); if (writeWorkbookHolder == null) { return; } Throwable throwable = null; - boolean isOutputStreamEncrypt = false; - try { - isOutputStreamEncrypt = doOutputStreamEncrypt07(); - } catch (Throwable t) { - throwable = t; + // Determine if you need to write excel + boolean writeExcel = !onException; + if (writeWorkbookHolder.getWriteExcelOnException()) { + writeExcel = Boolean.TRUE; + } + // No data is written if an exception is thrown + if (writeExcel) { + try { + isOutputStreamEncrypt = doOutputStreamEncrypt07(); + } catch (Throwable t) { + throwable = t; + } } if (!isOutputStreamEncrypt) { try { - writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); + if (writeExcel) { + writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); + } writeWorkbookHolder.getWorkbook().close(); } catch (Throwable t) { throwable = t; @@ -289,7 +306,7 @@ public class WriteContextImpl implements WriteContext { throwable = t; } - if (!isOutputStreamEncrypt) { + if (writeExcel && !isOutputStreamEncrypt) { try { doFileEncrypt07(); } catch (Throwable t) { diff --git a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java index e53c1f8..77e8b59 100644 --- a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java +++ b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java @@ -37,6 +37,7 @@ import com.alibaba.excel.converters.string.StringBooleanConverter; import com.alibaba.excel.converters.string.StringErrorConverter; import com.alibaba.excel.converters.string.StringNumberConverter; import com.alibaba.excel.converters.string.StringStringConverter; +import com.alibaba.excel.converters.url.UrlImageConverter; /** * Load default handler @@ -71,6 +72,7 @@ public class DefaultConverterLoader { putWriteConverter(new InputStreamImageConverter()); putWriteConverter(new ByteArrayImageConverter()); putWriteConverter(new BoxingByteArrayImageConverter()); + putWriteConverter(new UrlImageConverter()); return defaultWriteConverter; } diff --git a/src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java b/src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java new file mode 100644 index 0000000..b622d66 --- /dev/null +++ b/src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java @@ -0,0 +1,52 @@ +package com.alibaba.excel.converters.url; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.util.IoUtils; + +/** + * Url and image converter + * + * @since 2.1.1 + * @author Jiaju Zhuang + */ +public class UrlImageConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return URL.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.IMAGE; + } + + @Override + public URL convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + throw new UnsupportedOperationException("Cannot convert images to url."); + } + + @Override + public CellData convertToExcelData(URL value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws IOException { + InputStream inputStream = null; + try { + inputStream = value.openStream(); + byte[] bytes = IoUtils.toByteArray(inputStream); + return new CellData(bytes); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + +} diff --git a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java index af3b05e..d494e23 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java @@ -166,7 +166,7 @@ public class ExcelReaderSheetBuilder { * * @return */ - public List doReadSync() { + public List doReadSync() { if (excelReader == null) { throw new ExcelAnalysisException("Must use 'EasyExcelFactory.read().sheet()' to call this method"); } @@ -174,7 +174,7 @@ public class ExcelReaderSheetBuilder { registerReadListener(syncReadListener); excelReader.read(build()); excelReader.finish(); - return syncReadListener.getList(); + return (List)syncReadListener.getList(); } } diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 157f90e..786d438 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -208,6 +208,9 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH List headNameList = headData.getHeadNameList(); String headName = headNameList.get(headNameList.size() - 1); for (Map.Entry stringEntry : dataMap.entrySet()) { + if (stringEntry == null) { + continue; + } String headString = stringEntry.getValue(); Integer stringKey = stringEntry.getKey(); if (StringUtils.isEmpty(headString)) { diff --git a/src/main/java/com/alibaba/excel/util/ConverterUtils.java b/src/main/java/com/alibaba/excel/util/ConverterUtils.java index b6244c2..200440e 100644 --- a/src/main/java/com/alibaba/excel/util/ConverterUtils.java +++ b/src/main/java/com/alibaba/excel/util/ConverterUtils.java @@ -33,10 +33,17 @@ public class ConverterUtils { */ public static Map convertToStringMap(Map cellDataMap, ReadHolder readHolder) { Map stringMap = new HashMap(cellDataMap.size() * 4 / 3 + 1); + int index = 0; for (Map.Entry entry : cellDataMap.entrySet()) { + Integer key = entry.getKey(); CellData cellData = entry.getValue(); + while (index < key) { + stringMap.put(index, null); + index++; + } + index++; if (cellData.getType() == CellDataTypeEnum.EMPTY) { - stringMap.put(entry.getKey(), null); + stringMap.put(key, null); continue; } Converter converter = @@ -46,7 +53,7 @@ public class ConverterUtils { "Converter not found, convert " + cellData.getType() + " to String"); } try { - stringMap.put(entry.getKey(), + stringMap.put(key, (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); } catch (Exception e) { throw new ExcelDataConvertException("Convert data " + cellData + " to String error ", e); diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index 1ea05c0..0df5cf4 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -12,6 +12,7 @@ import com.alibaba.excel.exception.ExcelDataConvertException; * @author Jiaju Zhuang **/ public class DateUtils { + public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss"; @@ -65,6 +66,8 @@ public class DateUtils { return DATE_FORMAT_17; case 14: return DATE_FORMAT_14; + case 10: + return DATE_FORMAT_10; default: throw new ExcelDataConvertException("can not find date format for:" + dateString); } diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java index 8a7d57d..4ef3b42 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java @@ -71,11 +71,14 @@ public interface ExcelBuilder { /** * Close io + * + * @param onException */ - void finish(); + void finish(boolean onException); /** * add password + * * @param data * @param writeSheet * @param writeTable diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index fc017fd..9658f55 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -31,10 +31,10 @@ public class ExcelBuilderImpl implements ExcelBuilder { FileUtils.createPoiFilesDirectory(); context = new WriteContextImpl(writeWorkbook); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } @@ -57,10 +57,10 @@ public class ExcelBuilderImpl implements ExcelBuilder { } excelWriteAddExecutor.add(data); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } @@ -80,18 +80,22 @@ public class ExcelBuilderImpl implements ExcelBuilder { } excelWriteFillExecutor.fill(data, fillConfig); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } + private void finishOnException() { + finish(true); + } + @Override - public void finish() { + public void finish(boolean onException) { if (context != null) { - context.finish(); + context.finish(onException); } } @@ -108,10 +112,10 @@ public class ExcelBuilderImpl implements ExcelBuilder { } excelWriteAddExecutor.add(data); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java index 8af1e62..f0d37ed 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java @@ -146,6 +146,14 @@ public class ExcelWriterBuilder { return this; } + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + public ExcelWriterBuilder writeExcelOnException(Boolean writeExcelOnException) { + writeWorkbook.setWriteExcelOnException(writeExcelOnException); + return this; + } + /** * The default is all excel objects.if true , you can use {@link com.alibaba.excel.annotation.ExcelIgnore} ignore a * field. if false , you must use {@link com.alibaba.excel.annotation.ExcelProperty} to use a filed. diff --git a/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java index 738299a..c28e041 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java +++ b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java @@ -63,6 +63,10 @@ public class WriteWorkbook extends WriteBasicParameter { * Comment and RichTextString are only supported in memory mode. */ private Boolean inMemory; + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + private Boolean writeExcelOnException; /** * The default is all excel objects.Default is true. *

@@ -169,4 +173,12 @@ public class WriteWorkbook extends WriteBasicParameter { public void setInMemory(Boolean inMemory) { this.inMemory = inMemory; } + + public Boolean getWriteExcelOnException() { + return writeExcelOnException; + } + + public void setWriteExcelOnException(Boolean writeExcelOnException) { + this.writeExcelOnException = writeExcelOnException; + } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index 4de6ade..c8f7555 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java @@ -103,6 +103,10 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { * Comment and RichTextString are only supported in memory mode. */ private Boolean inMemory; + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + private Boolean writeExcelOnException; public WriteWorkbookHolder(WriteWorkbook writeWorkbook) { super(writeWorkbook, null, writeWorkbook.getConvertAllFiled()); @@ -128,7 +132,10 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { throw new ExcelGenerateException("Copy template failure.", e); } if (writeWorkbook.getExcelType() == null) { - if (file != null && file.getName().endsWith(ExcelTypeEnum.XLS.getValue())) { + boolean isXls = (file != null && file.getName().endsWith(ExcelTypeEnum.XLS.getValue())) + || (writeWorkbook.getTemplateFile() != null + && writeWorkbook.getTemplateFile().getName().endsWith(ExcelTypeEnum.XLS.getValue())); + if (isXls) { this.excelType = ExcelTypeEnum.XLS; } else { this.excelType = ExcelTypeEnum.XLSX; @@ -148,6 +155,11 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { } else { this.inMemory = writeWorkbook.getInMemory(); } + if (writeWorkbook.getWriteExcelOnException() == null) { + this.writeExcelOnException = Boolean.FALSE; + } else { + this.writeExcelOnException = writeWorkbook.getWriteExcelOnException(); + } } private void copyTemplate() throws IOException { @@ -281,6 +293,14 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.inMemory = inMemory; } + public Boolean getWriteExcelOnException() { + return writeExcelOnException; + } + + public void setWriteExcelOnException(Boolean writeExcelOnException) { + this.writeExcelOnException = writeExcelOnException; + } + @Override public HolderEnum holderType() { return HolderEnum.WORKBOOK; diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java index 0159cbc..32a0185 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java @@ -19,12 +19,15 @@ import com.alibaba.excel.write.metadata.fill.FillConfig; /** * 写的填充写法 * + * @since 2.1.1 * @author Jiaju Zhuang */ @Ignore public class FillTest { /** * 最简单的填充 + * + * @since 2.1.1 */ @Test public void simpleFill() { @@ -51,6 +54,8 @@ public class FillTest { /** * 填充列表 + * + * @since 2.1.1 */ @Test public void listFill() { @@ -76,6 +81,8 @@ public class FillTest { /** * 复杂的填充 + * + * @since 2.1.1 */ @Test public void complexFill() { @@ -105,6 +112,8 @@ public class FillTest { * 数据量大的复杂填充 *

* 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 */ @Test public void complexFillWithTable() { @@ -145,6 +154,8 @@ public class FillTest { /** * 横向的填充 + * + * @since 2.1.1 */ @Test public void horizontalFill() { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java new file mode 100644 index 0000000..8ca6b02 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java @@ -0,0 +1,15 @@ +package com.alibaba.easyexcel.test.demo.read; + +import java.util.List; + +/** + * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 + * + * @author Jiaju Zhuang + **/ +public class DemoDAO { + + public void save(List list) { + // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java index 434af3b..f26ff4b 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java @@ -23,19 +23,52 @@ public class DemoDataListener extends AnalysisEventListener { */ private static final int BATCH_COUNT = 5; List list = new ArrayList(); + /** + * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 + */ + private DemoDAO demoDAO; + + public DemoDataListener() { + // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 + demoDAO = new DemoDAO(); + } + + /** + * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 + * + * @param demoDAO + */ + public DemoDataListener(DemoDAO demoDAO) { + this.demoDAO = demoDAO; + } + /** + * 这个每一条数据解析都会来调用 + * + * @param data + * one row value. Is is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ @Override public void invoke(DemoData data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); + // 存储完成清理 list list.clear(); } } + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ @Override public void doAfterAllAnalysed(AnalysisContext context) { + // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); LOGGER.info("所有数据解析完成!"); } @@ -45,6 +78,7 @@ public class DemoDataListener extends AnalysisEventListener { */ private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); + demoDAO.save(list); LOGGER.info("存储数据库成功!"); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 9ebafa4..0dc22aa 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -202,17 +202,15 @@ public class ReadTest { public void synchronousRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish - List list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); - for (Object obj : list) { - DemoData data = (DemoData)obj; + List list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); + for (DemoData data : list) { LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish - list = EasyExcel.read(fileName).sheet().doReadSync(); - for (Object obj : list) { + List> listMap = EasyExcel.read(fileName).sheet().doReadSync(); + for (Map data : listMap) { // 返回每条数据的键值对 表示所在的列 和所在列的值 - Map data = (Map)obj; LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java new file mode 100644 index 0000000..eac9662 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java @@ -0,0 +1,20 @@ +package com.alibaba.easyexcel.test.demo.web; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.alibaba.easyexcel.test.demo.read.DemoData; + +/** + * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 + * + * @author Jiaju Zhuang + **/ +@Repository +public class UploadDAO { + + public void save(List list) { + // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java index 7475ced..e61a5d6 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java @@ -15,26 +15,61 @@ import com.alibaba.fastjson.JSON; * * @author Jiaju Zhuang */ +// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 public class UploadDataListener extends AnalysisEventListener { - private static final Logger LOGGER = LoggerFactory.getLogger(UploadDataListener.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(com.alibaba.easyexcel.test.demo.read.DemoDataListener.class); /** * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 5; List list = new ArrayList(); + /** + * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 + */ + private UploadDAO uploadDAO; + + public UploadDataListener() { + // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 + uploadDAO = new UploadDAO(); + } + + /** + * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 + * + * @param uploadDAO + */ + public UploadDataListener(UploadDAO uploadDAO) { + this.uploadDAO = uploadDAO; + } + /** + * 这个每一条数据解析都会来调用 + * + * @param data + * one row value. Is is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ @Override public void invoke(UploadData data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); + // 存储完成清理 list list.clear(); } } + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ @Override public void doAfterAllAnalysed(AnalysisContext context) { + // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); LOGGER.info("所有数据解析完成!"); } @@ -44,6 +79,7 @@ public class UploadDataListener extends AnalysisEventListener { */ private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); + uploadDAO.save(list); LOGGER.info("存储数据库成功!"); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java index cc52562..98f400a 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java @@ -2,14 +2,15 @@ package com.alibaba.easyexcel.test.demo.web; import java.io.IOException; import java.net.URLEncoder; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson.JSON; /** * web读写案例 @@ -25,8 +27,12 @@ import com.alibaba.excel.EasyExcel; **/ @Controller public class WebTest { + + @Autowired + private UploadDAO uploadDAO; + /** - * 文件下载 + * 文件下载(失败了会返回一个有部分数据的Excel) *

* 1. 创建excel对应的实体对象 参照{@link DownloadData} *

@@ -45,6 +51,35 @@ public class WebTest { EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); } + /** + * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel) + * + * @since 2.1.1 + */ + @GetMapping("downloadFailedUsingJson") + public void downloadFailedUsingJson(HttpServletResponse response) throws IOException { + // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman + try { + response.setContentType("application/vnd.ms-excel"); + response.setCharacterEncoding("utf-8"); + // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 + String fileName = URLEncoder.encode("测试", "UTF-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + // 这里需要设置不关闭流 + EasyExcel.write(response.getOutputStream(), DownloadData.class).autoCloseStream(Boolean.FALSE).sheet("模板") + .doWrite(data()); + } catch (Exception e) { + // 重置response + response.reset(); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + Map map = new HashMap(); + map.put("status", "failure"); + map.put("message", "下载文件失败" + e.getMessage()); + response.getWriter().println(JSON.toJSONString(map)); + } + } + /** * 文件上传 *

@@ -57,7 +92,7 @@ public class WebTest { @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { - EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead(); + EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java index 951026c..3fcb948 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.demo.write; import java.io.File; import java.io.InputStream; +import java.net.URL; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; @@ -27,4 +28,10 @@ public class ImageData { @ExcelProperty(converter = StringImageConverter.class) private String string; private byte[] byteArray; + /** + * 根据url导出 + * + * @since 2.1.1 + */ + private URL url; } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index f9a69db..3e27bd3 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.demo.write; import java.io.File; import java.io.InputStream; +import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -215,12 +216,14 @@ public class WriteTest { ImageData imageData = new ImageData(); list.add(imageData); String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"; - // 放入四种类型的图片 实际使用只要选一种即可 + // 放入五种类型的图片 实际使用只要选一种即可 imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath))); imageData.setFile(new File(imagePath)); imageData.setString(imagePath); inputStream = FileUtils.openInputStream(new File(imagePath)); imageData.setInputStream(inputStream); + imageData.setUrl(new URL( + "https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg")); EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list); } finally { if (inputStream != null) { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java new file mode 100644 index 0000000..196b2da --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java @@ -0,0 +1,42 @@ +package com.alibaba.easyexcel.test.temp.read; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.fastjson.JSON; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class HDListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(HDListener.class); + /** + * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.info("HEAD:{}", JSON.toJSONString(headMap)); + + } + + @Override + public void invoke(HeadReadData data, AnalysisContext context) { + LOGGER.info("index:{}", context.readRowHolder().getRowIndex()); + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("所有数据解析完成!"); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java new file mode 100644 index 0000000..38ee661 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java @@ -0,0 +1,18 @@ +package com.alibaba.easyexcel.test.temp.read; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Data +public class HeadReadData { + @ExcelProperty("头1") + private String h1; + @ExcelProperty({"头", "头2"}) + private String h2; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java new file mode 100644 index 0000000..f356524 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java @@ -0,0 +1,25 @@ +package com.alibaba.easyexcel.test.temp.read; + +import java.io.File; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.EasyExcel; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +public class HeadReadTest { + private static final Logger LOGGER = LoggerFactory.getLogger(HeadReadTest.class); + + @Test + public void test() throws Exception { + File file = new File("D:\\test\\headt1.xlsx"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).sheet().doRead(); + } + +} diff --git a/update.md b/update.md index edc64f4..4055d43 100644 --- a/update.md +++ b/update.md @@ -1,5 +1,12 @@ -# 2.1.0-bea5 +# 2.1.1 +* 发布正式版 * 修改map返回为LinkedHashMap +* 修改同步读取返回对象支持泛型 +* 修复03版不能直接读取第二个sheet的bug [Issue #772](https://github.com/alibaba/easyexcel/issues/772) +* 新增支持图片导出用URL [Issue #774](https://github.com/alibaba/easyexcel/issues/774) +* 加入多次关闭判断,防止多次关闭异常 +* 加入根据模板自动识别导出的excel类型 +* 修改默认失败后,不再往文件流写入数据。通过参数`writeExcelOnException` 参数设置异常了也要写入前面的数据。 # 2.1.0-beta4 * 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747) From cba3a9cea9b3e02f6c0fcfed52e6f41eeb369e09 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Mon, 4 Nov 2019 18:28:41 +0800 Subject: [PATCH 18/30] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E8=A7=A3=E6=9E=90class?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../excel/analysis/v03/XlsSaxAnalyser.java | 2 + .../v03/handlers/IndexRecordHandler.java | 42 +++++ .../v07/handlers/CountRowCellHandler.java | 4 +- .../excel/event/AnalysisEventListener.java | 2 +- .../exception/ExcelDataConvertException.java | 35 ++-- .../com/alibaba/excel/metadata/CellData.java | 7 +- .../metadata/property/ExcelHeadProperty.java | 46 +----- .../listener/ModelBuildEventListener.java | 3 +- .../metadata/holder/AbstractReadHolder.java | 3 +- .../read/metadata/holder/ReadSheetHolder.java | 28 +++- .../com/alibaba/excel/util/ClassUtils.java | 150 ++++++++++++++++++ .../alibaba/excel/util/ConverterUtils.java | 19 ++- .../com/alibaba/excel/util/DateUtils.java | 2 +- .../executor/AbstractExcelWriteExecutor.java | 16 +- .../write/executor/ExcelWriteAddExecutor.java | 18 +-- .../executor/ExcelWriteFillExecutor.java | 38 ++++- .../write/metadata/fill/AnalysisCell.java | 9 ++ .../test/demo/read/DemoExceptionListener.java | 4 +- .../test/demo/read/DemoHeadDataListener.java | 4 +- .../easyexcel/test/temp/FillTempTest.java | 110 +++++++++++++ .../easyexcel/test/temp/poi/PoiTest.java | 4 +- .../easyexcel/test/temp/read/HDListener.java | 1 + .../test/temp/read/HeadReadData.java | 2 + .../test/temp/read/HeadReadTest.java | 7 +- update.md | 5 + 26 files changed, 450 insertions(+), 113 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java create mode 100644 src/main/java/com/alibaba/excel/util/ClassUtils.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java diff --git a/pom.xml b/pom.xml index a618349..7dad24d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.0-beta4 + 2.1.1 jar easyexcel diff --git a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java index 76a869a..e0c150c 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -27,6 +27,7 @@ import com.alibaba.excel.analysis.ExcelReadExecutor; import com.alibaba.excel.analysis.v03.handlers.BlankOrErrorRecordHandler; import com.alibaba.excel.analysis.v03.handlers.BofRecordHandler; import com.alibaba.excel.analysis.v03.handlers.FormulaRecordHandler; +import com.alibaba.excel.analysis.v03.handlers.IndexRecordHandler; import com.alibaba.excel.analysis.v03.handlers.LabelRecordHandler; import com.alibaba.excel.analysis.v03.handlers.MissingCellDummyRecordHandler; import com.alibaba.excel.analysis.v03.handlers.NoteRecordHandler; @@ -211,6 +212,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { recordHandlers.add(new RkRecordHandler()); recordHandlers.add(new SstRecordHandler()); recordHandlers.add(new MissingCellDummyRecordHandler()); + recordHandlers.add(new IndexRecordHandler(analysisContext)); Collections.sort(recordHandlers); } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java new file mode 100644 index 0000000..6837ebd --- /dev/null +++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java @@ -0,0 +1,42 @@ +package com.alibaba.excel.analysis.v03.handlers; + +import org.apache.poi.hssf.record.IndexRecord; +import org.apache.poi.hssf.record.Record; + +import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler; +import com.alibaba.excel.context.AnalysisContext; + +/** + * Record handler + * + * @author Jiaju Zhuang + */ +public class IndexRecordHandler extends AbstractXlsRecordHandler { + + private AnalysisContext context; + + public IndexRecordHandler(AnalysisContext context) { + this.context = context; + } + + @Override + public boolean support(Record record) { + return record instanceof IndexRecord; + } + + @Override + public void init() {} + + @Override + public void processRecord(Record record) { + if (context.readSheetHolder() == null) { + return; + } + context.readSheetHolder().setApproximateTotalRowNumber(((IndexRecord)record).getLastRowAdd1()); + } + + @Override + public int getOrder() { + return 1; + } +} diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java index ea63409..b94483f 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java @@ -10,7 +10,7 @@ import com.alibaba.excel.context.AnalysisContext; /** * Cell Handler - * + * * @author jipengfei */ public class CountRowCellHandler implements XlsxCellHandler { @@ -31,7 +31,7 @@ public class CountRowCellHandler implements XlsxCellHandler { String d = attributes.getValue(DIMENSION_REF); String totalStr = d.substring(d.indexOf(":") + 1, d.length()); String c = totalStr.toUpperCase().replaceAll("[A-Z]", ""); - analysisContext.readSheetHolder().setTotal(Integer.parseInt(c)); + analysisContext.readSheetHolder().setApproximateTotalRowNumber(Integer.parseInt(c)); } @Override diff --git a/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java b/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java index 89bca75..176bd79 100644 --- a/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java +++ b/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java @@ -16,7 +16,7 @@ public abstract class AnalysisEventListener implements ReadListener { @Override public void invokeHead(Map headMap, AnalysisContext context) { - invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context.currentReadHolder()), context); + invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context), context); } /** diff --git a/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java b/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java index 2ffdde5..5198c20 100644 --- a/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java +++ b/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java @@ -1,5 +1,6 @@ package com.alibaba.excel.exception; +import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.write.builder.ExcelWriterBuilder; @@ -17,6 +18,10 @@ public class ExcelDataConvertException extends RuntimeException { * NotNull. */ private Integer columnIndex; + /** + * NotNull. + */ + private CellData cellData; /** * Nullable.Only when the header is configured and when the class header is used is not null. * @@ -24,34 +29,24 @@ public class ExcelDataConvertException extends RuntimeException { */ private ExcelContentProperty excelContentProperty; - public ExcelDataConvertException(String message) { - super(message); - } - - public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, ExcelContentProperty excelContentProperty, - String message) { + public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData, + ExcelContentProperty excelContentProperty, String message) { super(message); this.rowIndex = rowIndex; this.columnIndex = columnIndex; + this.cellData = cellData; this.excelContentProperty = excelContentProperty; } - public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, ExcelContentProperty excelContentProperty, - String message, Throwable cause) { + public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData, + ExcelContentProperty excelContentProperty, String message, Throwable cause) { super(message, cause); this.rowIndex = rowIndex; this.columnIndex = columnIndex; + this.cellData = cellData; this.excelContentProperty = excelContentProperty; } - public ExcelDataConvertException(String message, Throwable cause) { - super(message, cause); - } - - public ExcelDataConvertException(Throwable cause) { - super(cause); - } - public Integer getRowIndex() { return rowIndex; } @@ -75,4 +70,12 @@ public class ExcelDataConvertException extends RuntimeException { public void setExcelContentProperty(ExcelContentProperty excelContentProperty) { this.excelContentProperty = excelContentProperty; } + + public CellData getCellData() { + return cellData; + } + + public void setCellData(CellData cellData) { + this.cellData = cellData; + } } diff --git a/src/main/java/com/alibaba/excel/metadata/CellData.java b/src/main/java/com/alibaba/excel/metadata/CellData.java index a6b7af5..7cd5496 100644 --- a/src/main/java/com/alibaba/excel/metadata/CellData.java +++ b/src/main/java/com/alibaba/excel/metadata/CellData.java @@ -229,18 +229,21 @@ public class CellData { @Override public String toString() { if (type == null) { - return "empty"; + return StringUtils.EMPTY; } switch (type) { case NUMBER: return numberValue.toString(); case BOOLEAN: return booleanValue.toString(); + case DIRECT_STRING: case STRING: case ERROR: return stringValue; + case IMAGE: + return "image[" + imageValue.length + "]"; default: - return "empty"; + return StringUtils.EMPTY; } } diff --git a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java index 9d6a544..782f635 100644 --- a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java +++ b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java @@ -24,6 +24,7 @@ import com.alibaba.excel.exception.ExcelCommonException; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.Holder; +import com.alibaba.excel.util.ClassUtils; import com.alibaba.excel.util.StringUtils; import com.alibaba.excel.write.metadata.holder.AbstractWriteHolder; @@ -119,51 +120,10 @@ public class ExcelHeadProperty { if (headClazz == null) { return; } - List fieldList = new ArrayList(); - Class tempClass = headClazz; - // When the parent class is null, it indicates that the parent class (Object class) has reached the top - // level. - while (tempClass != null) { - Collections.addAll(fieldList, tempClass.getDeclaredFields()); - // Get the parent class and give it to yourself - tempClass = tempClass.getSuperclass(); - } - - ExcelIgnoreUnannotated excelIgnoreUnannotated = - (ExcelIgnoreUnannotated)headClazz.getAnnotation(ExcelIgnoreUnannotated.class); - // Screening of field + // Declared fields List defaultFieldList = new ArrayList(); Map customFiledMap = new TreeMap(); - for (Field field : fieldList) { - ExcelIgnore excelIgnore = field.getAnnotation(ExcelIgnore.class); - if (excelIgnore != null) { - ignoreMap.put(field.getName(), field); - continue; - } - ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); - boolean noExcelProperty = excelProperty == null - && ((convertAllFiled != null && !convertAllFiled) || excelIgnoreUnannotated != null); - if (noExcelProperty) { - ignoreMap.put(field.getName(), field); - continue; - } - boolean isStaticFinalOrTransient = - (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) - || Modifier.isTransient(field.getModifiers()); - if (excelProperty == null && isStaticFinalOrTransient) { - ignoreMap.put(field.getName(), field); - continue; - } - if (excelProperty == null || excelProperty.index() < 0) { - defaultFieldList.add(field); - continue; - } - if (customFiledMap.containsKey(excelProperty.index())) { - throw new ExcelGenerateException("The index of '" + customFiledMap.get(excelProperty.index()).getName() - + "' and '" + field.getName() + "' must be inconsistent"); - } - customFiledMap.put(excelProperty.index(), field); - } + ClassUtils.declaredFields(headClazz, defaultFieldList, customFiledMap, ignoreMap, convertAllFiled); int index = 0; for (Field field : defaultFieldList) { diff --git a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java index 1cfe182..f5594db 100644 --- a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java +++ b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java @@ -93,7 +93,8 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener try { resultModel = excelReadHeadProperty.getHeadClazz().newInstance(); } catch (Exception e) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0, + new CellData(CellDataTypeEnum.EMPTY), null, "Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e); } Map headMap = excelReadHeadProperty.getHeadMap(); diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 786d438..02b2ef5 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -190,8 +190,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) { return; } - Map dataMap = - ConverterUtils.convertToStringMap(cellDataMap, analysisContext.currentReadHolder()); + Map dataMap = ConverterUtils.convertToStringMap(cellDataMap, analysisContext); ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty(); Map headMapData = excelHeadPropertyData.getHeadMap(); Map contentPropertyMapData = excelHeadPropertyData.getContentPropertyMap(); diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java index 64d1d6c..84dd4c4 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java @@ -26,10 +26,9 @@ public class ReadSheetHolder extends AbstractReadHolder { */ private String sheetName; /** - * get total row , Data may be inaccurate + * Gets the total number of rows , data may be inaccurate */ - @Deprecated - private Integer total; + private Integer approximateTotalRowNumber; public ReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { super(readSheet, readWorkbookHolder, readWorkbookHolder.getReadWorkbook().getConvertAllFiled()); @@ -71,12 +70,29 @@ public class ReadSheetHolder extends AbstractReadHolder { this.sheetName = sheetName; } + /** + * + * Approximate total number of rows + * + * @return + * @see #getApproximateTotalRowNumber() + */ + @Deprecated public Integer getTotal() { - return total; + return approximateTotalRowNumber; + } + + /** + * Approximate total number of rows + * + * @return + */ + public Integer getApproximateTotalRowNumber() { + return approximateTotalRowNumber; } - public void setTotal(Integer total) { - this.total = total; + public void setApproximateTotalRowNumber(Integer approximateTotalRowNumber) { + this.approximateTotalRowNumber = approximateTotalRowNumber; } @Override diff --git a/src/main/java/com/alibaba/excel/util/ClassUtils.java b/src/main/java/com/alibaba/excel/util/ClassUtils.java new file mode 100644 index 0000000..64bc29d --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/ClassUtils.java @@ -0,0 +1,150 @@ +package com.alibaba.excel.util; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.exception.ExcelCommonException; +import com.alibaba.excel.metadata.BaseRowModel; + +/** + * Class utils + * + * @author Jiaju Zhuang + **/ +public class ClassUtils { + private static final Map> FIELD_CACHE = + new ConcurrentHashMap>(); + + public static void declaredFields(Class clazz, List defaultFieldList, Map customFiledMap, + Map ignoreMap, Boolean convertAllFiled) { + FieldCache fieldCache = getFieldCache(clazz, convertAllFiled); + if (fieldCache != null) { + defaultFieldList.addAll(fieldCache.getDefaultFieldList()); + customFiledMap.putAll(fieldCache.getCustomFiledMap()); + ignoreMap.putAll(fieldCache.getIgnoreMap()); + } + } + + public static void declaredFields(Class clazz, List fieldList, Boolean convertAllFiled) { + FieldCache fieldCache = getFieldCache(clazz, convertAllFiled); + if (fieldCache != null) { + fieldList.addAll(fieldCache.getAllFieldList()); + } + } + + private static FieldCache getFieldCache(Class clazz, Boolean convertAllFiled) { + if (clazz == null) { + return null; + } + SoftReference fieldCacheSoftReference = FIELD_CACHE.get(clazz); + if (fieldCacheSoftReference != null && fieldCacheSoftReference.get() != null) { + return fieldCacheSoftReference.get(); + } + synchronized (clazz) { + fieldCacheSoftReference = FIELD_CACHE.get(clazz); + if (fieldCacheSoftReference != null && fieldCacheSoftReference.get() != null) { + return fieldCacheSoftReference.get(); + } + declaredFields(clazz, convertAllFiled); + } + return FIELD_CACHE.get(clazz).get(); + } + + private static void declaredFields(Class clazz, Boolean convertAllFiled) { + List tempFieldList = new ArrayList(); + Class tempClass = clazz; + // When the parent class is null, it indicates that the parent class (Object class) has reached the top + // level. + while (tempClass != null && tempClass != BaseRowModel.class) { + Collections.addAll(tempFieldList, tempClass.getDeclaredFields()); + // Get the parent class and give it to yourself + tempClass = tempClass.getSuperclass(); + } + // Screening of field + List defaultFieldList = new ArrayList(); + Map customFiledMap = new TreeMap(); + List allFieldList = new ArrayList(); + Map ignoreMap = new HashMap(16); + + ExcelIgnoreUnannotated excelIgnoreUnannotated = + (ExcelIgnoreUnannotated)clazz.getAnnotation(ExcelIgnoreUnannotated.class); + for (Field field : tempFieldList) { + ExcelIgnore excelIgnore = field.getAnnotation(ExcelIgnore.class); + if (excelIgnore != null) { + ignoreMap.put(field.getName(), field); + continue; + } + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + boolean noExcelProperty = excelProperty == null + && ((convertAllFiled != null && !convertAllFiled) || excelIgnoreUnannotated != null); + if (noExcelProperty) { + ignoreMap.put(field.getName(), field); + continue; + } + boolean isStaticFinalOrTransient = + (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) + || Modifier.isTransient(field.getModifiers()); + if (excelProperty == null && isStaticFinalOrTransient) { + ignoreMap.put(field.getName(), field); + continue; + } + if (excelProperty == null || excelProperty.index() < 0) { + defaultFieldList.add(field); + allFieldList.add(field); + continue; + } + if (customFiledMap.containsKey(excelProperty.index())) { + throw new ExcelCommonException("The index of '" + customFiledMap.get(excelProperty.index()).getName() + + "' and '" + field.getName() + "' must be inconsistent"); + } + customFiledMap.put(excelProperty.index(), field); + allFieldList.add(field); + } + + FIELD_CACHE.put(clazz, + new SoftReference(new FieldCache(defaultFieldList, customFiledMap, allFieldList, ignoreMap))); + } + + private static class FieldCache { + private List defaultFieldList; + private Map customFiledMap; + private List allFieldList; + private Map ignoreMap; + + public FieldCache(List defaultFieldList, Map customFiledMap, List allFieldList, + Map ignoreMap) { + this.defaultFieldList = defaultFieldList; + this.customFiledMap = customFiledMap; + this.allFieldList = allFieldList; + this.ignoreMap = ignoreMap; + } + + public List getDefaultFieldList() { + return defaultFieldList; + } + + public Map getCustomFiledMap() { + return customFiledMap; + } + + public List getAllFieldList() { + return allFieldList; + } + + public Map getIgnoreMap() { + return ignoreMap; + } + + } +} diff --git a/src/main/java/com/alibaba/excel/util/ConverterUtils.java b/src/main/java/com/alibaba/excel/util/ConverterUtils.java index 200440e..43b12c5 100644 --- a/src/main/java/com/alibaba/excel/util/ConverterUtils.java +++ b/src/main/java/com/alibaba/excel/util/ConverterUtils.java @@ -6,6 +6,7 @@ import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; +import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.converters.ConverterKeyBuild; import com.alibaba.excel.enums.CellDataTypeEnum; @@ -28,11 +29,12 @@ public class ConverterUtils { * Convert it into a String map * * @param cellDataMap - * @param readHolder + * @param context * @return */ - public static Map convertToStringMap(Map cellDataMap, ReadHolder readHolder) { + public static Map convertToStringMap(Map cellDataMap, AnalysisContext context) { Map stringMap = new HashMap(cellDataMap.size() * 4 / 3 + 1); + ReadHolder currentReadHolder = context.currentReadHolder(); int index = 0; for (Map.Entry entry : cellDataMap.entrySet()) { Integer key = entry.getKey(); @@ -47,16 +49,17 @@ public class ConverterUtils { continue; } Converter converter = - readHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); + currentReadHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); if (converter == null) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), key, cellData, null, "Converter not found, convert " + cellData.getType() + " to String"); } try { stringMap.put(key, - (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); + (String)(converter.convertToJavaData(cellData, null, currentReadHolder.globalConfiguration()))); } catch (Exception e) { - throw new ExcelDataConvertException("Convert data " + cellData + " to String error ", e); + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), key, cellData, null, + "Convert data " + cellData + " to String error ", e); } } return stringMap; @@ -123,13 +126,13 @@ public class ConverterUtils { converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType())); } if (converter == null) { - throw new ExcelDataConvertException(rowIndex, columnIndex, contentProperty, + throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty, "Converter not found, convert " + cellData.getType() + " to " + clazz.getName()); } try { return converter.convertToJavaData(cellData, contentProperty, globalConfiguration); } catch (Exception e) { - throw new ExcelDataConvertException(rowIndex, columnIndex, contentProperty, + throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty, "Convert data " + cellData + " to " + clazz + " error ", e); } } diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index 0df5cf4..a581a95 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -69,7 +69,7 @@ public class DateUtils { case 10: return DATE_FORMAT_10; default: - throw new ExcelDataConvertException("can not find date format for:" + dateString); + throw new IllegalArgumentException("can not find date format for:" + dateString); } } diff --git a/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java b/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java index a174629..1f09d47 100644 --- a/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java @@ -59,8 +59,9 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { case EMPTY: return cellData; default: - throw new ExcelDataConvertException("Not supported data:" + value + " return type:" + cell.getCellType() - + "at row:" + cell.getRow().getRowNum()); + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), cellData, + excelContentProperty, "Not supported data:" + value + " return type:" + cell.getCellType() + + "at row:" + cell.getRow().getRowNum()); } } @@ -102,7 +103,8 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { converter = currentWriteHolder.converterMap().get(ConverterKeyBuild.buildKey(clazz)); } if (converter == null) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), + new CellData(CellDataTypeEnum.EMPTY), excelContentProperty, "Can not find 'Converter' support class " + clazz.getSimpleName() + "."); } CellData cellData; @@ -110,11 +112,13 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { cellData = converter.convertToExcelData(value, excelContentProperty, currentWriteHolder.globalConfiguration()); } catch (Exception e) { - throw new ExcelDataConvertException("Convert data:" + value + " error,at row:" + cell.getRow().getRowNum(), - e); + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), + new CellData(CellDataTypeEnum.EMPTY), excelContentProperty, + "Convert data:" + value + " error,at row:" + cell.getRow().getRowNum(), e); } if (cellData == null || cellData.getType() == null) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), + new CellData(CellDataTypeEnum.EMPTY), excelContentProperty, "Convert data:" + value + " return null,at row:" + cell.getRow().getRowNum()); } return cellData; diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java index 0fa0cae..5b0d8ae 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java @@ -2,7 +2,6 @@ package com.alibaba.excel.write.executor; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -13,10 +12,10 @@ import org.apache.poi.ss.usermodel.Row; import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.enums.HeadKindEnum; -import com.alibaba.excel.metadata.BaseRowModel; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.util.ClassUtils; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.WorkBookUtil; import com.alibaba.excel.util.WriteHandlerUtils; @@ -158,13 +157,11 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { continue; } Object value = beanMap.get(filedName); - if (value == null) { - continue; - } WriteHandlerUtils.beforeCellCreate(writeContext, row, null, cellIndex, relativeRowIndex, Boolean.FALSE); Cell cell = WorkBookUtil.createCell(row, cellIndex++); WriteHandlerUtils.afterCellCreate(writeContext, cell, null, relativeRowIndex, Boolean.FALSE); - CellData cellData = converterAndSet(currentWriteHolder, value.getClass(), cell, value, null); + CellData cellData = + converterAndSet(currentWriteHolder, value == null ? null : value.getClass(), cell, value, null); WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, null, relativeRowIndex, Boolean.FALSE); } } @@ -173,13 +170,8 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { if (!fieldList.isEmpty()) { return; } - Class tempClass = clazz; - while (tempClass != null) { - if (tempClass != BaseRowModel.class) { - Collections.addAll(fieldList, tempClass.getDeclaredFields()); - } - tempClass = tempClass.getSuperclass(); - } + ClassUtils.declaredFields(clazz, fieldList, + writeContext.writeWorkbookHolder().getWriteWorkbook().getConvertAllFiled()); } } diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index d7cb630..c7b227f 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -3,9 +3,11 @@ package com.alibaba.excel.write.executor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; @@ -57,6 +59,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { */ private Map> collectionFieldStyleCache = new HashMap>(8); + /** + * Row height cache for collection + */ + private Map collectionRowHeightCache = new HashMap(8); /** * Last index cache for collection fields */ @@ -121,7 +127,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (collectionLastIndexMap == null) { number--; } - sheet.shiftRows(maxRowIndex + 1, lastRowIndex, number); + sheet.shiftRows(maxRowIndex + 1, lastRowIndex, number, true, false); for (AnalysisCell analysisCell : templateAnalysisCache.get(writeContext.writeSheetHolder().getSheetNo())) { if (analysisCell.getRowIndex() > maxRowIndex) { analysisCell.setRowIndex(analysisCell.getRowIndex() + number); @@ -245,7 +251,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } else { row = sheet.createRow(lastRowIndex); } + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row, sheetNo); WriteHandlerUtils.afterRowCreate(writeContext, row, null, Boolean.FALSE); + } else { + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row, sheetNo); } } Cell cell = row.getCell(lastColumnIndex); @@ -271,6 +280,21 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { return cell; } + private void checkRowHeight(AnalysisCell analysisCell, FillConfig fillConfig, boolean isOriginalCell, Row row, + Integer sheetNo) { + if (!analysisCell.getFirstColumn() || !WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection())) { + return; + } + if (isOriginalCell) { + collectionRowHeightCache.put(sheetNo, row.getHeight()); + return; + } + Short rowHeight = collectionRowHeightCache.get(sheetNo); + if (rowHeight != null) { + row.setHeight(rowHeight); + } + } + private List readTemplateData(Map> analysisCache) { Integer sheetNo = writeContext.writeSheetHolder().getSheetNo(); List analysisCellList = analysisCache.get(sheetNo); @@ -280,6 +304,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { Sheet sheet = writeContext.writeSheetHolder().getCachedSheet(); analysisCellList = new ArrayList(); List collectionAnalysisCellList = new ArrayList(); + Set firstColumnCache = new HashSet(); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) { @@ -290,7 +315,8 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (cell == null) { continue; } - String preparedData = prepareData(cell, analysisCellList, collectionAnalysisCellList, i, j); + String preparedData = + prepareData(cell, analysisCellList, collectionAnalysisCellList, i, j, firstColumnCache); // Prevent empty data from not being replaced if (preparedData != null) { cell.setCellValue(preparedData); @@ -310,10 +336,11 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { * @param collectionAnalysisCellList * @param rowIndex * @param columnIndex + * @param firstColumnCache * @return Returns the data that the cell needs to replace */ private String prepareData(Cell cell, List analysisCellList, - List collectionAnalysisCellList, int rowIndex, int columnIndex) { + List collectionAnalysisCellList, int rowIndex, int columnIndex, Set firstColumnCache) { if (!CellType.STRING.equals(cell.getCellTypeEnum())) { return null; } @@ -386,6 +413,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (WriteTemplateAnalysisCellTypeEnum.COMMON.equals(analysisCell.getCellType())) { analysisCellList.add(analysisCell); } else { + if (!firstColumnCache.contains(rowIndex)) { + analysisCell.setFirstColumn(Boolean.TRUE); + firstColumnCache.add(rowIndex); + } collectionAnalysisCellList.add(analysisCell); } return preparedData.toString(); @@ -403,6 +434,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { List prepareDataList = new ArrayList(); analysisCell.setPrepareDataList(prepareDataList); analysisCell.setCellType(WriteTemplateAnalysisCellTypeEnum.COMMON); + analysisCell.setFirstColumn(Boolean.FALSE); return analysisCell; } diff --git a/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java b/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java index 791e264..47a2594 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java +++ b/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java @@ -16,6 +16,7 @@ public class AnalysisCell { private List prepareDataList; private Boolean onlyOneVariable; private WriteTemplateAnalysisCellTypeEnum cellType; + private Boolean firstColumn; public int getColumnIndex() { return columnIndex; @@ -65,6 +66,14 @@ public class AnalysisCell { this.cellType = cellType; } + public Boolean getFirstColumn() { + return firstColumn; + } + + public void setFirstColumn(Boolean firstColumn) { + this.firstColumn = firstColumn; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java index 0f00719..e4f9571 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java @@ -39,8 +39,8 @@ public class DemoExceptionListener extends AnalysisEventListener { LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; - LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(), - excelDataConvertException.getColumnIndex()); + LOGGER.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), + excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData()); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java new file mode 100644 index 0000000..6c82082 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java @@ -0,0 +1,110 @@ +package com.alibaba.easyexcel.test.temp; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Ignore; +import org.junit.Test; + +import com.alibaba.easyexcel.test.demo.fill.FillData; +import com.alibaba.easyexcel.test.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy; + +/** + * 写的填充写法 + * + * @since 2.1.1 + * @author Jiaju Zhuang + */ +@Ignore +public class FillTempTest { + + /** + * 复杂的填充 + * + * @since 2.1.1 + */ + @Test + public void complexFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + String templateFileName = "D:\\test\\complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。 + // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用 + // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存 + // 如果数据量大 list不是最后一行 参照下一个 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + map.put("total", 1000); + excelWriter.fill(map, writeSheet); + excelWriter.finish(); + } + + /** + * 数据量大的复杂填充 + *

+ * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 + */ + @Test + public void complexFillWithTable() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + // 这里模板 删除了list以后的数据,也就是统计的这一行 + String templateFileName = "D:\\test\\complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 直接写入数据 + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data(), writeSheet); + + // 写入list之前的数据 + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + + // list 后面还有个统计 想办法手动写入 + // 这里偷懒直接用list 也可以用对象 + List> totalListList = new ArrayList>(); + List totalList = new ArrayList(); + totalListList.add(totalList); + totalList.add(null); + totalList.add(null); + totalList.add(null); + // 第四列 + totalList.add("统计:1000"); + // 这里是write 别和fill 搞错了 + excelWriter.write(totalListList, writeSheet); + excelWriter.finish(); + // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 + // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + } + return list; + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java index 367ee58..f777833 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java @@ -79,11 +79,11 @@ public class PoiTest { @Test public void lastRowNum255() throws IOException, InvalidFormatException { - String file = TestFileUtil.getPath() + "fill" + File.separator + "complex.xlsx"; + String file = "D:\\test\\complex.xlsx"; XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook); Sheet xssfSheet = xssfWorkbook.getSheetAt(0); - xssfSheet.shiftRows(2, 4, 10); + xssfSheet.shiftRows(1, 4, 10, true, true); FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); sxssfWorkbook.write(fileout); diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java index 196b2da..e52dc51 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java @@ -25,6 +25,7 @@ public class HDListener extends AnalysisEventListener { @Override public void invokeHeadMap(Map headMap, AnalysisContext context) { LOGGER.info("HEAD:{}", JSON.toJSONString(headMap)); + LOGGER.info("total:{}", context.readSheetHolder().getTotal()); } diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java index 38ee661..538e266 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java @@ -3,6 +3,7 @@ package com.alibaba.easyexcel.test.temp.read; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; +import lombok.experimental.Accessors; /** * 临时测试 @@ -10,6 +11,7 @@ import lombok.Data; * @author Jiaju Zhuang **/ @Data +@Accessors(chain = true) public class HeadReadData { @ExcelProperty("头1") private String h1; diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java index f356524..5127f72 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.temp.read; import java.io.File; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,13 +14,15 @@ import com.alibaba.excel.EasyExcel; * * @author Jiaju Zhuang **/ +@Ignore public class HeadReadTest { private static final Logger LOGGER = LoggerFactory.getLogger(HeadReadTest.class); @Test public void test() throws Exception { - File file = new File("D:\\test\\headt1.xlsx"); - EasyExcel.read(file, HeadReadData.class, new HDListener()).sheet().doRead(); + File file = new File("D:\\test\\headt1.xls"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).sheet(0).doRead(); + } } diff --git a/update.md b/update.md index 4055d43..9bd246e 100644 --- a/update.md +++ b/update.md @@ -7,6 +7,11 @@ * 加入多次关闭判断,防止多次关闭异常 * 加入根据模板自动识别导出的excel类型 * 修改默认失败后,不再往文件流写入数据。通过参数`writeExcelOnException` 参数设置异常了也要写入前面的数据。 +* 循环合并策略支持一次性合并多列 +* `ExcelDataConvertException`返回新增具体报错的数据 +* 加入解析class缓存 +* 修复填充的时候行高不复制的Bug [Issue #780](https://github.com/alibaba/easyexcel/issues/780) +* 修复03版无法获取大概总行数的bug # 2.1.0-beta4 * 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747) From d7af9b42eac762ec19a03ec11ea56ad836e5dc97 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Mon, 4 Nov 2019 18:53:08 +0800 Subject: [PATCH 19/30] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E8=A7=A3=E6=9E=90class?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../excel/write/executor/ExcelWriteFillExecutor.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index c7b227f..800b7c0 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -350,6 +350,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } StringBuilder preparedData = new StringBuilder(); AnalysisCell analysisCell = null; + int startIndex = 0; int length = value.length(); int lastPrepareDataIndex = 0; @@ -403,6 +404,13 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } lastPrepareDataIndex = suffixIndex + 1; } + return dealAnalysisCell(analysisCell, value, rowIndex, lastPrepareDataIndex, length, analysisCellList, + collectionAnalysisCellList, firstColumnCache, preparedData); + } + + private String dealAnalysisCell(AnalysisCell analysisCell, String value, int rowIndex, int lastPrepareDataIndex, + int length, List analysisCellList, List collectionAnalysisCellList, + Set firstColumnCache, StringBuilder preparedData) { if (analysisCell != null) { if (lastPrepareDataIndex == length) { analysisCell.getPrepareDataList().add(StringUtils.EMPTY); From 7b9890ae517ac3eb98c80223f8e6713958ddba82 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 5 Nov 2019 19:30:27 +0800 Subject: [PATCH 20/30] =?UTF-8?q?=E6=96=B0=E5=A2=9Esince?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alibaba/excel/write/ExcelBuilderImpl.java | 7 +++++-- .../alibaba/easyexcel/test/demo/write/WriteTest.java | 2 ++ .../easyexcel/test/temp/read/HeadReadTest.java | 12 ++++++++++++ update.md | 4 +++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index 9658f55..59f78bf 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -25,10 +25,13 @@ public class ExcelBuilderImpl implements ExcelBuilder { private ExcelWriteFillExecutor excelWriteFillExecutor; private ExcelWriteAddExecutor excelWriteAddExecutor; + static { + // Create temporary cache directory at initialization time to avoid POI concurrent write bugs + FileUtils.createPoiFilesDirectory(); + } + public ExcelBuilderImpl(WriteWorkbook writeWorkbook) { try { - // Create temporary cache directory at initialization time to avoid POI concurrent write bugs - FileUtils.createPoiFilesDirectory(); context = new WriteContextImpl(writeWorkbook); } catch (RuntimeException e) { finishOnException(); diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index 3e27bd3..7b7d6b3 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -73,6 +73,8 @@ public class WriteTest { * 2. 根据自己或者排除自己需要的列 *

* 3. 直接写即可 + * + * @since 2.1.1 */ @Test public void excludeOrIncludeWrite() { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java index 5127f72..b046d14 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java @@ -8,6 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.cache.Ehcache; /** * 临时测试 @@ -25,4 +26,15 @@ public class HeadReadTest { } + @Test + public void testCache() throws Exception { + File file = new File("D:\\test\\headt1.xls"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).readCache(new Ehcache(20)).sheet(0).doRead(); + + LOGGER.info("------------------"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).readCache(new Ehcache(20)).sheet(0).doRead(); + LOGGER.info("------------------"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).readCache(new Ehcache(20)).sheet(0).doRead(); + } + } diff --git a/update.md b/update.md index 9bd246e..eaaed2b 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,5 @@ +# 2.1.2 + # 2.1.1 * 发布正式版 * 修改map返回为LinkedHashMap @@ -61,7 +63,7 @@ * 修复监听器转换异常会重复提示的bug # 2.0.1 -* 降级poi为3.1.7 兼容jdk6 +* 降级poi为3.17 兼容jdk6 # 2.0.0 * 修复当cell为空可能会抛出空指针的bug From 67e4e10d86a6f3dfe4d647b7b25487c9a30193fd Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 7 Nov 2019 18:49:22 +0800 Subject: [PATCH 21/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=BA=E5=88=B6?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=96=B0=E8=A1=8C=E5=A1=AB=E5=85=85=EF=BC=8C?= =?UTF-8?q?=E5=8F=AA=E6=9C=89=E4=B8=80=E8=A1=8C=E6=95=B0=E6=8D=AE=E4=BC=9A?= =?UTF-8?q?=E6=9C=AA=E5=A1=AB=E5=85=85=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../write/executor/ExcelWriteFillExecutor.java | 3 +++ .../easyexcel/test/temp/FillTempTest.java | 17 +++++++++++++---- .../easyexcel/test/temp/fill/FillData2.java | 11 +++++++++++ update.md | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/fill/FillData2.java diff --git a/pom.xml b/pom.xml index 7dad24d..c818dd3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.1 + 2.1.2 jar easyexcel diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 800b7c0..473d4e3 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -127,6 +127,9 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (collectionLastIndexMap == null) { number--; } + if (number <= 0) { + return; + } sheet.shiftRows(maxRowIndex + 1, lastRowIndex, number, true, false); for (AnalysisCell analysisCell : templateAnalysisCache.get(writeContext.writeSheetHolder().getSheetNo())) { if (analysisCell.getRowIndex() > maxRowIndex) { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java index 6c82082..8049d89 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java @@ -1,6 +1,5 @@ package com.alibaba.easyexcel.test.temp; -import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -10,12 +9,12 @@ import org.junit.Ignore; import org.junit.Test; import com.alibaba.easyexcel.test.demo.fill.FillData; +import com.alibaba.easyexcel.test.temp.fill.FillData2; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.fill.FillConfig; -import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy; /** * 写的填充写法 @@ -46,7 +45,7 @@ public class FillTempTest { // 如果数据量大 list不是最后一行 参照下一个 FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); excelWriter.fill(data(), fillConfig, writeSheet); - excelWriter.fill(data(), fillConfig, writeSheet); +// excelWriter.fill(data2(), fillConfig, writeSheet); Map map = new HashMap(); map.put("date", "2019年10月9日13:28:28"); map.put("total", 1000); @@ -73,7 +72,7 @@ public class FillTempTest { WriteSheet writeSheet = EasyExcel.writerSheet().build(); // 直接写入数据 excelWriter.fill(data(), writeSheet); - excelWriter.fill(data(), writeSheet); + excelWriter.fill(data2(), writeSheet); // 写入list之前的数据 Map map = new HashMap(); @@ -97,6 +96,16 @@ public class FillTempTest { // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 } + private List data2() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData2 fillData = new FillData2(); + list.add(fillData); + fillData.setTest("ttttttt" + i); + } + return list; + } + private List data() { List list = new ArrayList(); for (int i = 0; i < 10; i++) { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/fill/FillData2.java b/src/test/java/com/alibaba/easyexcel/test/temp/fill/FillData2.java new file mode 100644 index 0000000..17c34cf --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/fill/FillData2.java @@ -0,0 +1,11 @@ +package com.alibaba.easyexcel.test.temp.fill; + +import lombok.Data; + +/** + * @author Jiaju Zhuang + */ +@Data +public class FillData2 { + private String test; +} diff --git a/update.md b/update.md index eaaed2b..4b6e5a4 100644 --- a/update.md +++ b/update.md @@ -1,4 +1,5 @@ # 2.1.2 +* 修复强制创建新行填充,只有一行数据会未填充的bug # 2.1.1 * 发布正式版 From 217d69a69a375c882275f5c8ae7af45b4db92548 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 14 Nov 2019 19:31:38 +0800 Subject: [PATCH 22/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E7=AD=96=E7=95=A5=20=E7=A9=BA=E6=8C=87=E9=92=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../excel/analysis/ExcelAnalyserImpl.java | 2 +- .../excel/analysis/v07/XlsxSaxAnalyser.java | 9 +- .../excel/context/WriteContextImpl.java | 11 +- .../read/builder/ExcelReaderBuilder.java | 19 +++ .../excel/read/metadata/ReadWorkbook.java | 21 ++++ .../metadata/holder/ReadWorkbookHolder.java | 22 ++++ .../com/alibaba/excel/util/FileUtils.java | 109 +++++++++++------- .../write/builder/ExcelWriterBuilder.java | 11 ++ .../builder/ExcelWriterSheetBuilder.java | 11 ++ .../builder/ExcelWriterTableBuilder.java | 12 +- .../executor/ExcelWriteFillExecutor.java | 27 ++++- .../write/merge/AbstractMergeStrategy.java | 2 +- .../excel/write/merge/LoopMergeStrategy.java | 22 ++-- .../merge/OnceAbsoluteMergeStrategy.java | 4 +- .../write/metadata/WriteBasicParameter.java | 13 +++ .../metadata/holder/AbstractWriteHolder.java | 31 ++++- .../write/metadata/holder/WriteHolder.java | 7 ++ .../AbstractHeadColumnWidthStyleStrategy.java | 8 +- .../SimpleColumnWidthStyleStrategy.java | 2 +- .../easyexcel/test/core/fill/FillData.java | 2 +- .../test/core/fill/FillDataTest.java | 18 +-- .../test/core/head/ComplexHeadDataTest.java | 20 ++++ .../alibaba/easyexcel/test/temp/LockTest.java | 2 +- update.md | 9 ++ 25 files changed, 306 insertions(+), 90 deletions(-) diff --git a/pom.xml b/pom.xml index c818dd3..e85d044 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.2 + 2.1.3 jar easyexcel diff --git a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java index f3ef950..eb4f188 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -176,7 +176,7 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { clearEncrypt03(); if (throwable != null) { - throw new ExcelAnalysisException("Can not close IO", throwable); + throw new ExcelAnalysisException("Can not close IO.", throwable); } } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java index a30b426..2862be2 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java @@ -34,6 +34,7 @@ import com.alibaba.excel.read.metadata.holder.ReadWorkbookHolder; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.FileUtils; import com.alibaba.excel.util.SheetUtils; +import com.alibaba.excel.util.StringUtils; /** * @@ -148,7 +149,13 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { private void parseXmlSource(InputStream inputStream, ContentHandler handler) { InputSource inputSource = new InputSource(inputStream); try { - SAXParserFactory saxFactory = SAXParserFactory.newInstance(); + SAXParserFactory saxFactory; + String xlsxSAXParserFactoryName = analysisContext.readWorkbookHolder().getXlsxSAXParserFactoryName(); + if (StringUtils.isEmpty(xlsxSAXParserFactoryName)) { + saxFactory = SAXParserFactory.newInstance(); + } else { + saxFactory = SAXParserFactory.newInstance(xlsxSAXParserFactoryName, null); + } saxFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index 3f2b51a..0935868 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -169,7 +169,9 @@ public class WriteContextImpl implements WriteContext { int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); newRowIndex += currentWriteHolder.relativeHeadRowIndex(); // Combined head - addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex); + if (currentWriteHolder.automaticMergeHead()) { + addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex); + } for (int relativeRowIndex = 0, i = newRowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + newRowIndex; i++, relativeRowIndex++) { WriteHandlerUtils.beforeRowCreate(this, newRowIndex, relativeRowIndex, Boolean.TRUE); @@ -182,8 +184,9 @@ public class WriteContextImpl implements WriteContext { private void addMergedRegionToCurrentSheet(ExcelWriteHeadProperty excelWriteHeadProperty, int rowIndex) { for (com.alibaba.excel.metadata.CellRange cellRangeModel : excelWriteHeadProperty.headCellRangeList()) { - writeSheetHolder.getSheet().addMergedRegion(new CellRangeAddress(cellRangeModel.getFirstRow() + rowIndex, - cellRangeModel.getLastRow() + rowIndex, cellRangeModel.getFirstCol(), cellRangeModel.getLastCol())); + writeSheetHolder.getSheet() + .addMergedRegionUnsafe(new CellRangeAddress(cellRangeModel.getFirstRow() + rowIndex, + cellRangeModel.getLastRow() + rowIndex, cellRangeModel.getFirstCol(), cellRangeModel.getLastCol())); } } @@ -325,7 +328,7 @@ public class WriteContextImpl implements WriteContext { clearEncrypt03(); if (throwable != null) { - throw new ExcelGenerateException("Can not close IO", throwable); + throw new ExcelGenerateException("Can not close IO.", throwable); } if (LOGGER.isDebugEnabled()) { diff --git a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java index 340548b..4f6cdda 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java @@ -5,6 +5,8 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import javax.xml.parsers.SAXParserFactory; + import com.alibaba.excel.ExcelReader; import com.alibaba.excel.cache.ReadCache; import com.alibaba.excel.cache.selector.ReadCacheSelector; @@ -233,6 +235,23 @@ public class ExcelReaderBuilder { return this; } + /** + * SAXParserFactory used when reading xlsx. + *

+ * The default will automatically find. + *

+ * Please pass in the name of a class ,like : "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" + * + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + * @param xlsxSAXParserFactoryName + * @return + */ + public ExcelReaderBuilder xlsxSAXParserFactoryName(String xlsxSAXParserFactoryName) { + readWorkbook.setXlsxSAXParserFactoryName(xlsxSAXParserFactoryName); + return this; + } + public ExcelReader build() { return new ExcelReader(readWorkbook); } diff --git a/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java b/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java index 3a873da..c6bbf7a 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java +++ b/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java @@ -3,6 +3,8 @@ package com.alibaba.excel.read.metadata; import java.io.File; import java.io.InputStream; +import javax.xml.parsers.SAXParserFactory; + import com.alibaba.excel.cache.ReadCache; import com.alibaba.excel.cache.selector.ReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; @@ -63,6 +65,17 @@ public class ReadWorkbook extends ReadBasicParameter { * Whether the encryption */ private String password; + /** + * SAXParserFactory used when reading xlsx. + *

+ * The default will automatically find. + *

+ * Please pass in the name of a class ,like : "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" + * + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + */ + private String xlsxSAXParserFactoryName; /** * The default is all excel objects.Default is true. *

@@ -176,4 +189,12 @@ public class ReadWorkbook extends ReadBasicParameter { public void setPassword(String password) { this.password = password; } + + public String getXlsxSAXParserFactoryName() { + return xlsxSAXParserFactoryName; + } + + public void setXlsxSAXParserFactoryName(String xlsxSAXParserFactoryName) { + this.xlsxSAXParserFactoryName = xlsxSAXParserFactoryName; + } } diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java index 35b3859..cbda93f 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadWorkbookHolder.java @@ -6,6 +6,8 @@ import java.io.InputStream; import java.util.HashSet; import java.util.Set; +import javax.xml.parsers.SAXParserFactory; + import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -83,6 +85,17 @@ public class ReadWorkbookHolder extends AbstractReadHolder { * Whether the encryption */ private String password; + /** + * SAXParserFactory used when reading xlsx. + *

+ * The default will automatically find. + *

+ * Please pass in the name of a class ,like : "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl" + * + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + */ + private String xlsxSAXParserFactoryName; /** * The default is all excel objects.if true , you can use {@link com.alibaba.excel.annotation.ExcelIgnore} ignore a * field. if false , you must use {@link com.alibaba.excel.annotation.ExcelProperty} to use a filed. @@ -172,6 +185,7 @@ public class ReadWorkbookHolder extends AbstractReadHolder { } else { this.defaultReturnMap = readWorkbook.getDefaultReturnMap(); } + this.xlsxSAXParserFactoryName = readWorkbook.getXlsxSAXParserFactoryName(); this.hasReadSheet = new HashSet(); this.ignoreRecord03 = Boolean.FALSE; this.password = readWorkbook.getPassword(); @@ -321,6 +335,14 @@ public class ReadWorkbookHolder extends AbstractReadHolder { this.password = password; } + public String getXlsxSAXParserFactoryName() { + return xlsxSAXParserFactoryName; + } + + public void setXlsxSAXParserFactoryName(String xlsxSAXParserFactoryName) { + this.xlsxSAXParserFactoryName = xlsxSAXParserFactoryName; + } + @Override public HolderEnum holderType() { return HolderEnum.WORKBOOK; diff --git a/src/main/java/com/alibaba/excel/util/FileUtils.java b/src/main/java/com/alibaba/excel/util/FileUtils.java index 34e6853..23762f6 100644 --- a/src/main/java/com/alibaba/excel/util/FileUtils.java +++ b/src/main/java/com/alibaba/excel/util/FileUtils.java @@ -9,24 +9,50 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.UUID; +import org.apache.poi.util.DefaultTempFileCreationStrategy; +import org.apache.poi.util.TempFile; + import com.alibaba.excel.exception.ExcelAnalysisException; -import com.alibaba.excel.exception.ExcelGenerateException; +import com.alibaba.excel.exception.ExcelCommonException; /** * * @author jipengfei */ public class FileUtils { + public static final String POI_FILES = "poifiles"; + public static final String EX_CACHE = "excache"; + /** + * If a server has multiple projects in use at the same time, a directory with the same name will be created under + * the temporary directory, but each project is run by a different user, so there is a permission problem, so each + * project creates a unique UUID as a separate Temporary Files. + */ + private static String tempFilePrefix = + System.getProperty(TempFile.JAVA_IO_TMPDIR) + UUID.randomUUID().toString() + File.separator; + /** + * Used to store poi temporary files. + */ + private static String poiFilesPath = tempFilePrefix + POI_FILES + File.separator; + /** + * Used to store easy excel temporary files. + */ + private static String cachePath = tempFilePrefix + EX_CACHE + File.separator; - private static final String JAVA_IO_TMPDIR = "java.io.tmpdir"; - - private static final String POIFILES = "poifiles"; - - private static final String CACHE = "excache"; private static final int WRITE_BUFF_SIZE = 8192; private FileUtils() {} + static { + // Create a temporary directory in advance + File tempFile = new File(tempFilePrefix); + createDirectory(tempFile); + tempFile.deleteOnExit(); + // Initialize the cache directory + File cacheFile = new File(cachePath); + createDirectory(cacheFile); + cacheFile.deleteOnExit(); + } + /** * Reads the contents of a file into a byte array. * The file is always closed. * @@ -106,19 +132,30 @@ public class FileUtils { } } - /** - */ public static void createPoiFilesDirectory() { - createTmpDirectory(POIFILES); + File poiFilesPathFile = new File(poiFilesPath); + createDirectory(poiFilesPathFile); + TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiFilesPathFile)); + poiFilesPathFile.deleteOnExit(); } public static File createCacheTmpFile() { - File directory = createTmpDirectory(CACHE); - File cache = new File(directory.getPath(), UUID.randomUUID().toString()); - if (!cache.mkdir()) { - throw new ExcelGenerateException("Can not create temp file!"); + return createDirectory(new File(cachePath + UUID.randomUUID().toString())); + } + + public static File createTmpFile(String fileName) { + return createDirectory(new File(tempFilePrefix + fileName)); + } + + /** + * + * @param directory + */ + private static File createDirectory(File directory) { + if (!directory.exists() && !directory.mkdirs()) { + throw new ExcelCommonException("Cannot create directory:" + directory.getAbsolutePath()); } - return cache; + return directory; } /** @@ -144,35 +181,27 @@ public class FileUtils { } } - public static File createTmpDirectory(String path) { - String tmpDir = System.getProperty(JAVA_IO_TMPDIR); - if (tmpDir == null) { - throw new RuntimeException( - "Systems temporary directory not defined - set the -D" + JAVA_IO_TMPDIR + " jvm property!"); - } - File directory = new File(tmpDir, path); - if (!directory.exists()) { - syncCreatePoiFilesDirectory(directory); - } - return directory; + public static String getTempFilePrefix() { + return tempFilePrefix; } - public static File createTmpFile(String fileName) { - String tmpDir = System.getProperty(JAVA_IO_TMPDIR); - if (tmpDir == null) { - throw new RuntimeException( - "Systems temporary directory not defined - set the -D" + JAVA_IO_TMPDIR + " jvm property!"); - } - return new File(tmpDir, fileName); + public static void setTempFilePrefix(String tempFilePrefix) { + FileUtils.tempFilePrefix = tempFilePrefix; } - /** - * - * @param directory - */ - private static synchronized void syncCreatePoiFilesDirectory(File directory) { - if (!directory.exists()) { - directory.mkdirs(); - } + public static String getPoiFilesPath() { + return poiFilesPath; + } + + public static void setPoiFilesPath(String poiFilesPath) { + FileUtils.poiFilesPath = poiFilesPath; + } + + public static String getCachePath() { + return cachePath; + } + + public static void setCachePath(String cachePath) { + FileUtils.cachePath = cachePath; } } diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java index f0d37ed..c1f2d94 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java @@ -91,6 +91,17 @@ public class ExcelWriterBuilder { return this; } + /** + * Whether to automatically merge headers.Default is true. + * + * @param automaticMergeHead + * @return + */ + public ExcelWriterBuilder automaticMergeHead(Boolean automaticMergeHead) { + writeWorkbook.setAutomaticMergeHead(automaticMergeHead); + return this; + } + /** * Whether the encryption. *

diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java index 50f230a..5cb71cc 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java @@ -86,6 +86,17 @@ public class ExcelWriterSheetBuilder { return this; } + /** + * Whether to automatically merge headers.Default is true. + * + * @param automaticMergeHead + * @return + */ + public ExcelWriterSheetBuilder automaticMergeHead(Boolean automaticMergeHead) { + writeSheet.setAutomaticMergeHead(automaticMergeHead); + return this; + } + /** * Custom type conversions override the default. * diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java index 40ca58b..2b461a9 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java @@ -90,6 +90,17 @@ public class ExcelWriterTableBuilder { return this; } + /** + * Whether to automatically merge headers.Default is true. + * + * @param automaticMergeHead + * @return + */ + public ExcelWriterTableBuilder automaticMergeHead(Boolean automaticMergeHead) { + writeTable.setAutomaticMergeHead(automaticMergeHead); + return this; + } + /** * Custom type conversions override the default. * @@ -129,7 +140,6 @@ public class ExcelWriterTableBuilder { return this; } - /** * Ignore the custom columns. */ diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 473d4e3..66cd4dd 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -21,7 +21,6 @@ import com.alibaba.excel.enums.WriteDirectionEnum; import com.alibaba.excel.enums.WriteTemplateAnalysisCellTypeEnum; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.CellData; -import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.StringUtils; @@ -69,6 +68,8 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { private Map> collectionLastIndexCache = new HashMap>(8); + private Map relativeRowIndexMap = new HashMap(8); + public ExcelWriteFillExecutor(WriteContext writeContext) { super(writeContext); } @@ -89,10 +90,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { shiftRows(collectionData.size(), analysisCellList); } while (iterator.hasNext()) { - doFill(analysisCellList, iterator.next(), fillConfig); + doFill(analysisCellList, iterator.next(), fillConfig, getRelativeRowIndex()); } } else { - doFill(readTemplateData(templateAnalysisCache), data, fillConfig); + doFill(readTemplateData(templateAnalysisCache), data, fillConfig, null); } } @@ -138,7 +139,8 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } } - private void doFill(List analysisCellList, Object oneRowData, FillConfig fillConfig) { + private void doFill(List analysisCellList, Object oneRowData, FillConfig fillConfig, + Integer relativeRowIndex) { Map dataMap; if (oneRowData instanceof Map) { dataMap = (Map)oneRowData; @@ -161,7 +163,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { Object value = dataMap.get(variable); CellData cellData = converterAndSet(writeSheetHolder, value == null ? null : value.getClass(), cell, value, fieldNameContentPropertyMap.get(variable)); - WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, null, null, Boolean.FALSE); + WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, null, relativeRowIndex, Boolean.FALSE); } else { StringBuilder cellValueBuild = new StringBuilder(); int index = 0; @@ -197,11 +199,24 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } cellValueBuild.append(analysisCell.getPrepareDataList().get(index)); cell.setCellValue(cellValueBuild.toString()); - WriteHandlerUtils.afterCellDispose(writeContext, cellDataList, cell, null, null, Boolean.FALSE); + WriteHandlerUtils.afterCellDispose(writeContext, cellDataList, cell, null, relativeRowIndex, + Boolean.FALSE); } } } + private int getRelativeRowIndex() { + Integer sheetNo = writeContext.writeSheetHolder().getSheetNo(); + Integer relativeRowIndex = relativeRowIndexMap.get(sheetNo); + if (relativeRowIndex == null) { + relativeRowIndex = 0; + } else { + relativeRowIndex++; + } + relativeRowIndexMap.put(sheetNo, relativeRowIndex); + return relativeRowIndex; + } + private Cell getOneCell(AnalysisCell analysisCell, FillConfig fillConfig) { Sheet cachedSheet = writeContext.writeSheetHolder().getCachedSheet(); if (WriteTemplateAnalysisCellTypeEnum.COMMON.equals(analysisCell.getCellType())) { diff --git a/src/main/java/com/alibaba/excel/write/merge/AbstractMergeStrategy.java b/src/main/java/com/alibaba/excel/write/merge/AbstractMergeStrategy.java index 13bdac3..b661c12 100644 --- a/src/main/java/com/alibaba/excel/write/merge/AbstractMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/AbstractMergeStrategy.java @@ -45,5 +45,5 @@ public abstract class AbstractMergeStrategy implements CellWriteHandler { * @param head * @param relativeRowIndex */ - protected abstract void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex); + protected abstract void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex); } diff --git a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java index b087804..615e408 100644 --- a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java @@ -39,14 +39,20 @@ public class LoopMergeStrategy extends AbstractMergeStrategy { } @Override - protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { - if (head.getColumnIndex() == columnIndex && relativeRowIndex % eachRow == 0) { - CellRangeAddress cellRangeAddress = new CellRangeAddress( - cell.getRowIndex(), - cell.getRowIndex() + eachRow - 1, - cell.getColumnIndex(), - cell.getColumnIndex() + columnCount - 1); - sheet.addMergedRegion(cellRangeAddress); + protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + if (relativeRowIndex == null) { + return; + } + Integer currentColumnIndex; + if (head != null) { + currentColumnIndex = head.getColumnIndex(); + } else { + currentColumnIndex = cell.getColumnIndex(); + } + if (currentColumnIndex == columnIndex && relativeRowIndex % eachRow == 0) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), + cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex() + columnCount - 1); + sheet.addMergedRegionUnsafe(cellRangeAddress); } } } diff --git a/src/main/java/com/alibaba/excel/write/merge/OnceAbsoluteMergeStrategy.java b/src/main/java/com/alibaba/excel/write/merge/OnceAbsoluteMergeStrategy.java index 6717b76..be05e22 100644 --- a/src/main/java/com/alibaba/excel/write/merge/OnceAbsoluteMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/OnceAbsoluteMergeStrategy.java @@ -29,11 +29,11 @@ public class OnceAbsoluteMergeStrategy extends AbstractMergeStrategy { } @Override - protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { + protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { if (cell.getRowIndex() == firstRowIndex && cell.getColumnIndex() == firstColumnIndex) { CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRowIndex, lastRowIndex, firstColumnIndex, lastColumnIndex); - sheet.addMergedRegion(cellRangeAddress); + sheet.addMergedRegionUnsafe(cellRangeAddress); } } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java b/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java index 61115af..b582576 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java +++ b/src/main/java/com/alibaba/excel/write/metadata/WriteBasicParameter.java @@ -29,6 +29,10 @@ public class WriteBasicParameter extends BasicParameter { * Use the default style.Default is true. */ private Boolean useDefaultStyle; + /** + * Whether to automatically merge headers.Default is true. + */ + private Boolean automaticMergeHead; /** * Ignore the custom columns. */ @@ -78,6 +82,14 @@ public class WriteBasicParameter extends BasicParameter { this.useDefaultStyle = useDefaultStyle; } + public Boolean getAutomaticMergeHead() { + return automaticMergeHead; + } + + public void setAutomaticMergeHead(Boolean automaticMergeHead) { + this.automaticMergeHead = automaticMergeHead; + } + public Collection getExcludeColumnIndexes() { return excludeColumnIndexes; } @@ -109,4 +121,5 @@ public class WriteBasicParameter extends BasicParameter { public void setIncludeColumnFiledNames(Collection includeColumnFiledNames) { this.includeColumnFiledNames = includeColumnFiledNames; } + } diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java index b28f267..375602a 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java @@ -66,6 +66,10 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ * Use the default style.Default is true. */ private Boolean useDefaultStyle; + /** + * Whether to automatically merge headers.Default is true. + */ + private Boolean automaticMergeHead; /** * Ignore the custom columns. */ @@ -127,6 +131,16 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ this.useDefaultStyle = writeBasicParameter.getUseDefaultStyle(); } + if (writeBasicParameter.getAutomaticMergeHead() == null) { + if (parentAbstractWriteHolder == null) { + this.automaticMergeHead = Boolean.TRUE; + } else { + this.automaticMergeHead = parentAbstractWriteHolder.getAutomaticMergeHead(); + } + } else { + this.automaticMergeHead = writeBasicParameter.getAutomaticMergeHead(); + } + if (writeBasicParameter.getExcludeColumnFiledNames() == null && parentAbstractWriteHolder != null) { this.excludeColumnFiledNames = parentAbstractWriteHolder.getExcludeColumnFiledNames(); } else { @@ -251,7 +265,7 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ } writeBasicParameter.getCustomWriteHandlerList().add(new AbstractHeadColumnWidthStyleStrategy() { @Override - protected Integer columnWidth(Head head) { + protected Integer columnWidth(Head head, Integer columnIndex) { if (columnWidthMap.containsKey(head.getColumnIndex())) { return columnWidthMap.get(head.getColumnIndex()) / 256; } @@ -300,7 +314,7 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ private void dealColumnWidth(List handlerList) { WriteHandler columnWidthStyleStrategy = new AbstractHeadColumnWidthStyleStrategy() { @Override - protected Integer columnWidth(Head head) { + protected Integer columnWidth(Head head, Integer columnIndex) { if (head == null) { return null; } @@ -441,6 +455,14 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ this.useDefaultStyle = useDefaultStyle; } + public Boolean getAutomaticMergeHead() { + return automaticMergeHead; + } + + public void setAutomaticMergeHead(Boolean automaticMergeHead) { + this.automaticMergeHead = automaticMergeHead; + } + public Collection getExcludeColumnIndexes() { return excludeColumnIndexes; } @@ -492,4 +514,9 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ public int relativeHeadRowIndex() { return getRelativeHeadRowIndex(); } + + @Override + public boolean automaticMergeHead() { + return getAutomaticMergeHead(); + } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java index 8f2ae7e..2aa2066 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteHolder.java @@ -44,6 +44,13 @@ public interface WriteHolder extends ConfigurationHolder { */ boolean needHead(); + /** + * Whether need automatic merge headers. + * + * @return + */ + boolean automaticMergeHead(); + /** * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. * diff --git a/src/main/java/com/alibaba/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java b/src/main/java/com/alibaba/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java index 9908144..1a88eff 100644 --- a/src/main/java/com/alibaba/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java +++ b/src/main/java/com/alibaba/excel/write/style/column/AbstractHeadColumnWidthStyleStrategy.java @@ -22,7 +22,7 @@ public abstract class AbstractHeadColumnWidthStyleStrategy extends AbstractColum if (!needSetWidth) { return; } - Integer width = columnWidth(head); + Integer width = columnWidth(head, cell.getColumnIndex()); if (width != null) { width = width * 256; writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), width); @@ -36,9 +36,11 @@ public abstract class AbstractHeadColumnWidthStyleStrategy extends AbstractColum * if return null,ignore * * @param head - * Nullable + * Nullable. + * @param columnIndex + * Not null. * @return */ - protected abstract Integer columnWidth(Head head); + protected abstract Integer columnWidth(Head head, Integer columnIndex); } diff --git a/src/main/java/com/alibaba/excel/write/style/column/SimpleColumnWidthStyleStrategy.java b/src/main/java/com/alibaba/excel/write/style/column/SimpleColumnWidthStyleStrategy.java index 1f3ae78..316ff2c 100644 --- a/src/main/java/com/alibaba/excel/write/style/column/SimpleColumnWidthStyleStrategy.java +++ b/src/main/java/com/alibaba/excel/write/style/column/SimpleColumnWidthStyleStrategy.java @@ -19,7 +19,7 @@ public class SimpleColumnWidthStyleStrategy extends AbstractHeadColumnWidthStyle } @Override - protected Integer columnWidth(Head head) { + protected Integer columnWidth(Head head, Integer columnIndex) { return columnWidth; } } diff --git a/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java index dcdf23f..a1dd56a 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java @@ -14,6 +14,6 @@ public class FillData { private String name; @NumberFormat("#") @ExcelProperty(converter = DoubleStringConverter.class) - private double number; + private Double number; private String empty; } diff --git a/src/test/java/com/alibaba/easyexcel/test/core/fill/FillDataTest.java b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillDataTest.java index 93a5457..04a8bd1 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/fill/FillDataTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillDataTest.java @@ -6,35 +6,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.poi.ss.usermodel.BorderStyle; -import org.apache.poi.ss.usermodel.FillPatternType; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.HorizontalAlignment; -import org.apache.poi.ss.usermodel.IndexedColors; -import org.apache.poi.ss.usermodel.VerticalAlignment; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; -import com.alibaba.easyexcel.test.core.style.StyleData; -import com.alibaba.easyexcel.test.core.style.StyleDataListener; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.enums.WriteDirectionEnum; -import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.merge.LoopMergeStrategy; -import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.fill.FillConfig; -import com.alibaba.excel.write.metadata.style.WriteCellStyle; -import com.alibaba.excel.write.metadata.style.WriteFont; -import com.alibaba.excel.write.style.AbstractVerticalCellStyleStrategy; -import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; -import com.alibaba.excel.write.style.column.SimpleColumnWidthStyleStrategy; -import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy; /** * @@ -121,7 +105,7 @@ public class FillDataTest { private void complexFill(File file, File template) { ExcelWriter excelWriter = EasyExcel.write(file).withTemplate(template).build(); - WriteSheet writeSheet = EasyExcel.writerSheet().build(); + WriteSheet writeSheet = EasyExcel.writerSheet().registerWriteHandler(new LoopMergeStrategy(2, 0)).build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); excelWriter.fill(data(), fillConfig, writeSheet); excelWriter.fill(data(), fillConfig, writeSheet); diff --git a/src/test/java/com/alibaba/easyexcel/test/core/head/ComplexHeadDataTest.java b/src/test/java/com/alibaba/easyexcel/test/core/head/ComplexHeadDataTest.java index f893684..25f7fed 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/head/ComplexHeadDataTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/head/ComplexHeadDataTest.java @@ -18,11 +18,15 @@ public class ComplexHeadDataTest { private static File file07; private static File file03; + private static File file07AutomaticMergeHead; + private static File file03AutomaticMergeHead; @BeforeClass public static void init() { file07 = TestFileUtil.createNewFile("complexHead07.xlsx"); file03 = TestFileUtil.createNewFile("complexHead03.xls"); + file07AutomaticMergeHead = TestFileUtil.createNewFile("complexHeadAutomaticMergeHead07.xlsx"); + file03AutomaticMergeHead = TestFileUtil.createNewFile("complexHeadAutomaticMergeHead03.xls"); } @Test @@ -37,6 +41,22 @@ public class ComplexHeadDataTest { private void readAndWrite(File file) { EasyExcel.write(file, ComplexHeadData.class).sheet().doWrite(data()); + EasyExcel.read(file, ComplexHeadData.class, new ComplexDataListener()) + .xlsxSAXParserFactoryName("com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl").sheet().doRead(); + } + + @Test + public void t03ReadAndWriteAutomaticMergeHead07() { + readAndWriteAutomaticMergeHead07(file07AutomaticMergeHead); + } + + @Test + public void t04ReadAndWriteAutomaticMergeHead0703() { + readAndWriteAutomaticMergeHead07(file03AutomaticMergeHead); + } + + private void readAndWriteAutomaticMergeHead07(File file) { + EasyExcel.write(file, ComplexHeadData.class).automaticMergeHead(Boolean.FALSE).sheet().doWrite(data()); EasyExcel.read(file, ComplexHeadData.class, new ComplexDataListener()).sheet().doRead(); } diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/LockTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/LockTest.java index fb39de1..27f820b 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/LockTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/LockTest.java @@ -39,7 +39,7 @@ public class LockTest { @Test public void test2() throws Exception { List list = - EasyExcel.read(new FileInputStream("D:\\test\\null.xlsx")).sheet().headRowNumber(0).doReadSync(); + EasyExcel.read(new FileInputStream("D:\\test\\开发部.xls")).sheet().headRowNumber(0).doReadSync(); for (Object data : list) { LOGGER.info("返回数据:{}", ((Map)data).size()); LOGGER.info("返回数据:{}", JSON.toJSONString(data)); diff --git a/update.md b/update.md index 4b6e5a4..757de15 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,12 @@ +# 2.1.3 +* 每个java进程单独创建一个缓存目录 [Issue #813](https://github.com/alibaba/easyexcel/issues/813) +* 统一修改合并为unsafe,提高大量数据导出的合并的效率 +* 修改merge返回参数`relativeRowIndex`为`Integer` +* 新增参数`automaticMergeHead` 可以设置不自动合并头 [Issue #822](https://github.com/alibaba/easyexcel/issues/822) +* 新增参数`xlsxSAXParserFactoryName` 可以指定`SAXParserFactory` +* 修复合并策略 空指针的问题 +* `SimpleColumnWidthStyleStrategy` 新增 参数`columnIndex` [Issue #806](https://github.com/alibaba/easyexcel/issues/806) + # 2.1.2 * 修复强制创建新行填充,只有一行数据会未填充的bug From 76cd90fe0e80d25ffeb66cfd7a7da5ecf765c3a6 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 14 Nov 2019 19:44:42 +0800 Subject: [PATCH 23/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E7=AD=96=E7=95=A5=20=E7=A9=BA=E6=8C=87=E9=92=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/excel/write/executor/ExcelWriteFillExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index 66cd4dd..495a134 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -205,7 +205,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } } - private int getRelativeRowIndex() { + private Integer getRelativeRowIndex() { Integer sheetNo = writeContext.writeSheetHolder().getSheetNo(); Integer relativeRowIndex = relativeRowIndexMap.get(sheetNo); if (relativeRowIndex == null) { From fe780d3c7d3f6e2576fe8c33acc08da09391da66 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 14 Nov 2019 19:48:12 +0800 Subject: [PATCH 24/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E7=AD=96=E7=95=A5=20=E7=A9=BA=E6=8C=87=E9=92=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/alibaba/excel/util/FileUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/util/FileUtils.java b/src/main/java/com/alibaba/excel/util/FileUtils.java index 23762f6..05e4471 100644 --- a/src/main/java/com/alibaba/excel/util/FileUtils.java +++ b/src/main/java/com/alibaba/excel/util/FileUtils.java @@ -28,7 +28,7 @@ public class FileUtils { * project creates a unique UUID as a separate Temporary Files. */ private static String tempFilePrefix = - System.getProperty(TempFile.JAVA_IO_TMPDIR) + UUID.randomUUID().toString() + File.separator; + System.getProperty(TempFile.JAVA_IO_TMPDIR) + File.separator + UUID.randomUUID().toString() + File.separator; /** * Used to store poi temporary files. */ From 827153fda31f071f762019b2071ed6b8aaf689e2 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Thu, 14 Nov 2019 19:56:00 +0800 Subject: [PATCH 25/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E7=AD=96=E7=95=A5=20=E7=A9=BA=E6=8C=87=E9=92=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/alibaba/excel/util/FileUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alibaba/excel/util/FileUtils.java b/src/main/java/com/alibaba/excel/util/FileUtils.java index 05e4471..20b16bc 100644 --- a/src/main/java/com/alibaba/excel/util/FileUtils.java +++ b/src/main/java/com/alibaba/excel/util/FileUtils.java @@ -144,7 +144,8 @@ public class FileUtils { } public static File createTmpFile(String fileName) { - return createDirectory(new File(tempFilePrefix + fileName)); + File directory = createDirectory(new File(tempFilePrefix)); + return new File(directory, fileName); } /** From f065e370ab2fa7891aa5838cef4b855cfc9ade2f Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 4 Dec 2019 18:51:58 +0800 Subject: [PATCH 26/30] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../excel/constant/BuiltinFormats.java | 204 +++ .../com/alibaba/excel/util/DateUtils.java | 200 ++- .../excel/util/NumberDataFormatterUtils.java | 154 ++ .../easyexcel/test/demo/read/ReadTest.java | 16 +- .../easyexcel/test/demo/web/WebTest.java | 2 +- .../easyexcel/test/temp/Lock2Test.java | 2 +- .../test/temp/dataformat/DataFormatData.java | 16 + .../test/temp/dataformat/DataFormatTest.java | 127 ++ .../test/temp/dataformat/DataFormatter1.java | 1292 +++++++++++++++++ .../easyexcel/test/temp/simple/Wirte.java | 2 +- 11 files changed, 2005 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/constant/BuiltinFormats.java create mode 100644 src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java diff --git a/README.md b/README.md index 1251ebd..72cc016 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja /** * 最简单的读 *

1. 创建excel对应的实体对象 参照{@link DemoData} - *

2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *

2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

3. 直接读即可 */ @Test @@ -81,7 +81,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja /** * 文件上传 *

1. 创建excel对应的实体对象 参照{@link UploadData} - *

2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} + *

2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} *

3. 直接读即可 */ @PostMapping("upload") diff --git a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java new file mode 100644 index 0000000..7b3c5bb --- /dev/null +++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java @@ -0,0 +1,204 @@ +package com.alibaba.excel.constant; + +import com.alibaba.excel.util.StringUtils; + +/** + * Excel's built-in format conversion.Currently only supports Chinese. + * + *

+ * If it is not Chinese, it is recommended to directly modify the builtinFormats, which will better support + * internationalization in the future. + * + *

+ * Specific correspondence please see: + * https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.numberingformat?view=openxml-2.8.1 + * + * @author Jiaju Zhuang + **/ +public class BuiltinFormats { + + public static String[] builtinFormats = { + // 0 + "General", + // 1 + "0", + // 2 + "0.00", + // 3 + "#,##0", + // 4 + "#,##0.00", + // 5 + "\"$\"#,##0_);(\"$\"#,##0)", + // 6 + "\"$\"#,##0_);[Red](\"$\"#,##0)", + // 7 + "\"$\"#,##0.00_);(\"$\"#,##0.00)", + // 8 + "\"$\"#,##0.00_);[Red](\"$\"#,##0.00)", + // 9 + "0%", + // 10 + "0.00%", + // 11 + "0.00E+00", + // 12 + "# ?/?", + // 13 + "# ??/??", + // 14 + "m/d/yy", + // 15 + "d-mmm-yy", + // 16 + "d-mmm", + // 17 + "mmm-yy", + // 18 + "h:mm AM/PM", + // 19 + "h:mm:ss AM/PM", + // 20 + "h:mm", + // 21 + "h:mm:ss", + // 22 + "m/d/yy h:mm", + // 23-26 No specific correspondence found in the official documentation. + // 23 + null, + // 24 + null, + // 25 + null, + // 26 + null, + // 27 + "yyyy\"5E74\"m\"6708\"", + // 28 + "m\"6708\"d\"65E5\"", + // 29 + "m\"6708\"d\"65E5\"", + // 30 + "m-d-yy", + // 31 + "yyyy\"5E74\"m\"6708\"d\"65E5\"", + // 32 + "h\"65F6\"mm\"5206\"", + // 33 + "h\"65F6\"mm\"5206\"ss\"79D2\"", + // 34 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", + // 35 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", + // 36 + "yyyy\"5E74\"m\"6708\"", + // 37 + "#,##0_);(#,##0)", + // 38 + "#,##0_);[Red](#,##0)", + // 39 + "#,##0.00_);(#,##0.00)", + // 40 + "#,##0.00_);[Red](#,##0.00)", + // 41 + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + // 42 + "_(\"$\"* #,##0_);_(\"$\"* (#,##0);_(\"$\"* \"-\"_);_(@_)", + // 43 + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + // 44 + "_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)", + // 45 + "mm:ss", + // 46 + "[h]:mm:ss", + // 47 + "mm:ss.0", + // 48 + "##0.0E+0", + // 49 + "@", + // 50 + "yyyy\"5E74\"m\"6708\"", + // 51 + "m\"6708\"d\"65E5\"", + // 52 + "yyyy\"5E74\"m\"6708\"", + // 53 + "m\"6708\"d\"65E5\"", + // 54 + "m\"6708\"d\"65E5\"", + // 55 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", + // 56 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", + // 57 + "yyyy\"5E74\"m\"6708\"", + // 58 + "m\"6708\"d\"65E5\"", + // 59 + "t0", + // 60 + "t0.00", + // 61 + "t#,##0", + // 62 + "t#,##0.00", + // 63-66 No specific correspondence found in the official documentation. + // 63 + null, + // 64 + null, + // 65 + null, + // 66 + null, + // 67 + "t0%", + // 68 + "t0.00%", + // 69 + "t# ?/?", + // 70 + "t# ??/??", + // 71 + "0E27/0E14/0E1B0E1B0E1B0E1B", + // 72 + "0E27-0E140E140E14-0E1B0E1B", + // 73 + "0E27-0E140E140E14", + // 74 + "0E140E140E14-0E1B0E1B", + // 75 + "0E0A:0E190E19", + // 76 + "0E0A:0E190E19:0E170E17", + // 77 + "0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19", + // 78 + "0E190E19:0E170E17", + // 79 + "[0E0A]:0E190E19:0E170E17", + // 80 + "0E190E19:0E170E17.0", + // 81 + "d/m/bb", + // end + }; + + public static String getBuiltinFormat(Integer index) { + if (index == null || index < 0 || index >= builtinFormats.length) { + return null; + } + return builtinFormats[index]; + } + + public static String getFormat(Integer index, String format) { + if (!StringUtils.isEmpty(format)) { + return format; + } + return getBuiltinFormat(index); + } + +} diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index a581a95..dfc8d20 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -1,10 +1,15 @@ package com.alibaba.excel.util; +import java.text.Format; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import com.alibaba.excel.exception.ExcelDataConvertException; +import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelNumberFormat; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; /** * Date utils @@ -12,6 +17,9 @@ import com.alibaba.excel.exception.ExcelDataConvertException; * @author Jiaju Zhuang **/ public class DateUtils { + + + public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; @@ -101,4 +109,194 @@ public class DateUtils { } return new SimpleDateFormat(dateFormat).format(date); } + +// +// /** +// * Determine if it is a date format. +// * +// * @param dataFormat +// * @param dataFormatString +// * @return +// */ +// public static boolean isDateFormatted(Integer dataFormat, String dataFormatString) { +// if (cell == null) { +// return false; +// } +// boolean isDate = false; +// +// double d = cell.getNumericCellValue(); +// if (DateUtil.isValidExcelDate(d)) { +// ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator); +// if (nf == null) { +// return false; +// } +// bDate = isADateFormat(nf); +// } +// return bDate; +// } +// +// private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { +// if (cell == null) { +// return null; +// } +// Format dateFormat = getFormat(cell, cfEvaluator); +// synchronized (dateFormat) { +// if(dateFormat instanceof ExcelStyleDateFormatter) { +// // Hint about the raw excel value +// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted( +// cell.getNumericCellValue() +// ); +// } +// Date d = cell.getDateCellValue(); +// return performDateFormatting(d, dateFormat); +// } +// } +// +// +// public static boolean isADateFormat(int formatIndex, String formatString) { +// // First up, is this an internal date format? +// if (isInternalDateFormat(formatIndex)) { +// return true; +// } +// if (StringUtils.isEmpty(formatString)) { +// return false; +// } +// +// // check the cache first +// if (isCached(formatString, formatIndex)) { +// return lastCachedResult.get(); +// } +// +// String fs = formatString; +// /*if (false) { +// // Normalize the format string. The code below is equivalent +// // to the following consecutive regexp replacements: +// +// // Translate \- into just -, before matching +// fs = fs.replaceAll("\\\\-","-"); +// // And \, into , +// fs = fs.replaceAll("\\\\,",","); +// // And \. into . +// fs = fs.replaceAll("\\\\\\.","."); +// // And '\ ' into ' ' +// fs = fs.replaceAll("\\\\ "," "); +// +// // If it end in ;@, that's some crazy dd/mm vs mm/dd +// // switching stuff, which we can ignore +// fs = fs.replaceAll(";@", ""); +// +// // The code above was reworked as suggested in bug 48425: +// // simple loop is more efficient than consecutive regexp replacements. +// }*/ +// final int length = fs.length(); +// StringBuilder sb = new StringBuilder(length); +// for (int i = 0; i < length; i++) { +// char c = fs.charAt(i); +// if (i < length - 1) { +// char nc = fs.charAt(i + 1); +// if (c == '\\') { +// switch (nc) { +// case '-': +// case ',': +// case '.': +// case ' ': +// case '\\': +// // skip current '\' and continue to the next char +// continue; +// } +// } else if (c == ';' && nc == '@') { +// i++; +// // skip ";@" duplets +// continue; +// } +// } +// sb.append(c); +// } +// fs = sb.toString(); +// +// // short-circuit if it indicates elapsed time: [h], [m] or [s] +// if (date_ptrn4.matcher(fs).matches()) { +// cache(formatString, formatIndex, true); +// return true; +// } +// // If it starts with [DBNum1] or [DBNum2] or [DBNum3] +// // then it could be a Chinese date +// fs = date_ptrn5.matcher(fs).replaceAll(""); +// // If it starts with [$-...], then could be a date, but +// // who knows what that starting bit is all about +// fs = date_ptrn1.matcher(fs).replaceAll(""); +// // If it starts with something like [Black] or [Yellow], +// // then it could be a date +// fs = date_ptrn2.matcher(fs).replaceAll(""); +// // You're allowed something like dd/mm/yy;[red]dd/mm/yy +// // which would place dates before 1900/1904 in red +// // For now, only consider the first one +// final int separatorIndex = fs.indexOf(';'); +// if (0 < separatorIndex && separatorIndex < fs.length() - 1) { +// fs = fs.substring(0, separatorIndex); +// } +// +// // Ensure it has some date letters in it +// // (Avoids false positives on the rest of pattern 3) +// if (!date_ptrn3a.matcher(fs).find()) { +// return false; +// } +// +// // If we get here, check it's only made up, in any case, of: +// // y m d h s - \ / , . : [ ] T +// // optionally followed by AM/PM +// +// boolean result = date_ptrn3b.matcher(fs).matches(); +// cache(formatString, formatIndex, result); +// return result; +// } +// +// /** +// * Given a format ID this will check whether the format represents an internal excel date format or not. +// * +// * @see #isADateFormat(int, java.lang.String) +// */ +// public static boolean isInternalDateFormat(int format) { +// switch (format) { +// // Internal Date Formats as described on page 427 in +// // Microsoft Excel Dev's Kit... +// // 14-22 +// case 0x0e: +// case 0x0f: +// case 0x10: +// case 0x11: +// case 0x12: +// case 0x13: +// case 0x14: +// case 0x15: +// case 0x16: +// // 27-36 +// case 0x1b: +// case 0x1c: +// case 0x1d: +// case 0x1e: +// case 0x1f: +// case 0x20: +// case 0x21: +// case 0x22: +// case 0x23: +// case 0x24: +// // 45-47 +// case 0x2d: +// case 0x2e: +// case 0x2f: +// // 50-58 +// case 0x32: +// case 0x33: +// case 0x34: +// case 0x35: +// case 0x36: +// case 0x37: +// case 0x38: +// case 0x39: +// case 0x3a: +// return true; +// } +// return false; +// } } diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java new file mode 100644 index 0000000..6a4b9c5 --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java @@ -0,0 +1,154 @@ +//package com.alibaba.excel.util; +// +//import java.text.Format; +// +//import org.apache.poi.ss.format.CellFormat; +//import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +//import org.apache.poi.ss.usermodel.Cell; +//import org.apache.poi.ss.usermodel.DataFormatter; +//import org.apache.poi.ss.usermodel.DateUtil; +//import org.apache.poi.ss.usermodel.ExcelNumberFormat; +//import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +//import org.apache.poi.util.POILogger; +// +///** +// * Convert number data, including date. +// * +// * @author Jiaju Zhuang +// **/ +//public class NumberDataFormatterUtils { +// +// /** +// * +// * @param data +// * Not null. +// * @param dataFormatString +// * Not null. +// * @return +// */ +// public String format(Double data, Integer dataFormat, String dataFormatString) { +// +// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { +// return getFormattedDateString(cell, cfEvaluator); +// } +// return getFormattedNumberString(cell, cfEvaluator); +// +// } +// +// private String getFormattedDateString(Double data,String dataFormatString) { +// +// +// if (cell == null) { +// return null; +// } +// Format dateFormat = getFormat(cell, cfEvaluator); +// synchronized (dateFormat) { +// if (dateFormat instanceof ExcelStyleDateFormatter) { +// // Hint about the raw excel value +// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue()); +// } +// Date d = cell.getDateCellValue(); +// return performDateFormatting(d, dateFormat); +// } +// } +// +// +// /** +// * Return a Format for the given cell if one exists, otherwise try to +// * create one. This method will return null if the any of the +// * following is true: +// *

    +// *
  • the cell's style is null
  • +// *
  • the style's data format string is null or empty
  • +// *
  • the format string cannot be recognized as either a number or date
  • +// *
+// * +// * @param cell The cell to retrieve a Format for +// * @return A Format for the format String +// */ +// private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { +// if (cell == null) return null; +// +// ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); +// +// if ( numFmt == null) { +// return null; +// } +// +// int formatIndex = numFmt.getIdx(); +// String formatStr = numFmt.getFormat(); +// if(formatStr == null || formatStr.trim().length() == 0) { +// return null; +// } +// return getFormat(cell.getNumericCellValue(), formatIndex, formatStr, isDate1904(cell)); +// } +// +// private boolean isDate1904(Cell cell) { +// if ( cell != null && cell.getSheet().getWorkbook() instanceof Date1904Support) { +// return ((Date1904Support)cell.getSheet().getWorkbook()).isDate1904(); +// +// } +// return false; +// } +// +// private Format getFormat(double cellValue, int formatIndex, String formatStrIn, boolean use1904Windowing) { +// localeChangedObservable.checkForLocaleChange(); +// +// // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. +// // That however would require other code to be re factored. +// // String[] formatBits = formatStrIn.split(";"); +// // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; +// // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; +// +// String formatStr = formatStrIn; +// +// // Excel supports 2+ part conditional data formats, eg positive/negative/zero, +// // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds +// // of different formats for different ranges, just +ve/-ve, we need to +// // handle these ourselves in a special way. +// // For now, if we detect 2+ parts, we call out to CellFormat to handle it +// // TODO Going forward, we should really merge the logic between the two classes +// if (formatStr.contains(";") && +// (formatStr.indexOf(';') != formatStr.lastIndexOf(';') +// || rangeConditionalPattern.matcher(formatStr).matches() +// ) ) { +// try { +// // Ask CellFormat to get a formatter for it +// CellFormat cfmt = CellFormat.getInstance(locale, formatStr); +// // CellFormat requires callers to identify date vs not, so do so +// Object cellValueO = Double.valueOf(cellValue); +// if (DateUtil.isADateFormat(formatIndex, formatStr) && +// // don't try to handle Date value 0, let a 3 or 4-part format take care of it +// ((Double)cellValueO).doubleValue() != 0.0) { +// cellValueO = DateUtil.getJavaDate(cellValue, use1904Windowing); +// } +// // Wrap and return (non-cachable - CellFormat does that) +// return new DataFormatter.CellFormatResultWrapper( cfmt.apply(cellValueO) ); +// } catch (Exception e) { +// logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e); +// } +// } +// +// // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. +// if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { +// formatStr = formatStr.replaceAll("#", ""); +// } +// +// // See if we already have it cached +// Format format = formats.get(formatStr); +// if (format != null) { +// return format; +// } +// +// // Is it one of the special built in types, General or @? +// if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { +// return generalNumberFormat; +// } +// +// // Build a formatter, and cache it +// format = createFormat(cellValue, formatIndex, formatStr); +// formats.put(formatStr, format); +// return format; +// } +// +//} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 0dc22aa..c3f4ada 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -33,7 +33,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 直接读即可 */ @@ -60,7 +60,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} *

* 3. 直接读即可 */ @@ -76,7 +76,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 直接读即可 */ @@ -108,7 +108,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} *

* 3. 直接读即可 */ @@ -130,7 +130,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数, * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。 @@ -150,7 +150,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} *

* 3. 直接读即可 */ @@ -167,7 +167,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} *

* 3. 直接读即可 */ @@ -184,7 +184,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener} *

* 3. 直接读即可 */ diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java index 98f400a..b80022d 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java @@ -85,7 +85,7 @@ public class WebTest { *

* 1. 创建excel对应的实体对象 参照{@link UploadData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} *

* 3. 直接读即可 */ diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java index 052b60b..1682aa1 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java @@ -29,7 +29,7 @@ public class Lock2Test { @Test public void test() throws Exception { - File file = new File("D:\\test\\000001.xlsx"); + File file = new File("D:\\test\\headt1.xlsx"); List list = EasyExcel.read(file).sheet().headRowNumber(0).doReadSync(); LOGGER.info("数据:{}", list.size()); diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java new file mode 100644 index 0000000..d057d8e --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java @@ -0,0 +1,16 @@ +package com.alibaba.easyexcel.test.temp.dataformat; + +import com.alibaba.excel.metadata.CellData; + +import lombok.Data; + +/** + * TODO + * + * @author 罗成 + **/ +@Data +public class DataFormatData { + private CellData date; + private CellData num; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java new file mode 100644 index 0000000..d9c7bb2 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java @@ -0,0 +1,127 @@ +package com.alibaba.easyexcel.test.temp.dataformat; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.easyexcel.test.temp.Lock2Test; +import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson.JSON; + +/** + * 格式测试 + * + * @author Jiaju Zhuang + **/ +@Ignore +public class DataFormatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class); + + @Test + public void test() throws Exception { + File file = new File("D:\\test\\dataformat.xlsx"); + + List list = + EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (DataFormatData data : list) { + Integer dataFormat = data.getDate().getDataFormat(); + + String dataFormatString = data.getDate().getDataFormatString(); + + if (dataFormat == null || dataFormatString == null) { + + } else { + LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString, + DateUtil.isADateFormat(dataFormat, dataFormatString)); + } + + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void testxls() throws Exception { + File file = new File("D:\\test\\dataformat.xls"); + + List list = + EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (DataFormatData data : list) { + Integer dataFormat = data.getDate().getDataFormat(); + + String dataFormatString = data.getDate().getDataFormatString(); + + if (dataFormat == null || dataFormatString == null) { + + } else { + LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString, + DateUtil.isADateFormat(dataFormat, dataFormatString)); + } + + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void test3() throws IOException { + String file = "D:\\test\\dataformat1.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + Cell cell = xssfSheet.getRow(0).getCell(0); + DataFormatter d = new DataFormatter(); + System.out.println(d.formatCellValue(cell)); + } + + @Test + public void test31() throws IOException { + System.out.println(DateUtil.isADateFormat(181, "[DBNum1][$-404]m\"\u6708\"d\"\u65e5\";@")); + } + + @Test + public void test43() throws IOException { + SimpleDateFormat s = new SimpleDateFormat("yyyy'年'm'月'd'日' h'点'mm'哈哈哈m'"); + System.out.println(s.format(new Date())); + } + + @Test + public void test463() throws IOException { + SimpleDateFormat s = new SimpleDateFormat("[$-804]yyyy年m月"); + System.out.println(s.format(new Date())); + } + + @Test + public void test1() throws Exception { + System.out.println(DateUtil.isADateFormat(181, "yyyy\"年啊\"m\"月\"d\"日\"\\ h")); + System.out.println(DateUtil.isADateFormat(180, "yyyy\"年\"m\"月\"d\"日\"\\ h\"点\"")); + } + + @Test + public void test2() throws Exception { + List list1 = new ArrayList(3000); + long start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + list1.clear(); + } + System.out.println("end:" + (System.currentTimeMillis() - start)); + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + list1 = new ArrayList(3000); + } + System.out.println("end:" + (System.currentTimeMillis() - start)); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java new file mode 100644 index 0000000..0239ed5 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java @@ -0,0 +1,1292 @@ +/* + * ==================================================================== Licensed to the Apache Software Foundation (ASF) + * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. The ASF licenses this file to You 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. + * + * 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file The details of changes as svn diff + * can be found in svn at location root/projects/3rd-party/src + * ==================================================================== + */ +package com.alibaba.easyexcel.test.temp.dataformat; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.poi.ss.format.CellFormat; +import org.apache.poi.ss.format.CellFormatResult; +import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat; +import org.apache.poi.ss.usermodel.ExcelNumberFormat; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import org.apache.poi.ss.usermodel.FormulaError; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.FractionFormat; +import org.apache.poi.ss.util.DateFormatConverter; +import org.apache.poi.ss.util.NumberToTextConverter; +import org.apache.poi.util.LocaleUtil; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * DataFormatter contains methods for formatting the value stored in an Cell. This can be useful for reports and GUI + * presentations when you need to display data exactly as it appears in Excel. Supported formats include currency, SSN, + * percentages, decimals, dates, phone numbers, zip codes, etc. + *

+ * Internally, formats will be implemented using subclasses of {@link Format} such as {@link DecimalFormat} and + * {@link java.text.SimpleDateFormat}. Therefore the formats used by this class must obey the same pattern rules as + * these Format subclasses. This means that only legal number pattern characters ("0", "#", ".", "," etc.) may appear in + * number formats. Other characters can be inserted before or after the number pattern to form a + * prefix or suffix. + *

+ *

+ * For example the Excel pattern "$#,##0.00 "USD"_);($#,##0.00 "USD")" + * will be correctly formatted as "$1,000.00 USD" or "($1,000.00 USD)". However the pattern + * "00-00-00" is incorrectly formatted by DecimalFormat as "000000--". For Excel formats that are not + * compatible with DecimalFormat, you can provide your own custom {@link Format} implementation via + * DataFormatter.addFormat(String,Format). The following custom formats are already provided by this class: + *

+ * + *
+ * 
  • SSN "000-00-0000"
  • + *
  • Phone Number "(###) ###-####"
  • + *
  • Zip plus 4 "00000-0000"
  • + *
+ *
+ *

+ * If the Excel format pattern cannot be parsed successfully, then a default format will be used. The default number + * format will mimic the Excel General format: "#" for whole numbers and "#.##########" for decimal numbers. You can + * override the default format pattern with + * DataFormatter.setDefaultNumberFormat(Format). Note: the default format will only be used when a Format + * cannot be created from the cell's data format string. + * + *

+ * Note that by default formatted numeric values are trimmed. Excel formats can contain spacers and padding and the + * default behavior is to strip them off. + *

+ *

+ * Example: + *

+ *

+ * Consider a numeric cell with a value 12.343 and format "##.##_ ". The trailing underscore + * and space ("_ ") in the format adds a space to the end and Excel formats this cell as "12.34 ", but + * DataFormatter trims the formatted value and returns "12.34". + *

+ * You can enable spaces by passing the emulateCSV=true flag in the DateFormatter cosntructor. + * If set to true, then the output tries to conform to what you get when you take an xls or xlsx in Excel and Save As + * CSV file: + *
    + *
  • returned values are not trimmed
  • + *
  • Invalid dates are formatted as 255 pound signs ("#")
  • + *
  • simulate Excel's handling of a format string of all # when the value is 0. Excel will output "", + * DataFormatter will output "0". + *
+ *

+ * Some formats are automatically "localized" by Excel, eg show as mm/dd/yyyy when loaded in Excel in some Locales but + * as dd/mm/yyyy in others. These are always returned in the "default" (US) format, as stored in the file. Some format + * strings request an alternate locale, eg [$-809]d/m/yy h:mm AM/PM which explicitly requests UK locale. + * These locale directives are (currently) ignored. You can use {@link DateFormatConverter} to do some of this + * localisation if you need it. + */ +public class DataFormatter1 implements Observer { + private static final String defaultFractionWholePartFormat = "#"; + private static final String defaultFractionFractionPartFormat = "#/##"; + /** Pattern to find a number format: "0" or "#" */ + private static final Pattern numPattern = Pattern.compile("[0#]+"); + + /** Pattern to find days of week as text "ddd...." */ + private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); + + /** Pattern to find "AM/PM" marker */ + private static final Pattern amPmPattern = Pattern.compile("((A|P)[M/P]*)", Pattern.CASE_INSENSITIVE); + + /** Pattern to find formats with condition ranges e.g. [>=100] */ + private static final Pattern rangeConditionalPattern = + Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); + + /** + * A regex to find locale patterns like [$$-1009] and [$?-452]. Note that we don't currently process these into + * locales + */ + private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])"); + + /** + * A regex to match the colour formattings rules. Allowed colours are: Black, Blue, Cyan, Green, Magenta, Red, + * White, Yellow, "Color n" (1<=n<=56) + */ + private static final Pattern colorPattern = Pattern.compile("(\\[BLACK\\])|(\\[BLUE\\])|(\\[CYAN\\])|(\\[GREEN\\])|" + + "(\\[MAGENTA\\])|(\\[RED\\])|(\\[WHITE\\])|(\\[YELLOW\\])|" + + "(\\[COLOR\\s*\\d\\])|(\\[COLOR\\s*[0-5]\\d\\])", Pattern.CASE_INSENSITIVE); + + /** + * A regex to identify a fraction pattern. This requires that replaceAll("\\?", "#") has already been called + */ + private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*\\/\\s*([#\\d]+)"); + + /** + * A regex to strip junk out of fraction formats + */ + private static final Pattern fractionStripper = Pattern.compile("(\"[^\"]*\")|([^ \\?#\\d\\/]+)"); + + /** + * A regex to detect if an alternate grouping character is used in a numeric format + */ + private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); + + /** + * Cells formatted with a date or time format and which contain invalid date or time values show 255 pound signs + * ("#"). + */ + private static final String invalidDateTimeString; + static { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < 255; i++) + buf.append('#'); + invalidDateTimeString = buf.toString(); + } + + /** + * The decimal symbols of the locale used for formatting values. + */ + private DecimalFormatSymbols decimalSymbols; + + /** + * The date symbols of the locale used for formatting values. + */ + private DateFormatSymbols dateSymbols; + + /** + * A default date format, if no date format was given + */ + private DateFormat defaultDateformat; + + /** General format for numbers. */ + private Format generalNumberFormat; + + /** A default format to use when a number pattern cannot be parsed. */ + private Format defaultNumFormat; + + /** + * A map to cache formats. Map formats + */ + private final Map formats = new HashMap(); + + private final boolean emulateCSV; + + /** stores the locale valid it the last formatting call */ + private Locale locale; + + /** stores if the locale should change according to {@link LocaleUtil#getUserLocale()} */ + private boolean localeIsAdapting; + + private class LocaleChangeObservable extends Observable { + void checkForLocaleChange() { + checkForLocaleChange(LocaleUtil.getUserLocale()); + } + + void checkForLocaleChange(Locale newLocale) { + if (!localeIsAdapting) + return; + if (newLocale.equals(locale)) + return; + super.setChanged(); + notifyObservers(newLocale); + } + } + + /** the Observable to notify, when the locale has been changed */ + private final LocaleChangeObservable localeChangedObservable = new LocaleChangeObservable(); + + /** For logging any problems we find */ + private static POILogger logger = POILogFactory.getLogger(DataFormatter.class); + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + */ + public DataFormatter1() { + this(false); + } + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + * + * @param emulateCSV + * whether to emulate CSV output. + */ + public DataFormatter1(boolean emulateCSV) { + this(LocaleUtil.getUserLocale(), true, emulateCSV); + } + + /** + * Creates a formatter using the given locale. + */ + public DataFormatter1(Locale locale) { + this(locale, false); + } + + /** + * Creates a formatter using the given locale. + * + * @param emulateCSV + * whether to emulate CSV output. + */ + public DataFormatter1(Locale locale, boolean emulateCSV) { + this(locale, false, emulateCSV); + } + + /** + * Creates a formatter using the given locale. + * + * @param localeIsAdapting + * (true only if locale is not user-specified) + * @param emulateCSV + * whether to emulate CSV output. + */ + private DataFormatter1(Locale locale, boolean localeIsAdapting, boolean emulateCSV) { + this.localeIsAdapting = true; + localeChangedObservable.addObserver(this); + // localeIsAdapting must be true prior to this first checkForLocaleChange call. + localeChangedObservable.checkForLocaleChange(locale); + // set localeIsAdapting so subsequent checks perform correctly + // (whether a specific locale was provided to this DataFormatter or DataFormatter should + // adapt to the current user locale as the locale changes) + this.localeIsAdapting = localeIsAdapting; + this.emulateCSV = emulateCSV; + } + + /** + * Return a Format for the given cell if one exists, otherwise try to create one. This method will return + * null if the any of the following is true: + *

    + *
  • the cell's style is null
  • + *
  • the style's data format string is null or empty
  • + *
  • the format string cannot be recognized as either a number or date
  • + *
+ * + * @param cell + * The cell to retrieve a Format for + * @return A Format for the format String + */ + private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + if (cell == null) + return null; + + ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); + + if (numFmt == null) { + return null; + } + + int formatIndex = numFmt.getIdx(); + String formatStr = numFmt.getFormat(); + if (formatStr == null || formatStr.trim().length() == 0) { + return null; + } + return getFormat(cell.getNumericCellValue(), formatIndex, formatStr); + } + + private Format getFormat(double cellValue, int formatIndex, String formatStrIn) { + localeChangedObservable.checkForLocaleChange(); + + // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. + // That however would require other code to be re factored. + // String[] formatBits = formatStrIn.split(";"); + // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; + // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; + + String formatStr = formatStrIn; + + // Excel supports 2+ part conditional data formats, eg positive/negative/zero, + // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds + // of different formats for different ranges, just +ve/-ve, we need to + // handle these ourselves in a special way. + // For now, if we detect 2+ parts, we call out to CellFormat to handle it + // TODO Going forward, we should really merge the logic between the two classes + if (formatStr.contains(";") && (formatStr.indexOf(';') != formatStr.lastIndexOf(';') + || rangeConditionalPattern.matcher(formatStr).matches())) { + try { + // Ask CellFormat to get a formatter for it + CellFormat cfmt = CellFormat.getInstance(locale, formatStr); + // CellFormat requires callers to identify date vs not, so do so + Object cellValueO = Double.valueOf(cellValue); + if (DateUtil.isADateFormat(formatIndex, formatStr) && + // don't try to handle Date value 0, let a 3 or 4-part format take care of it + ((Double)cellValueO).doubleValue() != 0.0) { + cellValueO = DateUtil.getJavaDate(cellValue); + } + // Wrap and return (non-cachable - CellFormat does that) + return new CellFormatResultWrapper(cfmt.apply(cellValueO)); + } catch (Exception e) { + logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e); + } + } + + // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. + if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { + formatStr = formatStr.replaceAll("#", ""); + } + + // See if we already have it cached + Format format = formats.get(formatStr); + if (format != null) { + return format; + } + + // Is it one of the special built in types, General or @? + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return generalNumberFormat; + } + + // Build a formatter, and cache it + format = createFormat(cellValue, formatIndex, formatStr); + formats.put(formatStr, format); + return format; + } + + /** + * Create and return a Format based on the format string from a cell's style. If the pattern cannot be parsed, + * return a default pattern. + * + * @param cell + * The Excel cell + * @return A Format representing the excel format. May return null. + */ + public Format createFormat(Cell cell) { + + int formatIndex = cell.getCellStyle().getDataFormat(); + String formatStr = cell.getCellStyle().getDataFormatString(); + return createFormat(cell.getNumericCellValue(), formatIndex, formatStr); + } + + private Format createFormat(double cellValue, int formatIndex, String sFormat) { + localeChangedObservable.checkForLocaleChange(); + + String formatStr = sFormat; + + // Remove colour formatting if present + Matcher colourM = colorPattern.matcher(formatStr); + while (colourM.find()) { + String colour = colourM.group(); + + // Paranoid replacement... + int at = formatStr.indexOf(colour); + if (at == -1) + break; + String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length()); + if (nFormatStr.equals(formatStr)) + break; + + // Try again in case there's multiple + formatStr = nFormatStr; + colourM = colorPattern.matcher(formatStr); + } + + // Strip off the locale information, we use an instance-wide locale for everything + Matcher m = localePatternGroup.matcher(formatStr); + while (m.find()) { + String match = m.group(); + String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-')); + if (symbol.indexOf('$') > -1) { + symbol = symbol.substring(0, symbol.indexOf('$')) + '\\' + + symbol.substring(symbol.indexOf('$'), symbol.length()); + } + formatStr = m.replaceAll(symbol); + m = localePatternGroup.matcher(formatStr); + } + + // Check for special cases + if (formatStr == null || formatStr.trim().length() == 0) { + return getDefaultFormat(cellValue); + } + + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return generalNumberFormat; + } + + if ("".equals("")||(DateUtil.isADateFormat(formatIndex, formatStr) && DateUtil.isValidExcelDate(cellValue))) { + return createDateFormat(formatStr, cellValue); + } + // Excel supports fractions in format strings, which Java doesn't + if (formatStr.contains("#/") || formatStr.contains("?/")) { + String[] chunks = formatStr.split(";"); + for (String chunk1 : chunks) { + String chunk = chunk1.replaceAll("\\?", "#"); + Matcher matcher = fractionStripper.matcher(chunk); + chunk = matcher.replaceAll(" "); + chunk = chunk.replaceAll(" +", " "); + Matcher fractionMatcher = fractionPattern.matcher(chunk); + // take the first match + if (fractionMatcher.find()) { + String wholePart = (fractionMatcher.group(1) == null) ? "" : defaultFractionWholePartFormat; + return new FractionFormat(wholePart, fractionMatcher.group(3)); + } + } + + // Strip custom text in quotes and escaped characters for now as it can cause performance problems in + // fractions. + // String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.", + // "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#"); + // System.out.println("formatStr: "+strippedFormatStr); + return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat); + } + + if (numPattern.matcher(formatStr).find()) { + return createNumberFormat(formatStr, cellValue); + } + + if (emulateCSV) { + return new ConstantStringFormat(cleanFormatForNumber(formatStr)); + } + // TODO - when does this occur? + return null; + } + + private Format createDateFormat(String pFormatStr, double cellValue) { + String formatStr = pFormatStr; + formatStr = formatStr.replaceAll("\\\\-", "-"); + formatStr = formatStr.replaceAll("\\\\,", ","); + formatStr = formatStr.replaceAll("\\\\\\.", "."); // . is a special regexp char + formatStr = formatStr.replaceAll("\\\\ ", " "); + formatStr = formatStr.replaceAll("\\\\/", "/"); // weird: m\\/d\\/yyyy + formatStr = formatStr.replaceAll(";@", ""); + formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy + formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting + formatStr = formatStr.replaceAll("\\\\T", "'T'"); // Quote the T is iso8601 style dates + + boolean hasAmPm = false; + Matcher amPmMatcher = amPmPattern.matcher(formatStr); + while (amPmMatcher.find()) { + formatStr = amPmMatcher.replaceAll("@"); + hasAmPm = true; + amPmMatcher = amPmPattern.matcher(formatStr); + } + formatStr = formatStr.replaceAll("@", "a"); + + Matcher dateMatcher = daysAsText.matcher(formatStr); + if (dateMatcher.find()) { + String match = dateMatcher.group(0).toUpperCase(Locale.ROOT).replaceAll("D", "E"); + formatStr = dateMatcher.replaceAll(match); + } + + // Convert excel date format to SimpleDateFormat. + // Excel uses lower and upper case 'm' for both minutes and months. + // From Excel help: + /* + The "m" or "mm" code must appear immediately after the "h" or"hh" + code or immediately before the "ss" code; otherwise, Microsoft + Excel displays the month instead of minutes." + */ + + StringBuilder sb = new StringBuilder(); + char[] chars = formatStr.toCharArray(); + boolean mIsMonth = true; + List ms = new ArrayList(); + boolean isElapsed = false; + for (int j = 0; j < chars.length; j++) { + char c = chars[j]; + if (c == '\'') { + sb.append(c); + j++; + + // skip until the next quote + while (j < chars.length) { + c = chars[j]; + sb.append(c); + if (c == '\'') { + break; + } + j++; + } + } else if (c == '[' && !isElapsed) { + isElapsed = true; + mIsMonth = false; + sb.append(c); + } else if (c == ']' && isElapsed) { + isElapsed = false; + sb.append(c); + } else if (isElapsed) { + if (c == 'h' || c == 'H') { + sb.append('H'); + } else if (c == 'm' || c == 'M') { + sb.append('m'); + } else if (c == 's' || c == 'S') { + sb.append('s'); + } else { + sb.append(c); + } + } else if (c == 'h' || c == 'H') { + mIsMonth = false; + if (hasAmPm) { + sb.append('h'); + } else { + sb.append('H'); + } + } else if (c == 'm' || c == 'M') { + if (mIsMonth) { + sb.append('M'); + ms.add(Integer.valueOf(sb.length() - 1)); + } else { + sb.append('m'); + } + } else if (c == 's' || c == 'S') { + sb.append('s'); + // if 'M' precedes 's' it should be minutes ('m') + for (int index : ms) { + if (sb.charAt(index) == 'M') { + sb.replace(index, index + 1, "m"); + } + } + mIsMonth = true; + ms.clear(); + } else if (Character.isLetter(c)) { + mIsMonth = true; + ms.clear(); + if (c == 'y' || c == 'Y') { + sb.append('y'); + } else if (c == 'd' || c == 'D') { + sb.append('d'); + } else { + sb.append(c); + } + } else { + if (Character.isWhitespace(c)) { + ms.clear(); + } + sb.append(c); + } + } + formatStr = sb.toString(); + + try { + return new ExcelStyleDateFormatter(formatStr, dateSymbols); + } catch (IllegalArgumentException iae) { + logger.log(POILogger.DEBUG, "Formatting failed for format " + formatStr + ", falling back", iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + + } + + private String cleanFormatForNumber(String formatStr) { + StringBuilder sb = new StringBuilder(formatStr); + + if (emulateCSV) { + // Requested spacers with "_" are replaced by a single space. + // Full-column-width padding "*" are removed. + // Not processing fractions at this time. Replace ? with space. + // This matches CSV output. + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*' || c == '?') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (c == '?') { + sb.setCharAt(i, ' '); + } else if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + if (c == '_') { + sb.setCharAt(i + 1, ' '); + } else { + sb.deleteCharAt(i + 1); + } + // Remove the character too + sb.deleteCharAt(i); + i--; + } + } + } + } else { + // If they requested spacers, with "_", + // remove those as we don't do spacing + // If they requested full-column-width + // padding, with "*", remove those too + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + sb.deleteCharAt(i + 1); + } + // Remove the _ too + sb.deleteCharAt(i); + i--; + } + } + } + + // Now, handle the other aspects like + // quoting and scientific notation + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + // remove quotes and back slashes + if (c == '\\' || c == '"') { + sb.deleteCharAt(i); + i--; + + // for scientific/engineering notation + } else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') { + sb.deleteCharAt(i); + i--; + } + } + + return sb.toString(); + } + + private static class InternalDecimalFormatWithScale extends Format { + + private static final Pattern endsWithCommas = Pattern.compile("(,+)$"); + private BigDecimal divider; + private static final BigDecimal ONE_THOUSAND = new BigDecimal(1000); + private final DecimalFormat df; + + private static final String trimTrailingCommas(String s) { + return s.replaceAll(",+$", ""); + } + + public InternalDecimalFormatWithScale(String pattern, DecimalFormatSymbols symbols) { + df = new DecimalFormat(trimTrailingCommas(pattern), symbols); + setExcelStyleRoundingMode(df); + Matcher endsWithCommasMatcher = endsWithCommas.matcher(pattern); + if (endsWithCommasMatcher.find()) { + String commas = (endsWithCommasMatcher.group(1)); + BigDecimal temp = BigDecimal.ONE; + for (int i = 0; i < commas.length(); ++i) { + temp = temp.multiply(ONE_THOUSAND); + } + divider = temp; + } else { + divider = null; + } + } + + private Object scaleInput(Object obj) { + if (divider != null) { + if (obj instanceof BigDecimal) { + obj = ((BigDecimal)obj).divide(divider, RoundingMode.HALF_UP); + } else if (obj instanceof Double) { + obj = (Double)obj / divider.doubleValue(); + } else { + throw new UnsupportedOperationException(); + } + } + return obj; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + obj = scaleInput(obj); + return df.format(obj, toAppendTo, pos); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } + } + + private Format createNumberFormat(String formatStr, double cellValue) { + String format = cleanFormatForNumber(formatStr); + DecimalFormatSymbols symbols = decimalSymbols; + + // Do we need to change the grouping character? + // eg for a format like #'##0 which wants 12'345 not 12,345 + Matcher agm = alternateGrouping.matcher(format); + if (agm.find()) { + char grouping = agm.group(2).charAt(0); + // Only replace the grouping character if it is not the default + // grouping character for the US locale (',') in order to enable + // correct grouping for non-US locales. + if (grouping != ',') { + symbols = DecimalFormatSymbols.getInstance(locale); + + symbols.setGroupingSeparator(grouping); + String oldPart = agm.group(1); + String newPart = oldPart.replace(grouping, ','); + format = format.replace(oldPart, newPart); + } + } + + try { + return new InternalDecimalFormatWithScale(format, symbols); + } catch (IllegalArgumentException iae) { + logger.log(POILogger.DEBUG, "Formatting failed for format " + formatStr + ", falling back", iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + } + + /** + * Returns a default format for a cell. + * + * @param cell + * The cell + * @return a default format + */ + public Format getDefaultFormat(Cell cell) { + return getDefaultFormat(cell.getNumericCellValue()); + } + + private Format getDefaultFormat(double cellValue) { + localeChangedObservable.checkForLocaleChange(); + + // for numeric cells try user supplied default + if (defaultNumFormat != null) { + return defaultNumFormat; + + // otherwise use general format + } + return generalNumberFormat; + } + + /** + * Performs Excel-style date formatting, using the supplied Date and format + */ + private String performDateFormatting(Date d, Format dateFormat) { + return (dateFormat != null ? dateFormat : defaultDateformat).format(d); + } + + /** + * Returns the formatted value of an Excel date as a String based on the cell's DataFormat. + * i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc. + *

+ * If any conditional format rules apply, the highest priority with a number format is used. If no rules contain a + * number format, or no rules apply, the cell's style format is used. If the style does not have a format, the + * default date format is applied. + * + * @param cell + * to format + * @param cfEvaluator + * ConditionalFormattingEvaluator (if available) + * @return Formatted value + */ + private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + Format dateFormat = getFormat(cell, cfEvaluator); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue()); + } + Date d = cell.getDateCellValue(); + return performDateFormatting(d, dateFormat); + } + + /** + * Returns the formatted value of an Excel number as a String based on the cell's DataFormat. + * Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800) + * 555-1234". + *

+ * Format comes from either the highest priority conditional format rule with a specified format, or from the cell + * style. + * + * @param cell + * The cell + * @param cfEvaluator + * if available, or null + * @return a formatted number string + */ + private String getFormattedNumberString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + + Format numberFormat = getFormat(cell, cfEvaluator); + double d = cell.getNumericCellValue(); + if (numberFormat == null) { + return String.valueOf(d); + } + String formatted = numberFormat.format(new Double(d)); + return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation + } + + /** + * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules. + * + * @see #formatCellValue(Cell) + */ + public String formatRawCellContents(double value, int formatIndex, String formatString) { + return formatRawCellContents(value, formatIndex, formatString, false); + } + + /** + * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules. + * + * @see #formatCellValue(Cell) + */ + public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) { + localeChangedObservable.checkForLocaleChange(); + + // Is it a date? + if (DateUtil.isADateFormat(formatIndex, formatString)) { + if (DateUtil.isValidExcelDate(value)) { + Format dateFormat = getFormat(value, formatIndex, formatString); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(value); + } + Date d = DateUtil.getJavaDate(value, use1904Windowing); + return performDateFormatting(d, dateFormat); + } + // RK: Invalid dates are 255 #s. + if (emulateCSV) { + return invalidDateTimeString; + } + } + + // else Number + Format numberFormat = getFormat(value, formatIndex, formatString); + if (numberFormat == null) { + return String.valueOf(value); + } + + // When formatting 'value', double to text to BigDecimal produces more + // accurate results than double to Double in JDK8 (as compared to + // previous versions). However, if the value contains E notation, this + // would expand the values, which we do not want, so revert to + // original method. + String result; + final String textValue = NumberToTextConverter.toText(value); + if (textValue.indexOf('E') > -1) { + result = numberFormat.format(new Double(value)); + } else { + result = numberFormat.format(new BigDecimal(textValue)); + } + // Complete scientific notation by adding the missing +. + if (result.indexOf('E') > -1 && !result.contains("E-")) { + result = result.replaceFirst("E", "E+"); + } + return result; + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel format + * pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formulas in formula type cells + * will not be evaluated. + *

+ * + * @param cell + * The cell + * @return the formatted cell value as a String + */ + public String formatCellValue(Cell cell) { + return formatCellValue(cell, null); + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number + * format pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated + * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula + * String will be returned. The caller is responsible for setting the currentRow on the evaluator + *

+ * + * @param cell + * The cell (can be null) + * @param evaluator + * The FormulaEvaluator (can be null) + * @return a string value of the cell + */ + public String formatCellValue(Cell cell, FormulaEvaluator evaluator) { + return formatCellValue(cell, evaluator, null); + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number + * format pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated + * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula + * String will be returned. The caller is responsible for setting the currentRow on the evaluator + *

+ *

+ * When a ConditionalFormattingEvaluator is present, it is checked first to see if there is a number format to + * apply. If multiple rules apply, the last one is used. If no ConditionalFormattingEvaluator is present, no rules + * apply, or the applied rules do not define a format, the cell's style format is used. + *

+ *

+ * The two evaluators should be from the same context, to avoid inconsistencies in cached values. + *

+ * + * @param cell + * The cell (can be null) + * @param evaluator + * The FormulaEvaluator (can be null) + * @param cfEvaluator + * ConditionalFormattingEvaluator (can be null) + * @return a string value of the cell + */ + public String formatCellValue(Cell cell, FormulaEvaluator evaluator, ConditionalFormattingEvaluator cfEvaluator) { + localeChangedObservable.checkForLocaleChange(); + + if (cell == null) { + return ""; + } + + CellType cellType = cell.getCellTypeEnum(); + if (cellType == CellType.FORMULA) { + if (evaluator == null) { + return cell.getCellFormula(); + } + cellType = evaluator.evaluateFormulaCellEnum(cell); + } + switch (cellType) { + case NUMERIC: + +// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { + return getFormattedDateString(cell, cfEvaluator); +// } +// return getFormattedNumberString(cell, cfEvaluator); + + case STRING: + return cell.getRichStringCellValue().getString(); + + case BOOLEAN: + return cell.getBooleanCellValue() ? "TRUE" : "FALSE"; + case BLANK: + return ""; + case ERROR: + return FormulaError.forInt(cell.getErrorCellValue()).getString(); + default: + throw new RuntimeException("Unexpected celltype (" + cellType + ")"); + } + } + + /** + *

+ * Sets a default number format to be used when the Excel format cannot be parsed successfully. Note: This is + * a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells + * with the General format. + *

+ *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param format + * A Format instance to be used as a default + * @see java.text.Format#format + */ + public void setDefaultNumberFormat(Format format) { + for (Map.Entry entry : formats.entrySet()) { + if (entry.getValue() == generalNumberFormat) { + entry.setValue(format); + } + } + defaultNumFormat = format; + } + + /** + * Adds a new format to the available formats. + *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param excelFormatStr + * The data format string + * @param format + * A Format instance + */ + public void addFormat(String excelFormatStr, Format format) { + formats.put(excelFormatStr, format); + } + + // Some custom formats + + /** + * @return a DecimalFormat with parseIntegerOnly set true + */ + private static DecimalFormat createIntegerOnlyFormat(String fmt) { + DecimalFormatSymbols dsf = DecimalFormatSymbols.getInstance(Locale.ROOT); + DecimalFormat result = new DecimalFormat(fmt, dsf); + result.setParseIntegerOnly(true); + return result; + } + + /** + * Enables excel style rounding mode (round half up) on the Decimal Format given. + */ + public static void setExcelStyleRoundingMode(DecimalFormat format) { + setExcelStyleRoundingMode(format, RoundingMode.HALF_UP); + } + + /** + * Enables custom rounding mode on the given Decimal Format. + * + * @param format + * DecimalFormat + * @param roundingMode + * RoundingMode + */ + public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) { + format.setRoundingMode(roundingMode); + } + + /** + * If the Locale has been changed via {@link LocaleUtil#setUserLocale(Locale)} the stored formats need to be + * refreshed. All formats which aren't originated from DataFormatter itself, i.e. all Formats added via + * {@link DataFormatter#addFormat(String, Format)} and {@link DataFormatter#setDefaultNumberFormat(Format)}, need to + * be added again. To notify callers, the returned {@link Observable} should be used. The Object in + * {@link Observer#update(Observable, Object)} is the new Locale. + * + * @return the listener object, where callers can register themselves + */ + public Observable getLocaleChangedObservable() { + return localeChangedObservable; + } + + /** + * Update formats when locale has been changed + * + * @param observable + * usually this is our own Observable instance + * @param localeObj + * only reacts on Locale objects + */ + public void update(Observable observable, Object localeObj) { + if (!(localeObj instanceof Locale)) + return; + Locale newLocale = (Locale)localeObj; + if (!localeIsAdapting || newLocale.equals(locale)) + return; + + locale = newLocale; + + dateSymbols = DateFormatSymbols.getInstance(locale); + decimalSymbols = DecimalFormatSymbols.getInstance(locale); + generalNumberFormat = new ExcelGeneralNumberFormat(locale); + + // taken from Date.toString() + defaultDateformat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", dateSymbols); + defaultDateformat.setTimeZone(LocaleUtil.getUserTimeZone()); + + // init built-in formats + + formats.clear(); + Format zipFormat = ZipPlusFourFormat.instance; + addFormat("00000\\-0000", zipFormat); + addFormat("00000-0000", zipFormat); + + Format phoneFormat = PhoneFormat.instance; + // allow for format string variations + addFormat("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####", phoneFormat); + addFormat("[<=9999999]###-####;(###) ###-####", phoneFormat); + addFormat("###\\-####;\\(###\\)\\ ###\\-####", phoneFormat); + addFormat("###-####;(###) ###-####", phoneFormat); + + Format ssnFormat = SSNFormat.instance; + addFormat("000\\-00\\-0000", ssnFormat); + addFormat("000-00-0000", ssnFormat); + } + + /** + * Format class for Excel's SSN format. This class mimics Excel's built-in SSN formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class SSNFormat extends Format { + public static final Format instance = new SSNFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private SSNFormat() { + // enforce singleton + } + + /** Format a number as an SSN */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 3) + '-' + result.substring(3, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel Zip + 4 format. This class mimics Excel's built-in formatting for Zip + 4. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class ZipPlusFourFormat extends Format { + public static final Format instance = new ZipPlusFourFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private ZipPlusFourFormat() { + // enforce singleton + } + + /** Format a number as Zip + 4 */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel phone number format. This class mimics Excel's built-in phone number formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class PhoneFormat extends Format { + public static final Format instance = new PhoneFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + + private PhoneFormat() { + // enforce singleton + } + + /** Format a number as a phone number */ + public static String format(Number num) { + String result = df.format(num); + StringBuilder sb = new StringBuilder(); + String seg1, seg2, seg3; + int len = result.length(); + if (len <= 4) { + return result; + } + + seg3 = result.substring(len - 4, len); + seg2 = result.substring(Math.max(0, len - 7), len - 4); + seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7)); + + if (seg1.trim().length() > 0) { + sb.append('(').append(seg1).append(") "); + } + if (seg2.trim().length() > 0) { + sb.append(seg2).append('-'); + } + sb.append(seg3); + return sb.toString(); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class that does nothing and always returns a constant string. + * + * This format is used to simulate Excel's handling of a format string of all # when the value is 0. Excel will + * output "", Java will output "0". + * + */ + @SuppressWarnings("serial") + private static final class ConstantStringFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + private final String str; + + public ConstantStringFormat(String s) { + str = s; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(str); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Workaround until we merge {@link DataFormatter} with {@link CellFormat}. Constant, non-cachable wrapper around a + * {@link CellFormatResult} + */ + @SuppressWarnings("serial") + private final class CellFormatResultWrapper extends Format { + private final CellFormatResult result; + + private CellFormatResultWrapper(CellFormatResult result) { + this.result = result; + } + + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + if (emulateCSV) { + return toAppendTo.append(result.text); + } else { + return toAppendTo.append(result.text.trim()); + } + } + + public Object parseObject(String source, ParsePosition pos) { + return null; // Not supported + } + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index b19ac81..44bc943 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -64,7 +64,7 @@ public class Wirte { List list = new ArrayList(); for (int i = 0; i < 10; i++) { DemoData data = new DemoData(); - data.setString("字符串" + i); + data.setString("640121807369666560" + i); data.setDate(new Date()); data.setDoubleData(null); list.add(data); From 569f42e0da55c99b3e4c0079cac46df0a748dc02 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 4 Dec 2019 19:12:25 +0800 Subject: [PATCH 27/30] =?UTF-8?q?=E6=96=B0=E5=A2=9E`useDefaultListener`=20?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=BF=BD=E7=95=A5=E9=BB=98=E8=AE=A4=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../excel/read/builder/ExcelReaderBuilder.java | 14 ++++++++++++++ .../alibaba/excel/read/metadata/ReadWorkbook.java | 15 +++++++++++++++ .../read/metadata/holder/AbstractReadHolder.java | 6 +++++- .../alibaba/easyexcel/test/temp/Lock2Test.java | 9 +-------- .../easyexcel/test/temp/LockDataListener.java | 8 +++++--- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java index 340548b..a458acf 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java @@ -11,6 +11,7 @@ import com.alibaba.excel.cache.selector.ReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.read.listener.ModelBuildEventListener; import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.read.metadata.ReadWorkbook; import com.alibaba.excel.support.ExcelTypeEnum; @@ -233,6 +234,19 @@ public class ExcelReaderBuilder { return this; } + /** + * Whether to use the default listener, which is used by default. + *

+ * The {@link ModelBuildEventListener} is loaded by default to convert the object. + * + * @param useDefaultListener + * @return + */ + public ExcelReaderBuilder useDefaultListener(Boolean useDefaultListener) { + readWorkbook.setUseDefaultListener(useDefaultListener); + return this; + } + public ExcelReader build() { return new ExcelReader(readWorkbook); } diff --git a/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java b/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java index 3a873da..6392ca0 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java +++ b/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java @@ -7,6 +7,7 @@ import com.alibaba.excel.cache.ReadCache; import com.alibaba.excel.cache.selector.ReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.read.listener.ModelBuildEventListener; import com.alibaba.excel.support.ExcelTypeEnum; /** @@ -63,6 +64,12 @@ public class ReadWorkbook extends ReadBasicParameter { * Whether the encryption */ private String password; + /** + * Whether to use the default listener, which is used by default. + *

+ * The {@link ModelBuildEventListener} is loaded by default to convert the object. + */ + private Boolean useDefaultListener; /** * The default is all excel objects.Default is true. *

@@ -176,4 +183,12 @@ public class ReadWorkbook extends ReadBasicParameter { public void setPassword(String password) { this.password = password; } + + public Boolean getUseDefaultListener() { + return useDefaultListener; + } + + public void setUseDefaultListener(Boolean useDefaultListener) { + this.useDefaultListener = useDefaultListener; + } } diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 02b2ef5..7133f99 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -26,6 +26,7 @@ import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.read.listener.ReadListenerRegistryCenter; import com.alibaba.excel.read.listener.event.AnalysisFinishEvent; import com.alibaba.excel.read.metadata.ReadBasicParameter; +import com.alibaba.excel.read.metadata.ReadWorkbook; import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.ConverterUtils; @@ -91,7 +92,10 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH this.readListenerList = new ArrayList(parentAbstractReadHolder.getReadListenerList()); } if (HolderEnum.WORKBOOK.equals(holderType())) { - readListenerList.add(new ModelBuildEventListener()); + Boolean useDefaultListener = ((ReadWorkbook)readBasicParameter).getUseDefaultListener(); + if (useDefaultListener == null || useDefaultListener) { + readListenerList.add(new ModelBuildEventListener()); + } } if (readBasicParameter.getCustomReadListenerList() != null && !readBasicParameter.getCustomReadListenerList().isEmpty()) { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java index 052b60b..f72e94d 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java @@ -1,7 +1,6 @@ package com.alibaba.easyexcel.test.temp; import java.io.File; -import java.io.FileInputStream; import java.util.List; import org.junit.Ignore; @@ -9,13 +8,7 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.easyexcel.test.demo.read.DemoData; -import com.alibaba.easyexcel.test.demo.read.DemoDataListener; -import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; -import com.alibaba.excel.ExcelReader; -import com.alibaba.excel.read.metadata.ReadSheet; -import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.fastjson.JSON; /** @@ -43,7 +36,7 @@ public class Lock2Test { // 写法1: String fileName = "D:\\test\\珠海 (1).xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 - EasyExcel.read(fileName, LockData.class, new LockDataListener()).sheet().doRead(); + EasyExcel.read(fileName, LockData.class, new LockDataListener()).useDefaultListener(false).sheet().doRead(); } @Test diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/LockDataListener.java b/src/test/java/com/alibaba/easyexcel/test/temp/LockDataListener.java index bda277c..d589111 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/LockDataListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/LockDataListener.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.temp; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,6 +10,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.easyexcel.test.demo.read.DemoDataListener; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.metadata.CellData; import com.alibaba.fastjson.JSON; /** @@ -16,16 +18,16 @@ import com.alibaba.fastjson.JSON; * * @author Jiaju Zhuang */ -public class LockDataListener extends AnalysisEventListener { +public class LockDataListener extends AnalysisEventListener> { private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class); /** * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 5; - List list = new ArrayList(); + List> list = new ArrayList>(); @Override - public void invoke(LockData data, AnalysisContext context) { + public void invoke(Map data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); if (list.size() >= BATCH_COUNT) { From 46266abccb54666af614a6f69ffd3511eaaf3e8b Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 4 Dec 2019 19:19:31 +0800 Subject: [PATCH 28/30] =?UTF-8?q?=E6=96=B0=E5=A2=9E`useDefaultListener`=20?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=BF=BD=E7=95=A5=E9=BB=98=E8=AE=A4=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- update.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e85d044..415b45a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.3 + 2.1.4 jar easyexcel diff --git a/update.md b/update.md index 757de15..830a7d1 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,6 @@ +# 2.1.4 +* 新增参数`useDefaultListener` 可以排除默认对象转换 + # 2.1.3 * 每个java进程单独创建一个缓存目录 [Issue #813](https://github.com/alibaba/easyexcel/issues/813) * 统一修改合并为unsafe,提高大量数据导出的合并的效率 From 9c7e06022b0e32d155e2a0fa8808b94791c15188 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Mon, 23 Dec 2019 19:10:58 +0800 Subject: [PATCH 29/30] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 + .../v03/handlers/NumberRecordHandler.java | 10 +- .../v07/handlers/DefaultCellHandler.java | 7 +- .../excel/constant/BuiltinFormats.java | 62 +- .../string/StringNumberConverter.java | 12 +- .../excel/metadata/BasicParameter.java | 13 + .../alibaba/excel/metadata/DataFormatter.java | 786 ++++++++++++++++++ .../excel/metadata/GlobalConfiguration.java | 15 + .../com/alibaba/excel/util/DateUtils.java | 408 ++++----- .../excel/util/NumberDataFormatterUtils.java | 201 ++--- .../excel/util/ThreadLocalCachedUtils.java | 14 + .../test/core/dataformat/DateFormatData.java | 14 + .../test/core/dataformat/DateFormatTest.java | 50 ++ .../test/temp/dataformat/DataFormatTest.java | 63 ++ .../easyexcel/test/temp/poi/PoiWriteTest.java | 61 -- src/test/resources/dataformat/dataformat.xlsx | Bin 0 -> 11489 bytes 16 files changed, 1263 insertions(+), 458 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/metadata/DataFormatter.java create mode 100644 src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java create mode 100644 src/test/resources/dataformat/dataformat.xlsx diff --git a/pom.xml b/pom.xml index e85d044..7e2235c 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,11 @@ poi-ooxml 3.17 + + org.apache.poi + poi-ooxml-schemas + 3.17 + cglib cglib diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java index 75c3128..d8f3524 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java @@ -7,6 +7,7 @@ import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler; +import com.alibaba.excel.constant.BuiltinFormats; import com.alibaba.excel.metadata.CellData; /** @@ -32,8 +33,13 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler { this.row = numrec.getRow(); this.column = numrec.getColumn(); this.cellData = new CellData(BigDecimal.valueOf(numrec.getValue())); - this.cellData.setDataFormat(formatListener.getFormatIndex(numrec)); - this.cellData.setDataFormatString(formatListener.getFormatString(numrec)); + int dataFormat = formatListener.getFormatIndex(numrec); + this.cellData.setDataFormat(dataFormat); + if (dataFormat <= BuiltinFormats.builtinFormats.length) { + this.cellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat)); + } else { + this.cellData.setDataFormatString(formatListener.getFormatString(numrec)); + } } @Override diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index e0b9c41..7312b2c 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java @@ -13,7 +13,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; -import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFRichTextString; @@ -21,6 +20,7 @@ import org.xml.sax.Attributes; import com.alibaba.excel.analysis.v07.XlsxCellHandler; import com.alibaba.excel.analysis.v07.XlsxRowResultHolder; +import com.alibaba.excel.constant.BuiltinFormats; import com.alibaba.excel.constant.ExcelXmlConstants; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.enums.CellDataTypeEnum; @@ -87,12 +87,11 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder int dateFormatIndexInteger = Integer.parseInt(dateFormatIndex); XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger); int dataFormat = xssfCellStyle.getDataFormat(); - String dataFormatString = xssfCellStyle.getDataFormatString(); currentCellData.setDataFormat(dataFormat); - if (dataFormatString == null) { + if (dataFormat <= BuiltinFormats.builtinFormats.length) { currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat)); } else { - currentCellData.setDataFormatString(dataFormatString); + currentCellData.setDataFormatString(xssfCellStyle.getDataFormatString()); } } } diff --git a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java index 7b3c5bb..5663054 100644 --- a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java +++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java @@ -47,7 +47,8 @@ public class BuiltinFormats { // 13 "# ??/??", // 14 - "m/d/yy", + // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d". + "yyyy/m/d", // 15 "d-mmm-yy", // 16 @@ -63,7 +64,8 @@ public class BuiltinFormats { // 21 "h:mm:ss", // 22 - "m/d/yy h:mm", + // The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy/m/d h:mm". + "yyyy/m/d h:mm", // 23-26 No specific correspondence found in the official documentation. // 23 null, @@ -74,25 +76,25 @@ public class BuiltinFormats { // 26 null, // 27 - "yyyy\"5E74\"m\"6708\"", + "yyyy\"年\"m\"月\"", // 28 - "m\"6708\"d\"65E5\"", + "m\"月\"d\"日\"", // 29 - "m\"6708\"d\"65E5\"", + "m\"月\"d\"日\"", // 30 "m-d-yy", // 31 - "yyyy\"5E74\"m\"6708\"d\"65E5\"", + "yyyy\"年\"m\"月\"d\"日\"", // 32 - "h\"65F6\"mm\"5206\"", + "h\"时\"mm\"分\"", // 33 - "h\"65F6\"mm\"5206\"ss\"79D2\"", + "h\"时\"mm\"分\"ss\"秒\"", // 34 - "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", + "上午/下午h\"时\"mm\"分\"", // 35 - "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", + "上午/下午h\"时\"mm\"分\"ss\"秒\"", // 36 - "yyyy\"5E74\"m\"6708\"", + "yyyy\"年\"m\"月\"", // 37 "#,##0_);(#,##0)", // 38 @@ -120,23 +122,23 @@ public class BuiltinFormats { // 49 "@", // 50 - "yyyy\"5E74\"m\"6708\"", + "yyyy\"年\"m\"月\"", // 51 - "m\"6708\"d\"65E5\"", + "m\"月\"d\"日\"", // 52 - "yyyy\"5E74\"m\"6708\"", + "yyyy\"年\"m\"月\"", // 53 - "m\"6708\"d\"65E5\"", + "m\"月\"d\"日\"", // 54 - "m\"6708\"d\"65E5\"", + "m\"月\"d\"日\"", // 55 - "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", + "上午/下午h\"时\"mm\"分\"", // 56 - "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", + "上午/下午h\"时\"mm\"分\"ss\"秒\"", // 57 - "yyyy\"5E74\"m\"6708\"", + "yyyy\"年\"m\"月\"", // 58 - "m\"6708\"d\"65E5\"", + "m\"月\"d\"日\"", // 59 "t0", // 60 @@ -163,25 +165,25 @@ public class BuiltinFormats { // 70 "t# ??/??", // 71 - "0E27/0E14/0E1B0E1B0E1B0E1B", + "ว/ด/ปปปป", // 72 - "0E27-0E140E140E14-0E1B0E1B", + "ว-ดดด-ปป", // 73 - "0E27-0E140E140E14", + "ว-ดดด", // 74 - "0E140E140E14-0E1B0E1B", + "ดดด-ปป", // 75 - "0E0A:0E190E19", + "ช:นน", // 76 - "0E0A:0E190E19:0E170E17", + "ช:นน:ทท", // 77 - "0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19", + "ว/ด/ปปปป ช:นน", // 78 - "0E190E19:0E170E17", + "นน:ทท", // 79 - "[0E0A]:0E190E19:0E170E17", + "[ช]:นน:ทท", // 80 - "0E190E19:0E170E17.0", + "นน:ทท.0", // 81 "d/m/bb", // end diff --git a/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java b/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java index f536a08..b31bc70 100644 --- a/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java +++ b/src/main/java/com/alibaba/excel/converters/string/StringNumberConverter.java @@ -10,7 +10,9 @@ import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.util.DateUtils; +import com.alibaba.excel.util.NumberDataFormatterUtils; import com.alibaba.excel.util.NumberUtils; +import com.alibaba.excel.util.StringUtils; /** * String and number converter @@ -44,13 +46,9 @@ public class StringNumberConverter implements Converter { return NumberUtils.format(cellData.getNumberValue(), contentProperty); } // Excel defines formatting - if (cellData.getDataFormat() != null) { - if (DateUtil.isADateFormat(cellData.getDataFormat(), cellData.getDataFormatString())) { - return DateUtils.format(DateUtil.getJavaDate(cellData.getNumberValue().doubleValue(), - globalConfiguration.getUse1904windowing(), null)); - } else { - return NumberUtils.format(cellData.getNumberValue(), contentProperty); - } + if (cellData.getDataFormat() != null && !StringUtils.isEmpty(cellData.getDataFormatString())) { + return NumberDataFormatterUtils.format(cellData.getNumberValue().doubleValue(), cellData.getDataFormat(), + cellData.getDataFormatString(), globalConfiguration); } // Default conversion number return NumberUtils.format(cellData.getNumberValue(), contentProperty); diff --git a/src/main/java/com/alibaba/excel/metadata/BasicParameter.java b/src/main/java/com/alibaba/excel/metadata/BasicParameter.java index 40bffe6..d00c93a 100644 --- a/src/main/java/com/alibaba/excel/metadata/BasicParameter.java +++ b/src/main/java/com/alibaba/excel/metadata/BasicParameter.java @@ -1,6 +1,7 @@ package com.alibaba.excel.metadata; import java.util.List; +import java.util.Locale; import com.alibaba.excel.converters.Converter; @@ -34,6 +35,11 @@ public class BasicParameter { * @return */ private Boolean use1904windowing; + /** + * A Locale object represents a specific geographical, political, or cultural region. This parameter is + * used when formatting dates and numbers. + */ + private Locale locale; public List> getHead() { return head; @@ -75,4 +81,11 @@ public class BasicParameter { this.use1904windowing = use1904windowing; } + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } } diff --git a/src/main/java/com/alibaba/excel/metadata/DataFormatter.java b/src/main/java/com/alibaba/excel/metadata/DataFormatter.java new file mode 100644 index 0000000..13bd162 --- /dev/null +++ b/src/main/java/com/alibaba/excel/metadata/DataFormatter.java @@ -0,0 +1,786 @@ +/* + * ==================================================================== Licensed to the Apache Software Foundation (ASF) + * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. The ASF licenses this file to You 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. + * + * 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file The details of changes as svn diff + * can be found in svn at location root/projects/3rd-party/src + * ==================================================================== + */ +package com.alibaba.excel.metadata; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DateFormatSymbols; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import org.apache.poi.ss.usermodel.FractionFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.util.DateUtils; + +/** + * Written with reference to {@link org.apache.poi.ss.usermodel.DataFormatter}.Made some optimizations for date + * conversion. + *

+ * This is a non-thread-safe class. + * + * @author Jiaju Zhuang + */ +public class DataFormatter { + /** For logging any problems we find */ + private static final Logger LOGGER = LoggerFactory.getLogger(DataFormatter.class); + private static final String defaultFractionWholePartFormat = "#"; + private static final String defaultFractionFractionPartFormat = "#/##"; + /** Pattern to find a number format: "0" or "#" */ + private static final Pattern numPattern = Pattern.compile("[0#]+"); + + /** Pattern to find days of week as text "ddd...." */ + private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); + + /** Pattern to find "AM/PM" marker */ + private static final Pattern amPmPattern = Pattern.compile("(([AP])[M/P]*)|(([上下])[午/下]*)", Pattern.CASE_INSENSITIVE); + + /** Pattern to find formats with condition ranges e.g. [>=100] */ + private static final Pattern rangeConditionalPattern = + Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); + + /** + * A regex to find locale patterns like [$$-1009] and [$?-452]. Note that we don't currently process these into + * locales + */ + private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+])"); + + /** + * A regex to match the colour formattings rules. Allowed colours are: Black, Blue, Cyan, Green, Magenta, Red, + * White, Yellow, "Color n" (1<=n<=56) + */ + private static final Pattern colorPattern = Pattern.compile( + "(\\[BLACK])|(\\[BLUE])|(\\[CYAN])|(\\[GREEN])|" + "(\\[MAGENTA])|(\\[RED])|(\\[WHITE])|(\\[YELLOW])|" + + "(\\[COLOR\\s*\\d])|(\\[COLOR\\s*[0-5]\\d])|(\\[DBNum(1|2|3)])|(\\[\\$-\\d{0,3}])", + Pattern.CASE_INSENSITIVE); + + /** + * A regex to identify a fraction pattern. This requires that replaceAll("\\?", "#") has already been called + */ + private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*/\\s*([#\\d]+)"); + + /** + * A regex to strip junk out of fraction formats + */ + private static final Pattern fractionStripper = Pattern.compile("(\"[^\"]*\")|([^ ?#\\d/]+)"); + + /** + * A regex to detect if an alternate grouping character is used in a numeric format + */ + private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); + + /** + * Cells formatted with a date or time format and which contain invalid date or time values show 255 pound signs + * ("#"). + */ + private static final String invalidDateTimeString; + static { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < 255; i++) + buf.append('#'); + invalidDateTimeString = buf.toString(); + } + + /** + * The decimal symbols of the locale used for formatting values. + */ + private DecimalFormatSymbols decimalSymbols; + + /** + * The date symbols of the locale used for formatting values. + */ + private DateFormatSymbols dateSymbols; + /** A default format to use when a number pattern cannot be parsed. */ + private Format defaultNumFormat; + /** + * A map to cache formats. Map formats + */ + private final Map formats = new HashMap(); + + /** stores the locale valid it the last formatting call */ + private Locale locale; + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + * + * default is false + * + * @return + */ + private Boolean use1904windowing; + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + */ + public DataFormatter() { + this(null, null); + } + + /** + * Creates a formatter using the given locale. + * + */ + public DataFormatter(Locale locale, Boolean use1904windowing) { + this.use1904windowing = use1904windowing != null ? use1904windowing : Boolean.FALSE; + this.locale = locale != null ? locale : Locale.getDefault(); + this.locale = Locale.US; + this.dateSymbols = DateFormatSymbols.getInstance(this.locale); + this.decimalSymbols = DecimalFormatSymbols.getInstance(this.locale); + } + + private Format getFormat(Integer dataFormat, String dataFormatString) { + // See if we already have it cached + Format format = formats.get(dataFormatString); + if (format != null) { + return format; + } + // Is it one of the special built in types, General or @? + if ("General".equalsIgnoreCase(dataFormatString) || "@".equals(dataFormatString)) { + format = getDefaultFormat(); + addFormat(dataFormatString, format); + return format; + } + + // Build a formatter, and cache it + format = createFormat(dataFormat, dataFormatString); + addFormat(dataFormatString, format); + return format; + } + + private Format createFormat(Integer dataFormat, String dataFormatString) { + String formatStr = dataFormatString; + + Format format = checkSpecialConverter(formatStr); + if (format != null) { + return format; + } + + // Remove colour formatting if present + Matcher colourM = colorPattern.matcher(formatStr); + while (colourM.find()) { + String colour = colourM.group(); + + // Paranoid replacement... + int at = formatStr.indexOf(colour); + if (at == -1) + break; + String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length()); + if (nFormatStr.equals(formatStr)) + break; + + // Try again in case there's multiple + formatStr = nFormatStr; + colourM = colorPattern.matcher(formatStr); + } + + // Strip off the locale information, we use an instance-wide locale for everything + Matcher m = localePatternGroup.matcher(formatStr); + while (m.find()) { + String match = m.group(); + String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-')); + if (symbol.indexOf('$') > -1) { + symbol = symbol.substring(0, symbol.indexOf('$')) + '\\' + symbol.substring(symbol.indexOf('$')); + } + formatStr = m.replaceAll(symbol); + m = localePatternGroup.matcher(formatStr); + } + + // Check for special cases + if (formatStr == null || formatStr.trim().length() == 0) { + return getDefaultFormat(); + } + + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return getDefaultFormat(); + } + + if (DateUtils.isADateFormat(dataFormat, formatStr)) { + return createDateFormat(formatStr); + } + // Excel supports fractions in format strings, which Java doesn't + if (formatStr.contains("#/") || formatStr.contains("?/")) { + String[] chunks = formatStr.split(";"); + for (String chunk1 : chunks) { + String chunk = chunk1.replaceAll("\\?", "#"); + Matcher matcher = fractionStripper.matcher(chunk); + chunk = matcher.replaceAll(" "); + chunk = chunk.replaceAll(" +", " "); + Matcher fractionMatcher = fractionPattern.matcher(chunk); + // take the first match + if (fractionMatcher.find()) { + String wholePart = (fractionMatcher.group(1) == null) ? "" : defaultFractionWholePartFormat; + return new FractionFormat(wholePart, fractionMatcher.group(3)); + } + } + + // Strip custom text in quotes and escaped characters for now as it can cause performance problems in + // fractions. + // String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.", + // "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#"); + return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat); + } + + if (numPattern.matcher(formatStr).find()) { + return createNumberFormat(formatStr); + } + return getDefaultFormat(); + } + + private Format checkSpecialConverter(String dataFormatString) { + if ("00000\\-0000".equals(dataFormatString) || "00000-0000".equals(dataFormatString)) { + return new ZipPlusFourFormat(); + } + if ("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####".equals(dataFormatString) + || "[<=9999999]###-####;(###) ###-####".equals(dataFormatString) + || "###\\-####;\\(###\\)\\ ###\\-####".equals(dataFormatString) + || "###-####;(###) ###-####".equals(dataFormatString)) { + return new PhoneFormat(); + } + if ("000\\-00\\-0000".equals(dataFormatString) || "000-00-0000".equals(dataFormatString)) { + return new SSNFormat(); + } + return null; + } + + private Format createDateFormat(String pFormatStr) { + String formatStr = pFormatStr; + formatStr = formatStr.replaceAll("\\\\-", "-"); + formatStr = formatStr.replaceAll("\\\\,", ","); + formatStr = formatStr.replaceAll("\\\\\\.", "."); // . is a special regexp char + formatStr = formatStr.replaceAll("\\\\ ", " "); + formatStr = formatStr.replaceAll("\\\\/", "/"); // weird: m\\/d\\/yyyy + formatStr = formatStr.replaceAll(";@", ""); + formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy + formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting + formatStr = formatStr.replaceAll("\\\\T", "'T'"); // Quote the T is iso8601 style dates + formatStr = formatStr.replace("\"", ""); + + boolean hasAmPm = false; + Matcher amPmMatcher = amPmPattern.matcher(formatStr); + while (amPmMatcher.find()) { + formatStr = amPmMatcher.replaceAll("@"); + hasAmPm = true; + amPmMatcher = amPmPattern.matcher(formatStr); + } + formatStr = formatStr.replaceAll("@", "a"); + + Matcher dateMatcher = daysAsText.matcher(formatStr); + if (dateMatcher.find()) { + String match = dateMatcher.group(0).toUpperCase(Locale.ROOT).replaceAll("D", "E"); + formatStr = dateMatcher.replaceAll(match); + } + + // Convert excel date format to SimpleDateFormat. + // Excel uses lower and upper case 'm' for both minutes and months. + // From Excel help: + /* + The "m" or "mm" code must appear immediately after the "h" or"hh" + code or immediately before the "ss" code; otherwise, Microsoft + Excel displays the month instead of minutes." + */ + StringBuilder sb = new StringBuilder(); + char[] chars = formatStr.toCharArray(); + boolean mIsMonth = true; + List ms = new ArrayList(); + boolean isElapsed = false; + for (int j = 0; j < chars.length; j++) { + char c = chars[j]; + if (c == '\'') { + sb.append(c); + j++; + + // skip until the next quote + while (j < chars.length) { + c = chars[j]; + sb.append(c); + if (c == '\'') { + break; + } + j++; + } + } else if (c == '[' && !isElapsed) { + isElapsed = true; + mIsMonth = false; + sb.append(c); + } else if (c == ']' && isElapsed) { + isElapsed = false; + sb.append(c); + } else if (isElapsed) { + if (c == 'h' || c == 'H') { + sb.append('H'); + } else if (c == 'm' || c == 'M') { + sb.append('m'); + } else if (c == 's' || c == 'S') { + sb.append('s'); + } else { + sb.append(c); + } + } else if (c == 'h' || c == 'H') { + mIsMonth = false; + if (hasAmPm) { + sb.append('h'); + } else { + sb.append('H'); + } + } else if (c == 'm' || c == 'M') { + if (mIsMonth) { + sb.append('M'); + ms.add(Integer.valueOf(sb.length() - 1)); + } else { + sb.append('m'); + } + } else if (c == 's' || c == 'S') { + sb.append('s'); + // if 'M' precedes 's' it should be minutes ('m') + for (int index : ms) { + if (sb.charAt(index) == 'M') { + sb.replace(index, index + 1, "m"); + } + } + mIsMonth = true; + ms.clear(); + } else if (Character.isLetter(c)) { + mIsMonth = true; + ms.clear(); + if (c == 'y' || c == 'Y') { + sb.append('y'); + } else if (c == 'd' || c == 'D') { + sb.append('d'); + } else { + sb.append(c); + } + } else { + if (Character.isWhitespace(c)) { + ms.clear(); + } + sb.append(c); + } + } + formatStr = sb.toString(); + + try { + return new ExcelStyleDateFormatter(formatStr, dateSymbols); + } catch (IllegalArgumentException iae) { + LOGGER.debug("Formatting failed for format {}, falling back", formatStr, iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(); + } + + } + + private String cleanFormatForNumber(String formatStr) { + StringBuilder sb = new StringBuilder(formatStr); + // If they requested spacers, with "_", + // remove those as we don't do spacing + // If they requested full-column-width + // padding, with "*", remove those too + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + sb.deleteCharAt(i + 1); + } + // Remove the _ too + sb.deleteCharAt(i); + i--; + } + } + + // Now, handle the other aspects like + // quoting and scientific notation + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + // remove quotes and back slashes + if (c == '\\' || c == '"') { + sb.deleteCharAt(i); + i--; + + // for scientific/engineering notation + } else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') { + sb.deleteCharAt(i); + i--; + } + } + + return sb.toString(); + } + + private static class InternalDecimalFormatWithScale extends Format { + + private static final Pattern endsWithCommas = Pattern.compile("(,+)$"); + private BigDecimal divider; + private static final BigDecimal ONE_THOUSAND = new BigDecimal(1000); + private final DecimalFormat df; + + private static String trimTrailingCommas(String s) { + return s.replaceAll(",+$", ""); + } + + public InternalDecimalFormatWithScale(String pattern, DecimalFormatSymbols symbols) { + df = new DecimalFormat(trimTrailingCommas(pattern), symbols); + setExcelStyleRoundingMode(df); + Matcher endsWithCommasMatcher = endsWithCommas.matcher(pattern); + if (endsWithCommasMatcher.find()) { + String commas = (endsWithCommasMatcher.group(1)); + BigDecimal temp = BigDecimal.ONE; + for (int i = 0; i < commas.length(); ++i) { + temp = temp.multiply(ONE_THOUSAND); + } + divider = temp; + } else { + divider = null; + } + } + + private Object scaleInput(Object obj) { + if (divider != null) { + if (obj instanceof BigDecimal) { + obj = ((BigDecimal)obj).divide(divider, RoundingMode.HALF_UP); + } else if (obj instanceof Double) { + obj = (Double)obj / divider.doubleValue(); + } else { + throw new UnsupportedOperationException(); + } + } + return obj; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + obj = scaleInput(obj); + return df.format(obj, toAppendTo, pos); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } + } + + private Format createNumberFormat(String formatStr) { + String format = cleanFormatForNumber(formatStr); + DecimalFormatSymbols symbols = decimalSymbols; + + // Do we need to change the grouping character? + // eg for a format like #'##0 which wants 12'345 not 12,345 + Matcher agm = alternateGrouping.matcher(format); + if (agm.find()) { + char grouping = agm.group(2).charAt(0); + // Only replace the grouping character if it is not the default + // grouping character for the US locale (',') in order to enable + // correct grouping for non-US locales. + if (grouping != ',') { + symbols = DecimalFormatSymbols.getInstance(locale); + + symbols.setGroupingSeparator(grouping); + String oldPart = agm.group(1); + String newPart = oldPart.replace(grouping, ','); + format = format.replace(oldPart, newPart); + } + } + + try { + return new InternalDecimalFormatWithScale(format, symbols); + } catch (IllegalArgumentException iae) { + LOGGER.debug("Formatting failed for format {}, falling back", formatStr, iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(); + } + } + + private Format getDefaultFormat() { + // for numeric cells try user supplied default + if (defaultNumFormat != null) { + return defaultNumFormat; + // otherwise use general format + } + defaultNumFormat = new ExcelGeneralNumberFormat(locale); + return defaultNumFormat; + } + + /** + * Performs Excel-style date formatting, using the supplied Date and format + */ + private String performDateFormatting(Date d, Format dateFormat) { + Format df = dateFormat != null ? dateFormat : getDefaultFormat(); + return df.format(d); + } + + /** + * Returns the formatted value of an Excel date as a String based on the cell's DataFormat. + * i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc. + *

+ * If any conditional format rules apply, the highest priority with a number format is used. If no rules contain a + * number format, or no rules apply, the cell's style format is used. If the style does not have a format, the + * default date format is applied. + * + * @param data + * to format + * @param dataFormat + * @param dataFormatString + * @return Formatted value + */ + private String getFormattedDateString(Double data, Integer dataFormat, String dataFormatString) { + Format dateFormat = getFormat(dataFormat, dataFormatString); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(data); + } + return performDateFormatting(DateUtil.getJavaDate(data, use1904windowing), dateFormat); + } + + /** + * Returns the formatted value of an Excel number as a String based on the cell's DataFormat. + * Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800) + * 555-1234". + *

+ * Format comes from either the highest priority conditional format rule with a specified format, or from the cell + * style. + * + * @param data + * to format + * @param dataFormat + * @param dataFormatString + * @return a formatted number string + */ + private String getFormattedNumberString(Double data, Integer dataFormat, String dataFormatString) { + Format numberFormat = getFormat(dataFormat, dataFormatString); + String formatted = numberFormat.format(data); + return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation + } + + /** + * Format data. + * + * @param data + * @param dataFormat + * @param dataFormatString + * @return + */ + public String format(Double data, Integer dataFormat, String dataFormatString) { + if (DateUtils.isADateFormat(dataFormat, dataFormatString)) { + return getFormattedDateString(data, dataFormat, dataFormatString); + } + return getFormattedNumberString(data, dataFormat, dataFormatString); + } + + /** + *

+ * Sets a default number format to be used when the Excel format cannot be parsed successfully. Note: This is + * a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells + * with the General format. + *

+ *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param format + * A Format instance to be used as a default + * @see Format#format + */ + public void setDefaultNumberFormat(Format format) { + for (Map.Entry entry : formats.entrySet()) { + if (entry.getValue() == defaultNumFormat) { + entry.setValue(format); + } + } + defaultNumFormat = format; + } + + /** + * Adds a new format to the available formats. + *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param excelFormatStr + * The data format string + * @param format + * A Format instance + */ + public void addFormat(String excelFormatStr, Format format) { + formats.put(excelFormatStr, format); + } + + // Some custom formats + + /** + * @return a DecimalFormat with parseIntegerOnly set true + */ + private static DecimalFormat createIntegerOnlyFormat(String fmt) { + DecimalFormatSymbols dsf = DecimalFormatSymbols.getInstance(Locale.ROOT); + DecimalFormat result = new DecimalFormat(fmt, dsf); + result.setParseIntegerOnly(true); + return result; + } + + /** + * Enables excel style rounding mode (round half up) on the Decimal Format given. + */ + public static void setExcelStyleRoundingMode(DecimalFormat format) { + setExcelStyleRoundingMode(format, RoundingMode.HALF_UP); + } + + /** + * Enables custom rounding mode on the given Decimal Format. + * + * @param format + * DecimalFormat + * @param roundingMode + * RoundingMode + */ + public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) { + format.setRoundingMode(roundingMode); + } + + /** + * Format class for Excel's SSN format. This class mimics Excel's built-in SSN formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class SSNFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private SSNFormat() { + // enforce singleton + } + + /** Format a number as an SSN */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 3) + '-' + result.substring(3, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel Zip + 4 format. This class mimics Excel's built-in formatting for Zip + 4. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class ZipPlusFourFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private ZipPlusFourFormat() { + // enforce singleton + } + + /** Format a number as Zip + 4 */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel phone number format. This class mimics Excel's built-in phone number formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class PhoneFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + + private PhoneFormat() { + // enforce singleton + } + + /** Format a number as a phone number */ + public static String format(Number num) { + String result = df.format(num); + StringBuilder sb = new StringBuilder(); + String seg1, seg2, seg3; + int len = result.length(); + if (len <= 4) { + return result; + } + + seg3 = result.substring(len - 4, len); + seg2 = result.substring(Math.max(0, len - 7), len - 4); + seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7)); + + if (seg1.trim().length() > 0) { + sb.append('(').append(seg1).append(") "); + } + if (seg2.trim().length() > 0) { + sb.append(seg2).append('-'); + } + sb.append(seg3); + return sb.toString(); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + +} diff --git a/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java b/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java index 921d70a..12cc8d3 100644 --- a/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java +++ b/src/main/java/com/alibaba/excel/metadata/GlobalConfiguration.java @@ -1,5 +1,7 @@ package com.alibaba.excel.metadata; +import java.util.Locale; + /** * Global configuration * @@ -18,6 +20,11 @@ public class GlobalConfiguration { * @return */ private Boolean use1904windowing; + /** + * A Locale object represents a specific geographical, political, or cultural region. This parameter is + * used when formatting dates and numbers. + */ + private Locale locale; public Boolean getUse1904windowing() { return use1904windowing; @@ -34,4 +41,12 @@ public class GlobalConfiguration { public void setAutoTrim(Boolean autoTrim) { this.autoTrim = autoTrim; } + + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } } diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index dfc8d20..b375733 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -1,24 +1,45 @@ package com.alibaba.excel.util; -import java.text.Format; +import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; - -import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.DateUtil; -import org.apache.poi.ss.usermodel.ExcelNumberFormat; -import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; /** * Date utils * * @author Jiaju Zhuang **/ -public class DateUtils { - +public class DateUtils implements ThreadLocalCachedUtils { + /** + * Is a cache of dates + */ + private static final ThreadLocal> DATE_THREAD_LOCAL = + new ThreadLocal>(); + /** + * Is a cache of dates + */ + private static final ThreadLocal> DATE_FORMAT_THREAD_LOCAL = + new ThreadLocal>(); + /** + * The following patterns are used in {@link #isADateFormat(Integer, String)} + */ + private static final Pattern date_ptrn1 = Pattern.compile("^\\[\\$\\-.*?\\]"); + private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+\\]"); + private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]"); + // add "\u5e74 \u6708 \u65e5" for Chinese/Japanese date format:2017 \u5e74 2 \u6708 7 \u65e5 + private static final Pattern date_ptrn3b = + Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0*[ampAMP/]*$"); + // elapsed time patterns: [h],[m] and [s] + private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)\\]"); + // for format which start with "[DBNum1]" or "[DBNum2]" or "[DBNum3]" could be a Chinese date + private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]"); + // for format which start with "年" or "月" or "日" or "时" or "分" or "秒" could be a Chinese date + private static final Pattern date_ptrn6 = Pattern.compile("(年|月|日|时|分|秒)+"); public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; @@ -41,7 +62,7 @@ public class DateUtils { if (StringUtils.isEmpty(dateFormat)) { dateFormat = switchDateFormat(dateString); } - return new SimpleDateFormat(dateFormat).parse(dateString); + return getCacheDateFormat(dateFormat).parse(dateString); } /** @@ -107,196 +128,183 @@ public class DateUtils { if (StringUtils.isEmpty(dateFormat)) { dateFormat = DATE_FORMAT_19; } - return new SimpleDateFormat(dateFormat).format(date); + return getCacheDateFormat(dateFormat).format(date); + } + + private static DateFormat getCacheDateFormat(String dateFormat) { + Map dateFormatMap = DATE_FORMAT_THREAD_LOCAL.get(); + if (dateFormatMap == null) { + dateFormatMap = new HashMap(); + DATE_FORMAT_THREAD_LOCAL.set(dateFormatMap); + } else { + SimpleDateFormat dateFormatCached = dateFormatMap.get(dateFormat); + if (dateFormatCached != null) { + return dateFormatCached; + } + } + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); + dateFormatMap.put(dateFormat, simpleDateFormat); + return simpleDateFormat; } -// -// /** -// * Determine if it is a date format. -// * -// * @param dataFormat -// * @param dataFormatString -// * @return -// */ -// public static boolean isDateFormatted(Integer dataFormat, String dataFormatString) { -// if (cell == null) { -// return false; -// } -// boolean isDate = false; -// -// double d = cell.getNumericCellValue(); -// if (DateUtil.isValidExcelDate(d)) { -// ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator); -// if (nf == null) { -// return false; -// } -// bDate = isADateFormat(nf); -// } -// return bDate; -// } -// -// private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { -// if (cell == null) { -// return null; -// } -// Format dateFormat = getFormat(cell, cfEvaluator); -// synchronized (dateFormat) { -// if(dateFormat instanceof ExcelStyleDateFormatter) { -// // Hint about the raw excel value -// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted( -// cell.getNumericCellValue() -// ); -// } -// Date d = cell.getDateCellValue(); -// return performDateFormatting(d, dateFormat); -// } -// } -// -// -// public static boolean isADateFormat(int formatIndex, String formatString) { -// // First up, is this an internal date format? -// if (isInternalDateFormat(formatIndex)) { -// return true; -// } -// if (StringUtils.isEmpty(formatString)) { -// return false; -// } -// -// // check the cache first -// if (isCached(formatString, formatIndex)) { -// return lastCachedResult.get(); -// } -// -// String fs = formatString; -// /*if (false) { -// // Normalize the format string. The code below is equivalent -// // to the following consecutive regexp replacements: -// -// // Translate \- into just -, before matching -// fs = fs.replaceAll("\\\\-","-"); -// // And \, into , -// fs = fs.replaceAll("\\\\,",","); -// // And \. into . -// fs = fs.replaceAll("\\\\\\.","."); -// // And '\ ' into ' ' -// fs = fs.replaceAll("\\\\ "," "); -// -// // If it end in ;@, that's some crazy dd/mm vs mm/dd -// // switching stuff, which we can ignore -// fs = fs.replaceAll(";@", ""); -// -// // The code above was reworked as suggested in bug 48425: -// // simple loop is more efficient than consecutive regexp replacements. -// }*/ -// final int length = fs.length(); -// StringBuilder sb = new StringBuilder(length); -// for (int i = 0; i < length; i++) { -// char c = fs.charAt(i); -// if (i < length - 1) { -// char nc = fs.charAt(i + 1); -// if (c == '\\') { -// switch (nc) { -// case '-': -// case ',': -// case '.': -// case ' ': -// case '\\': -// // skip current '\' and continue to the next char -// continue; -// } -// } else if (c == ';' && nc == '@') { -// i++; -// // skip ";@" duplets -// continue; -// } -// } -// sb.append(c); -// } -// fs = sb.toString(); -// -// // short-circuit if it indicates elapsed time: [h], [m] or [s] -// if (date_ptrn4.matcher(fs).matches()) { -// cache(formatString, formatIndex, true); -// return true; -// } -// // If it starts with [DBNum1] or [DBNum2] or [DBNum3] -// // then it could be a Chinese date -// fs = date_ptrn5.matcher(fs).replaceAll(""); -// // If it starts with [$-...], then could be a date, but -// // who knows what that starting bit is all about -// fs = date_ptrn1.matcher(fs).replaceAll(""); -// // If it starts with something like [Black] or [Yellow], -// // then it could be a date -// fs = date_ptrn2.matcher(fs).replaceAll(""); -// // You're allowed something like dd/mm/yy;[red]dd/mm/yy -// // which would place dates before 1900/1904 in red -// // For now, only consider the first one -// final int separatorIndex = fs.indexOf(';'); -// if (0 < separatorIndex && separatorIndex < fs.length() - 1) { -// fs = fs.substring(0, separatorIndex); -// } -// -// // Ensure it has some date letters in it -// // (Avoids false positives on the rest of pattern 3) -// if (!date_ptrn3a.matcher(fs).find()) { -// return false; -// } -// -// // If we get here, check it's only made up, in any case, of: -// // y m d h s - \ / , . : [ ] T -// // optionally followed by AM/PM -// -// boolean result = date_ptrn3b.matcher(fs).matches(); -// cache(formatString, formatIndex, result); -// return result; -// } -// -// /** -// * Given a format ID this will check whether the format represents an internal excel date format or not. -// * -// * @see #isADateFormat(int, java.lang.String) -// */ -// public static boolean isInternalDateFormat(int format) { -// switch (format) { -// // Internal Date Formats as described on page 427 in -// // Microsoft Excel Dev's Kit... -// // 14-22 -// case 0x0e: -// case 0x0f: -// case 0x10: -// case 0x11: -// case 0x12: -// case 0x13: -// case 0x14: -// case 0x15: -// case 0x16: -// // 27-36 -// case 0x1b: -// case 0x1c: -// case 0x1d: -// case 0x1e: -// case 0x1f: -// case 0x20: -// case 0x21: -// case 0x22: -// case 0x23: -// case 0x24: -// // 45-47 -// case 0x2d: -// case 0x2e: -// case 0x2f: -// // 50-58 -// case 0x32: -// case 0x33: -// case 0x34: -// case 0x35: -// case 0x36: -// case 0x37: -// case 0x38: -// case 0x39: -// case 0x3a: -// return true; -// } -// return false; -// } + /** + * Determine if it is a date format. + * + * @param formatIndex + * @param formatString + * @return + */ + public static boolean isADateFormat(Integer formatIndex, String formatString) { + if (formatIndex == null) { + return false; + } + Map isDateCache = DATE_THREAD_LOCAL.get(); + if (isDateCache == null) { + isDateCache = new HashMap(); + DATE_THREAD_LOCAL.set(isDateCache); + } else { + Boolean isDateCachedData = isDateCache.get(formatIndex); + if (isDateCachedData != null) { + return isDateCachedData; + } + } + boolean isDate = isADateFormatUncached(formatIndex, formatString); + isDateCache.put(formatIndex, isDate); + return isDate; + } + + /** + * Determine if it is a date format. + * + * @param formatIndex + * @param formatString + * @return + */ + public static boolean isADateFormatUncached(Integer formatIndex, String formatString) { + // First up, is this an internal date format? + if (isInternalDateFormat(formatIndex)) { + return true; + } + if (StringUtils.isEmpty(formatString)) { + return false; + } + String fs = formatString; + final int length = fs.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = fs.charAt(i); + if (i < length - 1) { + char nc = fs.charAt(i + 1); + if (c == '\\') { + switch (nc) { + case '-': + case ',': + case '.': + case ' ': + case '\\': + // skip current '\' and continue to the next char + continue; + } + } else if (c == ';' && nc == '@') { + i++; + // skip ";@" duplets + continue; + } + } + sb.append(c); + } + fs = sb.toString(); + + // short-circuit if it indicates elapsed time: [h], [m] or [s] + if (date_ptrn4.matcher(fs).matches()) { + return true; + } + // If it starts with [DBNum1] or [DBNum2] or [DBNum3] + // then it could be a Chinese date + fs = date_ptrn5.matcher(fs).replaceAll(""); + // If it starts with [$-...], then could be a date, but + // who knows what that starting bit is all about + fs = date_ptrn1.matcher(fs).replaceAll(""); + // If it starts with something like [Black] or [Yellow], + // then it could be a date + fs = date_ptrn2.matcher(fs).replaceAll(""); + // You're allowed something like dd/mm/yy;[red]dd/mm/yy + // which would place dates before 1900/1904 in red + // For now, only consider the first one + final int separatorIndex = fs.indexOf(';'); + if (0 < separatorIndex && separatorIndex < fs.length() - 1) { + fs = fs.substring(0, separatorIndex); + } + + // Ensure it has some date letters in it + // (Avoids false positives on the rest of pattern 3) + if (!date_ptrn3a.matcher(fs).find()) { + return false; + } + + // If we get here, check it's only made up, in any case, of: + // y m d h s - \ / , . : [ ] T + // optionally followed by AM/PM + boolean result = date_ptrn3b.matcher(fs).matches(); + if (result) { + return true; + } + result = date_ptrn6.matcher(fs).find(); + return result; + } + + /** + * Given a format ID this will check whether the format represents an internal excel date format or not. + * + * @see #isADateFormat(Integer, String) + */ + public static boolean isInternalDateFormat(int format) { + switch (format) { + // Internal Date Formats as described on page 427 in + // Microsoft Excel Dev's Kit... + // 14-22 + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + // 27-36 + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + // 45-47 + case 0x2d: + case 0x2e: + case 0x2f: + // 50-58 + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3a: + return true; + } + return false; + } + + @Override + public void removeThreadLocalCache() { + DATE_THREAD_LOCAL.remove(); + DATE_FORMAT_THREAD_LOCAL.remove(); + } } diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java index 6a4b9c5..52ced2a 100644 --- a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java +++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java @@ -1,154 +1,47 @@ -//package com.alibaba.excel.util; -// -//import java.text.Format; -// -//import org.apache.poi.ss.format.CellFormat; -//import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; -//import org.apache.poi.ss.usermodel.Cell; -//import org.apache.poi.ss.usermodel.DataFormatter; -//import org.apache.poi.ss.usermodel.DateUtil; -//import org.apache.poi.ss.usermodel.ExcelNumberFormat; -//import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; -//import org.apache.poi.util.POILogger; -// -///** -// * Convert number data, including date. -// * -// * @author Jiaju Zhuang -// **/ -//public class NumberDataFormatterUtils { -// -// /** -// * -// * @param data -// * Not null. -// * @param dataFormatString -// * Not null. -// * @return -// */ -// public String format(Double data, Integer dataFormat, String dataFormatString) { -// -// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { -// return getFormattedDateString(cell, cfEvaluator); -// } -// return getFormattedNumberString(cell, cfEvaluator); -// -// } -// -// private String getFormattedDateString(Double data,String dataFormatString) { -// -// -// if (cell == null) { -// return null; -// } -// Format dateFormat = getFormat(cell, cfEvaluator); -// synchronized (dateFormat) { -// if (dateFormat instanceof ExcelStyleDateFormatter) { -// // Hint about the raw excel value -// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue()); -// } -// Date d = cell.getDateCellValue(); -// return performDateFormatting(d, dateFormat); -// } -// } -// -// -// /** -// * Return a Format for the given cell if one exists, otherwise try to -// * create one. This method will return null if the any of the -// * following is true: -// *
    -// *
  • the cell's style is null
  • -// *
  • the style's data format string is null or empty
  • -// *
  • the format string cannot be recognized as either a number or date
  • -// *
-// * -// * @param cell The cell to retrieve a Format for -// * @return A Format for the format String -// */ -// private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { -// if (cell == null) return null; -// -// ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); -// -// if ( numFmt == null) { -// return null; -// } -// -// int formatIndex = numFmt.getIdx(); -// String formatStr = numFmt.getFormat(); -// if(formatStr == null || formatStr.trim().length() == 0) { -// return null; -// } -// return getFormat(cell.getNumericCellValue(), formatIndex, formatStr, isDate1904(cell)); -// } -// -// private boolean isDate1904(Cell cell) { -// if ( cell != null && cell.getSheet().getWorkbook() instanceof Date1904Support) { -// return ((Date1904Support)cell.getSheet().getWorkbook()).isDate1904(); -// -// } -// return false; -// } -// -// private Format getFormat(double cellValue, int formatIndex, String formatStrIn, boolean use1904Windowing) { -// localeChangedObservable.checkForLocaleChange(); -// -// // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. -// // That however would require other code to be re factored. -// // String[] formatBits = formatStrIn.split(";"); -// // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; -// // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; -// -// String formatStr = formatStrIn; -// -// // Excel supports 2+ part conditional data formats, eg positive/negative/zero, -// // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds -// // of different formats for different ranges, just +ve/-ve, we need to -// // handle these ourselves in a special way. -// // For now, if we detect 2+ parts, we call out to CellFormat to handle it -// // TODO Going forward, we should really merge the logic between the two classes -// if (formatStr.contains(";") && -// (formatStr.indexOf(';') != formatStr.lastIndexOf(';') -// || rangeConditionalPattern.matcher(formatStr).matches() -// ) ) { -// try { -// // Ask CellFormat to get a formatter for it -// CellFormat cfmt = CellFormat.getInstance(locale, formatStr); -// // CellFormat requires callers to identify date vs not, so do so -// Object cellValueO = Double.valueOf(cellValue); -// if (DateUtil.isADateFormat(formatIndex, formatStr) && -// // don't try to handle Date value 0, let a 3 or 4-part format take care of it -// ((Double)cellValueO).doubleValue() != 0.0) { -// cellValueO = DateUtil.getJavaDate(cellValue, use1904Windowing); -// } -// // Wrap and return (non-cachable - CellFormat does that) -// return new DataFormatter.CellFormatResultWrapper( cfmt.apply(cellValueO) ); -// } catch (Exception e) { -// logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e); -// } -// } -// -// // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. -// if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { -// formatStr = formatStr.replaceAll("#", ""); -// } -// -// // See if we already have it cached -// Format format = formats.get(formatStr); -// if (format != null) { -// return format; -// } -// -// // Is it one of the special built in types, General or @? -// if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { -// return generalNumberFormat; -// } -// -// // Build a formatter, and cache it -// format = createFormat(cellValue, formatIndex, formatStr); -// formats.put(formatStr, format); -// return format; -// } -// -//} +package com.alibaba.excel.util; + +import com.alibaba.excel.metadata.DataFormatter; +import com.alibaba.excel.metadata.GlobalConfiguration; + +/** + * Convert number data, including date. + * + * @author Jiaju Zhuang + **/ +public class NumberDataFormatterUtils implements ThreadLocalCachedUtils { + /** + * Cache DataFormatter. + */ + private static final ThreadLocal DATA_FORMATTER_THREAD_LOCAL = new ThreadLocal(); + + /** + * Format number data. + * + * @param data + * @param dataFormat + * Not null. + * @param dataFormatString + * @param globalConfiguration + * @return + */ + public static String format(Double data, Integer dataFormat, String dataFormatString, + GlobalConfiguration globalConfiguration) { + DataFormatter dataFormatter = DATA_FORMATTER_THREAD_LOCAL.get(); + if (dataFormatter == null) { + if (globalConfiguration != null) { + dataFormatter = + new DataFormatter(globalConfiguration.getLocale(), globalConfiguration.getUse1904windowing()); + } else { + dataFormatter = new DataFormatter(); + } + DATA_FORMATTER_THREAD_LOCAL.set(dataFormatter); + } + return dataFormatter.format(data, dataFormat, dataFormatString); + + } + + @Override + public void removeThreadLocalCache() { + DATA_FORMATTER_THREAD_LOCAL.remove(); + } +} diff --git a/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java b/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java new file mode 100644 index 0000000..d136703 --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java @@ -0,0 +1,14 @@ +package com.alibaba.excel.util; + +/** + * Thread local cache in the current tool class. + * + * @author Jiaju Zhuang + **/ +public interface ThreadLocalCachedUtils { + + /** + * Remove remove thread local cached. + */ + void removeThreadLocalCache(); +} diff --git a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java new file mode 100644 index 0000000..d627e46 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java @@ -0,0 +1,14 @@ +package com.alibaba.easyexcel.test.core.dataformat; + +import lombok.Data; + +/** + * @author Jiaju Zhuang + */ +@Data +public class DateFormatData { + private String date; + private String dateString; + private String number; + private String numberString; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java new file mode 100644 index 0000000..69b6709 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java @@ -0,0 +1,50 @@ +package com.alibaba.easyexcel.test.core.dataformat; + +import java.io.File; +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.easyexcel.test.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson.JSON; + +/** + * + * @author Jiaju Zhuang + */ +public class DateFormatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(DateFormatTest.class); + + private static File file07; + private static File file03; + + @BeforeClass + public static void init() { + file07 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx"); + file03 = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xls"); + } + + @Test + public void t01Read07() { + read(file07); + } + + @Test + public void t02Read03() { + read(file03); + } + + private void read(File file) { + List list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync(); + for (DateFormatData data : list) { + if (!data.getDate().equals(data.getDateString())) { + LOGGER.info("返回:{}", JSON.toJSONString(data)); + } + } + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java index d9c7bb2..d3db5e0 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java @@ -6,10 +6,14 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.junit.Ignore; @@ -17,7 +21,9 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.easyexcel.test.core.dataformat.DateFormatData; import com.alibaba.easyexcel.test.temp.Lock2Test; +import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson.JSON; @@ -124,4 +130,61 @@ public class DataFormatTest { System.out.println("end:" + (System.currentTimeMillis() - start)); } + @Test + public void test355() throws IOException, InvalidFormatException { + File file = TestFileUtil.readFile("dataformat" + File.separator + "dataformat.xlsx"); + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + DataFormatter d = new DataFormatter(Locale.CHINA); + + for (int i = 0; i < xssfSheet.getLastRowNum(); i++) { + Row row = xssfSheet.getRow(i); + System.out.println(d.formatCellValue(row.getCell(0))); + } + + } + + @Test + public void test3556() throws IOException, InvalidFormatException { + String file = "D://test/dataformat.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + DataFormatter d = new DataFormatter(Locale.CHINA); + + for (int i = 0; i < xssfSheet.getLastRowNum(); i++) { + Row row = xssfSheet.getRow(i); + System.out.println(d.formatCellValue(row.getCell(0))); + } + + } + + @Test + public void tests() throws IOException, InvalidFormatException { + SimpleDateFormat s1 = new SimpleDateFormat("yyyy\"5E74\"m\"6708\"d\"65E5\""); + System.out.println(s1.format(new Date())); + s1 = new SimpleDateFormat("yyyy年m月d日"); + System.out.println(s1.format(new Date())); + } + + @Test + public void tests1() throws IOException, InvalidFormatException { + String file = "D://test/dataformat1.xlsx"; + List list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync(); + for (DateFormatData data : list) { + LOGGER.info("返回:{}", JSON.toJSONString(data)); + } + } + + @Test + public void tests3() throws IOException, InvalidFormatException { + SimpleDateFormat s1 = new SimpleDateFormat("ah\"时\"mm\"分\""); + System.out.println(s1.format(new Date())); + } + + private static final Pattern date_ptrn6 = Pattern.compile("^.*(年|月|日|时|分|秒)+.*$"); + + @Test + public void tests34() throws IOException, InvalidFormatException { + System.out.println(date_ptrn6.matcher("2017但是").matches()); + } } diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java index 3a70d0d..68fff70 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java @@ -2,12 +2,8 @@ package com.alibaba.easyexcel.test.temp.poi; import java.io.FileOutputStream; import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.regex.Pattern; -import org.apache.poi.ss.formula.functions.T; import org.apache.poi.xssf.streaming.SXSSFCell; import org.apache.poi.xssf.streaming.SXSSFRow; import org.apache.poi.xssf.streaming.SXSSFSheet; @@ -17,11 +13,8 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.excel.metadata.CellData; import com.alibaba.fastjson.JSON; -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; - /** * 测试poi * @@ -99,58 +92,4 @@ public class PoiWriteTest { } - @Test - public void test() throws Exception { - Class clazz = TestCell.class; - - Field field = clazz.getDeclaredField("c2"); - // 通过getDeclaredField可以获得成员变量,但是对于Map来说,仅仅可以知道它是个Map,无法知道键值对各自的数据类型 - - Type gType = field.getGenericType(); - // 获得field的泛型类型 - - // 如果gType是ParameterizedType对象(参数化) - if (gType instanceof ParameterizedType) { - - ParameterizedType pType = (ParameterizedType)gType; - // 就把它转换成ParameterizedType对象 - - Type[] tArgs = pType.getActualTypeArguments(); - // 获得泛型类型的泛型参数(实际类型参数) - ParameterizedTypeImpl c = (ParameterizedTypeImpl)pType.getActualTypeArguments()[0]; - Class ttt = c.getRawType(); - System.out.println(ttt); - } else { - System.out.println("出错!!!"); - } - - } - - @Test - public void test2() throws Exception { - Class clazz = TestCell.class; - - Field field = clazz.getDeclaredField("c2"); - // 通过getDeclaredField可以获得成员变量,但是对于Map来说,仅仅可以知道它是个Map,无法知道键值对各自的数据类型 - - Type gType = field.getGenericType(); - // 获得field的泛型类型 - - // 如果gType是ParameterizedType对象(参数化) - if (gType instanceof ParameterizedType) { - - ParameterizedType pType = (ParameterizedType)gType; - // 就把它转换成ParameterizedType对象 - - Type[] tArgs = pType.getActualTypeArguments(); - // 获得泛型类型的泛型参数(实际类型参数) - ParameterizedTypeImpl c = (ParameterizedTypeImpl)pType.getActualTypeArguments()[0]; - Class ttt = c.getRawType(); - System.out.println(ttt); - } else { - System.out.println("出错!!!"); - } - - } - } diff --git a/src/test/resources/dataformat/dataformat.xlsx b/src/test/resources/dataformat/dataformat.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..370d983a931862faf90274a954ed71e38cb92ac5 GIT binary patch literal 11489 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 literal 0 HcmV?d00001 From 35558838718454cf4784f70288fc90960c07e403 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 24 Dec 2019 20:44:03 +0800 Subject: [PATCH 30/30] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=A8String?= =?UTF-8?q?=E6=8E=A5=E6=94=B6=E6=97=A5=E6=9C=9F=E3=80=81=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=92=8Cexcel=E6=98=BE=E7=A4=BA=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- .../excel/analysis/ExcelAnalyserImpl.java | 9 + .../excel/analysis/v03/XlsSaxAnalyser.java | 6 +- .../v03/handlers/NumberRecordHandler.java | 13 +- .../v07/handlers/DefaultCellHandler.java | 8 +- .../excel/constant/BuiltinFormats.java | 193 +++++++++++++++++- .../excel/context/AnalysisContext.java | 1 - .../excel/context/WriteContextImpl.java | 9 + .../byteconverter/ByteNumberConverter.java | 2 +- .../floatconverter/FloatNumberConverter.java | 2 +- .../integer/IntegerNumberConverter.java | 2 +- .../shortconverter/ShortNumberConverter.java | 2 +- .../excel/metadata/AbstractHolder.java | 12 +- .../metadata/AbstractParameterBuilder.java | 93 +++++++++ .../alibaba/excel/metadata/DataFormatter.java | 4 +- .../AbstractExcelReaderParameterBuilder.java | 47 +++++ .../read/builder/ExcelReaderBuilder.java | 103 +--------- .../read/builder/ExcelReaderSheetBuilder.java | 101 +-------- .../com/alibaba/excel/util/DateUtils.java | 5 +- .../excel/util/NumberDataFormatterUtils.java | 5 +- .../excel/util/ThreadLocalCachedUtils.java | 14 -- .../AbstractExcelWriterParameterBuilder.java | 104 ++++++++++ .../write/builder/ExcelWriterBuilder.java | 133 +----------- .../builder/ExcelWriterSheetBuilder.java | 136 +----------- .../builder/ExcelWriterTableBuilder.java | 135 +----------- .../test/core/dataformat/DateFormatData.java | 6 +- .../test/core/dataformat/DateFormatTest.java | 27 ++- .../test/temp/dataformat/DataFormatTest.java | 2 +- .../easyexcel/test/temp/poi/PoiWriteTest.java | 12 ++ .../easyexcel/test/temp/simple/JsonData.java | 15 ++ .../easyexcel/test/temp/simple/Wirte.java | 41 +++- .../easyexcel/test/temp/simple/WriteData.java | 13 ++ src/test/resources/dataformat/dataformat.xls | Bin 0 -> 26112 bytes src/test/resources/dataformat/dataformat.xlsx | Bin 11489 -> 11754 bytes update.md | 6 + 35 files changed, 618 insertions(+), 647 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/metadata/AbstractParameterBuilder.java create mode 100644 src/main/java/com/alibaba/excel/read/builder/AbstractExcelReaderParameterBuilder.java delete mode 100644 src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java create mode 100644 src/main/java/com/alibaba/excel/write/builder/AbstractExcelWriterParameterBuilder.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/simple/JsonData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/temp/simple/WriteData.java create mode 100644 src/test/resources/dataformat/dataformat.xls diff --git a/pom.xml b/pom.xml index 7e2235c..5aff423 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ UTF-8 - 1.7 + 1.6 @@ -84,7 +84,7 @@ org.ehcache ehcache - 3.7.1 + 3.4.0 diff --git a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java index eb4f188..feec16b 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -22,7 +22,9 @@ import com.alibaba.excel.read.metadata.ReadWorkbook; import com.alibaba.excel.read.metadata.holder.ReadWorkbookHolder; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.util.CollectionUtils; +import com.alibaba.excel.util.DateUtils; import com.alibaba.excel.util.FileUtils; +import com.alibaba.excel.util.NumberDataFormatterUtils; import com.alibaba.excel.util.StringUtils; /** @@ -175,11 +177,18 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { clearEncrypt03(); + removeThreadLocalCache(); + if (throwable != null) { throw new ExcelAnalysisException("Can not close IO.", throwable); } } + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); + } + private void clearEncrypt03() { if (StringUtils.isEmpty(analysisContext.readWorkbookHolder().getPassword()) || !ExcelTypeEnum.XLS.equals(analysisContext.readWorkbookHolder().getExcelType())) { diff --git a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java index e0c150c..fd73e67 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -3,10 +3,10 @@ package com.alibaba.excel.analysis.v03; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; @@ -191,7 +191,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { analysisContext.readRowHolder( new ReadRowHolder(lastRowNumber, analysisContext.readSheetHolder().getGlobalConfiguration())); analysisContext.readSheetHolder().notifyEndOneRow(new EachRowAnalysisFinishEvent(records), analysisContext); - records.clear(); + records = new HashMap(); lastColumnNumber = -1; } @@ -208,7 +208,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { recordHandlers.add(new FormulaRecordHandler(stubWorkbook, formatListener)); recordHandlers.add(new LabelRecordHandler()); recordHandlers.add(new NoteRecordHandler()); - recordHandlers.add(new NumberRecordHandler(formatListener)); + recordHandlers.add(new NumberRecordHandler(analysisContext, formatListener)); recordHandlers.add(new RkRecordHandler()); recordHandlers.add(new SstRecordHandler()); recordHandlers.add(new MissingCellDummyRecordHandler()); diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java index d8f3524..af02488 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java @@ -8,6 +8,7 @@ import org.apache.poi.hssf.record.Record; import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler; import com.alibaba.excel.constant.BuiltinFormats; +import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.metadata.CellData; /** @@ -18,7 +19,10 @@ import com.alibaba.excel.metadata.CellData; public class NumberRecordHandler extends AbstractXlsRecordHandler { private FormatTrackingHSSFListener formatListener; - public NumberRecordHandler(FormatTrackingHSSFListener formatListener) { + private AnalysisContext context; + + public NumberRecordHandler(AnalysisContext context, FormatTrackingHSSFListener formatListener) { + this.context = context; this.formatListener = formatListener; } @@ -35,11 +39,8 @@ public class NumberRecordHandler extends AbstractXlsRecordHandler { this.cellData = new CellData(BigDecimal.valueOf(numrec.getValue())); int dataFormat = formatListener.getFormatIndex(numrec); this.cellData.setDataFormat(dataFormat); - if (dataFormat <= BuiltinFormats.builtinFormats.length) { - this.cellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat)); - } else { - this.cellData.setDataFormatString(formatListener.getFormatString(numrec)); - } + this.cellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat, + formatListener.getFormatString(numrec), context.readSheetHolder().getGlobalConfiguration().getLocale())); } @Override diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 7312b2c..3d96a90 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java @@ -88,11 +88,9 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(dateFormatIndexInteger); int dataFormat = xssfCellStyle.getDataFormat(); currentCellData.setDataFormat(dataFormat); - if (dataFormat <= BuiltinFormats.builtinFormats.length) { - currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat)); - } else { - currentCellData.setDataFormatString(xssfCellStyle.getDataFormatString()); - } + currentCellData.setDataFormatString( + BuiltinFormats.getBuiltinFormat(dataFormat, xssfCellStyle.getDataFormatString(), + analysisContext.readSheetHolder().getGlobalConfiguration().getLocale())); } } // cell is formula diff --git a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java index 5663054..1dec2c6 100644 --- a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java +++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java @@ -1,6 +1,6 @@ package com.alibaba.excel.constant; -import com.alibaba.excel.util.StringUtils; +import java.util.Locale; /** * Excel's built-in format conversion.Currently only supports Chinese. @@ -17,7 +17,179 @@ import com.alibaba.excel.util.StringUtils; **/ public class BuiltinFormats { - public static String[] builtinFormats = { + private static final String[] BUILTIN_FORMATS_CN = { + // 0 + "General", + // 1 + "0", + // 2 + "0.00", + // 3 + "#,##0", + // 4 + "#,##0.00", + // 5 + "\"¥\"#,##0_);(\"¥\"#,##0)", + // 6 + "\"¥\"#,##0_);[Red](\"¥\"#,##0)", + // 7 + "\"¥\"#,##0.00_);(\"¥\"#,##0.00)", + // 8 + "\"¥\"#,##0.00_);[Red](\"¥\"#,##0.00)", + // 9 + "0%", + // 10 + "0.00%", + // 11 + "0.00E+00", + // 12 + "# ?/?", + // 13 + "# ??/??", + // 14 + // The official documentation shows "m/d/yy", but the actual test is "yyyy/m/d". + "yyyy/m/d", + // 15 + "d-mmm-yy", + // 16 + "d-mmm", + // 17 + "mmm-yy", + // 18 + "h:mm AM/PM", + // 19 + "h:mm:ss AM/PM", + // 20 + "h:mm", + // 21 + "h:mm:ss", + // 22 + // The official documentation shows "m/d/yy h:mm", but the actual test is "yyyy/m/d h:mm". + "yyyy/m/d h:mm", + // 23-26 No specific correspondence found in the official documentation. + // 23 + null, + // 24 + null, + // 25 + null, + // 26 + null, + // 27 + "yyyy\"年\"m\"月\"", + // 28 + "m\"月\"d\"日\"", + // 29 + "m\"月\"d\"日\"", + // 30 + "m-d-yy", + // 31 + "yyyy\"年\"m\"月\"d\"日\"", + // 32 + "h\"时\"mm\"分\"", + // 33 + "h\"时\"mm\"分\"ss\"秒\"", + // 34 + "上午/下午h\"时\"mm\"分\"", + // 35 + "上午/下午h\"时\"mm\"分\"ss\"秒\"", + // 36 + "yyyy\"年\"m\"月\"", + // 37 + "#,##0_);(#,##0)", + // 38 + "#,##0_);[Red](#,##0)", + // 39 + "#,##0.00_);(#,##0.00)", + // 40 + "#,##0.00_);[Red](#,##0.00)", + // 41 + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + // 42 + "_(\"¥\"* #,##0_);_(\"¥\"* (#,##0);_(\"¥\"* \"-\"_);_(@_)", + // 43 + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + // 44 + "_(\"¥\"* #,##0.00_);_(\"¥\"* (#,##0.00);_(\"¥\"* \"-\"??_);_(@_)", + // 45 + "mm:ss", + // 46 + "[h]:mm:ss", + // 47 + "mm:ss.0", + // 48 + "##0.0E+0", + // 49 + "@", + // 50 + "yyyy\"年\"m\"月\"", + // 51 + "m\"月\"d\"日\"", + // 52 + "yyyy\"年\"m\"月\"", + // 53 + "m\"月\"d\"日\"", + // 54 + "m\"月\"d\"日\"", + // 55 + "上午/下午h\"时\"mm\"分\"", + // 56 + "上午/下午h\"时\"mm\"分\"ss\"秒\"", + // 57 + "yyyy\"年\"m\"月\"", + // 58 + "m\"月\"d\"日\"", + // 59 + "t0", + // 60 + "t0.00", + // 61 + "t#,##0", + // 62 + "t#,##0.00", + // 63-66 No specific correspondence found in the official documentation. + // 63 + null, + // 64 + null, + // 65 + null, + // 66 + null, + // 67 + "t0%", + // 68 + "t0.00%", + // 69 + "t# ?/?", + // 70 + "t# ??/??", + // 71 + "ว/ด/ปปปป", + // 72 + "ว-ดดด-ปป", + // 73 + "ว-ดดด", + // 74 + "ดดด-ปป", + // 75 + "ช:นน", + // 76 + "ช:นน:ทท", + // 77 + "ว/ด/ปปปป ช:นน", + // 78 + "นน:ทท", + // 79 + "[ช]:นน:ทท", + // 80 + "นน:ทท.0", + // 81 + "d/m/bb", + // end + }; + + private static final String[] BUILTIN_FORMATS_US = { // 0 "General", // 1 @@ -189,18 +361,19 @@ public class BuiltinFormats { // end }; - public static String getBuiltinFormat(Integer index) { - if (index == null || index < 0 || index >= builtinFormats.length) { - return null; + public static String getBuiltinFormat(Integer index, String defaultFormat, Locale locale) { + String[] builtinFormat = switchBuiltinFormats(locale); + if (index == null || index < 0 || index >= builtinFormat.length) { + return defaultFormat; } - return builtinFormats[index]; + return builtinFormat[index]; } - public static String getFormat(Integer index, String format) { - if (!StringUtils.isEmpty(format)) { - return format; + private static String[] switchBuiltinFormats(Locale locale) { + if (locale != null && Locale.US.getCountry().equals(locale.getCountry())) { + return BUILTIN_FORMATS_US; } - return getBuiltinFormat(index); + return BUILTIN_FORMATS_CN; } } diff --git a/src/main/java/com/alibaba/excel/context/AnalysisContext.java b/src/main/java/com/alibaba/excel/context/AnalysisContext.java index f44c2b3..66c69a5 100644 --- a/src/main/java/com/alibaba/excel/context/AnalysisContext.java +++ b/src/main/java/com/alibaba/excel/context/AnalysisContext.java @@ -2,7 +2,6 @@ package com.alibaba.excel.context; import java.io.InputStream; -import com.alibaba.excel.analysis.ExcelReadExecutor; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.read.metadata.ReadSheet; diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index 0935868..7b9bb81 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -27,7 +27,9 @@ import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.support.ExcelTypeEnum; +import com.alibaba.excel.util.DateUtils; import com.alibaba.excel.util.FileUtils; +import com.alibaba.excel.util.NumberDataFormatterUtils; import com.alibaba.excel.util.StringUtils; import com.alibaba.excel.util.WorkBookUtil; import com.alibaba.excel.util.WriteHandlerUtils; @@ -327,6 +329,8 @@ public class WriteContextImpl implements WriteContext { clearEncrypt03(); + removeThreadLocalCache(); + if (throwable != null) { throw new ExcelGenerateException("Can not close IO.", throwable); } @@ -336,6 +340,11 @@ public class WriteContextImpl implements WriteContext { } } + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); + } + @Override public Sheet getCurrentSheet() { return writeSheetHolder.getSheet(); diff --git a/src/main/java/com/alibaba/excel/converters/byteconverter/ByteNumberConverter.java b/src/main/java/com/alibaba/excel/converters/byteconverter/ByteNumberConverter.java index 8c16a77..f1facdf 100644 --- a/src/main/java/com/alibaba/excel/converters/byteconverter/ByteNumberConverter.java +++ b/src/main/java/com/alibaba/excel/converters/byteconverter/ByteNumberConverter.java @@ -34,7 +34,7 @@ public class ByteNumberConverter implements Converter { @Override public CellData convertToExcelData(Byte value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return new CellData(BigDecimal.valueOf(value)); + return new CellData(new BigDecimal(Byte.toString(value))); } } diff --git a/src/main/java/com/alibaba/excel/converters/floatconverter/FloatNumberConverter.java b/src/main/java/com/alibaba/excel/converters/floatconverter/FloatNumberConverter.java index a1b6471..69cc22e 100644 --- a/src/main/java/com/alibaba/excel/converters/floatconverter/FloatNumberConverter.java +++ b/src/main/java/com/alibaba/excel/converters/floatconverter/FloatNumberConverter.java @@ -34,7 +34,7 @@ public class FloatNumberConverter implements Converter { @Override public CellData convertToExcelData(Float value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return new CellData(BigDecimal.valueOf(value)); + return new CellData(new BigDecimal(Float.toString(value))); } } diff --git a/src/main/java/com/alibaba/excel/converters/integer/IntegerNumberConverter.java b/src/main/java/com/alibaba/excel/converters/integer/IntegerNumberConverter.java index 3b0deac..117d4c1 100644 --- a/src/main/java/com/alibaba/excel/converters/integer/IntegerNumberConverter.java +++ b/src/main/java/com/alibaba/excel/converters/integer/IntegerNumberConverter.java @@ -34,7 +34,7 @@ public class IntegerNumberConverter implements Converter { @Override public CellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return new CellData(BigDecimal.valueOf(value)); + return new CellData(new BigDecimal(Integer.toString(value))); } } diff --git a/src/main/java/com/alibaba/excel/converters/shortconverter/ShortNumberConverter.java b/src/main/java/com/alibaba/excel/converters/shortconverter/ShortNumberConverter.java index 7d1d7da..357c6ae 100644 --- a/src/main/java/com/alibaba/excel/converters/shortconverter/ShortNumberConverter.java +++ b/src/main/java/com/alibaba/excel/converters/shortconverter/ShortNumberConverter.java @@ -34,7 +34,7 @@ public class ShortNumberConverter implements Converter { @Override public CellData convertToExcelData(Short value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return new CellData(BigDecimal.valueOf(value)); + return new CellData(new BigDecimal(Short.toString(value))); } } diff --git a/src/main/java/com/alibaba/excel/metadata/AbstractHolder.java b/src/main/java/com/alibaba/excel/metadata/AbstractHolder.java index 37a1942..6bfd825 100644 --- a/src/main/java/com/alibaba/excel/metadata/AbstractHolder.java +++ b/src/main/java/com/alibaba/excel/metadata/AbstractHolder.java @@ -1,6 +1,7 @@ package com.alibaba.excel.metadata; import java.util.List; +import java.util.Locale; import java.util.Map; import com.alibaba.excel.converters.Converter; @@ -27,7 +28,6 @@ public abstract class AbstractHolder implements ConfigurationHolder { * Some global variables */ private GlobalConfiguration globalConfiguration; - /** *

* Read key: @@ -58,6 +58,16 @@ public abstract class AbstractHolder implements ConfigurationHolder { } else { globalConfiguration.setAutoTrim(basicParameter.getAutoTrim()); } + + if (basicParameter.getLocale() == null) { + if (prentAbstractHolder == null) { + globalConfiguration.setLocale(Locale.getDefault()); + } else { + globalConfiguration.setLocale(prentAbstractHolder.getGlobalConfiguration().getLocale()); + } + } else { + globalConfiguration.setLocale(basicParameter.getLocale()); + } } public Boolean getNewInitialization() { diff --git a/src/main/java/com/alibaba/excel/metadata/AbstractParameterBuilder.java b/src/main/java/com/alibaba/excel/metadata/AbstractParameterBuilder.java new file mode 100644 index 0000000..5840046 --- /dev/null +++ b/src/main/java/com/alibaba/excel/metadata/AbstractParameterBuilder.java @@ -0,0 +1,93 @@ +package com.alibaba.excel.metadata; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import com.alibaba.excel.converters.Converter; + +/** + * ExcelBuilder + * + * @author Jiaju Zhuang + */ +public abstract class AbstractParameterBuilder { + /** + * You can only choose one of the {@link #head(List)} and {@link #head(Class)} + * + * @param head + * @return + */ + public T head(List> head) { + parameter().setHead(head); + return self(); + } + + /** + * You can only choose one of the {@link #head(List)} and {@link #head(Class)} + * + * @param clazz + * @return + */ + public T head(Class clazz) { + parameter().setClazz(clazz); + return self(); + } + + /** + * Custom type conversions override the default. + * + * @param converter + * @return + */ + public T registerConverter(Converter converter) { + if (parameter().getCustomConverterList() == null) { + parameter().setCustomConverterList(new ArrayList()); + } + parameter().getCustomConverterList().add(converter); + return self(); + } + + /** + * true if date uses 1904 windowing, or false if using 1900 date windowing. + * + * default is false + * + * @param use1904windowing + * @return + */ + public T use1904windowing(Boolean use1904windowing) { + parameter().setUse1904windowing(use1904windowing); + return self(); + } + + /** + * A Locale object represents a specific geographical, political, or cultural region. This parameter is + * used when formatting dates and numbers. + * + * @param locale + * @return + */ + public T locale(Locale locale) { + parameter().setLocale(locale); + return self(); + } + + /** + * Automatic trim includes sheet name and content + * + * @param autoTrim + * @return + */ + public T autoTrim(Boolean autoTrim) { + parameter().setAutoTrim(autoTrim); + return self(); + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T)this; + } + + protected abstract C parameter(); +} diff --git a/src/main/java/com/alibaba/excel/metadata/DataFormatter.java b/src/main/java/com/alibaba/excel/metadata/DataFormatter.java index 13bd162..7467cd2 100644 --- a/src/main/java/com/alibaba/excel/metadata/DataFormatter.java +++ b/src/main/java/com/alibaba/excel/metadata/DataFormatter.java @@ -63,7 +63,8 @@ public class DataFormatter { private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); /** Pattern to find "AM/PM" marker */ - private static final Pattern amPmPattern = Pattern.compile("(([AP])[M/P]*)|(([上下])[午/下]*)", Pattern.CASE_INSENSITIVE); + private static final Pattern amPmPattern = + Pattern.compile("(([AP])[M/P]*)|(([上下])[午/下]*)", Pattern.CASE_INSENSITIVE); /** Pattern to find formats with condition ranges e.g. [>=100] */ private static final Pattern rangeConditionalPattern = @@ -152,7 +153,6 @@ public class DataFormatter { public DataFormatter(Locale locale, Boolean use1904windowing) { this.use1904windowing = use1904windowing != null ? use1904windowing : Boolean.FALSE; this.locale = locale != null ? locale : Locale.getDefault(); - this.locale = Locale.US; this.dateSymbols = DateFormatSymbols.getInstance(this.locale); this.decimalSymbols = DecimalFormatSymbols.getInstance(this.locale); } diff --git a/src/main/java/com/alibaba/excel/read/builder/AbstractExcelReaderParameterBuilder.java b/src/main/java/com/alibaba/excel/read/builder/AbstractExcelReaderParameterBuilder.java new file mode 100644 index 0000000..4e5e370 --- /dev/null +++ b/src/main/java/com/alibaba/excel/read/builder/AbstractExcelReaderParameterBuilder.java @@ -0,0 +1,47 @@ +package com.alibaba.excel.read.builder; + +import java.util.ArrayList; + +import com.alibaba.excel.metadata.AbstractParameterBuilder; +import com.alibaba.excel.read.listener.ReadListener; +import com.alibaba.excel.read.metadata.ReadBasicParameter; + +/** + * Build ExcelBuilder + * + * @author Jiaju Zhuang + */ +public abstract class AbstractExcelReaderParameterBuilder extends AbstractParameterBuilder { + /** + * Count the number of added heads when read sheet. + * + *

+ * 0 - This Sheet has no head ,since the first row are the data + *

+ * 1 - This Sheet has one row head , this is the default + *

+ * 2 - This Sheet has two row head ,since the third row is the data + * + * @param headRowNumber + * @return + */ + public T headRowNumber(Integer headRowNumber) { + parameter().setHeadRowNumber(headRowNumber); + return self(); + } + + /** + * Custom type listener run after default + * + * @param readListener + * @return + */ + public T registerReadListener(ReadListener readListener) { + if (parameter().getCustomReadListenerList() == null) { + parameter().setCustomReadListenerList(new ArrayList()); + } + parameter().getCustomReadListenerList().add(readListener); + return self(); + } +} diff --git a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java index 4f6cdda..1bdfd6d 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java @@ -2,8 +2,6 @@ package com.alibaba.excel.read.builder; import java.io.File; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; import javax.xml.parsers.SAXParserFactory; @@ -11,9 +9,7 @@ import com.alibaba.excel.ExcelReader; import com.alibaba.excel.cache.ReadCache; import com.alibaba.excel.cache.selector.ReadCacheSelector; import com.alibaba.excel.context.AnalysisContext; -import com.alibaba.excel.converters.Converter; import com.alibaba.excel.event.AnalysisEventListener; -import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.read.metadata.ReadWorkbook; import com.alibaba.excel.support.ExcelTypeEnum; @@ -22,7 +18,7 @@ import com.alibaba.excel.support.ExcelTypeEnum; * * @author Jiaju Zhuang */ -public class ExcelReaderBuilder { +public class ExcelReaderBuilder extends AbstractExcelReaderParameterBuilder { /** * Workbook */ @@ -132,98 +128,6 @@ public class ExcelReaderBuilder { return this; } - /** - * Count the number of added heads when read sheet. - * - *

- * 0 - This Sheet has no head ,since the first row are the data - *

- * 1 - This Sheet has one row head , this is the default - *

- * 2 - This Sheet has two row head ,since the third row is the data - * - * @param headRowNumber - * @return - */ - public ExcelReaderBuilder headRowNumber(Integer headRowNumber) { - readWorkbook.setHeadRowNumber(headRowNumber); - return this; - } - - /** - * You can only choose one of the {@link ExcelReaderBuilder#head(List)} and {@link ExcelReaderBuilder#head(Class)} - * - * @param head - * @return - */ - public ExcelReaderBuilder head(List> head) { - readWorkbook.setHead(head); - return this; - } - - /** - * You can only choose one of the {@link ExcelReaderBuilder#head(List)} and {@link ExcelReaderBuilder#head(Class)} - * - * @param clazz - * @return - */ - public ExcelReaderBuilder head(Class clazz) { - readWorkbook.setClazz(clazz); - return this; - } - - /** - * Custom type conversions override the default. - * - * @param converter - * @return - */ - public ExcelReaderBuilder registerConverter(Converter converter) { - if (readWorkbook.getCustomConverterList() == null) { - readWorkbook.setCustomConverterList(new ArrayList()); - } - readWorkbook.getCustomConverterList().add(converter); - return this; - } - - /** - * Custom type listener run after default - * - * @param readListener - * @return - */ - public ExcelReaderBuilder registerReadListener(ReadListener readListener) { - if (readWorkbook.getCustomReadListenerList() == null) { - readWorkbook.setCustomReadListenerList(new ArrayList()); - } - readWorkbook.getCustomReadListenerList().add(readListener); - return this; - } - - /** - * true if date uses 1904 windowing, or false if using 1900 date windowing. - * - * default is false - * - * @param use1904windowing - * @return - */ - public ExcelReaderBuilder use1904windowing(Boolean use1904windowing) { - readWorkbook.setUse1904windowing(use1904windowing); - return this; - } - - /** - * Automatic trim includes sheet name and content - * - * @param autoTrim - * @return - */ - public ExcelReaderBuilder autoTrim(Boolean autoTrim) { - readWorkbook.setAutoTrim(autoTrim); - return this; - } - /** * Whether the encryption * @@ -285,4 +189,9 @@ public class ExcelReaderBuilder { } return excelReaderSheetBuilder; } + + @Override + protected ReadWorkbook parameter() { + return readWorkbook; + } } diff --git a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java index d494e23..84ec6ba 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java @@ -1,14 +1,11 @@ package com.alibaba.excel.read.builder; -import java.util.ArrayList; import java.util.List; import com.alibaba.excel.ExcelReader; -import com.alibaba.excel.converters.Converter; import com.alibaba.excel.event.SyncReadListener; import com.alibaba.excel.exception.ExcelAnalysisException; import com.alibaba.excel.exception.ExcelGenerateException; -import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.read.metadata.ReadSheet; /** @@ -16,7 +13,7 @@ import com.alibaba.excel.read.metadata.ReadSheet; * * @author Jiaju Zhuang */ -public class ExcelReaderSheetBuilder { +public class ExcelReaderSheetBuilder extends AbstractExcelReaderParameterBuilder { private ExcelReader excelReader; /** * Sheet @@ -54,98 +51,6 @@ public class ExcelReaderSheetBuilder { return this; } - /** - * Count the number of added heads when read sheet. - * - *

- * 0 - This Sheet has no head ,since the first row are the data - *

- * 1 - This Sheet has one row head , this is the default - *

- * 2 - This Sheet has two row head ,since the third row is the data - * - * @param headRowNumber - * @return - */ - public ExcelReaderSheetBuilder headRowNumber(Integer headRowNumber) { - readSheet.setHeadRowNumber(headRowNumber); - return this; - } - - /** - * You can only choose one of the {@link ExcelReaderBuilder#head(List)} and {@link ExcelReaderBuilder#head(Class)} - * - * @param head - * @return - */ - public ExcelReaderSheetBuilder head(List> head) { - readSheet.setHead(head); - return this; - } - - /** - * You can only choose one of the {@link ExcelReaderBuilder#head(List)} and {@link ExcelReaderBuilder#head(Class)} - * - * @param clazz - * @return - */ - public ExcelReaderSheetBuilder head(Class clazz) { - readSheet.setClazz(clazz); - return this; - } - - /** - * Custom type conversions override the default. - * - * @param converter - * @return - */ - public ExcelReaderSheetBuilder registerConverter(Converter converter) { - if (readSheet.getCustomConverterList() == null) { - readSheet.setCustomConverterList(new ArrayList()); - } - readSheet.getCustomConverterList().add(converter); - return this; - } - - /** - * Custom type listener run after default - * - * @param readListener - * @return - */ - public ExcelReaderSheetBuilder registerReadListener(ReadListener readListener) { - if (readSheet.getCustomReadListenerList() == null) { - readSheet.setCustomReadListenerList(new ArrayList()); - } - readSheet.getCustomReadListenerList().add(readListener); - return this; - } - - /** - * true if date uses 1904 windowing, or false if using 1900 date windowing. - * - * default is false - * - * @param use1904windowing - * @return - */ - public ExcelReaderSheetBuilder use1904windowing(Boolean use1904windowing) { - readSheet.setUse1904windowing(use1904windowing); - return this; - } - - /** - * Automatic trim includes sheet name and content - * - * @param autoTrim - * @return - */ - public ExcelReaderSheetBuilder autoTrim(Boolean autoTrim) { - readSheet.setAutoTrim(autoTrim); - return this; - } - public ReadSheet build() { return readSheet; } @@ -177,4 +82,8 @@ public class ExcelReaderSheetBuilder { return (List)syncReadListener.getList(); } + @Override + protected ReadSheet parameter() { + return readSheet; + } } diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index b375733..815257b 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -13,7 +13,7 @@ import java.util.regex.Pattern; * * @author Jiaju Zhuang **/ -public class DateUtils implements ThreadLocalCachedUtils { +public class DateUtils { /** * Is a cache of dates */ @@ -302,8 +302,7 @@ public class DateUtils implements ThreadLocalCachedUtils { return false; } - @Override - public void removeThreadLocalCache() { + public static void removeThreadLocalCache() { DATE_THREAD_LOCAL.remove(); DATE_FORMAT_THREAD_LOCAL.remove(); } diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java index 52ced2a..5bceb4a 100644 --- a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java +++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java @@ -8,7 +8,7 @@ import com.alibaba.excel.metadata.GlobalConfiguration; * * @author Jiaju Zhuang **/ -public class NumberDataFormatterUtils implements ThreadLocalCachedUtils { +public class NumberDataFormatterUtils { /** * Cache DataFormatter. */ @@ -40,8 +40,7 @@ public class NumberDataFormatterUtils implements ThreadLocalCachedUtils { } - @Override - public void removeThreadLocalCache() { + public static void removeThreadLocalCache() { DATA_FORMATTER_THREAD_LOCAL.remove(); } } diff --git a/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java b/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java deleted file mode 100644 index d136703..0000000 --- a/src/main/java/com/alibaba/excel/util/ThreadLocalCachedUtils.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.alibaba.excel.util; - -/** - * Thread local cache in the current tool class. - * - * @author Jiaju Zhuang - **/ -public interface ThreadLocalCachedUtils { - - /** - * Remove remove thread local cached. - */ - void removeThreadLocalCache(); -} diff --git a/src/main/java/com/alibaba/excel/write/builder/AbstractExcelWriterParameterBuilder.java b/src/main/java/com/alibaba/excel/write/builder/AbstractExcelWriterParameterBuilder.java new file mode 100644 index 0000000..d44c0ad --- /dev/null +++ b/src/main/java/com/alibaba/excel/write/builder/AbstractExcelWriterParameterBuilder.java @@ -0,0 +1,104 @@ +package com.alibaba.excel.write.builder; + +import java.util.ArrayList; +import java.util.Collection; + +import com.alibaba.excel.metadata.AbstractParameterBuilder; +import com.alibaba.excel.write.handler.WriteHandler; +import com.alibaba.excel.write.metadata.WriteBasicParameter; + +/** + * Build ExcelBuilder + * + * @author Jiaju Zhuang + */ +public abstract class AbstractExcelWriterParameterBuilder extends AbstractParameterBuilder { + /** + * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. + * + * @param relativeHeadRowIndex + * @return + */ + public T relativeHeadRowIndex(Integer relativeHeadRowIndex) { + parameter().setRelativeHeadRowIndex(relativeHeadRowIndex); + return self(); + } + + /** + * Need Head + */ + public T needHead(Boolean needHead) { + parameter().setNeedHead(needHead); + return self(); + } + + /** + * Custom write handler + * + * @param writeHandler + * @return + */ + public T registerWriteHandler(WriteHandler writeHandler) { + if (parameter().getCustomWriteHandlerList() == null) { + parameter().setCustomWriteHandlerList(new ArrayList()); + } + parameter().getCustomWriteHandlerList().add(writeHandler); + return self(); + } + + /** + * Use the default style.Default is true. + * + * @param useDefaultStyle + * @return + */ + public T useDefaultStyle(Boolean useDefaultStyle) { + parameter().setUseDefaultStyle(useDefaultStyle); + return self(); + } + + /** + * Whether to automatically merge headers.Default is true. + * + * @param automaticMergeHead + * @return + */ + public T automaticMergeHead(Boolean automaticMergeHead) { + parameter().setAutomaticMergeHead(automaticMergeHead); + return self(); + } + + /** + * Ignore the custom columns. + */ + public T excludeColumnIndexes(Collection excludeColumnIndexes) { + parameter().setExcludeColumnIndexes(excludeColumnIndexes); + return self(); + } + + /** + * Ignore the custom columns. + */ + public T excludeColumnFiledNames(Collection excludeColumnFiledNames) { + parameter().setExcludeColumnFiledNames(excludeColumnFiledNames); + return self(); + } + + /** + * Only output the custom columns. + */ + public T includeColumnIndexes(Collection includeColumnIndexes) { + parameter().setIncludeColumnIndexes(includeColumnIndexes); + return self(); + } + + /** + * Only output the custom columns. + */ + public T includeColumnFiledNames(Collection includeColumnFiledNames) { + parameter().setIncludeColumnFiledNames(includeColumnFiledNames); + return self(); + } + +} diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java index c1f2d94..12c2f22 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java @@ -3,12 +3,8 @@ package com.alibaba.excel.write.builder; import java.io.File; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.converters.Converter; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.handler.WriteHandler; import com.alibaba.excel.write.metadata.WriteWorkbook; @@ -18,7 +14,7 @@ import com.alibaba.excel.write.metadata.WriteWorkbook; * * @author Jiaju Zhuang */ -public class ExcelWriterBuilder { +public class ExcelWriterBuilder extends AbstractExcelWriterParameterBuilder { /** * Workbook */ @@ -28,47 +24,6 @@ public class ExcelWriterBuilder { this.writeWorkbook = new WriteWorkbook(); } - /** - * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. - * - * @param relativeHeadRowIndex - * @return - */ - public ExcelWriterBuilder relativeHeadRowIndex(Integer relativeHeadRowIndex) { - writeWorkbook.setRelativeHeadRowIndex(relativeHeadRowIndex); - return this; - } - - /** - * You can only choose one of the {@link ExcelWriterBuilder#head(List)} and {@link ExcelWriterBuilder#head(Class)} - * - * @param head - * @return - */ - public ExcelWriterBuilder head(List> head) { - writeWorkbook.setHead(head); - return this; - } - - /** - * You can only choose one of the {@link ExcelWriterBuilder#head(List)} and {@link ExcelWriterBuilder#head(Class)} - * - * @param clazz - * @return - */ - public ExcelWriterBuilder head(Class clazz) { - writeWorkbook.setClazz(clazz); - return this; - } - - /** - * Need Head - */ - public ExcelWriterBuilder needHead(Boolean needHead) { - writeWorkbook.setNeedHead(needHead); - return this; - } - /** * Default true * @@ -80,28 +35,6 @@ public class ExcelWriterBuilder { return this; } - /** - * Use the default style.Default is true. - * - * @param useDefaultStyle - * @return - */ - public ExcelWriterBuilder useDefaultStyle(Boolean useDefaultStyle) { - writeWorkbook.setUseDefaultStyle(useDefaultStyle); - return this; - } - - /** - * Whether to automatically merge headers.Default is true. - * - * @param automaticMergeHead - * @return - */ - public ExcelWriterBuilder automaticMergeHead(Boolean automaticMergeHead) { - writeWorkbook.setAutomaticMergeHead(automaticMergeHead); - return this; - } - /** * Whether the encryption. *

@@ -125,38 +58,6 @@ public class ExcelWriterBuilder { return this; } - /** - * Ignore the custom columns. - */ - public ExcelWriterBuilder excludeColumnIndexes(Collection excludeColumnIndexes) { - writeWorkbook.setExcludeColumnIndexes(excludeColumnIndexes); - return this; - } - - /** - * Ignore the custom columns. - */ - public ExcelWriterBuilder excludeColumnFiledNames(Collection excludeColumnFiledNames) { - writeWorkbook.setExcludeColumnFiledNames(excludeColumnFiledNames); - return this; - } - - /** - * Only output the custom columns. - */ - public ExcelWriterBuilder includeColumnIndexes(Collection includeColumnIndexes) { - writeWorkbook.setIncludeColumnIndexes(includeColumnIndexes); - return this; - } - - /** - * Only output the custom columns. - */ - public ExcelWriterBuilder includeColumnFiledNames(Collection includeColumnFiledNames) { - writeWorkbook.setIncludeColumnFiledNames(includeColumnFiledNames); - return this; - } - /** * Excel is also written in the event of an exception being thrown.The default false. */ @@ -181,34 +82,6 @@ public class ExcelWriterBuilder { return this; } - /** - * Custom type conversions override the default. - * - * @param converter - * @return - */ - public ExcelWriterBuilder registerConverter(Converter converter) { - if (writeWorkbook.getCustomConverterList() == null) { - writeWorkbook.setCustomConverterList(new ArrayList()); - } - writeWorkbook.getCustomConverterList().add(converter); - return this; - } - - /** - * Custom write handler - * - * @param writeHandler - * @return - */ - public ExcelWriterBuilder registerWriteHandler(WriteHandler writeHandler) { - if (writeWorkbook.getCustomWriteHandlerList() == null) { - writeWorkbook.setCustomWriteHandlerList(new ArrayList()); - } - writeWorkbook.getCustomWriteHandlerList().add(writeHandler); - return this; - } - public ExcelWriterBuilder excelType(ExcelTypeEnum excelType) { writeWorkbook.setExcelType(excelType); return this; @@ -281,4 +154,8 @@ public class ExcelWriterBuilder { return excelWriterSheetBuilder; } + @Override + protected WriteWorkbook parameter() { + return writeWorkbook; + } } diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java index 5cb71cc..f79b771 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterSheetBuilder.java @@ -1,13 +1,9 @@ package com.alibaba.excel.write.builder; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.converters.Converter; import com.alibaba.excel.exception.ExcelGenerateException; -import com.alibaba.excel.write.handler.WriteHandler; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.fill.FillConfig; @@ -16,7 +12,7 @@ import com.alibaba.excel.write.metadata.fill.FillConfig; * * @author Jiaju Zhuang */ -public class ExcelWriterSheetBuilder { +public class ExcelWriterSheetBuilder extends AbstractExcelWriterParameterBuilder { private ExcelWriter excelWriter; /** * Sheet @@ -32,99 +28,6 @@ public class ExcelWriterSheetBuilder { this.excelWriter = excelWriter; } - /** - * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. - * - * @param relativeHeadRowIndex - * @return - */ - public ExcelWriterSheetBuilder relativeHeadRowIndex(Integer relativeHeadRowIndex) { - writeSheet.setRelativeHeadRowIndex(relativeHeadRowIndex); - return this; - } - - /** - * You can only choose one of the {@link ExcelWriterSheetBuilder#head(List)} and - * {@link ExcelWriterSheetBuilder#head(Class)} - * - * @param head - * @return - */ - public ExcelWriterSheetBuilder head(List> head) { - writeSheet.setHead(head); - return this; - } - - /** - * You can only choose one of the {@link ExcelWriterSheetBuilder#head(List)} and - * {@link ExcelWriterSheetBuilder#head(Class)} - * - * @param clazz - * @return - */ - public ExcelWriterSheetBuilder head(Class clazz) { - writeSheet.setClazz(clazz); - return this; - } - - /** - * Need Head - */ - public ExcelWriterSheetBuilder needHead(Boolean needHead) { - writeSheet.setNeedHead(needHead); - return this; - } - - /** - * Use the default style.Default is true. - * - * @param useDefaultStyle - * @return - */ - public ExcelWriterSheetBuilder useDefaultStyle(Boolean useDefaultStyle) { - writeSheet.setUseDefaultStyle(useDefaultStyle); - return this; - } - - /** - * Whether to automatically merge headers.Default is true. - * - * @param automaticMergeHead - * @return - */ - public ExcelWriterSheetBuilder automaticMergeHead(Boolean automaticMergeHead) { - writeSheet.setAutomaticMergeHead(automaticMergeHead); - return this; - } - - /** - * Custom type conversions override the default. - * - * @param converter - * @return - */ - public ExcelWriterSheetBuilder registerConverter(Converter converter) { - if (writeSheet.getCustomConverterList() == null) { - writeSheet.setCustomConverterList(new ArrayList()); - } - writeSheet.getCustomConverterList().add(converter); - return this; - } - - /** - * Custom write handler - * - * @param writeHandler - * @return - */ - public ExcelWriterSheetBuilder registerWriteHandler(WriteHandler writeHandler) { - if (writeSheet.getCustomWriteHandlerList() == null) { - writeSheet.setCustomWriteHandlerList(new ArrayList()); - } - writeSheet.getCustomWriteHandlerList().add(writeHandler); - return this; - } - /** * Starting from 0 * @@ -147,38 +50,6 @@ public class ExcelWriterSheetBuilder { return this; } - /** - * Ignore the custom columns. - */ - public ExcelWriterSheetBuilder excludeColumnIndexes(Collection excludeColumnIndexes) { - writeSheet.setExcludeColumnIndexes(excludeColumnIndexes); - return this; - } - - /** - * Ignore the custom columns. - */ - public ExcelWriterSheetBuilder excludeColumnFiledNames(Collection excludeColumnFiledNames) { - writeSheet.setExcludeColumnFiledNames(excludeColumnFiledNames); - return this; - } - - /** - * Only output the custom columns. - */ - public ExcelWriterSheetBuilder includeColumnIndexes(Collection includeColumnIndexes) { - writeSheet.setIncludeColumnIndexes(includeColumnIndexes); - return this; - } - - /** - * Only output the custom columns. - */ - public ExcelWriterSheetBuilder includeColumnFiledNames(Collection includeColumnFiledNames) { - writeSheet.setIncludeColumnFiledNames(includeColumnFiledNames); - return this; - } - public WriteSheet build() { return writeSheet; } @@ -215,4 +86,9 @@ public class ExcelWriterSheetBuilder { return excelWriterTableBuilder; } + @Override + protected WriteSheet parameter() { + return writeSheet; + } + } diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java index 2b461a9..077361a 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterTableBuilder.java @@ -1,13 +1,9 @@ package com.alibaba.excel.write.builder; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import com.alibaba.excel.ExcelWriter; -import com.alibaba.excel.converters.Converter; import com.alibaba.excel.exception.ExcelGenerateException; -import com.alibaba.excel.write.handler.WriteHandler; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.WriteTable; @@ -16,7 +12,7 @@ import com.alibaba.excel.write.metadata.WriteTable; * * @author Jiaju Zhuang */ -public class ExcelWriterTableBuilder { +public class ExcelWriterTableBuilder extends AbstractExcelWriterParameterBuilder { private ExcelWriter excelWriter; @@ -36,99 +32,6 @@ public class ExcelWriterTableBuilder { this.writeTable = new WriteTable(); } - /** - * Writes the head relative to the existing contents of the sheet. Indexes are zero-based. - * - * @param relativeHeadRowIndex - * @return - */ - public ExcelWriterTableBuilder relativeHeadRowIndex(Integer relativeHeadRowIndex) { - writeTable.setRelativeHeadRowIndex(relativeHeadRowIndex); - return this; - } - - /** - * You can only choose one of the {@link ExcelWriterTableBuilder#head(List)} and - * {@link ExcelWriterTableBuilder#head(Class)} - * - * @param head - * @return - */ - public ExcelWriterTableBuilder head(List> head) { - writeTable.setHead(head); - return this; - } - - /** - * You can only choose one of the {@link ExcelWriterTableBuilder#head(List)} and - * {@link ExcelWriterTableBuilder#head(Class)} - * - * @param clazz - * @return - */ - public ExcelWriterTableBuilder head(Class clazz) { - writeTable.setClazz(clazz); - return this; - } - - /** - * Need Head - */ - public ExcelWriterTableBuilder needHead(Boolean needHead) { - writeTable.setNeedHead(needHead); - return this; - } - - /** - * Use the default style.Default is true. - * - * @param useDefaultStyle - * @return - */ - public ExcelWriterTableBuilder useDefaultStyle(Boolean useDefaultStyle) { - writeTable.setUseDefaultStyle(useDefaultStyle); - return this; - } - - /** - * Whether to automatically merge headers.Default is true. - * - * @param automaticMergeHead - * @return - */ - public ExcelWriterTableBuilder automaticMergeHead(Boolean automaticMergeHead) { - writeTable.setAutomaticMergeHead(automaticMergeHead); - return this; - } - - /** - * Custom type conversions override the default. - * - * @param converter - * @return - */ - public ExcelWriterTableBuilder registerConverter(Converter converter) { - if (writeTable.getCustomConverterList() == null) { - writeTable.setCustomConverterList(new ArrayList()); - } - writeTable.getCustomConverterList().add(converter); - return this; - } - - /** - * Custom write handler - * - * @param writeHandler - * @return - */ - public ExcelWriterTableBuilder registerWriteHandler(WriteHandler writeHandler) { - if (writeTable.getCustomWriteHandlerList() == null) { - writeTable.setCustomWriteHandlerList(new ArrayList()); - } - writeTable.getCustomWriteHandlerList().add(writeHandler); - return this; - } - /** * Starting from 0 * @@ -140,38 +43,6 @@ public class ExcelWriterTableBuilder { return this; } - /** - * Ignore the custom columns. - */ - public ExcelWriterTableBuilder excludeColumnIndexes(Collection excludeColumnIndexes) { - writeTable.setExcludeColumnIndexes(excludeColumnIndexes); - return this; - } - - /** - * Ignore the custom columns. - */ - public ExcelWriterTableBuilder excludeColumnFiledNames(Collection excludeColumnFiledNames) { - writeTable.setExcludeColumnFiledNames(excludeColumnFiledNames); - return this; - } - - /** - * Only output the custom columns. - */ - public ExcelWriterTableBuilder includeColumnIndexes(Collection includeColumnIndexes) { - writeTable.setIncludeColumnIndexes(includeColumnIndexes); - return this; - } - - /** - * Only output the custom columns. - */ - public ExcelWriterTableBuilder includeColumnFiledNames(Collection includeColumnFiledNames) { - writeSheet.setIncludeColumnFiledNames(includeColumnFiledNames); - return this; - } - public WriteTable build() { return writeTable; } @@ -184,4 +55,8 @@ public class ExcelWriterTableBuilder { excelWriter.finish(); } + @Override + protected WriteTable parameter() { + return writeTable; + } } diff --git a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java index d627e46..1930bce 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java @@ -8,7 +8,9 @@ import lombok.Data; @Data public class DateFormatData { private String date; - private String dateString; + private String dateStringCn; + private String dateStringUs; private String number; - private String numberString; + private String numberStringCn; + private String numberStringUs; } diff --git a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java index 69b6709..477652b 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java @@ -2,7 +2,9 @@ package com.alibaba.easyexcel.test.core.dataformat; import java.io.File; import java.util.List; +import java.util.Locale; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; @@ -10,7 +12,6 @@ import org.slf4j.LoggerFactory; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; -import com.alibaba.fastjson.JSON; /** * @@ -30,21 +31,31 @@ public class DateFormatTest { @Test public void t01Read07() { - read(file07); + readCn(file07); + readUs(file07); } @Test public void t02Read03() { - read(file03); + readCn(file03); + readUs(file03); } - private void read(File file) { - List list = EasyExcel.read(file, DateFormatData.class, null).sheet().doReadSync(); + private void readCn(File file) { + List list = + EasyExcel.read(file, DateFormatData.class, null).locale(Locale.CHINA).sheet().doReadSync(); for (DateFormatData data : list) { - if (!data.getDate().equals(data.getDateString())) { - LOGGER.info("返回:{}", JSON.toJSONString(data)); - } + Assert.assertEquals(data.getDate(), data.getDateStringCn()); + Assert.assertEquals(data.getNumber(), data.getNumberStringCn()); } } + private void readUs(File file) { + List list = + EasyExcel.read(file, DateFormatData.class, null).locale(Locale.US).sheet().doReadSync(); + for (DateFormatData data : list) { + Assert.assertEquals(data.getDate(), data.getDateStringUs()); + Assert.assertEquals(data.getNumber(), data.getNumberStringUs()); + } + } } diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java index d3db5e0..e165d98 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java @@ -146,7 +146,7 @@ public class DataFormatTest { @Test public void test3556() throws IOException, InvalidFormatException { - String file = "D://test/dataformat.xlsx"; + String file = "D://test/dataformat1.xlsx"; XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); Sheet xssfSheet = xssfWorkbook.getSheetAt(0); DataFormatter d = new DataFormatter(Locale.CHINA); diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java index 68fff70..fa41704 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiWriteTest.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.temp.poi; import java.io.FileOutputStream; import java.io.IOException; +import java.math.BigDecimal; import java.util.regex.Pattern; import org.apache.poi.xssf.streaming.SXSSFCell; @@ -35,9 +36,20 @@ public class PoiWriteTest { cell1.setCellValue(999999999999999L); SXSSFCell cell2 = row.createCell(1); cell2.setCellValue(1000000000000001L); + SXSSFCell cell32 = row.createCell(2); + cell32.setCellValue(300.35f); sxxsFWorkbook.write(fileOutputStream); } + @Test + public void write01() throws IOException { + float ff = 300.35f; + BigDecimal bd = new BigDecimal(Float.toString(ff)); + System.out.println(bd.doubleValue()); + System.out.println(bd.floatValue()); + + } + @Test public void write() throws IOException { FileOutputStream fileOutputStream = diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/JsonData.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/JsonData.java new file mode 100644 index 0000000..a9ba7fa --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/JsonData.java @@ -0,0 +1,15 @@ +package com.alibaba.easyexcel.test.temp.simple; + +import lombok.Data; + +/** + * TODO + * + * @author Jiaju Zhuang + **/ +@Data +public class JsonData { + private String SS1; + private String sS2; + private String ss3; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index 44bc943..6157181 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -14,6 +14,7 @@ import com.alibaba.easyexcel.test.core.large.LargeData; import com.alibaba.easyexcel.test.demo.write.DemoData; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson.JSON; import net.sf.cglib.beans.BeanMap; @@ -43,7 +44,35 @@ public class Wirte { String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 - EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); + EasyExcel.write(fileName, DemoData.class).relativeHeadRowIndex(10).sheet("模板").doWrite(data()); + } + + @Test + public void simpleWrite2() { + // 写法1 + String fileName = TestFileUtil.getPath() + "t22" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 如果这里想使用03 则 传入excelType参数即可 + EasyExcel.write(fileName, WriteData.class).sheet("模板").doWrite(data1()); + } + + @Test + public void json() { + JsonData jsonData = new JsonData(); + jsonData.setSS1("11"); + jsonData.setSS2("22"); + jsonData.setSs3("33"); + System.out.println(JSON.toJSONString(jsonData)); + + } + + @Test + public void json3() { + String json = "{\"SS1\":\"11\",\"sS2\":\"22\",\"ss3\":\"33\"}"; + + JsonData jsonData = JSON.parseObject(json, JsonData.class); + System.out.println(JSON.toJSONString(jsonData)); + } private List> head() { @@ -72,4 +101,14 @@ public class Wirte { return list; } + private List data1() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + WriteData data = new WriteData(); + data.setF(300.35f); + list.add(data); + } + return list; + } + } diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/WriteData.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/WriteData.java new file mode 100644 index 0000000..b8ff14d --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/WriteData.java @@ -0,0 +1,13 @@ +package com.alibaba.easyexcel.test.temp.simple; + +import lombok.Data; + +/** + * write data + * + * @author Jiaju Zhuang + **/ +@Data +public class WriteData { + private float f; +} diff --git a/src/test/resources/dataformat/dataformat.xls b/src/test/resources/dataformat/dataformat.xls new file mode 100644 index 0000000000000000000000000000000000000000..c03847e4e5cde9f156ddf23b5d8263dc1357453e GIT binary patch literal 26112 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 Hx%~eDWlI1` literal 0 HcmV?d00001 diff --git a/src/test/resources/dataformat/dataformat.xlsx b/src/test/resources/dataformat/dataformat.xlsx index 370d983a931862faf90274a954ed71e38cb92ac5..0085930c31d5b3a08eb63f6f868089eb89c6a29d 100644 GIT binary patch delta 7423 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 R|C%Wne@zx7DdvCB{{byD`cD7= delta 7099 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 3.4.0(jdk6) +* 修复xls 用Map接收时多次接收会是同一个对象的bug +* 修复浮点型数据导入到excel 会丢失精度的bug + # 2.1.3 * 每个java进程单独创建一个缓存目录 [Issue #813](https://github.com/alibaba/easyexcel/issues/813) * 统一修改合并为unsafe,提高大量数据导出的合并的效率