From 22dc33d4da1b6b7477eef6ee38e50d4112c47bd3 Mon Sep 17 00:00:00 2001 From: zhuangjiaju Date: Tue, 6 Aug 2019 16:54:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AF=BB=E5=86=99=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- img/readme/quickstart/read/demo.png | Bin 0 -> 10527 bytes quickstart.md | 565 +++++++++--------- .../excel/context/WriteContextImpl.java | 16 +- .../converters/DefaultConverterLoader.java | 132 ++-- .../alibaba/excel/enums/WriteLastRowType.java | 12 +- .../com/alibaba/excel/util/WorkBookUtil.java | 13 +- .../alibaba/excel/write/ExcelBuilderImpl.java | 18 +- .../excel/write/merge/LoopMergeStrategy.java | 17 +- .../metadata/holder/WriteSheetHolder.java | 35 +- .../metadata/holder/WriteWorkbookHolder.java | 14 + .../property/ExcelWriteHeadProperty.java | 14 +- .../test/core/template/TemplateDataTest.java | 4 +- .../easyexcel/test/demo/poi/PoiTest.java | 54 ++ .../easyexcel/test/demo/read/ReadTest.java | 14 +- .../easyexcel/test/demo/web/WebTest.java | 2 +- .../test/demo/write/ComplexHeadData.java | 22 + .../test/demo/write/ConverterData.java | 35 ++ .../write/CustomStringStringConverter.java | 59 ++ .../easyexcel/test/demo/write/IndexData.java | 25 + .../test/demo/write/WidthAndHeightData.java | 32 + .../easyexcel/test/demo/write/WriteTest.java | 185 +++++- src/test/resources/demo/demo.xlsx | Bin 10112 -> 10117 bytes src/test/resources/template/template03.xls | Bin 18944 -> 18944 bytes src/test/resources/template/template07.xlsx | Bin 9994 -> 10025 bytes update.md | 2 + 26 files changed, 863 insertions(+), 411 deletions(-) create mode 100644 img/readme/quickstart/read/demo.png create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java diff --git a/README.md b/README.md index 0cf42e0..17ce619 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ # JAVA解析Excel工具easyexcel Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便 ## 相关文档 -* [关于软件](/abouteasyexcel.md) * [快速使用](/quickstart.md) +* [关于软件](/abouteasyexcel.md) * [常见问题](/problem.md) * [更新记事](/update.md) * [English-README](/easyexcel_en.md) @@ -74,7 +74,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja * 文件下载 *
  • 1. 创建excel对应的实体对象 参照{@link DownloadData} *
  • 2. 设置返回的 参数 - *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭异常问题不大 + *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { diff --git a/img/readme/quickstart/read/demo.png b/img/readme/quickstart/read/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac47ceaa0536dec4dc1a70b82474fe2662ab301 GIT binary patch literal 10527 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 F{{^Bcu>Sx6 literal 0 HcmV?d00001 diff --git a/quickstart.md b/quickstart.md index 296e276..b597669 100644 --- a/quickstart.md +++ b/quickstart.md @@ -1,4 +1,10 @@ # easyexcel核心功能 +## 目录 +### 读 +DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/demo/read/ReadTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java) +* [最简单的读](#simpleRead) + +### 写 ## *读任意大小的03、07版Excel不会OO]
    ## *读Excel自动通过注解,把结果映射为java模型
    @@ -12,343 +18,304 @@ ## *写Excel时一个sheet可以写多个Table
    ## *写Excel时候自定义是否需要写表头
    -## 读Excel - -使用easyexcel解析03、07版本的Excel只是ExcelTypeEnum不同,其他使用完全相同,使用者无需知道底层解析的差异。 - -### 无java模型直接把excel解析的每行结果以List<String>返回 在ExcelListener获取解析结果 -读excel代码示例如下: +## 读excel样例 +### 最简单的读 +#### excel示例 +![img](img/readme/quickstart/read/demo.png) +#### 对象 +```java +@Data +public class DemoData { + private String string; + private Date date; + private Double doubleData; +} ``` - @Test - public void testExcel2003NoModel() { - InputStream inputStream = getInputStream("loan1.xls"); - try { - // 解析每行结果在listener中处理 - ExcelListener listener = new ExcelListener(); - - ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener); - excelReader.read(); - } catch (Exception e) { - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } +#### 监听器 +```java +public class DemoDataListener 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(); + + @Override + public void invoke(DemoData data, AnalysisContext context) { + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + list.add(data); + if (list.size() >= BATCH_COUNT) { + saveData(); + list.clear(); } } -``` -ExcelListener示例代码如下: -``` - /* 解析监听器, - * 每解析一行会回调invoke()方法。 - * 整个excel解析结束会执行doAfterAllAnalysed()方法 - * - * 下面只是我写的一个样例而已,可以根据自己的逻辑修改该类。 - * @author jipengfei - * @date 2017/03/14 - */ -public class ExcelListener extends AnalysisEventListener { - //自定义用于暂时存储data。 - //可以通过实例获取该值 - private List datas = new ArrayList(); - public void invoke(Object object, AnalysisContext context) { - System.out.println("当前行:"+context.getCurrentRowNum()); - System.out.println(object); - datas.add(object);//数据存储到list,供批量处理,或后续自己业务逻辑处理。 - doSomething(object);//根据自己业务做处理 - } - private void doSomething(Object object) { - //1、入库调用接口 - } + @Override public void doAfterAllAnalysed(AnalysisContext context) { - // datas.clear();//解析结束销毁不用的资源 - } - public List getDatas() { - return datas; + saveData(); + LOGGER.info("所有数据解析完成!"); } - public void setDatas(List datas) { - this.datas = datas; + + /** + * 加上存储数据库 + */ + private void saveData() { + LOGGER.info("{}条数据,开始存储数据库!", list.size()); + LOGGER.info("存储数据库成功!"); } } ``` -### 有java模型映射 -java模型写法如下: +#### 代码 +```java + /** + * 最简单的读 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *
  • 3. 直接读即可 + */ + @Test + public void simpleRead() { + // 写法1: + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 + EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); + + // 写法2: + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + ExcelReader excelReader = EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).build(); + ReadSheet readSheet = EasyExcelFactory.readSheet(0).build(); + excelReader.read(readSheet); + // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 + excelReader.finish(); + } ``` -public class LoanInfo extends BaseRowModel { - @ExcelProperty(index = 0) - private String bankLoanId; - - @ExcelProperty(index = 1) - private Long customerId; - - @ExcelProperty(index = 2,format = "yyyy/MM/dd") - private Date loanDate; - - @ExcelProperty(index = 3) - private BigDecimal quota; - - @ExcelProperty(index = 4) - private String bankInterestRate; - - @ExcelProperty(index = 5) - private Integer loanTerm; - - @ExcelProperty(index = 6,format = "yyyy/MM/dd") - private Date loanEndDate; - - @ExcelProperty(index = 7) - private BigDecimal interestPerMonth; - @ExcelProperty(value = {"一级表头","二级表头"}) - private BigDecimal sax; +### 指定列的下标或者列名 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +```java +@Data +public class IndexOrNameData { + /** + * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配 + */ + @ExcelProperty(index = 2) + private Double doubleData; + /** + * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据 + */ + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; } ``` -@ExcelProperty(index = 3)数字代表该字段与excel对应列号做映射,也可以采用 @ExcelProperty(value = {"一级表头","二级表头"})用于解决不确切知道excel第几列和该字段映射,位置不固定,但表头的内容知道的情况。 -``` +#### 监听器 +参照:[监听器](#simpleReadListener) 只是泛型变了而已 +#### 代码 +```java + /** + * 指定列的下标或者列名 + * + *
  • 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + *
  • 3. 直接读即可 + */ @Test - public void testExcel2003WithReflectModel() { - InputStream inputStream = getInputStream("loan1.xls"); - try { - // 解析每行结果在listener中处理 - AnalysisEventListener listener = new ExcelListener(); - - ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener); - - excelReader.read(new Sheet(1, 2, LoanInfo.class)); - } catch (Exception e) { - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - + public void indexOrNameRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里默认读取第一个sheet + EasyExcelFactory.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead(); } ``` -带模型解析与不带模型解析主要在构造new Sheet(1, 2, LoanInfo.class)时候包含class。Class需要继承BaseRowModel暂时BaseRowModel没有任何内容,后面升级可能会增加一些默认的数据。 - -## 写Excel - -### 每行数据是List<String>无表头 - -``` - OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); - //写第一个sheet, sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0); - sheet1.setSheetName("第一个sheet"); - writer.write(getListString(), sheet1); - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } -``` -### 每行数据是一个java模型有表头----表头层级为一 - -生成Excel格式如下图 -![屏幕快照 2017-06-02 上午9.49.39.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/dfcb44d05380e2e26bce93f850d9fc99.png) - -模型写法如下: +### 读多个sheet +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +参照:[对象](#simpleReadObject) +#### 监听器 +参照:[监听器](#simpleReadListener) +#### 代码 +```java + /** + * 读多个sheet,这里注意一个sheet不能读取多次,一定要多次需要重新读取文件 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *
  • 3. 直接读即可 + */ + @Test + public void repeatedRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + ExcelReader excelReader = EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).build(); + ReadSheet readSheet1 = EasyExcelFactory.readSheet(0).build(); + ReadSheet readSheet2 = EasyExcelFactory.readSheet(1).build(); + excelReader.read(readSheet1); + excelReader.read(readSheet2); + // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 + excelReader.finish(); + } ``` -public class ExcelPropertyIndexModel extends BaseRowModel { - - @ExcelProperty(value = "姓名" ,index = 0) - private String name; - - @ExcelProperty(value = "年龄",index = 1) - private String age; - - @ExcelProperty(value = "邮箱",index = 2) - private String email; - - @ExcelProperty(value = "地址",index = 3) - private String address; - @ExcelProperty(value = "性别",index = 4) - private String sax; - - @ExcelProperty(value = "高度",index = 5) - private String heigh; - - @ExcelProperty(value = "备注",index = 6) - private String last; +### 日期、数字或者自定义格式转换 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +```java +@Data +public class ConverterData { + /** + * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:” + */ + @ExcelProperty(converter = CustomStringStringConverter.class) + private String string; + /** + * 这里用string 去接日期才能格式化。我想接收年月日格式 + */ + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + private String date; + /** + * 我想接收百分比的数字 + */ + @NumberFormat("#.##%") + private String doubleData; } ``` - @ExcelProperty(value = "姓名",index = 0) value是表头数据,默认会写在excel的表头位置,index代表第几列。 -``` - @Test - public void test1() throws FileNotFoundException { - OutputStream out = new FileOutputStream("/Users/jipengfei/78.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX); - //写第一个sheet, sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0,ExcelPropertyIndexModel.class); - writer.write(getData(), sheet1); - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } +#### 监听器 +参照:[监听器](#simpleReadListener) 只是泛型变了 +#### 自定义转换器 +````java +public class CustomStringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; } -``` - -### 每行数据是一个java模型有表头----表头层级为多层级 - -生成Excel格式如下图: -![屏幕快照 2017-06-02 上午9.53.07.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/0cdb1673665e7940cd670871afb4b3d7.png) -java模型写法如下: -``` -public class MultiLineHeadExcelModel extends BaseRowModel { - - @ExcelProperty(value = {"表头1","表头1","表头31"},index = 0) - private String p1; - @ExcelProperty(value = {"表头1","表头1","表头32"},index = 1) - private String p2; - - @ExcelProperty(value = {"表头3","表头3","表头3"},index = 2) - private int p3; - - @ExcelProperty(value = {"表头4","表头4","表头4"},index = 3) - private long p4; - - @ExcelProperty(value = {"表头5","表头51","表头52"},index = 4) - private String p5; - - @ExcelProperty(value = {"表头6","表头61","表头611"},index = 5) - private String p6; + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } - @ExcelProperty(value = {"表头6","表头61","表头612"},index = 6) - private String p7; + /** + * 这里读的时候会调用 + * + * @param cellData + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return "自定义:" + cellData.getStringValue(); + } - @ExcelProperty(value = {"表头6","表头62","表头621"},index = 7) - private String p8; + /** + * 这里是写的时候会调用 不用管 + * + * @param value + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new CellData(value); + } - @ExcelProperty(value = {"表头6","表头62","表头622"},index = 8) - private String p9; } -``` -写Excel写法同上,只需将ExcelPropertyIndexModel.class改为MultiLineHeadExcelModel.class - - -### 一个Excel多个sheet写法 - -``` - @Test - public void test1() throws FileNotFoundException { - - OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); - //写第一个sheet, sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0); - sheet1.setSheetName("第一个sheet"); - writer.write(getListString(), sheet1); - - //写第二个sheet sheet2 模型上打有表头的注解,合并单元格 - Sheet sheet2 = new Sheet(2, 3, MultiLineHeadExcelModel.class, "第二个sheet", null); - sheet2.setTableStyle(getTableStyle1()); - writer.write(getModeldatas(), sheet2); - - //写sheet3 模型上没有注解,表头数据动态传入 - List> head = new ArrayList>(); - List headCoulumn1 = new ArrayList(); - List headCoulumn2 = new ArrayList(); - List headCoulumn3 = new ArrayList(); - headCoulumn1.add("第一列"); - headCoulumn2.add("第二列"); - headCoulumn3.add("第三列"); - head.add(headCoulumn1); - head.add(headCoulumn2); - head.add(headCoulumn3); - Sheet sheet3 = new Sheet(3, 1, NoAnnModel.class, "第三个sheet", head); - writer.write(getNoAnnModels(), sheet3); - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } +```` +#### 代码 +```java + /** + * 日期、数字或者自定义格式转换 + *

    + * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} + *

  • 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} + *
  • 3. 直接读即可 + */ + @Test + public void converterRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcelFactory.read(fileName, ConverterData.class, new ConverterDataListener()) + // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。 + // 如果就想单个字段使用请使用@ExcelProperty 指定converter + // .registerConverter(new CustomStringStringConverter()) + // 读取sheet + .sheet().doRead(); } ``` -### 一个sheet中有多个表格 - +### 多行头 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +参照:[对象](#simpleReadObject) +#### 监听器 +参照:[监听器](#simpleReadListener) +#### 代码 +```java + /** + * 多行头 + * + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *
  • 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数, + * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。 + */ + @Test + public void complexHeaderRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish + EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet() + // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行 + .headRowNumber(1).doRead(); + } ``` -@Test - public void test2() throws FileNotFoundException { - OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx"); - try { - ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); - - //写sheet1 数据全是List 无模型映射关系 - Sheet sheet1 = new Sheet(1, 0); - sheet1.setSheetName("第一个sheet"); - Table table1 = new Table(1); - writer.write(getListString(), sheet1, table1); - writer.write(getListString(), sheet1, table1); - - //写sheet2 模型上打有表头的注解 - Table table2 = new Table(2); - table2.setTableStyle(getTableStyle1()); - table2.setClazz(MultiLineHeadExcelModel.class); - writer.write(getModeldatas(), sheet1, table2); - //写sheet3 模型上没有注解,表头数据动态传入,此情况下模型field顺序与excel现实顺序一致 - List> head = new ArrayList>(); - List headCoulumn1 = new ArrayList(); - List headCoulumn2 = new ArrayList(); - List headCoulumn3 = new ArrayList(); - headCoulumn1.add("第一列"); - headCoulumn2.add("第二列"); - headCoulumn3.add("第三列"); - head.add(headCoulumn1); - head.add(headCoulumn2); - head.add(headCoulumn3); - Table table3 = new Table(3); - table3.setHead(head); - table3.setClazz(NoAnnModel.class); - table3.setTableStyle(getTableStyle2()); - writer.write(getNoAnnModels(), sheet1, table3); - writer.write(getNoAnnModels(), sheet1, table3); +### 同步的返回 +#### excel示例 +参照:[excel示例](#simpleReadExcel) +#### 对象 +参照:[对象](#simpleReadObject) +#### 代码 +```java + /** + * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 + */ + @Test + public void synchronousRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish + List list = EasyExcelFactory.read(fileName).head(DemoData.class).sheet().doReadSync(); + for (Object obj : list) { + DemoData data = (DemoData)obj; + LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); + } - writer.finish(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } + // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish + list = EasyExcelFactory.read(fileName).sheet().doReadSync(); + for (Object obj : list) { + // 返回每条数据的键值对 表示所在的列 和所在列的值 + Map data = (Map)obj; + LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } } ``` -## 测试数据分析 +## 测试数据分析 ![POI usermodel PK easyexcel(Excel 2003).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/02c4bfbbab99a649788523d04f84a42f.png) ![POI usermodel PK easyexcel(Excel 2007).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/f6a8a19ec959f0eb564e652de523fc9e.png) ![POI usermodel PK easyexcel(Excel 2003) (1).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/26888f7ea1cb8dc56db494926544edf7.png) diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index 3564cda..e4dbec8 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -13,7 +13,6 @@ import org.apache.poi.ss.util.CellRangeAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.excel.enums.WriteLastRowType; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.util.WorkBookUtil; @@ -198,18 +197,13 @@ public class WriteContextImpl implements WriteContext { if (!currentWriteHolder.needHead() || !currentWriteHolder.excelWriteHeadProperty().hasHead()) { return; } - int lastRowNum = writeSheetHolder.getSheet().getLastRowNum(); - // 'lastRowNum' doesn't matter if it has one or zero,is's zero - if (WriteLastRowType.HAVE_DATA == writeSheetHolder.getWriteLastRowType()) { - lastRowNum++; - } - writeSheetHolder.setWriteLastRowType(WriteLastRowType.HAVE_DATA); - int rowIndex = lastRowNum + currentWriteHolder.relativeHeadRowIndex(); + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); + newRowIndex += currentWriteHolder.relativeHeadRowIndex(); // Combined head - addMergedRegionToCurrentSheet(excelWriteHeadProperty, rowIndex); - for (int relativeRowIndex = 0, i = rowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + rowIndex; + addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex); + for (int relativeRowIndex = 0, i = newRowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + newRowIndex; i++, relativeRowIndex++) { - beforeRowCreate(rowIndex, relativeRowIndex); + beforeRowCreate(newRowIndex, relativeRowIndex); Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i); afterRowCreate(row, relativeRowIndex); addOneRowOfHeadDataToExcel(row, excelWriteHeadProperty.getHeadMap(), relativeRowIndex); diff --git a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java index 8bb4fdb..0bbf8f0 100644 --- a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java +++ b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java @@ -40,28 +40,34 @@ import com.alibaba.excel.converters.string.StringStringConverter; * @author Jiaju Zhuang */ public class DefaultConverterLoader { + private static Map defaultWriteConverter; + private static Map allConverter; + /** * Load default write converter * * @return */ public static Map loadDefaultWriteConverter() { - Map converterMap = new HashMap(16); - putWriteConverter(converterMap, new BigDecimalNumberConverter()); - putWriteConverter(converterMap, new BooleanBooleanConverter()); - putWriteConverter(converterMap, new ByteNumberConverter()); - putWriteConverter(converterMap, new DateStringConverter()); - putWriteConverter(converterMap, new DoubleNumberConverter()); - putWriteConverter(converterMap, new FloatNumberConverter()); - putWriteConverter(converterMap, new IntegerNumberConverter()); - putWriteConverter(converterMap, new LongNumberConverter()); - putWriteConverter(converterMap, new ShortNumberConverter()); - putWriteConverter(converterMap, new StringStringConverter()); - return converterMap; + if (defaultWriteConverter != null) { + return defaultWriteConverter; + } + defaultWriteConverter = new HashMap(16); + putWriteConverter(new BigDecimalNumberConverter()); + putWriteConverter(new BooleanBooleanConverter()); + putWriteConverter(new ByteNumberConverter()); + putWriteConverter(new DateStringConverter()); + putWriteConverter(new DoubleNumberConverter()); + putWriteConverter(new FloatNumberConverter()); + putWriteConverter(new IntegerNumberConverter()); + putWriteConverter(new LongNumberConverter()); + putWriteConverter(new ShortNumberConverter()); + putWriteConverter(new StringStringConverter()); + return defaultWriteConverter; } - private static void putWriteConverter(Map converterMap, Converter converter) { - converterMap.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); + private static void putWriteConverter(Converter converter) { + defaultWriteConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter); } /** @@ -70,51 +76,63 @@ public class DefaultConverterLoader { * @return */ public static Map loadDefaultReadConverter() { - Map converterMap = new HashMap(64); - putReadConverter(converterMap, new BigDecimalBooleanConverter()); - putReadConverter(converterMap, new BigDecimalNumberConverter()); - putReadConverter(converterMap, new BigDecimalStringConverter()); - - putReadConverter(converterMap, new BooleanBooleanConverter()); - putReadConverter(converterMap, new BooleanNumberConverter()); - putReadConverter(converterMap, new BooleanStringConverter()); - - putReadConverter(converterMap, new ByteBooleanConverter()); - putReadConverter(converterMap, new ByteNumberConverter()); - putReadConverter(converterMap, new ByteStringConverter()); - - putReadConverter(converterMap, new DateNumberConverter()); - putReadConverter(converterMap, new DateStringConverter()); - - putReadConverter(converterMap, new DoubleBooleanConverter()); - putReadConverter(converterMap, new DoubleNumberConverter()); - putReadConverter(converterMap, new DoubleStringConverter()); - - putReadConverter(converterMap, new FloatBooleanConverter()); - putReadConverter(converterMap, new FloatNumberConverter()); - putReadConverter(converterMap, new FloatStringConverter()); - - putReadConverter(converterMap, new IntegerBooleanConverter()); - putReadConverter(converterMap, new IntegerNumberConverter()); - putReadConverter(converterMap, new IntegerStringConverter()); - - putReadConverter(converterMap, new LongBooleanConverter()); - putReadConverter(converterMap, new LongNumberConverter()); - putReadConverter(converterMap, new LongStringConverter()); - - putReadConverter(converterMap, new ShortBooleanConverter()); - putReadConverter(converterMap, new ShortNumberConverter()); - putReadConverter(converterMap, new ShortStringConverter()); - - putReadConverter(converterMap, new StringBooleanConverter()); - putReadConverter(converterMap, new StringNumberConverter()); - putReadConverter(converterMap, new StringStringConverter()); - putReadConverter(converterMap, new StringErrorConverter()); - return converterMap; + return loadAllConverter(); + } + + /** + * Load all converter + * + * @return + */ + public static Map loadAllConverter() { + if (allConverter != null) { + return allConverter; + } + allConverter = new HashMap(64); + putAllConverter(new BigDecimalBooleanConverter()); + putAllConverter(new BigDecimalNumberConverter()); + putAllConverter(new BigDecimalStringConverter()); + + putAllConverter(new BooleanBooleanConverter()); + putAllConverter(new BooleanNumberConverter()); + putAllConverter(new BooleanStringConverter()); + + putAllConverter(new ByteBooleanConverter()); + putAllConverter(new ByteNumberConverter()); + putAllConverter(new ByteStringConverter()); + + putAllConverter(new DateNumberConverter()); + putAllConverter(new DateStringConverter()); + + putAllConverter(new DoubleBooleanConverter()); + putAllConverter(new DoubleNumberConverter()); + putAllConverter(new DoubleStringConverter()); + + putAllConverter(new FloatBooleanConverter()); + putAllConverter(new FloatNumberConverter()); + putAllConverter(new FloatStringConverter()); + + putAllConverter(new IntegerBooleanConverter()); + putAllConverter(new IntegerNumberConverter()); + putAllConverter(new IntegerStringConverter()); + + putAllConverter(new LongBooleanConverter()); + putAllConverter(new LongNumberConverter()); + putAllConverter(new LongStringConverter()); + + putAllConverter(new ShortBooleanConverter()); + putAllConverter(new ShortNumberConverter()); + putAllConverter(new ShortStringConverter()); + + putAllConverter(new StringBooleanConverter()); + putAllConverter(new StringNumberConverter()); + putAllConverter(new StringStringConverter()); + putAllConverter(new StringErrorConverter()); + return allConverter; } - private static void putReadConverter(Map converterMap, Converter converter) { - converterMap.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), + private static void putAllConverter(Converter converter) { + allConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()), converter); } } diff --git a/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java b/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java index d68bb7b..bd477bd 100644 --- a/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java +++ b/src/main/java/com/alibaba/excel/enums/WriteLastRowType.java @@ -7,11 +7,15 @@ package com.alibaba.excel.enums; **/ public enum WriteLastRowType { /** - * Tables are created without templates ,And any data has been written; + * Excel are created without templates ,And any data has been written; */ - EMPTY, + COMMON_EMPTY, /** - * It's supposed to have data in it.Tables are created with templates ,or any data has been written; + * Excel are created with templates ,And any data has been written; */ - HAVE_DATA,; + TEMPLATE_EMPTY, + /** + * Any data has been written; + */ + HAS_DATA,; } diff --git a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java index 71bbffd..1fa7834 100644 --- a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java +++ b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java @@ -25,11 +25,20 @@ public class WorkBookUtil { public static Workbook createWorkBook(WriteWorkbookHolder writeWorkbookHolder) throws IOException, InvalidFormatException { if (ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) { + XSSFWorkbook xssfWorkbook = null; if (writeWorkbookHolder.getTemplateFile() != null) { - return new SXSSFWorkbook(new XSSFWorkbook(writeWorkbookHolder.getTemplateFile())); + xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTemplateFile()); } if (writeWorkbookHolder.getTemplateInputStream() != null) { - return new SXSSFWorkbook(new XSSFWorkbook(writeWorkbookHolder.getTemplateInputStream())); + xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTemplateInputStream()); + } + // When using SXSSFWorkbook, you can't get the actual last line.But we need to read the last line when we + // are using the template, so we cache it + if (xssfWorkbook != null) { + for (int i = 0; i < xssfWorkbook.getNumberOfSheets(); i++) { + writeWorkbookHolder.getTemplateLastRowMap().put(i, xssfWorkbook.getSheetAt(i).getLastRowNum()); + } + return new SXSSFWorkbook(xssfWorkbook); } return new SXSSFWorkbook(500); } diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index 6f06e11..a15ed79 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -10,14 +10,12 @@ import java.util.Set; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.context.WriteContextImpl; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.converters.ConverterKeyBuild; -import com.alibaba.excel.enums.WriteLastRowType; import com.alibaba.excel.exception.ExcelDataConvertException; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.BaseRowModel; @@ -64,22 +62,14 @@ public class ExcelBuilderImpl implements ExcelBuilder { return; } WriteSheetHolder writeSheetHolder = context.writeSheetHolder(); - Sheet currentSheet = writeSheetHolder.getSheet(); - int lastRowNum = currentSheet.getLastRowNum(); - // 'lastRowNum' doesn't matter if it has one or zero,is's zero - if (lastRowNum == 0 && WriteLastRowType.EMPTY == writeSheetHolder.getWriteLastRowType()) { - lastRowNum--; - } - if (!data.isEmpty()) { - writeSheetHolder.setWriteLastRowType(WriteLastRowType.HAVE_DATA); - } + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) { - lastRowNum += context.currentWriteHolder().relativeHeadRowIndex(); + newRowIndex += context.currentWriteHolder().relativeHeadRowIndex(); } - // Beanmap is out of order,so use fieldList + // BeanMap is out of order,so use fieldList List fieldList = new ArrayList(); for (int relativeRowIndex = 0; relativeRowIndex < data.size(); relativeRowIndex++) { - int n = relativeRowIndex + lastRowNum + 1; + int n = relativeRowIndex + newRowIndex; addOneRowOfDataToExcel(data.get(relativeRowIndex), n, relativeRowIndex, fieldList); } } 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 086891f..85694a8 100644 --- a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java @@ -13,21 +13,24 @@ import com.alibaba.excel.metadata.Head; */ public class LoopMergeStrategy extends AbstractMergeStrategy { private int eachRow; - private int eachColumn; + private int columnIndex; - public LoopMergeStrategy(int eachRow, int eachColumn) { - if (eachRow < 1 || eachColumn < 1) { - throw new IllegalArgumentException("All parameters must be greater than 1"); + public LoopMergeStrategy(int eachRow, int columnIndex) { + if (eachRow < 1) { + throw new IllegalArgumentException("EachRows must be greater than 1"); + } + if (columnIndex < 0) { + throw new IllegalArgumentException("ColumnIndex must be greater than 0"); } this.eachRow = eachRow; - this.eachColumn = eachColumn; + this.columnIndex = columnIndex; } @Override protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { - if (relativeRowIndex % eachRow == 0 && head.getColumnIndex() % eachColumn == 0) { + if (head.getColumnIndex() == columnIndex && relativeRowIndex % eachRow == 0) { CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), - cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex() + eachColumn - 1); + cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex()); sheet.addMergedRegion(cellRangeAddress); } } 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 432c622..7cce9f1 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 @@ -7,6 +7,7 @@ import org.apache.poi.ss.usermodel.Sheet; import com.alibaba.excel.enums.HolderEnum; import com.alibaba.excel.enums.WriteLastRowType; +import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.metadata.WriteSheet; /** @@ -60,9 +61,9 @@ public class WriteSheetHolder extends AbstractWriteHolder { this.parentWriteWorkbookHolder = writeWorkbookHolder; this.hasBeenInitializedTable = new HashMap(); if (writeWorkbookHolder.getTemplateInputStream() == null && writeWorkbookHolder.getTemplateFile() == null) { - writeLastRowType = WriteLastRowType.EMPTY; + writeLastRowType = WriteLastRowType.COMMON_EMPTY; } else { - writeLastRowType = WriteLastRowType.HAVE_DATA; + writeLastRowType = WriteLastRowType.TEMPLATE_EMPTY; } } @@ -122,6 +123,36 @@ public class WriteSheetHolder extends AbstractWriteHolder { this.writeLastRowType = writeLastRowType; } + /** + * Get the last line of index,you have to make sure that the data is written next + * + * @return + */ + public int getNewRowIndexAndStartDoWrite() { + // 'getLastRowNum' doesn't matter if it has one or zero,is's zero + int newRowIndex = 0; + switch (writeLastRowType) { + case TEMPLATE_EMPTY: + if (parentWriteWorkbookHolder.getExcelType() == ExcelTypeEnum.XLSX) { + if (parentWriteWorkbookHolder.getTemplateLastRowMap().containsKey(sheetNo)) { + newRowIndex = parentWriteWorkbookHolder.getTemplateLastRowMap().get(sheetNo); + } + } else { + newRowIndex = sheet.getLastRowNum(); + } + newRowIndex++; + break; + case HAS_DATA: + newRowIndex = sheet.getLastRowNum(); + newRowIndex++; + break; + default: + break; + } + writeLastRowType = WriteLastRowType.HAS_DATA; + return newRowIndex; + } + @Override public HolderEnum holderType() { return HolderEnum.SHEET; 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 36a6ada..d3c07f5 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 @@ -72,6 +72,11 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { * prevent duplicate creation of sheet objects */ private Map hasBeenInitializedSheet; + /** + * When using SXSSFWorkbook, you can't get the actual last line.But we need to read the last line when we are using + * the template, so we cache it + */ + private Map templateLastRowMap; public WriteWorkbookHolder(WriteWorkbook writeWorkbook) { super(writeWorkbook, null, writeWorkbook.getConvertAllFiled()); @@ -114,6 +119,7 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.mandatoryUseInputStream = writeWorkbook.getMandatoryUseInputStream(); } this.hasBeenInitializedSheet = new HashMap(); + this.templateLastRowMap = new HashMap(8); } public Workbook getWorkbook() { @@ -196,6 +202,14 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.mandatoryUseInputStream = mandatoryUseInputStream; } + public Map getTemplateLastRowMap() { + return templateLastRowMap; + } + + public void setTemplateLastRowMap(Map templateLastRowMap) { + this.templateLastRowMap = templateLastRowMap; + } + @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 8d05424..fbd33c4 100644 --- a/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java +++ b/src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java @@ -7,9 +7,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.alibaba.excel.annotation.format.NumberFormat; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import com.alibaba.excel.converters.ConverterKeyBuild; +import com.alibaba.excel.converters.DefaultConverterLoader; +import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.enums.HeadKindEnum; import com.alibaba.excel.metadata.CellRange; import com.alibaba.excel.metadata.Head; @@ -48,8 +52,16 @@ public class ExcelWriteHeadProperty extends ExcelHeadProperty { columnWidth = parentColumnWidth; } headData.setColumnWidthProperty(ColumnWidthProperty.build(columnWidth)); - } + // If have @NumberFormat, 'NumberStringConverter' is specified by default + if (excelContentPropertyData.getConverter() == null) { + NumberFormat numberFormat = field.getAnnotation(NumberFormat.class); + if (numberFormat != null) { + excelContentPropertyData.setConverter(DefaultConverterLoader.loadAllConverter() + .get(ConverterKeyBuild.buildKey(field.getType(), CellDataTypeEnum.STRING))); + } + } + } } public RowHeightProperty getHeadRowHeightProperty() { diff --git a/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java b/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java index 07f2c15..369bc8b 100644 --- a/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java @@ -42,14 +42,14 @@ public class TemplateDataTest { EasyExcel.write(file, TemplateData.class) .withTemplate(TestFileUtil.readFile("template" + File.separator + "template07.xlsx")).sheet() .doWrite(data()); - EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(2).sheet().doRead(); + EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead(); } private void readAndWrite03(File file) { EasyExcel.write(file, TemplateData.class) .withTemplate(TestFileUtil.readFile("template" + File.separator + "template03.xls")).sheet() .doWrite(data()); - EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(2).sheet().doRead(); + EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead(); } private List data() { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java new file mode 100644 index 0000000..dab6c99 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java @@ -0,0 +1,54 @@ +package com.alibaba.easyexcel.test.demo.poi; + +import java.io.File; +import java.io.IOException; + +import org.apache.poi.xssf.streaming.SXSSFRow; +import org.apache.poi.xssf.streaming.SXSSFSheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +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.util.TestFileUtil; + +/** + * 测试poi + * + * @author Jiaju Zhuang + **/ +@Ignore +public class PoiTest { + private static final Logger LOGGER = LoggerFactory.getLogger(PoiTest.class); + + @Test + public void lastRowNum() throws IOException { + String file = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + 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 lastRowNumXSSF() throws IOException { + String file = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets()); + XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + XSSFRow row = xssfSheet.getRow(0); + LOGGER.info("第一行数据:{}", row); + xssfSheet.createRow(20); + LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum()); + + + } +} 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 8a671eb..4bbae8a 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 @@ -13,6 +13,8 @@ import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcelFactory; import com.alibaba.excel.ExcelReader; import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.format.NumberFormat; import com.alibaba.excel.converters.DefaultConverterLoader; import com.alibaba.excel.read.metadata.ReadSheet; import com.alibaba.fastjson.JSON; @@ -50,7 +52,10 @@ public class ReadTest { /** * 指定列的下标或者列名 - *
  • 使用{@link com.alibaba.excel.annotation.ExcelProperty}注解即可 + * + *
  • 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} + *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + *
  • 3. 直接读即可 */ @Test public void indexOrNameRead() { @@ -78,10 +83,10 @@ public class ReadTest { } /** - * 日期、数字或者自定义格式转换。 + * 日期、数字或者自定义格式转换 *

    * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} - *

  • 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解. + *
  • 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} *
  • 3. 直接读即可 */ @@ -98,7 +103,7 @@ public class ReadTest { } /** - * 多行头. + * 多行头 * *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} *
  • 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} @@ -134,7 +139,6 @@ public class ReadTest { Map data = (Map)obj; LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } - } } 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 c70414e..37287d3 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 @@ -26,7 +26,7 @@ public class WebTest { * 文件下载 *
  • 1. 创建excel对应的实体对象 参照{@link DownloadData} *
  • 2. 设置返回的 参数 - *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭异常问题不大 + *
  • 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java new file mode 100644 index 0000000..44ce5cc --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java @@ -0,0 +1,22 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; + +/** + * 复杂头数据.这里最终效果是第一行就一个主标题,第二行分类 + * + * @author Jiaju Zhuang + **/ +@Data +public class ComplexHeadData { + @ExcelProperty({"主标题", "字符串标题"}) + private String string; + @ExcelProperty({"主标题", "日期标题"}) + private Date date; + @ExcelProperty({"主标题", "数字标题"}) + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java new file mode 100644 index 0000000..864cfcb --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java @@ -0,0 +1,35 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.format.NumberFormat; + +import lombok.Data; + +/** + * 基础数据类.这里的排序和excel里面的排序一致 + * + * @author Jiaju Zhuang + **/ +@Data +public class ConverterData { + /** + * 我想所有的 字符串起前面加上"自定义:"三个字 + */ + @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class) + private String string; + /** + * 我想写到excel 用年月日的格式 + */ + @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") + @ExcelProperty("日期标题") + private Date date; + /** + * 我想写到excel 用百分比表示 + */ + @NumberFormat("#.##%") + @ExcelProperty(value = "数字标题") + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java new file mode 100644 index 0000000..45ebc8c --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java @@ -0,0 +1,59 @@ +package com.alibaba.easyexcel.test.demo.write; + +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; + +/** + * String and string converter + * + * @author Jiaju Zhuang + */ +public class CustomStringStringConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + /** + * 这里是读的时候会调用 不用管 + * + * @param cellData + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return cellData.getStringValue(); + } + + /** + * 这里是写的时候会调用 不用管 + * + * @param value + * NotNull + * @param contentProperty + * Nullable + * @param globalConfiguration + * NotNull + * @return + */ + @Override + public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new CellData("自定义:" + value); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java new file mode 100644 index 0000000..81c2d4e --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java @@ -0,0 +1,25 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Data +public class IndexData { + @ExcelProperty(value = "字符串标题", index = 0) + private String string; + @ExcelProperty(value = "日期标题", index = 1) + private Date date; + /** + * 这里设置3 会导致第二列空的 + */ + @ExcelProperty(value = "数字标题", index = 3) + private Double doubleData; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java new file mode 100644 index 0000000..42a7f88 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java @@ -0,0 +1,32 @@ +package com.alibaba.easyexcel.test.demo.write; + +import java.util.Date; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.alibaba.excel.annotation.write.style.ContentRowHeight; +import com.alibaba.excel.annotation.write.style.HeadRowHeight; + +import lombok.Data; + +/** + * 基础数据类 + * + * @author Jiaju Zhuang + **/ +@Data +@ContentRowHeight(10) +@HeadRowHeight(20) +@ColumnWidth(25) +public class WidthAndHeightData { + @ExcelProperty("字符串标题") + private String string; + @ExcelProperty("日期标题") + private Date date; + /** + * 宽度为50 + */ + @ColumnWidth(50) + @ExcelProperty("数字标题") + private Double doubleData; +} 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 24b49c7..c3c19ad 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 @@ -1,16 +1,30 @@ package com.alibaba.easyexcel.test.demo.write; +import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.IndexedColors; import org.junit.Ignore; import org.junit.Test; import com.alibaba.easyexcel.test.util.TestFileUtil; import com.alibaba.excel.EasyExcelFactory; import com.alibaba.excel.ExcelWriter; +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.annotation.write.style.ContentRowHeight; +import com.alibaba.excel.annotation.write.style.HeadRowHeight; +import com.alibaba.excel.write.merge.LoopMergeStrategy; import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.WriteTable; +import com.alibaba.excel.write.metadata.style.WriteCellStyle; +import com.alibaba.excel.write.metadata.style.WriteFont; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; /** * 写的常见写法 @@ -21,19 +35,19 @@ import com.alibaba.excel.write.metadata.WriteSheet; public class WriteTest { /** * 最简单的写 - *
  • 1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData} + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} *
  • 2. 直接写即可 */ @Test public void simpleWrite() { // 写法1 - String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; + String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcelFactory.write(fileName, DemoData.class).sheet("模板").doWrite(data()); // 写法2 - fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; + fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读 ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").build(); @@ -42,12 +56,175 @@ public class WriteTest { excelWriter.finish(); } + /** + * 指定写入的列 + *
  • 1. 创建excel对应的实体对象 参照{@link IndexData} + *
  • 2. 使用{@link ExcelProperty}注解指定写入的列 + *
  • 3. 直接写即可 + */ + @Test + public void indexWrite() { + String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, IndexData.class).sheet("模板").doWrite(data()); + } + + /** + * 复杂头写入 指定写入的列 + *
  • 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} + *
  • 2. 使用{@link ExcelProperty}注解指定复杂的头 + *
  • 3. 直接写即可 + */ + @Test + public void complexHeadWrite() { + String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data()); + } + + /** + * 重复多次写入 + *
  • 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} + *
  • 2. 使用{@link ExcelProperty}注解指定复杂的头 + *
  • 3. 直接调用二次写入即可 + */ + @Test + public void repeatedWrite() { + String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读 + ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build(); + WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").build(); + // 第一次写入会创建头 + excelWriter.write(data(), writeSheet); + // 第二次写入会在上一次写入的最后一行后面写入 + excelWriter.write(data(), writeSheet); + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); + + } + + /** + * 日期、数字或者自定义格式转换。 + *
  • 1. 创建excel对应的实体对象 参照{@link ConverterData} + *
  • 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 + *
  • 3. 直接写即可 + */ + @Test + public void converterWrite() { + String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, ConverterData.class).sheet("模板").doWrite(data()); + } + + /** + * 指定写入的列 + *
  • 1. 创建excel对应的实体对象 参照{@link IndexData} + *
  • 2. 使用{@link ExcelProperty}注解指定写入的列 + *
  • 3. 使用withTemplate 读取模板 + *
  • 4. 直接写即可 + */ + @Test + public void templateWrite() { + String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data()); + } + + /** + * 列宽、行高 + *
  • 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData} + *
  • 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度 + *
  • 3. 直接写即可 + */ + @Test + public void widthAndHeightWrite() { + String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data()); + } + + /** + * 自定义样式 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 创建一个style策略 并注册 + *
  • 3. 直接写即可 + */ + @Test + public void styleWrite() { + String fileName = TestFileUtil.getPath() + "styleWrite" + System.currentTimeMillis() + ".xlsx"; + // 头的策略 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + // 背景设置为红色 + headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short)20); + headWriteCellStyle.setWriteFont(headWriteFont); + // 内容的策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 + contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); + // 背景绿色 + contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short)20); + contentWriteCellStyle.setWriteFont(contentWriteFont); + // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 + HorizontalCellStyleStrategy horizontalCellStyleStrategy = + new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板") + .doWrite(data()); + } + + /** + * 合并单元格 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 创建一个merge策略 并注册 + *
  • 3. 直接写即可 + */ + @Test + public void mergeWrite() { + String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; + // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写 + LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); + // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 + EasyExcelFactory.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板") + .doWrite(data()); + } + + /** + * 使用table去写入 + *
  • 1. 创建excel对应的实体对象 参照{@link DemoData} + *
  • 2. 然后写入table即可 + */ + @Test + public void tableWrite() { + String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例 + // 这里 需要指定写用哪个class去读 + ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build(); + // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 + WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").needHead(Boolean.FALSE).build(); + // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要 + WriteTable writeTable0 = EasyExcelFactory.writerTable(0).needHead(Boolean.TRUE).build(); + WriteTable writeTable1 = EasyExcelFactory.writerTable(1).needHead(Boolean.TRUE).build(); + // 第一次写入会创建头 + excelWriter.write(data(), writeSheet, writeTable0); + // 第二次写如也会创建头,然后在第一次的后面写入数据 + excelWriter.write(data(), writeSheet, writeTable1); + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); + + } private List data() { List list = new ArrayList(); for (int i = 0; i < 10; i++) { DemoData data = new DemoData(); - data.setString("字符串" + 0); + data.setString("字符串" + i); data.setDate(new Date()); data.setDoubleData(0.56); list.add(data); diff --git a/src/test/resources/demo/demo.xlsx b/src/test/resources/demo/demo.xlsx index 722ead1c4c74b01efbc1220ce842150d21fedfee..303733cf81ddf2a0f6760f487d0552aec56f2bef 100644 GIT binary patch delta 5130 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 U27ze*C;AGm&CQ3y#{LiZFQrJ9%K!iX delta 5146 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 diff --git a/src/test/resources/template/template03.xls b/src/test/resources/template/template03.xls index e8b1631e6387228ce499d2f27dbff46f41fc7bc4..7c17eee9e880a7968f755cda84854de5ee85cf93 100644 GIT binary patch delta 1023 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 WLbzkZs3~F)>XeU)Fp}%9^Zx;~RBlQD delta 3972 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 M5tX8H1^