diff --git a/README.md b/README.md index fd5c5a2..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 @@ -62,29 +62,32 @@ 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()); } /** * 文件上传 *

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

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

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

3. 直接读即可 */ @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/img/readme/quickstart/fill/complexFill.png b/img/readme/quickstart/fill/complexFill.png deleted file mode 100644 index d37bb0f..0000000 Binary files a/img/readme/quickstart/fill/complexFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/complexFillTemplate.png b/img/readme/quickstart/fill/complexFillTemplate.png deleted file mode 100644 index 14e26b9..0000000 Binary files a/img/readme/quickstart/fill/complexFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/complexFillWithTable.png b/img/readme/quickstart/fill/complexFillWithTable.png deleted file mode 100644 index c107520..0000000 Binary files a/img/readme/quickstart/fill/complexFillWithTable.png and /dev/null differ diff --git a/img/readme/quickstart/fill/complexFillWithTableTemplate.png b/img/readme/quickstart/fill/complexFillWithTableTemplate.png deleted file mode 100644 index 902cd0d..0000000 Binary files a/img/readme/quickstart/fill/complexFillWithTableTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/horizontalFill.png b/img/readme/quickstart/fill/horizontalFill.png deleted file mode 100644 index 10f90cd..0000000 Binary files a/img/readme/quickstart/fill/horizontalFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/horizontalFillTemplate.png b/img/readme/quickstart/fill/horizontalFillTemplate.png deleted file mode 100644 index 462b86e..0000000 Binary files a/img/readme/quickstart/fill/horizontalFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/listFill.png b/img/readme/quickstart/fill/listFill.png deleted file mode 100644 index 3baca5a..0000000 Binary files a/img/readme/quickstart/fill/listFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/listFillTemplate.png b/img/readme/quickstart/fill/listFillTemplate.png deleted file mode 100644 index 010c8c1..0000000 Binary files a/img/readme/quickstart/fill/listFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/simpleFill.png b/img/readme/quickstart/fill/simpleFill.png deleted file mode 100644 index 4f57562..0000000 Binary files a/img/readme/quickstart/fill/simpleFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/simpleFillTemplate.png b/img/readme/quickstart/fill/simpleFillTemplate.png deleted file mode 100644 index 97a9119..0000000 Binary files a/img/readme/quickstart/fill/simpleFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/read/demo.png b/img/readme/quickstart/read/demo.png deleted file mode 100644 index 8bd1413..0000000 Binary files a/img/readme/quickstart/read/demo.png and /dev/null differ diff --git a/img/readme/quickstart/write/complexHeadWrite.png b/img/readme/quickstart/write/complexHeadWrite.png deleted file mode 100644 index 995ffc3..0000000 Binary files a/img/readme/quickstart/write/complexHeadWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/converterWrite.png b/img/readme/quickstart/write/converterWrite.png deleted file mode 100644 index 5c9f289..0000000 Binary files a/img/readme/quickstart/write/converterWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/customHandlerWrite.png b/img/readme/quickstart/write/customHandlerWrite.png deleted file mode 100644 index 41916c3..0000000 Binary files a/img/readme/quickstart/write/customHandlerWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/dynamicHeadWrite.png b/img/readme/quickstart/write/dynamicHeadWrite.png deleted file mode 100644 index 3b25ad7..0000000 Binary files a/img/readme/quickstart/write/dynamicHeadWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/imageWrite.png b/img/readme/quickstart/write/imageWrite.png deleted file mode 100644 index c6a0c67..0000000 Binary files a/img/readme/quickstart/write/imageWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/indexWrite.png b/img/readme/quickstart/write/indexWrite.png deleted file mode 100644 index 8dbec17..0000000 Binary files a/img/readme/quickstart/write/indexWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/longestMatchColumnWidthWrite.png b/img/readme/quickstart/write/longestMatchColumnWidthWrite.png deleted file mode 100644 index c8a0604..0000000 Binary files a/img/readme/quickstart/write/longestMatchColumnWidthWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/mergeWrite.png b/img/readme/quickstart/write/mergeWrite.png deleted file mode 100644 index b631e59..0000000 Binary files a/img/readme/quickstart/write/mergeWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/repeatedWrite.png b/img/readme/quickstart/write/repeatedWrite.png deleted file mode 100644 index fb204a4..0000000 Binary files a/img/readme/quickstart/write/repeatedWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/simpleWrite.png b/img/readme/quickstart/write/simpleWrite.png deleted file mode 100644 index e5924b1..0000000 Binary files a/img/readme/quickstart/write/simpleWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/styleWrite.png b/img/readme/quickstart/write/styleWrite.png deleted file mode 100644 index 356c3a7..0000000 Binary files a/img/readme/quickstart/write/styleWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/tableWrite.png b/img/readme/quickstart/write/tableWrite.png deleted file mode 100644 index 0006a9e..0000000 Binary files a/img/readme/quickstart/write/tableWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/templateWrite.png b/img/readme/quickstart/write/templateWrite.png deleted file mode 100644 index 75f7cb1..0000000 Binary files a/img/readme/quickstart/write/templateWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/widthAndHeightWrite.png b/img/readme/quickstart/write/widthAndHeightWrite.png deleted file mode 100644 index d59d897..0000000 Binary files a/img/readme/quickstart/write/widthAndHeightWrite.png and /dev/null differ diff --git a/img/readme/wechat.png b/img/readme/wechat.png deleted file mode 100644 index b805760..0000000 Binary files a/img/readme/wechat.png and /dev/null differ diff --git a/pom.xml b/pom.xml index 68fe674..cb5bb97 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.0-beta1 + 2.1.4 jar easyexcel @@ -17,7 +17,7 @@ UTF-8 - 1.7 + 1.6 @@ -66,6 +66,11 @@ poi-ooxml 3.17 + + org.apache.poi + poi-ooxml-schemas + 3.17 + cglib cglib @@ -79,7 +84,7 @@ org.ehcache ehcache - 3.7.1 + 3.4.0 @@ -132,11 +137,11 @@ - + org.apache.maven.plugins maven-pmd-plugin - 3.12.0 + 3.8 true true @@ -157,7 +162,6 @@ - pmd-check-verify validate @@ -170,7 +174,7 @@ com.alibaba.p3c p3c-pmd - 2.0.0 + 1.3.6 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 6e1f435..feec16b 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -1,13 +1,10 @@ package com.alibaba.excel.analysis; -import java.io.FileInputStream; import java.io.InputStream; import java.util.List; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.crypt.Decryptor; -import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.filesystem.DocumentFactoryHelper; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.IOUtils; @@ -25,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; /** @@ -37,6 +36,10 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { private AnalysisContext analysisContext; private ExcelReadExecutor excelReadExecutor; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; public ExcelAnalyserImpl(ReadWorkbook readWorkbook) { try { @@ -124,31 +127,37 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { @Override public void finish() { + if (finished) { + return; + } + finished = true; if (analysisContext == null || analysisContext.readWorkbookHolder() == null) { return; } 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 +165,28 @@ 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(); + + removeThreadLocalCache(); + + if (throwable != null) { + throw new ExcelAnalysisException("Can not close IO.", throwable); + } + } + + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); } private void clearEncrypt03() { @@ -177,10 +197,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/v03/XlsSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java index e5df4bf..5ef640e 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -4,9 +4,9 @@ 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 com.alibaba.excel.util.StringUtils; import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; @@ -33,6 +33,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; @@ -86,7 +87,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; try { this.poiWorkbook = WorkbookFactory.create(poifsFileSystem); @@ -134,7 +135,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { private void init() { lastRowNumber = 0; lastColumnNumber = 0; - records = new TreeMap(); + records = new LinkedHashMap(); buildXlsRecordHandlers(); } @@ -227,7 +228,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { analysisContext.readRowHolder().setRowComments(rowComments); } analysisContext.readSheetHolder().notifyEndOneRow(new EachRowAnalysisFinishEvent(records), analysisContext); - records.clear(); + records = new HashMap(); lastColumnNumber = -1; } @@ -244,10 +245,11 @@ 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()); + recordHandlers.add(new IndexRecordHandler(analysisContext)); Collections.sort(recordHandlers); } 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/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/v03/handlers/NumberRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/NumberRecordHandler.java index 75c3128..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 @@ -7,6 +7,8 @@ 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.context.AnalysisContext; import com.alibaba.excel.metadata.CellData; /** @@ -17,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; } @@ -32,8 +37,10 @@ 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); + 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/XlsxSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java index 591932e..374dc45 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.CommentsTable; import org.apache.poi.xssf.model.StylesTable; @@ -164,7 +163,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/analysis/v07/handlers/CountRowCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java index c92ec6c..c838aec 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/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 2dc0fdb..f4534fb 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,11 +9,10 @@ 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; -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; @@ -37,7 +37,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; @@ -54,7 +54,7 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder @Override public void clearResult() { - curRowContent = new TreeMap(); + curRowContent = new LinkedHashMap(); } @Override @@ -87,13 +87,10 @@ 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) { - currentCellData.setDataFormatString(BuiltinFormats.getBuiltinFormat(dataFormat)); - } else { - currentCellData.setDataFormatString(dataFormatString); - } + 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 new file mode 100644 index 0000000..1dec2c6 --- /dev/null +++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java @@ -0,0 +1,379 @@ +package com.alibaba.excel.constant; + +import java.util.Locale; + +/** + * 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 { + + 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 + "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 + }; + + 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 builtinFormat[index]; + } + + private static String[] switchBuiltinFormats(Locale locale) { + if (locale != null && Locale.US.getCountry().equals(locale.getCountry())) { + return BUILTIN_FORMATS_US; + } + 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/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 ffacd52..2130169 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,11 @@ 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.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; import com.alibaba.excel.write.metadata.WriteSheet; @@ -72,6 +67,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) { @@ -172,20 +171,24 @@ 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); 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.TRUE); } } 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())); } } @@ -197,8 +200,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); } } @@ -256,25 +258,39 @@ 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) { - throwCanNotCloseIo(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) { - throwCanNotCloseIo(t); + throwable = t; } } @@ -284,7 +300,7 @@ public class WriteContextImpl implements WriteContext { ((SXSSFWorkbook)workbook).dispose(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } try { @@ -292,14 +308,14 @@ public class WriteContextImpl implements WriteContext { writeWorkbookHolder.getOutputStream().close(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } - if (!isOutputStreamEncrypt) { + if (writeExcel && !isOutputStreamEncrypt) { try { doFileEncrypt07(); } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } } @@ -308,18 +324,25 @@ public class WriteContextImpl implements WriteContext { writeWorkbookHolder.getTempTemplateInputStream().close(); } } catch (Throwable t) { - throwCanNotCloseIo(t); + throwable = t; } clearEncrypt03(); + removeThreadLocalCache(); + + 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); + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); } @Override @@ -428,7 +451,9 @@ public class WriteContextImpl implements WriteContext { OutputStream outputStream = encryptor.getDataStream(fileSystem); opcPackage.save(outputStream); } finally { - opcPackage.close(); + if (opcPackage != null) { + opcPackage.close(); + } } return fileSystem; } 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/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/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/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/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/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/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/CellData.java b/src/main/java/com/alibaba/excel/metadata/CellData.java index a3a40c6..7cd5496 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,16 +228,22 @@ public class CellData { @Override public String toString() { + if (type == null) { + 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/DataFormatter.java b/src/main/java/com/alibaba/excel/metadata/DataFormatter.java new file mode 100644 index 0000000..7467cd2 --- /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.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/metadata/property/ExcelHeadProperty.java b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java index 6921161..782f635 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,16 @@ 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.ClassUtils; import com.alibaba.excel.util.StringUtils; +import com.alibaba.excel.write.metadata.holder.AbstractWriteHolder; /** * Define the header attribute of excel @@ -46,7 +48,6 @@ public class ExcelHeadProperty { * The number of rows in the line with the most rows */ private int headRowNumber; - /** * Configuration header information */ @@ -64,7 +65,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 +74,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,73 +116,49 @@ public class ExcelHeadProperty { } } - private void initColumnProperties(Boolean convertAllFiled) { + private void initColumnProperties(Holder holder, Boolean convertAllFiled) { 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) { while (customFiledMap.containsKey(index)) { - initOneColumnProperty(index, customFiledMap.get(index), Boolean.TRUE); + Field customFiled = customFiledMap.get(index); customFiledMap.remove(index); + if (!initOneColumnProperty(holder, index, customFiled, 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 +190,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/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 340548b..ae3b79c 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderBuilder.java @@ -2,15 +2,15 @@ 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; 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.ModelBuildEventListener; import com.alibaba.excel.read.listener.ReadListener; import com.alibaba.excel.read.metadata.ReadWorkbook; import com.alibaba.excel.support.ExcelTypeEnum; @@ -20,7 +20,7 @@ import com.alibaba.excel.support.ExcelTypeEnum; * * @author Jiaju Zhuang */ -public class ExcelReaderBuilder { +public class ExcelReaderBuilder extends AbstractExcelReaderParameterBuilder { /** * Workbook */ @@ -131,105 +131,43 @@ public class ExcelReaderBuilder { } /** - * 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 + * Whether the encryption * - * @param use1904windowing + * @param password * @return */ - public ExcelReaderBuilder use1904windowing(Boolean use1904windowing) { - readWorkbook.setUse1904windowing(use1904windowing); + public ExcelReaderBuilder password(String password) { + readWorkbook.setPassword(password); return this; } /** - * Automatic trim includes sheet name and content + * 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" * - * @param autoTrim + * @see SAXParserFactory#newInstance() + * @see SAXParserFactory#newInstance(String, ClassLoader) + * @param xlsxSAXParserFactoryName * @return */ - public ExcelReaderBuilder autoTrim(Boolean autoTrim) { - readWorkbook.setAutoTrim(autoTrim); + public ExcelReaderBuilder xlsxSAXParserFactoryName(String xlsxSAXParserFactoryName) { + readWorkbook.setXlsxSAXParserFactoryName(xlsxSAXParserFactoryName); return this; } /** - * Whether the encryption + * Whether to use the default listener, which is used by default. + *

+ * The {@link ModelBuildEventListener} is loaded by default to convert the object. * - * @param password + * @param useDefaultListener * @return */ - public ExcelReaderBuilder password(String password) { - readWorkbook.setPassword(password); + public ExcelReaderBuilder useDefaultListener(Boolean useDefaultListener) { + readWorkbook.setUseDefaultListener(useDefaultListener); return this; } @@ -266,4 +204,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 af3b05e..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; } @@ -166,7 +71,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 +79,11 @@ public class ExcelReaderSheetBuilder { registerReadListener(syncReadListener); excelReader.read(build()); excelReader.finish(); - return syncReadListener.getList(); + return (List)syncReadListener.getList(); } + @Override + protected ReadSheet parameter() { + return readSheet; + } } 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..f5594db 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(); @@ -92,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/ReadWorkbook.java b/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java index 3a873da..7ddab56 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java +++ b/src/main/java/com/alibaba/excel/read/metadata/ReadWorkbook.java @@ -3,10 +3,13 @@ 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; import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.read.listener.ModelBuildEventListener; import com.alibaba.excel.support.ExcelTypeEnum; /** @@ -63,6 +66,23 @@ 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; + /** + * 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 +196,20 @@ 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; + } + + 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 4ca8d63..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; @@ -70,7 +71,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()) { @@ -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()) { @@ -144,7 +148,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; @@ -158,7 +162,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 { @@ -168,7 +171,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; @@ -191,8 +194,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(); @@ -206,9 +208,12 @@ 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()) { + 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/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/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/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/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 b6244c2..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,28 +29,37 @@ 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(); 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 = - 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(entry.getKey(), - (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); + stringMap.put(key, + (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; @@ -116,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 1ea05c0..815257b 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -1,10 +1,12 @@ package com.alibaba.excel.util; +import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; - -import com.alibaba.excel.exception.ExcelDataConvertException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; /** * Date utils @@ -12,6 +14,34 @@ import com.alibaba.excel.exception.ExcelDataConvertException; * @author Jiaju Zhuang **/ public class DateUtils { + /** + * 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"; public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss"; @@ -32,7 +62,7 @@ public class DateUtils { if (StringUtils.isEmpty(dateFormat)) { dateFormat = switchDateFormat(dateString); } - return new SimpleDateFormat(dateFormat).parse(dateString); + return getCacheDateFormat(dateFormat).parse(dateString); } /** @@ -65,8 +95,10 @@ 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); + throw new IllegalArgumentException("can not find date format for:" + dateString); } } @@ -96,6 +128,182 @@ 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 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; + } + + public static void removeThreadLocalCache() { + DATE_THREAD_LOCAL.remove(); + DATE_FORMAT_THREAD_LOCAL.remove(); } } diff --git a/src/main/java/com/alibaba/excel/util/FileUtils.java b/src/main/java/com/alibaba/excel/util/FileUtils.java index 34e6853..20b16bc 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) + File.separator + 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,31 @@ 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) { + File directory = createDirectory(new File(tempFilePrefix)); + return new File(directory, 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 +182,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/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java new file mode 100644 index 0000000..5bceb4a --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java @@ -0,0 +1,46 @@ +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 { + /** + * 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); + + } + + public static void removeThreadLocalCache() { + DATA_FORMATTER_THREAD_LOCAL.remove(); + } +} 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/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..59f78bf 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -25,16 +25,19 @@ 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) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } @@ -57,10 +60,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 +83,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 +115,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/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 82de038..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,11 +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.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; @@ -17,7 +14,7 @@ import com.alibaba.excel.write.metadata.WriteWorkbook; * * @author Jiaju Zhuang */ -public class ExcelWriterBuilder { +public class ExcelWriterBuilder extends AbstractExcelWriterParameterBuilder { /** * Workbook */ @@ -27,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 * @@ -79,17 +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 the encryption. *

@@ -104,46 +49,36 @@ public class ExcelWriterBuilder { } /** - * 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. + * Write excel in memory. Default false,the cache file is created and finally written to excel. *

- * Default true - * - * @param convertAllFiled - * @return - * @deprecated Just to be compatible with historical data, The default is always going to be convert all filed. + * Comment and RichTextString are only supported in memory mode. */ - @Deprecated - public ExcelWriterBuilder convertAllFiled(Boolean convertAllFiled) { - writeWorkbook.setConvertAllFiled(convertAllFiled); + public ExcelWriterBuilder inMemory(Boolean inMemory) { + writeWorkbook.setInMemory(inMemory); return this; } /** - * Custom type conversions override the default. - * - * @param converter - * @return + * Excel is also written in the event of an exception being thrown.The default false. */ - public ExcelWriterBuilder registerConverter(Converter converter) { - if (writeWorkbook.getCustomConverterList() == null) { - writeWorkbook.setCustomConverterList(new ArrayList()); - } - writeWorkbook.getCustomConverterList().add(converter); + public ExcelWriterBuilder writeExcelOnException(Boolean writeExcelOnException) { + writeWorkbook.setWriteExcelOnException(writeExcelOnException); return this; } /** - * Custom write handler + * 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. + *

+ * Default true * - * @param writeHandler + * @param convertAllFiled * @return + * @deprecated Just to be compatible with historical data, The default is always going to be convert all filed. */ - public ExcelWriterBuilder registerWriteHandler(WriteHandler writeHandler) { - if (writeWorkbook.getCustomWriteHandlerList() == null) { - writeWorkbook.setCustomWriteHandlerList(new ArrayList()); - } - writeWorkbook.getCustomWriteHandlerList().add(writeHandler); + @Deprecated + public ExcelWriterBuilder convertAllFiled(Boolean convertAllFiled) { + writeWorkbook.setConvertAllFiled(convertAllFiled); return this; } @@ -219,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 26acff5..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,12 +1,9 @@ package com.alibaba.excel.write.builder; -import java.util.ArrayList; 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; @@ -15,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 @@ -31,88 +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; - } - - /** - * 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 * @@ -171,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 eca1fe0..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,12 +1,9 @@ package com.alibaba.excel.write.builder; -import java.util.ArrayList; 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; @@ -15,7 +12,7 @@ import com.alibaba.excel.write.metadata.WriteTable; * * @author Jiaju Zhuang */ -public class ExcelWriterTableBuilder { +public class ExcelWriterTableBuilder extends AbstractExcelWriterParameterBuilder { private ExcelWriter excelWriter; @@ -35,88 +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; - } - - /** - * 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,4 +55,8 @@ public class ExcelWriterTableBuilder { excelWriter.finish(); } + @Override + protected WriteTable parameter() { + return writeTable; + } } 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..1f09d47 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()); @@ -56,15 +59,16 @@ 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()); } } 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) { @@ -99,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; @@ -107,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 8843e2c..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; @@ -98,6 +97,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 +123,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,18 +152,16 @@ 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; } 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); } } @@ -167,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 217f21e..495a134 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -3,21 +3,24 @@ 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; +import org.apache.poi.ss.usermodel.CellType; 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; 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; @@ -55,12 +58,18 @@ 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 */ private Map> collectionLastIndexCache = new HashMap>(8); + private Map relativeRowIndexMap = new HashMap(8); + public ExcelWriteFillExecutor(WriteContext writeContext) { super(writeContext); } @@ -81,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); } } @@ -119,7 +128,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (collectionLastIndexMap == null) { number--; } - sheet.shiftRows(maxRowIndex + 1, lastRowIndex, 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) { analysisCell.setRowIndex(analysisCell.getRowIndex() + number); @@ -127,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; @@ -141,19 +154,25 @@ 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; } 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; 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; } @@ -161,27 +180,43 @@ 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)); cell.setCellValue(cellValueBuild.toString()); - WriteHandlerUtils.afterCellDispose(writeContext, cellDataList, cell, null, null, Boolean.FALSE); + WriteHandlerUtils.afterCellDispose(writeContext, cellDataList, cell, null, relativeRowIndex, + Boolean.FALSE); } } } + private Integer 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())) { @@ -234,7 +269,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); @@ -260,6 +298,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); @@ -269,6 +322,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) { @@ -279,7 +333,12 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (cell == null) { continue; } - prepareData(cell.getStringCellValue(), 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); + } } } templateAnalysisCache.put(sheetNo, analysisCellList); @@ -287,12 +346,29 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { return analysisCache.get(sheetNo); } - private void prepareData(String value, List analysisCellList, - List collectionAnalysisCellList, int rowIndex, int columnIndex) { + /** + * To prepare data + * + * @param cell + * @param analysisCellList + * @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, Set firstColumnCache) { + if (!CellType.STRING.equals(cell.getCellTypeEnum())) { + return null; + } + String value = cell.getStringCellValue(); if (StringUtils.isEmpty(value)) { - return; + return null; } + StringBuilder preparedData = new StringBuilder(); AnalysisCell analysisCell = null; + int startIndex = 0; int length = value.length(); int lastPrepareDataIndex = 0; @@ -322,15 +398,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)) { @@ -347,12 +415,20 @@ 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; } + 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); @@ -363,9 +439,29 @@ 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(); } + return null; + } + + 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); + analysisCell.setFirstColumn(Boolean.FALSE); + return analysisCell; } private String convertPrepareData(String prepareData) { 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 85694a8..615e408 100644 --- a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java @@ -13,25 +13,46 @@ 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) { + 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()); - sheet.addMergedRegion(cellRangeAddress); + 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 2689cd9..b582576 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,26 @@ 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. + */ + 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 +81,45 @@ public class WriteBasicParameter extends BasicParameter { public void setUseDefaultStyle(Boolean useDefaultStyle) { this.useDefaultStyle = useDefaultStyle; } + + public Boolean getAutomaticMergeHead() { + return automaticMergeHead; + } + + public void setAutomaticMergeHead(Boolean automaticMergeHead) { + this.automaticMergeHead = automaticMergeHead; + } + + 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..c28e041 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,16 @@ 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; + /** + * 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. *

@@ -155,4 +165,20 @@ 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; + } + + public Boolean getWriteExcelOnException() { + return writeExcelOnException; + } + + public void setWriteExcelOnException(Boolean writeExcelOnException) { + this.writeExcelOnException = writeExcelOnException; + } } 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/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/AbstractWriteHolder.java index 72f5f9b..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 @@ -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,26 @@ 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. + */ + 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 +131,39 @@ 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 { + 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 +200,6 @@ public abstract class AbstractWriteHolder extends AbstractHolder implements Writ getConverterMap().put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); } } - } /** @@ -214,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; } @@ -263,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; } @@ -343,6 +394,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 +455,46 @@ 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; + } + + 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(); @@ -402,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 53c11db..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 @@ -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 * @@ -35,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/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/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index d802085..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 @@ -97,6 +97,16 @@ 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; + /** + * 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()); @@ -122,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; @@ -137,6 +150,16 @@ 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(); + } + if (writeWorkbook.getWriteExcelOnException() == null) { + this.writeExcelOnException = Boolean.FALSE; + } else { + this.writeExcelOnException = writeWorkbook.getWriteExcelOnException(); + } } private void copyTemplate() throws IOException { @@ -146,7 +169,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 { @@ -262,6 +285,22 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.password = password; } + public Boolean getInMemory() { + return inMemory; + } + + public void setInMemory(Boolean inMemory) { + 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/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/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/LongestMatchColumnWidthStyleStrategy.java b/src/main/java/com/alibaba/excel/write/style/column/LongestMatchColumnWidthStyleStrategy.java index 006927f..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; @@ -21,7 +22,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); @@ -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/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/dataformat/DateFormatData.java b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java new file mode 100644 index 0000000..1930bce --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatData.java @@ -0,0 +1,16 @@ +package com.alibaba.easyexcel.test.core.dataformat; + +import lombok.Data; + +/** + * @author Jiaju Zhuang + */ +@Data +public class DateFormatData { + private String date; + private String dateStringCn; + private String dateStringUs; + private String number; + 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 new file mode 100644 index 0000000..477652b --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/dataformat/DateFormatTest.java @@ -0,0 +1,61 @@ +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; +import org.slf4j.LoggerFactory; + +import com.alibaba.easyexcel.test.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; + +/** + * + * @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() { + readCn(file07); + readUs(file07); + } + + @Test + public void t02Read03() { + readCn(file03); + readUs(file03); + } + + private void readCn(File file) { + List list = + EasyExcel.read(file, DateFormatData.class, null).locale(Locale.CHINA).sheet().doReadSync(); + for (DateFormatData data : list) { + 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/core/fill/FillData.java b/src/test/java/com/alibaba/easyexcel/test/core/fill/FillData.java index 13dfa17..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,5 +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/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/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/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 112c529..cd45f2e 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. 直接读即可 */ @@ -184,7 +184,7 @@ public class ReadTest { *

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

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

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

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

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

* 3. 直接读即可 */ @@ -219,17 +219,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..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 @@ -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,19 +51,48 @@ 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)); + } + } + /** * 文件上传 *

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

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

* 3. 直接读即可 */ @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 2582f10..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 @@ -2,9 +2,12 @@ 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; import java.util.List; +import java.util.Set; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.IndexedColors; @@ -48,13 +51,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); @@ -62,6 +65,37 @@ public class WriteTest { excelWriter.finish(); } + /** + * 根据参数只导出指定列 + *

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

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

+ * 3. 直接写即可 + * + * @since 2.1.1 + */ + @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()); + } + /** * 指定写入的列 *

@@ -74,7 +108,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()); } @@ -90,7 +124,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()); } @@ -107,7 +141,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(); @@ -163,7 +197,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()); } @@ -184,12 +218,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) { @@ -205,7 +241,7 @@ public class WriteTest { *

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

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

* 4. 直接写即可 */ @@ -213,7 +249,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()); } @@ -229,7 +265,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()); } @@ -266,7 +302,7 @@ public class WriteTest { HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); - // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板") .doWrite(data()); } @@ -285,7 +321,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()); } @@ -300,7 +336,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(); @@ -354,7 +390,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()); } @@ -373,7 +409,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()); } @@ -385,7 +421,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()); } 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..8049d89 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java @@ -0,0 +1,119 @@ +package com.alibaba.easyexcel.test.temp; + +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.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; + +/** + * 写的填充写法 + * + * @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(data2(), 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(data2(), 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 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++) { + 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/Lock2Test.java b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java index 052b60b..ece5aa0 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; /** @@ -29,7 +22,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()); @@ -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) { 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/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..e165d98 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java @@ -0,0 +1,190 @@ +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 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; +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; + +/** + * 格式测试 + * + * @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)); + } + + @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/dataformat1.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/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/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/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..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); @@ -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/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..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,12 +2,9 @@ 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.math.BigDecimal; 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 +14,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 * @@ -42,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 = @@ -99,58 +104,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/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..e52dc51 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java @@ -0,0 +1,43 @@ +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)); + LOGGER.info("total:{}", context.readSheetHolder().getTotal()); + + } + + @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..538e266 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java @@ -0,0 +1,20 @@ +package com.alibaba.easyexcel.test.temp.read; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Data +@Accessors(chain = true) +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..b046d14 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java @@ -0,0 +1,40 @@ +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; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.cache.Ehcache; + +/** + * 临时测试 + * + * @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.xls"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).sheet(0).doRead(); + + } + + @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/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 27bea33..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 @@ -1,23 +1,23 @@ 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; +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.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.fastjson.JSON; +import net.sf.cglib.beans.BeanMap; + /** * 测试poi * @@ -27,13 +27,52 @@ 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 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() { @@ -54,9 +93,19 @@ 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(0.56); + data.setDoubleData(null); + list.add(data); + } + 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 0000000..c03847e Binary files /dev/null and b/src/test/resources/dataformat/dataformat.xls differ diff --git a/src/test/resources/dataformat/dataformat.xlsx b/src/test/resources/dataformat/dataformat.xlsx new file mode 100644 index 0000000..0085930 Binary files /dev/null and b/src/test/resources/dataformat/dataformat.xlsx differ diff --git a/src/test/resources/fill/simple.xls b/src/test/resources/fill/simple.xls index ea66e94..317ef6d 100644 Binary files a/src/test/resources/fill/simple.xls and b/src/test/resources/fill/simple.xls differ diff --git a/src/test/resources/fill/simple.xlsx b/src/test/resources/fill/simple.xlsx index be1753c..a441eba 100644 Binary files a/src/test/resources/fill/simple.xlsx and b/src/test/resources/fill/simple.xlsx differ diff --git a/update.md b/update.md index 2ae7d28..1d7c6a1 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,57 @@ +# 2.2.0-beta1 +* 修复用String接收日期、数字和excel显示不一致的bug(不是完美修复,但是大部分情况已经兼容) +* 降低Ehcache版本 3.7.1(jkd7) -> 3.4.0(jdk6) +* 修复xls 用Map接收时多次接收会是同一个对象的bug +* 修复浮点型数据导入到excel 会丢失精度的bug + +# 2.1.4 +* 新增参数`useDefaultListener` 可以排除默认对象转换 + +# 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 + +# 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` 参数设置异常了也要写入前面的数据。 +* 循环合并策略支持一次性合并多列 +* `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) +* 修改afterRowDispose错误 [Issue #751](https://github.com/alibaba/easyexcel/issues/751) +* 修复多个头的情况下会读取数据为空 + +# 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 +* 修复空模板会空一行的bug + # 2.1.0-beta1 * 新增支持导入、导出支持公式 * 新增支持读取单元格类型、写入指定单元格类型 @@ -28,7 +82,7 @@ * 修复监听器转换异常会重复提示的bug # 2.0.1 -* 降级poi为3.1.7 兼容jdk6 +* 降级poi为3.17 兼容jdk6 # 2.0.0 * 修复当cell为空可能会抛出空指针的bug