diff --git a/CHANGELOG.md b/CHANGELOG.md index d45f0d7e7..0652a9506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【dfa 】 增加FoundWord(pr#1290@Github) * 【core 】 增加Segment(pr#1290@Github) * 【core 】 增加CharSequenceUtil +* 【poi 】 Excel07SaxReader拆分出SheetDataSaxHandler ### Bug修复 * 【cache 】 修复Cache中get重复misCount计数问题(issue#1281@Github) diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java index 9ed1ca7aa..4c55b90d2 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java @@ -2,30 +2,19 @@ package cn.hutool.poi.excel.sax; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import cn.hutool.poi.exceptions.POIException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; -import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.xssf.eventusermodel.XSSFReader; -import org.apache.poi.xssf.model.SharedStringsTable; -import org.apache.poi.xssf.model.StylesTable; -import org.apache.poi.xssf.usermodel.XSSFCellStyle; -import org.xml.sax.Attributes; -import org.xml.sax.helpers.DefaultHandler; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; /** * Sax方式读取Excel文件
@@ -34,52 +23,11 @@ import java.util.List; * @author Looly * @since 3.1.2 */ -public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader { +public class Excel07SaxReader implements ExcelSaxReader { // sheet r:Id前缀 private static final String RID_PREFIX = "rId"; - - // 单元格的格式表,对应style.xml - private StylesTable stylesTable; - // excel 2007 的共享字符串表,对应sharedString.xml - private SharedStringsTable sharedStringsTable; - // sheet的索引 - private int sheetIndex; - - // 当前非空行 - private int index; - // 当前列 - private int curCell; - // 单元数据类型 - private CellDataType cellDataType; - // 当前行号,从0开始 - private long rowNumber; - // 当前列坐标, 如A1,B5 - private String curCoordinate; - // 当前节点名称 - private ElementName curElementName; - // 前一个列的坐标 - private String preCoordinate; - // 行的最大列坐标 - private String maxCellCoordinate; - // 单元格样式 - private XSSFCellStyle xssfCellStyle; - // 单元格存储的格式化字符串,nmtFmt的formatCode属性的值 - private String numFmtString; - // 是否处于sheetData标签内,sax只解析此标签内的内容,其它标签忽略 - private boolean isInSheetData; - - // 上一次的内容 - private final StrBuilder lastContent = StrUtil.strBuilder(); - // 上一次的内容 - private final StrBuilder lastFormula = StrUtil.strBuilder(); - // 存储每行的列元素 - private List rowCellList = new ArrayList<>(); - - /** - * 行处理器 - */ - private RowHandler rowHandler; + private final SheetDataSaxHandler handler; /** * 构造 @@ -87,7 +35,7 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader -1) { + if (this.handler.sheetIndex > -1) { // 根据 rId# 或 rSheet# 查找sheet - sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.sheetIndex + 1)); - ExcelSaxUtil.readFrom(sheetInputStream, this); - rowHandler.doAfterAllAnalysed(); + sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.handler.sheetIndex + 1)); + ExcelSaxUtil.readFrom(sheetInputStream, this.handler); + this.handler.rowHandler.doAfterAllAnalysed(); } else { - this.sheetIndex = -1; + this.handler.sheetIndex = -1; // 遍历所有sheet final Iterator sheetInputStreams = xssfReader.getSheetsData(); while (sheetInputStreams.hasNext()) { // 重新读取一个sheet时行归零 - index = 0; - this.sheetIndex++; + this.handler.index = 0; + this.handler.sheetIndex++; sheetInputStream = sheetInputStreams.next(); - ExcelSaxUtil.readFrom(sheetInputStream, this); - rowHandler.doAfterAllAnalysed(); + ExcelSaxUtil.readFrom(sheetInputStream, this.handler); + this.handler.rowHandler.doAfterAllAnalysed(); } } } catch (RuntimeException e) { @@ -325,143 +190,5 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader(curCell + 1); - // 行数增加 - index++; - // 当前列置0 - curCell = 0; - // 置空当前列坐标和前一列坐标 - curCoordinate = null; - preCoordinate = null; - } - - /** - * 在一行中的指定列增加值 - * - * @param index 位置 - * @param value 值 - */ - private void addCellValue(int index, Object value) { - this.rowCellList.add(index, value); - this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle); - } - - /** - * 填充空白单元格,如果前一个单元格大于后一个,不需要填充
- * - * @param preCoordinate 前一个单元格坐标 - * @param curCoordinate 当前单元格坐标 - * @param isEnd 是否为最后一个单元格 - */ - private void fillBlankCell(String preCoordinate, String curCoordinate, boolean isEnd) { - if (false == curCoordinate.equals(preCoordinate)) { - int len = ExcelSaxUtil.countNullCell(preCoordinate, curCoordinate); - if (isEnd) { - len++; - } - while (len-- > 0) { - addCellValue(curCell++, StrUtil.EMPTY); - } - } - } - - /** - * 设置单元格的类型 - * - * @param attributes 属性 - */ - private void setCellType(Attributes attributes) { - // numFmtString的值 - numFmtString = StrUtil.EMPTY; - this.cellDataType = CellDataType.of(AttributeName.t.getValue(attributes)); - - // 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf - if (null != this.stylesTable) { - final String xfIndexStr = AttributeName.s.getValue(attributes); - if (null != xfIndexStr) { - this.xssfCellStyle = stylesTable.getStyleAt(Integer.parseInt(xfIndexStr)); - // 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引 - final int numFmtIndex = xssfCellStyle.getDataFormat(); - this.numFmtString = ObjectUtil.defaultIfNull( - xssfCellStyle.getDataFormatString(), - BuiltinFormats.getBuiltinFormat(numFmtIndex)); - if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) { - cellDataType = CellDataType.DATE; - } - } - } - - } // --------------------------------------------------------------------------------------- Private method end } \ No newline at end of file diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java new file mode 100644 index 000000000..b6d63ee7b --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -0,0 +1,307 @@ +package cn.hutool.poi.excel.sax; + +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.poi.excel.cell.FormulaCellValue; +import cn.hutool.poi.excel.sax.handler.RowHandler; +import org.apache.poi.ss.usermodel.BuiltinFormats; +import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * sheetData标签内容读取处理器 + * + *
+ * <sheetData></sheetData>
+ * 
+ * @since 5.5.3 + */ +public class SheetDataSaxHandler extends DefaultHandler { + + // 单元格的格式表,对应style.xml + protected StylesTable stylesTable; + // excel 2007 的共享字符串表,对应sharedString.xml + protected SharedStringsTable sharedStringsTable; + // sheet的索引 + protected int sheetIndex; + + // 当前非空行 + protected int index; + // 当前列 + private int curCell; + // 单元数据类型 + private CellDataType cellDataType; + // 当前行号,从0开始 + private long rowNumber; + // 当前列坐标, 如A1,B5 + private String curCoordinate; + // 当前节点名称 + private ElementName curElementName; + // 前一个列的坐标 + private String preCoordinate; + // 行的最大列坐标 + private String maxCellCoordinate; + // 单元格样式 + private XSSFCellStyle xssfCellStyle; + // 单元格存储的格式化字符串,nmtFmt的formatCode属性的值 + private String numFmtString; + // 是否处于sheetData标签内,sax只解析此标签内的内容,其它标签忽略 + private boolean isInSheetData; + + // 上一次的内容 + private final StrBuilder lastContent = StrUtil.strBuilder(); + // 上一次的内容 + private final StrBuilder lastFormula = StrUtil.strBuilder(); + // 存储每行的列元素 + private List rowCellList = new ArrayList<>(); + + public SheetDataSaxHandler(RowHandler rowHandler){ + this.rowHandler = rowHandler; + } + + /** + * 行处理器 + */ + protected RowHandler rowHandler; + + /** + * 设置行处理器 + * + * @param rowHandler 行处理器 + */ + public void setRowHandler(RowHandler rowHandler) { + this.rowHandler = rowHandler; + } + + /** + * 读到一个xml开始标签时的回调处理方法 + */ + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) { + if ("sheetData".equals(qName)) { + this.isInSheetData = true; + return; + } + + if (false == this.isInSheetData) { + // 非sheetData标签,忽略解析 + return; + } + + final ElementName name = ElementName.of(qName); + this.curElementName = name; + + if (null != name) { + switch (name) { + case row: + // 行开始 + startRow(attributes); + break; + case c: + // 单元格元素 + startCell(attributes); + break; + } + } + } + + /** + * 标签结束的回调处理方法 + */ + @Override + public void endElement(String uri, String localName, String qName) { + if ("sheetData".equals(qName)) { + // sheetData结束,不再解析别的标签 + this.isInSheetData = false; + return; + } + + if (false == this.isInSheetData) { + // 非sheetData标签,忽略解析 + return; + } + + this.curElementName = null; + if (ElementName.c.match(qName)) { // 单元格结束 + endCell(); + } else if (ElementName.row.match(qName)) {// 行结束 + endRow(); + } + // 其它标签忽略 + } + + /** + * s标签结束的回调处理方法 + */ + @Override + public void characters(char[] ch, int start, int length) { + if (false == this.isInSheetData) { + // 非sheetData标签,忽略解析 + return; + } + + final ElementName elementName = this.curElementName; + if (null != elementName) { + switch (elementName) { + case v: + // 得到单元格内容的值 + lastContent.append(ch, start, length); + break; + case f: + // 得到单元格内容的值 + lastFormula.append(ch, start, length); + break; + } + } + // 其它标签忽略 + } + + // --------------------------------------------------------------------------------------- Private method start + + /** + * 行开始 + * + * @param attributes 属性列表 + */ + private void startRow(Attributes attributes) { + final String rValue = AttributeName.r.getValue(attributes); + if (null != rValue) { + this.rowNumber = Long.parseLong(rValue) - 1; + } + } + + /** + * 单元格开始 + * + * @param attributes 属性列表 + */ + private void startCell(Attributes attributes) { + // 获取当前列坐标 + final String tempCurCoordinate = AttributeName.r.getValue(attributes); + // 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64 + if (preCoordinate == null) { + preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR); + } else { + // 存在,则前一列要设置为上一列的坐标 + preCoordinate = curCoordinate; + } + // 重置当前列 + curCoordinate = tempCurCoordinate; + // 设置单元格类型 + setCellType(attributes); + + // 清空之前的数据 + lastContent.reset(); + lastFormula.reset(); + } + + /** + * 一行结尾 + */ + private void endRow() { + // 最大列坐标以第一个非空行的为准 + if (index == 0) { + maxCellCoordinate = curCoordinate; + } + + // 补全一行尾部可能缺失的单元格 + if (maxCellCoordinate != null) { + fillBlankCell(curCoordinate, maxCellCoordinate, true); + } + + rowHandler.handle(sheetIndex, rowNumber, rowCellList); + + // 一行结束 + // 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理) + rowCellList = new ArrayList<>(curCell + 1); + // 行数增加 + index++; + // 当前列置0 + curCell = 0; + // 置空当前列坐标和前一列坐标 + curCoordinate = null; + preCoordinate = null; + } + + /** + * 一个单元格结尾 + */ + private void endCell() { + // 补全单元格之间的空格 + fillBlankCell(preCoordinate, curCoordinate, false); + + final String contentStr = StrUtil.trim(lastContent); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + if (false == this.lastFormula.isEmpty()) { + value = new FormulaCellValue(StrUtil.trim(lastFormula), value); + } + addCellValue(curCell++, value); + } + + /** + * 在一行中的指定列增加值 + * + * @param index 位置 + * @param value 值 + */ + private void addCellValue(int index, Object value) { + this.rowCellList.add(index, value); + this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle); + } + + /** + * 填充空白单元格,如果前一个单元格大于后一个,不需要填充
+ * + * @param preCoordinate 前一个单元格坐标 + * @param curCoordinate 当前单元格坐标 + * @param isEnd 是否为最后一个单元格 + */ + private void fillBlankCell(String preCoordinate, String curCoordinate, boolean isEnd) { + if (false == curCoordinate.equals(preCoordinate)) { + int len = ExcelSaxUtil.countNullCell(preCoordinate, curCoordinate); + if (isEnd) { + len++; + } + while (len-- > 0) { + addCellValue(curCell++, StrUtil.EMPTY); + } + } + } + + /** + * 设置单元格的类型 + * + * @param attributes 属性 + */ + private void setCellType(Attributes attributes) { + // numFmtString的值 + numFmtString = StrUtil.EMPTY; + this.cellDataType = CellDataType.of(AttributeName.t.getValue(attributes)); + + // 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf + if (null != this.stylesTable) { + final String xfIndexStr = AttributeName.s.getValue(attributes); + if (null != xfIndexStr) { + this.xssfCellStyle = stylesTable.getStyleAt(Integer.parseInt(xfIndexStr)); + // 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引 + final int numFmtIndex = xssfCellStyle.getDataFormat(); + this.numFmtString = ObjectUtil.defaultIfNull( + xssfCellStyle.getDataFormatString(), + BuiltinFormats.getBuiltinFormat(numFmtIndex)); + if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) { + cellDataType = CellDataType.DATE; + } + } + } + + } + + // --------------------------------------------------------------------------------------- Private method end +}