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 21977a4d..e5df4bfa 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -3,10 +3,12 @@ package com.alibaba.excel.analysis.v03; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; +import com.alibaba.excel.util.StringUtils; import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder; import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; @@ -19,6 +21,11 @@ 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.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,11 +81,18 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { private HSSFWorkbook stubWorkbook; private List recordHandlers = new ArrayList(); private AnalysisContext analysisContext; + private Workbook poiWorkbook; + private Map rowComments; public XlsSaxAnalyser(AnalysisContext context, POIFSFileSystem poifsFileSystem) { this.analysisContext = context; this.records = new TreeMap(); this.poifsFileSystem = poifsFileSystem; + try { + this.poiWorkbook = WorkbookFactory.create(poifsFileSystem); + } catch (IOException e) { + e.printStackTrace(); + } analysisContext.readWorkbookHolder().setPoifsFileSystem(poifsFileSystem); } @@ -139,6 +153,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { thisRow = handler.getRow(); thisColumn = handler.getColumn(); cellData = handler.getCellData(); + handleComments(thisRow, thisColumn); if (cellData != null) { cellData.checkEmpty(); if (CellDataTypeEnum.EMPTY != cellData.getType()) { @@ -170,6 +185,26 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { processLastCellOfRow(record); } + public void handleComments(int row, int col) { + if (null == this.poiWorkbook || null == analysisContext.readSheetHolder() || row < 0 || col < 0) { + return; + } + Sheet currentSheet = poiWorkbook.getSheetAt(analysisContext.readSheetHolder().getSheetNo()); + Map cellComments = currentSheet.getCellComments(); + if (CollectionUtils.isEmpty(cellComments)) { + return; + } + Comment comment = cellComments.get(new CellAddress(row, col)); + if (null == comment) { + return; + } + String commentsStr = comment.getString().toString(); + if (!StringUtils.isEmpty(commentsStr)) { + rowComments = rowComments == null ? new HashMap(8) : rowComments; + rowComments.put(col, commentsStr); + } + } + private boolean ignoreRecord(Record record) { return analysisContext.readWorkbookHolder().getIgnoreRecord03() && record.getSid() != BoundSheetRecord.sid && record.getSid() != BOFRecord.sid; @@ -188,6 +223,9 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { } analysisContext.readRowHolder( new ReadRowHolder(lastRowNumber, analysisContext.readSheetHolder().getGlobalConfiguration())); + if (!CollectionUtils.isEmpty(rowComments)) { + analysisContext.readRowHolder().setRowComments(rowComments); + } analysisContext.readSheetHolder().notifyEndOneRow(new EachRowAnalysisFinishEvent(records), analysisContext); records.clear(); lastColumnNumber = -1; diff --git a/src/main/java/com/alibaba/excel/analysis/v07/XlsxCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/XlsxCellHandler.java index a4db7f4a..30687250 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/XlsxCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/XlsxCellHandler.java @@ -34,4 +34,12 @@ public interface XlsxCellHandler { * Tag name */ void endHandle(String name); + + /** + * Set the comment of the cell + * + * @param comment + * cell comment + */ + void handleComments(String comment); } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/XlsxRowHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/XlsxRowHandler.java index ececb471..b6702c1b 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/XlsxRowHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/XlsxRowHandler.java @@ -2,7 +2,12 @@ package com.alibaba.excel.analysis.v07; import java.util.List; +import com.alibaba.excel.constant.ExcelXmlConstants; +import com.alibaba.excel.util.StringUtils; +import org.apache.poi.ss.util.CellAddress; +import org.apache.poi.xssf.model.CommentsTable; import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.usermodel.XSSFComment; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -17,6 +22,12 @@ public class XlsxRowHandler extends DefaultHandler { private List cellHandlers; private XlsxRowResultHolder rowResultHolder; + private CommentsTable commentsTable; + + public XlsxRowHandler(AnalysisContext analysisContext, StylesTable stylesTable, CommentsTable commentsTable) { + this(analysisContext, stylesTable); + this.commentsTable = commentsTable; + } public XlsxRowHandler(AnalysisContext analysisContext, StylesTable stylesTable) { this.cellHandlers = XlsxHandlerFactory.buildCellHandlers(analysisContext, stylesTable); @@ -33,6 +44,7 @@ public class XlsxRowHandler extends DefaultHandler { for (XlsxCellHandler cellHandler : cellHandlers) { if (cellHandler.support(name)) { cellHandler.startHandle(name, attributes); + handleComment(cellHandler, attributes.getValue(ExcelXmlConstants.POSITION)); } } } @@ -52,4 +64,18 @@ public class XlsxRowHandler extends DefaultHandler { rowResultHolder.appendCurrentCellValue(ch, start, length); } } + + private void handleComment(XlsxCellHandler cellHandler, String address) { + if (StringUtils.isEmpty(address) || null == commentsTable) { + return; + } + XSSFComment xssfComment = commentsTable.getCellComments().get(new CellAddress(address)); + if (null == xssfComment) { + return; + } + String comments = xssfComment.getString().toString(); + if (!StringUtils.isEmpty(comments)) { + cellHandler.handleComments(comments); + } + } } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java index dd468b4b..591932e3 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/XlsxSaxAnalyser.java @@ -17,6 +17,7 @@ import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.eventusermodel.XSSFReader; +import org.apache.poi.xssf.model.CommentsTable; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFRelation; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook; @@ -46,6 +47,14 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { private AnalysisContext analysisContext; private List sheetList; private Map sheetMap; + + /** + * excel comments + * key: sheetNo + * value: CommentsTanle + */ + private Map commentsTableMap; + /** * Current style information */ @@ -77,6 +86,7 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { stylesTable = xssfReader.getStylesTable(); sheetList = new ArrayList(); sheetMap = new HashMap(); + commentsTableMap = new HashMap(); XSSFReader.SheetIterator ite = (XSSFReader.SheetIterator)xssfReader.getSheetsData(); int index = 0; if (!ite.hasNext()) { @@ -86,6 +96,10 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { InputStream inputStream = ite.next(); sheetList.add(new ReadSheet(index, ite.getSheetName())); sheetMap.put(index, inputStream); + CommentsTable commentsTable = ite.getSheetComments(); + if (null != commentsTable) { + commentsTableMap.put(index, commentsTable); + } index++; } } @@ -181,7 +195,8 @@ public class XlsxSaxAnalyser implements ExcelReadExecutor { analysisContext.readWorkbookHolder().getGlobalConfiguration()); if (readSheet != null) { analysisContext.currentSheet(readSheet); - parseXmlSource(sheetMap.get(readSheet.getSheetNo()), new XlsxRowHandler(analysisContext, stylesTable)); + Integer sheetNo = readSheet.getSheetNo(); + parseXmlSource(sheetMap.get(sheetNo), new XlsxRowHandler(analysisContext, stylesTable, commentsTableMap.get(sheetNo))); // The last sheet is read analysisContext.readSheetHolder().notifyAfterAllAnalysed(analysisContext); } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java index ea63409b..c92ec6ca 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java @@ -39,4 +39,9 @@ public class CountRowCellHandler implements XlsxCellHandler { } + @Override + public void handleComments(String comment) { + + } + } diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 0d44a95b..2dc0fdb9 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java @@ -160,6 +160,11 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder } } + @Override + public void handleComments(String comment) { + analysisContext.readRowHolder().addComments(curCol, comment); + } + @Override public void appendCurrentCellValue(char[] ch, int start, int length) { String currentTag = currentTagDeque.peek(); diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/ProcessResultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/ProcessResultCellHandler.java index 95816463..32b75ed5 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/ProcessResultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/ProcessResultCellHandler.java @@ -45,4 +45,9 @@ public class ProcessResultCellHandler implements XlsxCellHandler { rowResultHandler.clearResult(); } + @Override + public void handleComments(String comment) { + + } + } diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadRowHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadRowHolder.java index 99160158..24079f77 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadRowHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadRowHolder.java @@ -3,6 +3,10 @@ package com.alibaba.excel.read.metadata.holder; import com.alibaba.excel.enums.HolderEnum; import com.alibaba.excel.metadata.GlobalConfiguration; import com.alibaba.excel.metadata.Holder; +import com.alibaba.excel.util.CollectionUtils; + +import java.util.HashMap; +import java.util.Map; /** * sheet holder @@ -24,6 +28,34 @@ public class ReadRowHolder implements Holder { */ private GlobalConfiguration globalConfiguration; + /** + * Return row comments + * key: col index + * value: comments + */ + private Map rowComments; + + public Map getRowComments() { + return rowComments; + } + + public void setRowComments(Map rowComments) { + this.rowComments = rowComments; + } + + public void addComments(Integer index, String comments) { + this.rowComments = this.rowComments == null ? new HashMap(8) : this.rowComments; + this.rowComments.put(index, comments); + } + + public String getComments(Integer index) { + if (CollectionUtils.isEmpty(rowComments)) { + return null; + } else { + return rowComments.get(index); + } + } + public ReadRowHolder(Integer rowIndex, GlobalConfiguration globalConfiguration) { this.rowIndex = rowIndex; this.globalConfiguration = globalConfiguration; diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoCellCommentsListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoCellCommentsListener.java new file mode 100644 index 00000000..733005cb --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoCellCommentsListener.java @@ -0,0 +1,85 @@ +package com.alibaba.easyexcel.test.demo.read; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.exception.ExcelDataConvertException; +import com.alibaba.excel.util.CollectionUtils; +import com.alibaba.fastjson.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 读取单元格的批注 + * + * @author: kaiux + * @date: 2019-10-23 14:10 + **/ +public class DemoCellCommentsListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(DemoCellCommentsListener.class); + /** + * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + List list = new ArrayList(); + + /** + * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 + * + * @param exception + * @param context + * @throws Exception + */ + @Override + public void onException(Exception exception, AnalysisContext context) { + LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); + if (exception instanceof ExcelDataConvertException) { + ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception; + LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(), + excelDataConvertException.getColumnIndex()); + } + } + + /** + * 这里会一行行的返回头 + * + * @param headMap + * @param context + */ + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + Map rowComments = context.readRowHolder().getRowComments(); + LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); + if (!CollectionUtils.isEmpty(rowComments)) { + for (Integer i : rowComments.keySet()) { + LOGGER.info("解析到头数据低{}列包含批注:{}", i, rowComments.get(i)); + } + } + } + + @Override + public void invoke(DemoData data, AnalysisContext context) { + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + if (list.size() >= BATCH_COUNT) { + saveData(); + list.clear(); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + saveData(); + LOGGER.info("所有数据解析完成!"); + } + + /** + * 加上存储数据库 + */ + private void saveData() { + LOGGER.info("{}条数据,开始存储数据库!", list.size()); + LOGGER.info("存储数据库成功!"); + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 9ebafa4f..112c529b 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 @@ -161,6 +161,23 @@ public class ReadTest { EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); } + /** + * 读取单元格批注 + * + *

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

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

+ * 3. 直接读即可 + */ + @Test + public void commentsRead() { + String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet + EasyExcel.read(fileName, DemoData.class, new DemoCellCommentsListener()).sheet().doRead(); + } + /** * 读取公式和单元格类型 * diff --git a/src/test/resources/demo/demo.xlsx b/src/test/resources/demo/demo.xlsx index 303733cf..48d3f87a 100644 Binary files a/src/test/resources/demo/demo.xlsx and b/src/test/resources/demo/demo.xlsx differ