From 791f0ca1662690f2c1ec3acedaa55a24bfb7036a Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Tue, 24 Sep 2019 18:35:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dxls=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E8=8E=B7=E5=8F=96sheetList=E7=9A=84bug=20#621?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- quickstart.md | 79 ++++++++++++++---- .../java/com/alibaba/excel/ExcelWriter.java | 9 ++ .../analysis/v03/XlsListSheetListener.java | 60 +++++++++++++ .../excel/analysis/v03/XlsSaxAnalyser.java | 26 ++++-- .../v03/handlers/BofRecordHandler.java | 28 ++++--- .../metadata/holder/AbstractReadHolder.java | 8 +- .../com/alibaba/excel/write/ExcelBuilder.java | 8 ++ .../alibaba/excel/write/ExcelBuilderImpl.java | 5 ++ .../metadata/holder/WriteWorkbookHolder.java | 1 - .../multiplesheets/MultipleSheetsData.java | 11 +++ .../MultipleSheetsDataTest.java | 56 +++++++++++++ .../MultipleSheetsListener.java | 41 +++++++++ .../easyexcel/test/demo/read/ReadTest.java | 12 +++ .../easyexcel/test/demo/web/WebTest.java | 9 +- .../easyexcel/test/demo/write/WriteTest.java | 36 +++++++- .../easyexcel/test/temp/poi/PoiTest.java | 2 + .../multiplesheets/multiplesheets.xls | Bin 0 -> 20992 bytes .../multiplesheets/multiplesheets.xlsx | Bin 0 -> 12018 bytes update.md | 4 + 20 files changed, 354 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/analysis/v03/XlsListSheetListener.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsData.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsDataTest.java create mode 100644 src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsListener.java create mode 100644 src/test/resources/multiplesheets/multiplesheets.xls create mode 100644 src/test/resources/multiplesheets/multiplesheets.xlsx diff --git a/pom.xml b/pom.xml index 78bf2fae..958a85d7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.0.1 + 2.0.2 jar easyexcel diff --git a/quickstart.md b/quickstart.md index dd4f3d6c..9ca53df9 100644 --- a/quickstart.md +++ b/quickstart.md @@ -1,13 +1,15 @@ # easyexcel核心功能 ## 目录 ### 前言 -#### 关于@Data -读写的对象都用到了[Lombok](https://www.projectlombok.org/),他会自动生成`get`,`set` ,如果不需要自己创建对象并生成`get`,`set` 。 #### 以下功能目前不支持 * 单个文件的并发写入、读取 * 读取图片 * 宏 * csv读取(这个后续可能会考虑) +#### 常见问题 +* 关于@Data,读写的对象都用到了[Lombok](https://www.projectlombok.org/),他会自动生成`get`,`set` ,如果不需要的话,自己创建对象并生成`get`,`set` 。 +* 如果在读的时候`Listener`里面需要使用spring的`@Autowired`,给`Listener`创建成员变量,然后在构造方法里面传进去。而别必须不让spring管理`Listener`,每次读取都要`new`一个。 +* 如果用`String`去接收数字,出现小数点等情况,这个是BUG,但是很难修复,后续版本会修复这个问题。目前请使用`@NumberFormat`直接,里面的参数就是调用了java自带的`NumberFormat.format`方法,不知道怎么入参的可以自己网上查询。 #### 详细参数介绍 有些参数不知道怎么用,或者有些功能不知道用什么参数,参照:[详细参数介绍](/docs/API.md) #### 开源项目不容易,如果觉得本项目对您的工作还是有帮助的话,请在右上角帮忙点个★Star。 @@ -28,7 +30,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja * [最简单的写](#simpleWrite) * [指定写入的列](#indexWrite) * [复杂头写入](#complexHeadWrite) -* [重复多次写入](#repeatedWrite) +* [重复多次写入(包括不同sheet)](#repeatedWrite) * [日期、数字或者自定义格式转换](#converterWrite) * [图片导出](#imageWrite) * [根据模板写入](#templateWrite) @@ -165,12 +167,16 @@ public class IndexOrNameData { ```java /** * 读多个sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件 - *

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

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

3. 直接读即可 + *

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

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

+ * 3. 直接读即可 */ @Test public void repeatedRead() { + // 方法1 如果 sheet1 sheet2 都是同一数据 监听器和头 都写到最外层 String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build(); ReadSheet readSheet1 = EasyExcel.readSheet(0).build(); @@ -179,6 +185,17 @@ public class IndexOrNameData { excelReader.read(readSheet2); // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 excelReader.finish(); + + // 方法2 如果 sheet1 sheet2 数据不一致的话 + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + excelReader = EasyExcel.read(fileName).build(); + // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener + readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); + readSheet2 = EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); + excelReader.read(readSheet1); + excelReader.read(readSheet2); + // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 + excelReader.finish(); } ``` @@ -572,7 +589,7 @@ public class ComplexHeadData { 参照:[对象](#simpleWriteObject) ##### 代码 ```java - /** + /** * 重复多次写入 *

* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} @@ -583,6 +600,7 @@ public class ComplexHeadData { */ @Test public void repeatedWrite() { + // 方法1 如果写到同一个sheet String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); @@ -596,6 +614,36 @@ public class ComplexHeadData { } /// 千万别忘记finish 会帮忙关闭流 excelWriter.finish(); + + // 方法2 如果写到不同的sheet 同一个对象 + fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 指定文件 + excelWriter = EasyExcel.write(fileName, DemoData.class).build(); + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 + for (int i = 0; i < 5; i++) { + // 每次都要创建writeSheet 这里注意必须指定sheetNo + writeSheet = EasyExcel.writerSheet(i, "模板").build(); + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); + + // 方法3 如果写到不同的sheet 不同的对象 + fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 指定文件 + excelWriter = EasyExcel.write(fileName).build(); + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 + for (int i = 0; i < 5; i++) { + // 每次都要创建writeSheet 这里注意必须指定sheetNo。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class 实际上可以一直变 + writeSheet = EasyExcel.writerSheet(i, "模板").head(DemoData.class).build(); + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); } ``` @@ -867,18 +915,19 @@ public class WidthAndHeightData { *

* 思路是这样子的,先创建List头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据 * - *

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

2. 然后写入table即可 + *

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

+ * 2. 然后写入table即可 */ @Test public void dynamicHeadWrite() { String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx"; - // write的时候 不传入 class 在table的时候传入 EasyExcel.write(fileName) // 这里放入动态头 .head(head()).sheet("模板") - // table的时候 传入class 并且设置needHead =false - .table().head(DemoData.class).needHead(Boolean.FALSE).doWrite(data()); + // 当然这里数据也可以用 List> 去传入 + .doWrite(data()); } private List> head() { @@ -1056,10 +1105,12 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { - // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会影像 + // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会有影响 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()); } ``` diff --git a/src/main/java/com/alibaba/excel/ExcelWriter.java b/src/main/java/com/alibaba/excel/ExcelWriter.java index c99787d1..e669b56e 100644 --- a/src/main/java/com/alibaba/excel/ExcelWriter.java +++ b/src/main/java/com/alibaba/excel/ExcelWriter.java @@ -5,6 +5,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.metadata.Table; import com.alibaba.excel.parameter.GenerateParam; @@ -297,4 +298,12 @@ public class ExcelWriter { excelBuilder.finish(); } + /** + * The context of the entire writing process + * + * @return + */ + public WriteContext writeContext() { + return excelBuilder.writeContext(); + } } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/XlsListSheetListener.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsListSheetListener.java new file mode 100644 index 00000000..62d55175 --- /dev/null +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsListSheetListener.java @@ -0,0 +1,60 @@ +package com.alibaba.excel.analysis.v03; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; +import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; +import org.apache.poi.hssf.eventusermodel.HSSFListener; +import org.apache.poi.hssf.eventusermodel.HSSFRequest; +import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +import com.alibaba.excel.analysis.v03.handlers.BofRecordHandler; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.exception.ExcelAnalysisException; +import com.alibaba.excel.read.metadata.ReadSheet; + +/** + * In some cases, you need to know the number of sheets in advance and only read the file once in advance. + * + * @author Jiaju Zhuang + */ +public class XlsListSheetListener implements HSSFListener { + private POIFSFileSystem poifsFileSystem; + private List sheetList; + private XlsRecordHandler bofRecordHandler; + + public XlsListSheetListener(AnalysisContext analysisContext, POIFSFileSystem poifsFileSystem) { + this.poifsFileSystem = poifsFileSystem; + sheetList = new ArrayList(); + bofRecordHandler = new BofRecordHandler(analysisContext, sheetList, false); + bofRecordHandler.init(); + } + + @Override + public void processRecord(Record record) { + bofRecordHandler.processRecord(record); + } + + public List getSheetList() { + MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this); + HSSFListener formatListener = new FormatTrackingHSSFListener(listener); + HSSFEventFactory factory = new HSSFEventFactory(); + HSSFRequest request = new HSSFRequest(); + EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener = + new EventWorkbookBuilder.SheetRecordCollectingListener(formatListener); + request.addListenerForAllRecords(workbookBuildingListener); + + try { + factory.processWorkbookEvents(request, poifsFileSystem); + } catch (IOException e) { + throw new ExcelAnalysisException(e); + } + return sheetList; + } +} 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 ddabd8cc..19aa2563 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -2,6 +2,7 @@ package com.alibaba.excel.analysis.v03; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -19,6 +20,8 @@ import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.alibaba.excel.analysis.ExcelExecutor; import com.alibaba.excel.analysis.v03.handlers.BlankOrErrorRecordHandler; @@ -55,6 +58,8 @@ import com.alibaba.excel.util.CollectionUtils; * @author jipengfei */ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { + private static final Logger LOGGER = LoggerFactory.getLogger(XlsSaxAnalyser.class); + private boolean outputFormulaValues = true; private POIFSFileSystem poifsFileSystem; private int lastRowNumber; @@ -66,12 +71,12 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { private EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener; private FormatTrackingHSSFListener formatListener; private Map records; - private List sheets = new ArrayList(); + private List sheets; private HSSFWorkbook stubWorkbook; private List recordHandlers = new ArrayList(); private AnalysisContext analysisContext; - public XlsSaxAnalyser(AnalysisContext context, POIFSFileSystem poifsFileSystem) throws IOException { + public XlsSaxAnalyser(AnalysisContext context, POIFSFileSystem poifsFileSystem) { this.analysisContext = context; this.records = new TreeMap(); this.poifsFileSystem = poifsFileSystem; @@ -80,6 +85,11 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { @Override public List sheetList() { + if (sheets == null) { + LOGGER.warn("Getting the 'sheetList' before reading will cause the file to be read twice."); + XlsListSheetListener xlsListSheetListener = new XlsListSheetListener(analysisContext, poifsFileSystem); + sheets = xlsListSheetListener.getSheetList(); + } return sheets; } @@ -92,9 +102,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { if (workbookBuildingListener != null && stubWorkbook == null) { stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook(); } - init(); - HSSFEventFactory factory = new HSSFEventFactory(); HSSFRequest request = new HSSFRequest(); if (outputFormulaValues) { @@ -102,7 +110,6 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { } else { request.addListenerForAllRecords(workbookBuildingListener); } - try { factory.processWorkbookEvents(request, poifsFileSystem); } catch (IOException e) { @@ -118,7 +125,6 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { lastRowNumber = 0; lastColumnNumber = 0; records = new TreeMap(); - sheets = new ArrayList(); buildXlsRecordHandlers(); } @@ -199,7 +205,13 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelExecutor { private void buildXlsRecordHandlers() { if (CollectionUtils.isEmpty(recordHandlers)) { recordHandlers.add(new BlankOrErrorRecordHandler()); - recordHandlers.add(new BofRecordHandler(workbookBuildingListener, analysisContext, sheets)); + // The table has been counted and there are no duplicate statistics + if (sheets == null) { + sheets = new ArrayList(); + recordHandlers.add(new BofRecordHandler(analysisContext, sheets, false)); + } else { + recordHandlers.add(new BofRecordHandler(analysisContext, sheets, true)); + } recordHandlers.add(new FormulaRecordHandler(stubWorkbook, formatListener)); recordHandlers.add(new LabelRecordHandler()); recordHandlers.add(new NoteRecordHandler()); 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 324d3e3e..384f7518 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 @@ -3,7 +3,6 @@ package com.alibaba.excel.analysis.v03.handlers; import java.util.ArrayList; import java.util.List; -import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.Record; @@ -23,13 +22,12 @@ public class BofRecordHandler extends AbstractXlsRecordHandler { private int sheetIndex; private List sheets; private AnalysisContext context; - private EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener; + private boolean alreadyInit; - public BofRecordHandler(EventWorkbookBuilder.SheetRecordCollectingListener workbookBuildingListener, - AnalysisContext context, List sheets) { + public BofRecordHandler(AnalysisContext context, List sheets, boolean alreadyInit) { this.context = context; - this.workbookBuildingListener = workbookBuildingListener; this.sheets = sheets; + this.alreadyInit = alreadyInit; } @Override @@ -47,14 +45,18 @@ public class BofRecordHandler extends AbstractXlsRecordHandler { if (orderedBsrs == null) { orderedBsrs = BoundSheetRecord.orderByBofPosition(boundSheetRecords); } - ReadSheet readSheet = new ReadSheet(sheetIndex, orderedBsrs[sheetIndex].getSheetname()); - sheets.add(readSheet); - if (sheetIndex == context.readSheetHolder().getSheetNo()) { - context.readWorkbookHolder().setIgnoreRecord03(Boolean.FALSE); - } else { - context.readWorkbookHolder().setIgnoreRecord03(Boolean.TRUE); + if (!alreadyInit) { + ReadSheet readSheet = new ReadSheet(sheetIndex, orderedBsrs[sheetIndex].getSheetname()); + sheets.add(readSheet); } sheetIndex++; + if (context.readSheetHolder() != null) { + if (sheetIndex == context.readSheetHolder().getSheetNo()) { + context.readWorkbookHolder().setIgnoreRecord03(Boolean.FALSE); + } else { + context.readWorkbookHolder().setIgnoreRecord03(Boolean.TRUE); + } + } } } } @@ -64,7 +66,9 @@ public class BofRecordHandler extends AbstractXlsRecordHandler { sheetIndex = 0; orderedBsrs = null; boundSheetRecords.clear(); - sheets.clear(); + if (!alreadyInit) { + sheets.clear(); + } } @Override 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 4e7041e6..f9dd0d66 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 @@ -120,9 +120,9 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH ReadRowHolder readRowHolder = analysisContext.readRowHolder(); readRowHolder.setCurrentRowAnalysisResult(cellDataMap); int rowIndex = readRowHolder.getRowIndex(); - int headRowNumber = analysisContext.readSheetHolder().getHeadRowNumber(); + int currentheadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber(); - if (rowIndex >= headRowNumber) { + if (rowIndex >= currentheadRowNumber) { // Now is data for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) { try { @@ -135,6 +135,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH throw new ExcelAnalysisException("Listen error!", exception); } } + break; } if (!readListener.hasNext(analysisContext)) { throw new ExcelAnalysisStopException(); @@ -142,7 +143,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH } } else { // Last head column - if (headRowNumber == rowIndex + 1) { + if (currentheadRowNumber == rowIndex + 1) { buildHead(analysisContext, cellDataMap); } @@ -158,6 +159,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH throw new ExcelAnalysisException("Listen error!", exception); } } + break; } if (!readListener.hasNext(analysisContext)) { throw new ExcelAnalysisStopException(); diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java index 119854a2..a801cc4c 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java @@ -2,6 +2,7 @@ package com.alibaba.excel.write; import java.util.List; +import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.WriteTable; @@ -51,6 +52,13 @@ public interface ExcelBuilder { @Deprecated void merge(int firstRow, int lastRow, int firstCol, int lastCol); + /** + * Gets the written data + * + * @return + */ + WriteContext writeContext(); + /** * Close io */ diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index 6f0ae1c4..3ac3c5b4 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -113,6 +113,11 @@ public class ExcelBuilderImpl implements ExcelBuilder { context.writeSheetHolder().getSheet().addMergedRegion(cra); } + @Override + public WriteContext writeContext() { + return context; + } + private void addOneRowOfDataToExcel(Object oneRowData, int n, int relativeRowIndex, List fieldList) { beforeRowCreate(n, relativeRowIndex); Row row = WorkBookUtil.createRow(context.writeSheetHolder().getSheet(), n); 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 d3c07f5f..fe23b2a1 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 @@ -24,7 +24,6 @@ import com.alibaba.excel.write.metadata.WriteWorkbook; * @author Jiaju Zhuang */ public class WriteWorkbookHolder extends AbstractWriteHolder { - private static final Logger LOGGER = LoggerFactory.getLogger(WriteWorkbookHolder.class); /*** * poi Workbook */ diff --git a/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsData.java b/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsData.java new file mode 100644 index 00000000..73da1c31 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsData.java @@ -0,0 +1,11 @@ +package com.alibaba.easyexcel.test.core.multiplesheets; + +import lombok.Data; + +/** + * @author Jiaju Zhuang + */ +@Data +public class MultipleSheetsData { + private String title; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsDataTest.java b/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsDataTest.java new file mode 100644 index 00000000..a309027a --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsDataTest.java @@ -0,0 +1,56 @@ +package com.alibaba.easyexcel.test.core.multiplesheets; + +import java.io.File; +import java.util.List; + +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.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelReader; +import com.alibaba.excel.read.metadata.ReadSheet; + +/** + * + * @author Jiaju Zhuang + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class MultipleSheetsDataTest { + + private static File file07; + private static File file03; + + @BeforeClass + public static void init() { + file07 = TestFileUtil.readFile("multiplesheets" + File.separator + "multiplesheets.xlsx"); + file03 = TestFileUtil.readFile("multiplesheets" + File.separator + "multiplesheets.xls"); + } + + @Test + public void t01Read07() { + read(file07); + } + + @Test + public void t020Read03() { + read(file03); + } + + private void read(File file) { + MultipleSheetsListener multipleSheetsListener = new MultipleSheetsListener(); + ExcelReader excelReader = EasyExcel.read(file, MultipleSheetsData.class, multipleSheetsListener).build(); + List sheets = excelReader.excelExecutor().sheetList(); + int count = 1; + for (ReadSheet readSheet : sheets) { + excelReader.read(readSheet); + Assert.assertEquals((long)multipleSheetsListener.getList().size(), (long)count); + count++; + } + excelReader.finish(); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsListener.java b/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsListener.java new file mode 100644 index 00000000..950f562b --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/core/multiplesheets/MultipleSheetsListener.java @@ -0,0 +1,41 @@ +package com.alibaba.easyexcel.test.core.multiplesheets; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.easyexcel.test.core.parameter.ParameterData; +import com.alibaba.easyexcel.test.core.parameter.ParameterDataListener; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.fastjson.JSON; + +/** + * @author Jiaju Zhuang + */ +public class MultipleSheetsListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(MultipleSheetsListener.class); + List list = new ArrayList(); + + @Override + public void invoke(MultipleSheetsData data, AnalysisContext context) { + list.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + Assert.assertEquals(list.get(0).getTitle(), "表1数据"); + LOGGER.debug("All row:{}", JSON.toJSONString(list)); + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} 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 b65f99ea..a8e6a61f 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 @@ -81,6 +81,7 @@ public class ReadTest { */ @Test public void repeatedRead() { + // 方法1 如果 sheet1 sheet2 都是同一数据 监听器和头 都写到最外层 String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build(); ReadSheet readSheet1 = EasyExcel.readSheet(0).build(); @@ -89,6 +90,17 @@ public class ReadTest { excelReader.read(readSheet2); // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 excelReader.finish(); + + // 方法2 如果 sheet1 sheet2 数据不一致的话 + fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + excelReader = EasyExcel.read(fileName).build(); + // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener + readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); + readSheet2 = EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); + excelReader.read(readSheet1); + excelReader.read(readSheet2); + // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 + excelReader.finish(); } /** 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 9d5e9f08..8fff02ca 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 @@ -1,10 +1,13 @@ 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.List; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; @@ -33,10 +36,12 @@ public class WebTest { */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { - // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会影像 + // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会有影响 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()); } 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 66367692..76c3c716 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 @@ -105,6 +105,7 @@ public class WriteTest { */ @Test public void repeatedWrite() { + // 方法1 如果写到同一个sheet String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读 ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build(); @@ -118,6 +119,36 @@ public class WriteTest { } /// 千万别忘记finish 会帮忙关闭流 excelWriter.finish(); + + // 方法2 如果写到不同的sheet 同一个对象 + fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 指定文件 + excelWriter = EasyExcel.write(fileName, DemoData.class).build(); + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 + for (int i = 0; i < 5; i++) { + // 每次都要创建writeSheet 这里注意必须指定sheetNo + writeSheet = EasyExcel.writerSheet(i, "模板").build(); + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); + + // 方法3 如果写到不同的sheet 不同的对象 + fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; + // 这里 指定文件 + excelWriter = EasyExcel.write(fileName).build(); + // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 + for (int i = 0; i < 5; i++) { + // 每次都要创建writeSheet 这里注意必须指定sheetNo。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class 实际上可以一直变 + writeSheet = EasyExcel.writerSheet(i, "模板").head(DemoData.class).build(); + // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 + List data = data(); + excelWriter.write(data, writeSheet); + } + /// 千万别忘记finish 会帮忙关闭流 + excelWriter.finish(); } /** @@ -297,12 +328,11 @@ public class WriteTest { @Test public void dynamicHeadWrite() { String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx"; - // write的时候 不传入 class 在table的时候传入 EasyExcel.write(fileName) // 这里放入动态头 .head(head()).sheet("模板") - // table的时候 传入class 并且设置needHead =false - .table().head(DemoData.class).needHead(Boolean.FALSE).doWrite(data()); + // 当然这里数据也可以用 List> 去传入 + .doWrite(data()); } /** 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 ea6dba15..9113960e 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 @@ -3,6 +3,7 @@ package com.alibaba.easyexcel.test.temp.poi; import java.io.File; import java.io.IOException; +import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.xssf.streaming.SXSSFRow; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; @@ -15,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.easyexcel.test.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; /** * 测试poi diff --git a/src/test/resources/multiplesheets/multiplesheets.xls b/src/test/resources/multiplesheets/multiplesheets.xls new file mode 100644 index 0000000000000000000000000000000000000000..a5601288c98831e2b8d1293f5b6f5770eecf214b GIT binary patch literal 20992 zcmeG^2UrwI)-^*KPy{5Xh$N8=lEp=W0YwD`6_hmrl0{HaR##atAZAevh?sLi7qgfH zCRD`5gaO5XhY8me!vgnSH$BtL^vr?({&%ke0a|gIKW8MFaRQb1LbpT0#-vqFQH;ISycrqEZ-}s~h9>WOEhiZlbJQ8w> zI+C9Bi9UL5yqz*-dq?a7mevn z27%<0K=K|W6po>dA;7}Xg7$fEkNu~y?PGOgT1}w6fn*{X1HVv8_r1Xik(5plGJ(Xv zbv#y(lFaMnM|C!~eeBak_4Jel(2ErnNWSV-rPHZAq6nS;G2xTF@~7cQ$P>uJ$XMW$ z*M~#^i|8PR|Np5Q#X~3$c{l|McIP!GJb=kTlv02Q)rR5@tPkay)Nv1mdyG1|Q!xp= zZ0w(Qu7H7Q2;YmsTR{&QrdNv0#_D;>&sh#A8HbwF?m*J439$AklPbG;znr5 zFk-`k>dQNW)3)Y9A4#|kIIrBv($V{seFinh>}wASb{efh57#`@B$%5Iq<5<@JB z1L2J%hV=|u!j(u~gu9W3r4420!Y~Hy7*nasWx5h|;X&wte6 zQz;={hF*p+au_0<2lo{G1D0Tt4aY#sg)a?`6uI#Km*G6>>Azmjd|ICbA_b*~PYv~t za|#N+zaB2HOW6fSR0_USLt{m=70rlzM+#1$r*hFD;H+G5N&1!)9emDd*se`IeJf5n zINxcAuS4PE+=haK|FHcEM_(>}aGu1CUk}gG(9mh71M<>(kZiXd)h^Dhxai>AiwlnP zFfKUG-MHXN4E#3&y~PWrXtW)1en;Vp!6o^m^&{ky#)s)2O-C9&_E3sG*!`rzNdqFU zOv#J$Mg~s%0}aQyBn^*@r13jZ_>y*)rq4DMKF(|FO=fDl&R#NzBOTi`e;35-WQZ552__Vx|_;f##Y`0Yt`H|KxZFfof_EPwy z=}6*ROW}w1|HjMJ(Qv9ji%i7^gvpBp40kx5EU+QT4d95thjk1sYR-mZ1Cv_76CFQU z&_=8Rx+@PBJ*dL2Vuk4YI441B;gRTJOp?*UVlg7vag`vLz+@7xQc_Zc(^wS_m=Xs} zzyU+1kX~KkN+CUD3Spj}NkW(uu`%ii#TM5IhHhb@xK6M?F*+&qZKO`biC{3GiBK|J zxF+P6ks*gD3Sm-Yz{nw$OG7+^@k}6IuSxyQ2?zfGM^V3{z|v!xS6FFvW&3 zOl{u0Swd6n6-OzKWC=_$-WVQoVeauPg%fKiqFtIA zQBrvl;&A9VP%Plu01A5*CbL~vo9ArczUyPhl}_KsXc7|!rqFOK-L7cN(bp6(=kB9jk&GL_UV5$KX7ERDgoS{!vDD=eDWJ za8`z`eb8G)+~viRdQ$brv|cO35}{o zj9}4#S3c+RVzUsMzF|X__|A0-L{!c&Wh|1zo@K8!f49~ z_5oxX0$Ub0grQ7Bh`8w>;s$%8I5#bY+~DAh0vHif+1#K##ksMT$qf$KNLrbg(VhAW zN2k`NI`x(WD!3x}KzAg8PvG-++vViFKu!&rzaGlh&ObmGYy)d|ERCgT%ou<#B- z)h^vUN}5ouK`2ovRt=_)yGsg=L0DXH zdkMjDCPt549ysGvj1f`{dT6xHRd5`_8p zm6ONKFhbJ%J@LXblW^61LUhEKh>p^6a66R75n*ZpOh8c^J$boKGThCDjH}sfOzPS% zG(I#cV!Z97F>$Jeekx}T)x91kum36IZl_WC=H6j>S90?|lI}UWT`#OFUR!$mS^11k z*B)t=tm>;8Y@MT|Iyd5~?d(JErdn@pyX~Hj-g)b|#}~Apzns+i(*6aSk9uEB3`lNY zl99Iizyrfu2dqkr-daBxal|CneO|A@6*sFbo_JSUnX2$zYj`N2i0x0cdg=>6`Yc$o zN99SdZI2AU4t|u-)>$v5*OQdJZ<0qBn+_jnvpU-;IJ7dz`EHT^>UoJT&i~|bw_pA- zztVCOlXG5Ex^_GEE;Y6HD*2Y>YuAs8ZXQ#|A6fd-ip0eAuY;zgdIu&(rTke_Q}bo? zizl1iG`fH7;d8ls%V77BRqAt+JvNOi`Vjvi|HZ8?CfW{+n!Y-+%+P5SRvR>ZV87aYC!?6|=8PX`@b z>Gd_s**&DwQ>Enby(Z5N-5#>xWnkfjq4y3So_YJGx3A5Rp+jyvubnXbXo%|wmoDCR z7gq-5A1FxMopXX`=e($L!Vj`=RJ3p?C3Ng*g8NiZ_$CyfBqP8;NX#0WvUmSR3?7Kc2~%F{62a@ni* zPnHvVl%KRLo!{-jLA{5|UT-<1ylP1B`#1xOTl2=&ooRVI&8*6H+ODie8y*H${G@&F z>-E7E-bbc>oH#jX?)pBJv(A0Ix8?VRIxY{}UmnumugwulzqdA_a=J;O{(Bsw`Y$m! zw?tikqN$vmQY%5+EyL3#y#}QnS2rs#45zA`hB!Hy!u>k@1z3?uN>YUU$7!iux7IIwswBK zbXsL*p08N<()k}(CaQgQjrqB~tL52IOH*%ejyJV$R(9{w;4`yoe!UczH}(0|o?Eh3 z$p;>;*_*U>;GOMqb24I^T`8J1Yh|DAspqpZm!%wO5$SHF&}YYuOPhuS&j|T+VH0sX zn%iNcMpfXAyJ;F@+`eWnAK($t?wl?ArQMEo*E9MYuCmkadh*mCIR_7} z96v8Hd&2p?_A~aCXLT^@cV&0K8`haN_q8?+Q~W(hU2THZvIpir{T%3fdF^7$^LDjs zw{NJ{JUo3!n^?6^V|!cM6|XG*aM<4{-E!&7-`=W3>-oM;jh<#TB5T&V={sY8x4C$t z_F(6zn5_M?GB@4Z$ZM(fvAwoRx1{yfD=XY)4RL;uZ{2%&O9zcW^}(h=uXuOOcI_Em zw&P)`yH>Ytt74Zd=gn}_O?fjVyXy?EPMyzBO|%R?QGHMEQ@)$Qs={?kho<{=&%Eo| z%059YGJnVLgym6F5&|^b?MLs64Gtpv&IA?t+^kjgc6}B!+UizL!IeLJUEe)9R$!Oa zsfY3~6QdfnkDiuOd-}GnR6TILdfh?ePc_{%XY8G}%_FL<+NNfk+Ua?fhbxV-iy3fh ze@x}3-OI<^in{;u`P4cjep|=#fyXnpMeO)Zzv#f>&$sAt zocm#>rPw&mr4rQ}8Ef|dURzKJ?o6`BeHvf4qd?yFPDwWK0W5}tA$lVH@WctE{POr727WXyxGc_4tZfl=4zJ*zz%$%aRgFdI`x^7$)^wfCcmaZ4` zi+rjlCa`CdHpXRz27^!?!|)c29_(5D`CNgU;^7SNff`Q}?d+ z`E$lIn?5;tUV;uUr(aljVZ+o@7gw6-T)LCDHSlY-Z}FgR+YEK$tnZc|c`<2NZgu&( zq}G|g4|*9mB04QRxqREs4_}{KAsT5_Wd3UPvtFGtsLUHu6yEetxg3$hnZZPbh&8a=Tw_GrJzMqI(uaI&|4Bz zePN#F&SLd5W!8JMvfDX7GQZpH;^xE?nLoEGUKSmlxZE!A7e(vjJr3Cwx`PtbKP&XS zVQ0PQ+@4CyI`_)?yAp;*{aTgecG>WK=l4&)m_2vQnD1DtHd)zCIqP#mCG;sCUkf$^ znWtHCRY3u)cNoI43RcfK6h{-__DAfNbi!AV3Se`D@yLdaig#12LpS;lVI9f~8{Zg` zpj}&59elE<6EbK5ncW>^)x@qwC#-oD<{Rvq++vD%J*-cQS;JYMvm??>i3}$G94{iO z06m<|6m?BK)r->*pTdY};2M<#4c3IlzpnyiWMz1T#}j&&E=E~0AHK<%FE#~RC>TN( zvS1m2rvbk~uqzx!;@}H{f$+7^Wa`R~`ko+$`mV@<_`^3uN$?fX1Qv!bz@kVr!0cg; z9Kyp5@icJb5NgJ)$QuY{5%9xugj?f=Az{NEDEKGb18e|yq~PapN3H?fiGpX~{#XOJ zGX)=pyHyR~E+iXvJF;04gE^>#JGk>Ld|!8URn(Ea#dXOg!+8=8bpn{)_@~^%OfCvZm%l#3-k9JNx>~CxD|Dr zPC-%uhe;i9M~$@LO@ort)SOit?q^WgGpVB~U`JBvxu+TH+FVGb!qb9vy`C6@hlT@p z_(Pruair2sPv*=cIk0GwBVvQXVjo1|A`oZ*A|Ky0#?3)YXkP&z!vS8mpv}!wqhNT$ zhSgBtq|#rZ5`uS#p++5V1LYoYrv)n~9{iT%AVuB_AmNZPwF6AKmXvrr*ztoa!AfL8 zIZDuqM6m<_bKn~;azL&rwp;_f;=3)IZc8+j$d>qinS?FTe8dZK16$Vn3YZ_S{!&}U zLL~cc%ck2>9tDz@_A)knDE{iQMX8u<5j zTQ=R6_)H?QB~Be08DkaU0T~~t6yYZ)*C<;8{`d4hr~!On!sjk@2pqFE6bI6cVQCApqQAfzP z7i4iAP>Y2CXU6ybP{x>SI*k_3<*|AQ!k`Uj|{{U4Dl zP+~_tAo(v7+SUY__={3g+y9H26_WWox8E05h5N%AG4_aX@Pbj$Bf7yk20SGkR#@>b qpD>C09`+|3`|-V0XE@rxO0hk>+=@4tMphQX-HPgpmRT0xVd_uC8Hvn5)>BK>f0%=;btHt zyOd;x$galM2GnL>dfVI;)?t|}xt_TOkAd@x%RAK8S!K$i4tEnL;l3_3%B=i>cwNd9 z7k5@bc6aVrBTZybfKA7w*c-QYb~6Tm@`_Z*7=N~#1+v$5-OLR!G!Z`p^r9}8^oZ~zPUpT7 zSk~oV%52Th<3{9Nsx@-0<@pGFX^GQW(KZVu8U=QSoQUx1a7K)Z@Q#dk7oq-{0@wGi zCE#{nPZyHw%qODWU5(d}5S{0$w_MBYcLjI0(N$%ZCwfEvnR4z$Z3H<`%B4XmNBeuq zjcgrE9&#QTQz_TQf+^-oaYuHX0RU#zD8x#?eW?H+N{*Up(pHC7X*3s9LvLtc7>pH) z+%ac2XV+GS@jO>cHcjhf)U5OqP>s2o!Wi6d`Jjeljlgw$rRDPycujocOV@ zpr2`$0`uZhSW$#|_KG?d`uhPvo@ojvQ4>F^-hMbES?c!CGX^p%039ZEFYy%aD`DmV zT)Uz3g(QEn2wcBJVYL_H%Jg{F?~kQ7;@>JDaoeGaueBo{a#;%6-);rc&5^pBnof); z&n!Bze?lO5Q(O(hFf>8mRUEWWvhuTIT#BqYoeLxd{@kjEV9i42g zA0Zzt`>Gq1{&T;3GUH6DNM-8$ef@>xNJQwqxkQe1cn%T@U~S0#;`?#?gJXM=U`N}^ zTlwcnfj=j<(F@>x1f=>W(>XM@OC6&K@G2)REwN;Ri!#F-aZ8l^5#=rSkV^&V<|5@&Kj=$+RH{UAr_0& zZ+A|)4X;Ck-^3dEU^3-wMMs@2!m266D!ea{`pRMuV2yB-h;^ZsO?UX+MWgHr<{@4G z%p#DWbh%kQ^2a|LJo{zfYU^O>Xl`QS^ptC2zQB6GgFuf01qO!uAGVJ!tdICt8n9Vl zL2bd=7C>#52%mgWYe36l1%*5YA+)$5qx3wSMXYMQU;65lNIGVBntNSPAF$r+Zkw>N zZ}f2v%~O6FllL{-NV}vKZ8h`7xvi@j2{$5*3}p?c{2?{mO`Yep>714i95NW2FiZ9d za*kFWFdDgroMPHHJ8}L>l~m`JA9=*TFepp)Lp8&7be%9BKRLsVC5x2~Jv;$@6E@O2 zQsNrHRTBJ-&VV!Z7KHNS9O_bxO!lzUqm6*Xwx*RCx*h8ZPO6Ez9m##g4c_$o-0FB7 zL1xU4Sa)NM^bjr-HXmv_KM4I0g*#-HTRxXM5GMc39$pQx!9~0B3DJBZQm|uaW6T#e zGhs+t0n2DNIr!7cN6*R>`(qRVZ<_6{${ciQJ!rEU9w8(L1My;e-$X<) z(WlU+vz$8)esV{Smy=1=(2Bhow6}SE`6H;({jkJO(+Ph@^>oy(1J*eMwF|!uC>aoib1G+$foG^7(kI z6U`GSl>$F$U}iKT0H+F?+)z^j#sP_qu`cLjr%k zy3%+y$Y3D&+=77kU*N;`2%k9Fs7V&opaaPp*rD^tIBa!IN)44#x@bYoO3$=K;|Xj!9dEw40As|Bis0f5`>mbp5x0q^KJjXHK>3(4Z>Zr&JEl6FwZhgDd&#S7JJQ5~GC5c*_*KpZ%1)FM3>|)7t>m-s`a4 zNya6r-1;fHj1slJREbVE_q{^wRwO=q z8kvKAGi-EFlm3zlys}Wdbz#|K`_yx@wt0(+Cefg|m}zxbBhvM2#^xbkqNX8uovo#&&@PDd+(5j09@Yat_$UUxY^)Hs-mq9sVm)e8w>Kt&8iSD8%uHKw{yNf>+DfJ{?JCU@_< zvHy%sMf^Kz5}6VWFCS0?_K2FkX z-%z7R{qgkQs1e`+p~j=z=m}~%VE#l+A_z6eQR^MQP$TpUHL?FfO(6(1yP#SAg&O#O zL=E}_YB(QJv%ysah$->oS7ygKBW6OR<-u9HQv$psxL+KE^i24X6=8-?R~(wlGxF8k z003Jfk}$l-I_6nWSilLch$k_VrV?U+V9vpJjwWoG9FP4q_exAl%3An?Iav^2e}foK znsW9*KimQ`CPGhh!=>$?s2NFkak>mD5&Hi-*Kqt3YUZYYqoxOh8ks&}it)Jj4Y%jH z`*WWYh~BV}$8N@*W9{7B4dA=LkZ^qRGm{#`0|dsq1pE2i*EL=CYlKXwOl0}{35cwm zZ!B=R2jAi1zwHlM5$;IEU5G~&;?y^htbmq?$8U*`5%xjNJ|+E_J%C>+IjLok?*gKl z8%tdE8O;vtsR6(6=iUA>F(<*|^#(b7D&?yxx-RR^6OLr|JgTO;XLr(>Q-enBFR2hp zar1SWw8J*^_)9-Q+-_Bsob)Mqh^!y2P|aWM?|n_+!9HeQAK{931{VC5D6265#z)YX{i z{DkWO?8qTzq{|`nh<>5uj)wa|^)R1HgbV3&+R{BP4lR>B_0tE;VAAj>);9v;7{pLc zha+2pcf|)sB3H<&eK_8G)EE|9mYGW#`@q`|yL z3ECRwn0oKLjie&YrftQ|6DondVl7?z-=N_91&X1E`bF#<`U(Z=e7wtx;sphC8FDVP zCMnI8`s9~>av2BZ*t*!Z1Nma*`Vn$Oq4kK(?WJ!O4L%$x=^gHYb?ff2_Asi3rF+@y z%WeBMKL9_4W;M-gK)7C7iCK%KlX})SU&enVL{Q6B z(rZibi%87)4*Ag12XUGqiO#{60AJZ$3M+*Sq*)eWLUT09Hd~b~>UpDEa@~E=*Sgev zIOz38rkfkKvE3|tif>w;6S!4I+L=sM7+>_^y%Ocezmh)9?+81uHZ8*a+&oVf2=FkmmpzY1xLa&RceQPxu| zL=yMPw%edK^a=K{GiZGx7euJut`tI=4EhAvbP-{B;oRy!^@J!|%W<(+K$sZ(??mz9 zABjRDq1^?I0`7%AWSHUZpG3hen@IeIg&}5h?L5Em=4>3_1>)~SaqV}1)!1^``4>?L zt<-N^yMl-U^*2%E|3wtZ4@6Pt%`Chtq$(1_X_!!u85lo*6jO0xYoM=V2~D6Zfynd@ z6-8Wpf&_mtC=^uEhN`(gt}cPypOd(MRz;~@m8cR!PO~bQ_t5OmZwNE!UetU?IFYU{ zISDrUMHDsTN5Ds-82&d=yzUY6uWEeTz#kOi@;rClgg>o49mhl$_}$RwWLQM!P^C`$ zGoUA!nV7@ro8jlH=gVZoSKal+E5K0*$YNXmxRaM1_z|=hrgBO@%^onZ`iO~r77Eu4 z75SmuP)m+BXRK5|<_pZGz9014UKl`W0g6mOml4N&hbzE0n<8*@&?D32N2L)5B%Tz| zL?C;P?RifzN*RrGp5V1oa(*KqhDi(sE1lk~TvhR;k?p$N;e`9HE#BjT>{hp_9qEhm zdn!lt9}+lgRE9i~Uk&mg@e`1B&`BeK2UX$`lR;O2%{q|^+ucj-1RwDKzHNFqd;x8n zoXkzEO`aUST-*_&M#6xBB@z7EjQ(ZzX#BVr+SgdNCEz6Vky!Pq_~B(aL8YU_n4ZF+ zVX&*pIX6a}9`3l+h>*Fnu7+A^2G2 z?1V#p)hA2?d=HvlwCEOoe1*)cErGj}UDK4>2gx&)HEK?$erYXb6~@6q8|8*HWM1%yiQEqz}-2 z!7IknpRC$c+Z$M#)(XzT8MW*5a$k<~gBjTDxsVe5s~FSLCRJ+_4n)#x!0RTRA~g%9 zRzV*VxlQqpQ^j108Vm}MRZ+FvH<~83b&Y!~$o;Fkju)Di)r-2$hcL!RA001#1om2d z?jexxH|Na|u?%);qCF*KTeWJ!Y=t?jl}5}?5uCxh7g3rQ{@o1 zNQp)}BTHHU;KPM+LN?3U8T3B8bg+^9DLjG0p&)zE1tnrQyxrn`w!drkdTiG$3gv*0 zulc;|+wFDmd24-5THpfUSs0?8*X;m_-dC|Ufy?8muF=7xs*wBBCfHG*yBb0`JdU{S zo~yv$q|8u6UkUVcPd zCai%k;(W-h-hQv4j$JCt!c=Hywv`B7Vpu20C=e1IBk+Wf1Vb@rkx`nc>4&_Y-GBtb zVoqTout@64X$5M+YWziHJM$!4ddJi|YZsdwY%>#0+ZZn#cz9op&_l0ILU!F=jKEyB zSTDlLoQ`rstOWSERD`yWT}kMSXK-tU>=cR#rHxF2%YKvP`h{xwHp>Q+{bC4WeI^{` zh9lwqjQlpi0O`RBKKx*@Jy-`MvaoR`HIqZ4AEqJRumUS?*-f^Js}0u|!gl^J?ZHc#alQtI((l1_h;D>6}H^+UOP{gcs`wm*V#e>fuw>HIA)FXs5&N?mN+2 z0;!Lfd`ps+bCy)LU9sk`N7c5)8a>aP-BrV~rFVn6&*lq$zQca8^n6hLwJcsEo$LXl z0VKYQfqXf;+3Q%6?pRb}XDUcYSR6R3Y0{1<3FW{RRO+#MH9lF2XxbgYoPbvV=9F)W z^Y|z&ZB-vWf8r0G9VDxq|0crIoD*EVCN9@2iIv1|-D@s}0idmpN2FC4+m}sJsg}=4bJOq*N%C zhT964Or_Z($k1pD#G$dEpk!&0w>>Fw@ji{uO!?@crHjW0^3*l_^)LGJz<55DQRbo@ zsf^DBqFM7?rz9xyD~O(Segns^2%YU(@g^$H8jY52IAXyPYHL4DtE)?P2zN|z>UqWL zU%i$@NhUW`DmTs$$M_X9PaEc^Dk_Q-ef%Pg>l+oB!OR%C9+tbzqMUP#20wK?3zQ2x zDF&8__TQTO((5J#W%+%y_{rGcdRND722836ip!hN1xLp` zdya)5?2^rpyv-A&&bipcAeDg5hNgn5My0w3K1W?rZn;vtw7`ccT$pMRn*i?5gYWah zBSpwxkc+#=(~(Z2^?aW2qKStjbvWz2RX_u@9kLi;L)DUgI<0r;R0eRk+T6>M#3c%^O+j{xawb6MDK3M%i(ET;o>fPF zaVw1BU*TKGZ$^NUhnPo1D7a<}Ys~^woUQ@x=9MN`Pn&(++4PLU)q3TE?vpt&>+ZN& z<>+w45SgwyTUkq_Wo~h}H;x&Yt>6;P`i@l~3Cm}A)qi1SoAF{!zw>=f4YK*#>p%BM z?DHoFhoIH%r7Rd2@~=hC(aGJ)#POltnbNklU6{tY`WBJr z{5bYp_fpYNGc<>^J%J{}F~f1hanvinu8bIkcP9Zu+-9ZT#91kZvhM(fZ$`j;NTAig zy2h9`%(~6Y!}p+O1A{uY+{WjeM@zwTm3sz-h&n+qkfe5_3&8(L@uS$Hh?%}bYDdgk z7uPK*;Vi2s5{kvR!uD(n96WM!N&!vee7lR6yT=g8vl1afsYU(mKI^%DCaD1~f$tq+ zvI*$c=~=MyHqy)Yb1WfxmscNy2U3S7@=3Hm<$Zmxi{^Q{*&D%(aT&Qzl5qb`R;nz& zK^;|`6*YCxim-9nQNBz!9a%gbrYmTrs{6__S@dIc+?#hxn9c<$x=FrG;#Ey8Qz)J( z#anb+9)|3S&*aI01@C9DWRI^sRh$u{`2tZ60?g{H?oV&z|e+krxtd z<8Kl-I<8y^g)mK{6I>>A`rp=BX-wB{mgSI!Hf|p3EDz(_e`Mg<2prv`8OwTwpvPK| zPyi%hm1r02L|hauH{lVF5Q3ZPiH-g#Ct+i^kEoaY%FP*Ve+a^lA^lutA_dXpIgUhu zT>ca!LP)KALPIrhu4c?Kre^qO^!pNMb`!ut3$|T;t$f|Eefe-~)kRs5V_Bej<*+J0 zwQFHIa~@jdiZ<@85WRWFIE z^Q!bGniGZ$CW4Y)G^1ZfVqGsYe7Iz@DGQjG`M$04shoZXnEGCkKD5?ke?KXmvhU^Y z4~0t2Dn>=Rq=qtza+&%H^lKR$kgJTE0#muv-BGhuXl`+d1f2=5uHyYW9QyBf76x95Ewu_R$toY6 z9FY!Y8JT2vq96LK?VgJeIGj9wrXRYPR{(B!RkS7^fX@}sZzzQz!&c2~K|V!^@LZ>x z0bg7^M;#Z>*U!M1LU&q%_1rYhq-u2b?cu11MMymOZ?eHn6r|bFag-P>I3wPYHaJq4_9VX7qFK`E z`&pLz<|@uxwF6nXGwOQS29Sm=xKM2FO573{D1H2bc4i=WpK-wv<;Hid%d`}u*9OP> z)a6@l+}UoKQZm1(GOpYFXro$Kk!w*$$!!2Q!Ckar&ols-ng3|~?|E{1Atvc63 zv>9Njn3ZG$clK_YyaCC$;HoQ|B?zTG4$ljH`II?3*pQTMA7KgGWKXUL5;Z>5C_WVn zL-k9iWTPfVHyy*55(49(W=nJvWP4Fjq^ygQu=Z2bp;9<^FLh^BHsB1{R0vjF=>Stwcmz%2ccs+aQSOcK(FbekN?EWUbpbf!mO?<`oe zvQS6BRDOW@Tf=Fu7emELG0)6(oZ|V`NbhkgQ_T^InloYf!p+Gpz#)yBV-zXpBzHW= zXI93bjN2U?=}UIenX`w$kf;gLxHMLCflh*_XfE|69M7vuAx&1zRjHF?BUPwZ`Yg%z zKA>xT3bx2GTv_EiMzWLTuDq`kn6|83RZPirFVrEUPQ0m*s*6)c-4AEU`qtxbVv&np zug_MKt=&#$PlgUUU-jK4&c5q|Yo~HVeJcPHP4<4QI@j9$WPM$KWtvuoP=TcL9Tk>^BwB3xd9%|p zZtLxJ_st_Byb!V=_CkNHCXaPSOnJHpohY+2ZJO=shCwWRbLYfG7FP}1hAwyzKE7+z zTTTl&+!OZFvA7!*_Pot?^gw{%o_`3Acu9%Y;F+H1AyN*{U7rW7wr7Gb!Z-h`NjHg1 zpBLQM-)}nR!cmr!wJQddBygvX+U{MvuWU?XKRj#4mZ#aR-d&vB z#GO+%#KRpaOA~jbp1B@hl;;XGBO;sJj4!hbW$>@V;uL5~8Zu&hqlM~{^v#Tu$P_m3 zttXL9UIQxQ9ESj4q2B`fCSg?3pkYGgvBjGq1mVHxJs`5bS{c_J-91-24^l2-(P;) zULuijJ%|vvV~g9EgM16U>F|*QE{^Uqdohz0w;$WppuU!KmmDD6V_uE4n)GLbU9)_ zlY##N_jO=4?43HRP9_zCl!qyHNEB7Jis-;ntFz)ILORW zCuv>VwGcm})H%)gW%b)^FRLy`^owZQ7j>}Sr)jNxC#wW=iPp)jKF9(nVD8keE#H<9 z3`=qtC(p}swqC~CEO5Wmi(EN^yI@8Zsb{=5wEY15@k3)?{M8)L8uydZGRrPc6M=JX zb#;?va#(?rH~cBmZQ*RZ(Nx1>mmy?50gS>}6j#$>gXGvjlg`%a>+obw_FH>ac?Zhl zm9l8St9HE)GxKae$$X>wwIOE*^!7v<2fa_MuJ8XW4L=Dyx19u)g#DnCu?MXQ=wQRq z+`z%aSjou&Xk+$RAv%w<%5@2%LXQ!L@{|-%?*c*tb509Fy5UzqkP1;d9>E{Q4HS`g z0HZ*l6YYo(0l>V1MAuCG*_PqWYU+yA@~JPzD-S;*90F4jL+f=gzD-#<6O&LSz(R)v z-qKAlD4kD$eK;qUvMd_|uTIl((!j1c1X9t$W(_wj&ym-hSlo%xH3a>Q4z^gn|4*pm zPzHekJEQV4lx05}HXHdd0G5l_sS+<&~^Q=fqvG!Js1cp?R1Q^87g}VwgwG z0o@!RrChp5e@0K#ZL;|o-zyW7GK}Y*A`doZ9PeF0V4Ik9DBlYdiK4?(Z|t7;8(lG4 z;UWz#)Y;BH*DSHB76$XLX+7h|-6}&^y1dUJ9Q52HIOXDK4rfEgp?yor&=(s$5x>c> z9?5f=LwPj4pPh|Ng6Rx=NnBJ(@+JKqku!s@0`#f+3R?#HMwVm0<#TZ9xC zzI=j>O1|7oUG_9?B3LBVWi5Eu?B}fDCurO6(yDOi>88HuCt1=m1?jGA*9q11{n+q2 zz<-nX{X7#zjE<+`7+Ww8Z5(iXM_FDllZR961Gj+DbN?4DHu5%u$Kt)2#PYJdJxg>U z9!)UzfWYQUTifE@krlq=T#-?B`gP@)bZ4t*tO$S4+}SqNQ!yAA?okJ4{-Fa`>EM!5 zQ$}Pl?Z70Zu&*21ZT1YwecEy)VU$^)F9jzjw>mpw#Pb2rGk$dae~&i*DV?p*CofPz;P-$A1H=CX{$FN)e+3@1+w$eJMF0yb?4d{Y0jXoT zg6oXUP;bloPThrlNGKu!byCHcooV?sg`QIuAthIeuBLlLa&QK-M!jzuuHS3*87; z`(jH`svEX&A5Uv`zTg9CbBh_Y35S#`^hw5DoFVpB=p29+-X^a45QX5@P!~lk7%~TW!(nr5FuZO) zyIBMLE=7sp1tvg%*R#km!k6NjDf%p1`b9F`rbzk2KEp0_bb4EXRC(*zr>kHl)t&_&rYAR*MrBe zPFeo)d3@XcS7#456~Eep67-?{O*Hyve@|tj55~Xx1C8;w+rLDlPgC?%0r}fI1qAiK zI{mjE@~Qh%4c~8f9MCP?Z}-0xegA9FAdi2whe3;jq-l>Dw};{W^OXFVC3UJthksMx zpILfJf)5@2%979D-Ts3TpAPtGIrTe^7x_OIS5G56J*)d2!4M>+dMMBSI=TB_$@+8b zULf`AUsqO7dwhDg^}ENszq|du>3SOAY5DX!fZy}Se*d1f|Mk!hvczBQVTR3q&G3Ws zUn0dnXYHv>@xk}kti}C@?{CrK)5m|RR{I@g0#t&4()PC}c#QI>U3(hkse0*m6gbda z{6mySEz{E|Pt`2Hqws@Doqve(sBL)~<*EGPcN8Vi@$WxGc@#rDjq>!u{C5;HP?7u( bQ68_)