add SheetDataSaxHandler

This commit is contained in:
Looly 2020-12-09 00:37:23 +08:00
parent 30e1eb929c
commit 7d4838a0a4
3 changed files with 324 additions and 289 deletions

View File

@ -15,6 +15,7 @@
* 【dfa 】 增加FoundWordpr#1290@Github * 【dfa 】 增加FoundWordpr#1290@Github
* 【core 】 增加Segmentpr#1290@Github * 【core 】 增加Segmentpr#1290@Github
* 【core 】 增加CharSequenceUtil * 【core 】 增加CharSequenceUtil
* 【poi 】 Excel07SaxReader拆分出SheetDataSaxHandler
### Bug修复 ### Bug修复
* 【cache 】 修复Cache中get重复misCount计数问题issue#1281@Github * 【cache 】 修复Cache中get重复misCount计数问题issue#1281@Github

View File

@ -2,30 +2,19 @@ package cn.hutool.poi.excel.sax;
import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; 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.excel.sax.handler.RowHandler;
import cn.hutool.poi.exceptions.POIException; import cn.hutool.poi.exceptions.POIException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage; 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.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.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
/** /**
* Sax方式读取Excel文件<br> * Sax方式读取Excel文件<br>
@ -34,52 +23,11 @@ import java.util.List;
* @author Looly * @author Looly
* @since 3.1.2 * @since 3.1.2
*/ */
public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<Excel07SaxReader> { public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
// sheet r:Id前缀 // sheet r:Id前缀
private static final String RID_PREFIX = "rId"; private static final String RID_PREFIX = "rId";
private final SheetDataSaxHandler handler;
// 单元格的格式表对应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;
// 当前列坐标 如A1B5
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<Object> rowCellList = new ArrayList<>();
/**
* 行处理器
*/
private RowHandler rowHandler;
/** /**
* 构造 * 构造
@ -87,7 +35,7 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
* @param rowHandler 行处理器 * @param rowHandler 行处理器
*/ */
public Excel07SaxReader(RowHandler rowHandler) { public Excel07SaxReader(RowHandler rowHandler) {
this.rowHandler = rowHandler; this.handler = new SheetDataSaxHandler(rowHandler);
} }
/** /**
@ -97,7 +45,7 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
* @return this * @return this
*/ */
public Excel07SaxReader setRowHandler(RowHandler rowHandler) { public Excel07SaxReader setRowHandler(RowHandler rowHandler) {
this.rowHandler = rowHandler; this.handler.setRowHandler(rowHandler);
return this; return this;
} }
@ -174,14 +122,14 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
public Excel07SaxReader read(XSSFReader xssfReader, String idOrRid) throws POIException { public Excel07SaxReader read(XSSFReader xssfReader, String idOrRid) throws POIException {
// 获取共享样式表 // 获取共享样式表
try { try {
stylesTable = xssfReader.getStylesTable(); this.handler.stylesTable = xssfReader.getStylesTable();
} catch (Exception e) { } catch (Exception e) {
//ignore //ignore
} }
// 获取共享字符串表 // 获取共享字符串表
try { try {
this.sharedStringsTable = xssfReader.getSharedStringsTable(); this.handler.sharedStringsTable = xssfReader.getSharedStringsTable();
} catch (IOException e) { } catch (IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} catch (InvalidFormatException e) { } catch (InvalidFormatException e) {
@ -192,89 +140,6 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
} }
// ------------------------------------------------------------------------------ Read end // ------------------------------------------------------------------------------ Read end
/**
* 读到一个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 // --------------------------------------------------------------------------------------- Private method start
/** /**
@ -295,25 +160,25 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
idOrRid = rid; idOrRid = rid;
} }
} }
this.sheetIndex = Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRid, RID_PREFIX)); this.handler.sheetIndex = Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRid, RID_PREFIX));
InputStream sheetInputStream = null; InputStream sheetInputStream = null;
try { try {
if (this.sheetIndex > -1) { if (this.handler.sheetIndex > -1) {
// 根据 rId# rSheet# 查找sheet // 根据 rId# rSheet# 查找sheet
sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.sheetIndex + 1)); sheetInputStream = xssfReader.getSheet(RID_PREFIX + (this.handler.sheetIndex + 1));
ExcelSaxUtil.readFrom(sheetInputStream, this); ExcelSaxUtil.readFrom(sheetInputStream, this.handler);
rowHandler.doAfterAllAnalysed(); this.handler.rowHandler.doAfterAllAnalysed();
} else { } else {
this.sheetIndex = -1; this.handler.sheetIndex = -1;
// 遍历所有sheet // 遍历所有sheet
final Iterator<InputStream> sheetInputStreams = xssfReader.getSheetsData(); final Iterator<InputStream> sheetInputStreams = xssfReader.getSheetsData();
while (sheetInputStreams.hasNext()) { while (sheetInputStreams.hasNext()) {
// 重新读取一个sheet时行归零 // 重新读取一个sheet时行归零
index = 0; this.handler.index = 0;
this.sheetIndex++; this.handler.sheetIndex++;
sheetInputStream = sheetInputStreams.next(); sheetInputStream = sheetInputStreams.next();
ExcelSaxUtil.readFrom(sheetInputStream, this); ExcelSaxUtil.readFrom(sheetInputStream, this.handler);
rowHandler.doAfterAllAnalysed(); this.handler.rowHandler.doAfterAllAnalysed();
} }
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -325,143 +190,5 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
} }
return this; return this;
} }
/**
* 行开始
*
* @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 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);
}
/**
* 一行结尾
*/
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;
}
/**
* 在一行中的指定列增加值
*
* @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);
}
/**
* 填充空白单元格如果前一个单元格大于后一个不需要填充<br>
*
* @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 // --------------------------------------------------------------------------------------- Private method end
} }

View File

@ -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标签内容读取处理器
*
* <pre>
* &lt;sheetData&gt;&lt;/sheetData&gt;
* </pre>
* @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;
// 当前列坐标 如A1B5
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<Object> 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);
}
/**
* 填充空白单元格如果前一个单元格大于后一个不需要填充<br>
*
* @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
}