/**
* Copyright (c) 2017, 吴汶泽 (wuwz@live.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wuwz.poi.core;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DataFormatter;
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.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.wuwz.poi.hanlder.ReadHandler;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
/**
* <p>
* *.xlsx文件解析器
* <p>
*
* @author wuwz
* @since 2017年4月10日
*/
public class XlsxReader extends DefaultHandler {
private SharedStringsTable mSharedStringsTable;
private String mLastContents; // 上一次的内容
private boolean mNextIsString;// 字符串标识
private int mSheetIndex = -1;
private int mCurrentRowIndex = 0;
private int mCurrentColumnIndex = 0;
private boolean mIsTElement;
private ReadHandler mReadHandler;
private short mFormatIndex;
private String mFormatString;
private StylesTable mStylesTable;
private CellValueType mNextDataType = CellValueType.STRING;
private final DataFormatter mFormatter = new DataFormatter();
private List<String> mRowData = new ArrayList<String>();
// 定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等
private String mPreviousRef = null, mCurrentRef = null;
// 定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格
private String mMaxRef = null;
// 空单元格的默认值
private String mEmptyCellValue = null;
public XlsxReader(ReadHandler handler) {
this.mReadHandler = handler;
}
public XlsxReader setEmptyCellValue(String ecv) {
this.mEmptyCellValue = ecv;
return this;
}
/**
* 处理所有sheet
*
* @param fileName
* excel文件
* @throws Exception
* IOException, OpenXML4JException, SAXException
*/
public void process(String fileName) throws Exception {
POIUtils.checkExcelFile(fileName);
OPCPackage pkg = OPCPackage.open(fileName);
XSSFReader xssfReader = new XSSFReader(pkg);
mStylesTable = xssfReader.getStylesTable();
SharedStringsTable sst = xssfReader.getSharedStringsTable();
XMLReader parser = this.fetchSheetParser(sst);
Iterator<InputStream> sheets = xssfReader.getSheetsData();
while (sheets.hasNext()) {
mCurrentRowIndex = 0;
mSheetIndex++;
InputStream sheet = sheets.next();
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
}
/**
* 处理指定sheet
*
* @param fileName
* excel文件
* @param sheetIndex
* 从0开始
* @throws Exception
* IOException, OpenXML4JException, SAXException
*/
public void process(String fileName, int sheetIndex) throws Exception {
POIUtils.checkExcelFile(fileName);
OPCPackage pkg = OPCPackage.open(fileName);
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
// rId2 found by processing the Workbook
// 根据 rId# 或 rSheet# 查找sheet
InputStream sheet = r.getSheet("rId" + (sheetIndex + 1));
mSheetIndex++;
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
private XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
this.mSharedStringsTable = sst;
parser.setContentHandler(this);
return parser;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
// c => 单元格
if ("c".equals(name)) {
// 设定单元格类型
this.setNextDataType(attributes);
// 前一个单元格的位置
mPreviousRef = mPreviousRef == null ? attributes.getValue("r") : mCurrentRef;
// 当前单元格的位置
mCurrentRef = attributes.getValue("r");
// Figure out if the value is an index in the SST
String cellType = attributes.getValue("t");
mNextIsString = (cellType != null && cellType.equals("s"));
}
mIsTElement = "t".equals(name);
mLastContents = "";
}
enum CellValueType {
BOOL, ERROR, FORMULA, INLINESTR, STRING, NUMBER, DATE, NULL
}
/**
* 处理数据类型
*
* @param attributes
*/
public void setNextDataType(Attributes attributes) {
mNextDataType = CellValueType.NUMBER;
mFormatIndex = -1;
mFormatString = null;
String cellType = attributes.getValue("t");
String cellStyleStr = attributes.getValue("s");
if ("b".equals(cellType)) {
mNextDataType = CellValueType.BOOL;
} else if ("e".equals(cellType)) {
mNextDataType = CellValueType.ERROR;
} else if ("inlineStr".equals(cellType)) {
mNextDataType = CellValueType.INLINESTR;
} else if ("s".equals(cellType)) {
mNextDataType = CellValueType.STRING;
} else if ("str".equals(cellType)) {
mNextDataType = CellValueType.FORMULA;
}
if (cellStyleStr != null) {
int styleIndex = Integer.parseInt(cellStyleStr);
XSSFCellStyle style = mStylesTable.getStyleAt(styleIndex);
mFormatIndex = style.getDataFormat();
mFormatString = style.getDataFormatString();
if ("m/d/yy" == mFormatString) {
mNextDataType = CellValueType.DATE;
mFormatString = "yyyy-MM-dd hh:mm:ss.SSS";
}
if (mFormatString == null) {
mNextDataType = CellValueType.NULL;
mFormatString = BuiltinFormats.getBuiltinFormat(mFormatIndex);
}
}
}
/**
* 对解析出来的数据进行类型处理
*
* @param value
* 单元格的值(这时候是一串数字)
* @param thisStr
* 一个空字符串
* @return string
*/
public String getDataValue(String value, String thisStr) {
switch (mNextDataType) {
// 这几个的顺序不能随便交换,交换了很可能会导致数据错误
case BOOL:
char first = value.charAt(0);
thisStr = first == '0' ? "FALSE" : "TRUE";
break;
case ERROR:
thisStr = "\"ERROR:" + value.toString() + '"';
break;
case FORMULA:
thisStr = '"' + value.toString() + '"';
break;
case INLINESTR:
XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
thisStr = rtsi.toString();
rtsi = null;
break;
case STRING:
String sstIndex = value.toString();
try {
int idx = Integer.parseInt(sstIndex);
XSSFRichTextString rtss = new XSSFRichTextString(mSharedStringsTable.getEntryAt(idx));
thisStr = rtss.toString();
rtss = null;
} catch (NumberFormatException ex) {
thisStr = value.toString();
}
break;
case NUMBER:
if (mFormatString != null) {
thisStr = mFormatter.formatRawCellContents(Double.parseDouble(value), mFormatIndex, mFormatString)
.trim();
} else {
thisStr = value;
}
thisStr = thisStr.replace("_", "").trim();
break;
case DATE:
thisStr = mFormatter.formatRawCellContents(Double.parseDouble(value), mFormatIndex, mFormatString);
// 对日期字符串作特殊处理
thisStr = thisStr.replace(" ", "T");
break;
default:
thisStr = " ";
break;
}
return thisStr;
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
// Process the last contents as required.
// Do now, as characters() may be called more than once
if (mNextIsString) {
int idx = Integer.parseInt(mLastContents);
mLastContents = new XSSFRichTextString(mSharedStringsTable.getEntryAt(idx)).toString();
mNextIsString = false;
}
// t元素也包含字符串
if (mIsTElement) {
// 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符
String value = mLastContents.trim();
mRowData.add(mCurrentColumnIndex, value);
mCurrentColumnIndex++;
mIsTElement = false;
} else if ("v".equals(name)) {
// v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引
String value = this.getDataValue(mLastContents.trim(), "");
// 补全单元格之间的空单元格
if (!mCurrentRef.equals(mPreviousRef)) {
for (int i = 0; i < countNullCell(mCurrentRef, mPreviousRef); i++) {
mRowData.add(mCurrentColumnIndex, mEmptyCellValue);
mCurrentColumnIndex++;
}
}
mRowData.add(mCurrentColumnIndex, value);
mCurrentColumnIndex++;
} else {
// 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法
if (name.equals("row")) {
// 默认第一行为表头,以该行单元格数目为最大数目
if (mCurrentRowIndex == 0) {
mMaxRef = mCurrentRef;
}
// 补全一行尾部可能缺失的单元格
if (mMaxRef != null) {
for (int i = 0; i < countNullCell(mMaxRef, mCurrentRef); i++) {
mRowData.add(mCurrentColumnIndex, mEmptyCellValue);
mCurrentColumnIndex++;
}
}
if (!mRowData.isEmpty()) {
mReadHandler.handler(mSheetIndex, mCurrentRowIndex, mRowData);
}
mRowData.clear();
mCurrentRowIndex++;
mCurrentColumnIndex = 0;
mPreviousRef = null;
mCurrentRef = null;
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 得到单元格内容的值
mLastContents += new String(ch, start, length);
}
private int countNullCell(String ref, String ref2) {
// excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD
String xfd = ref.replaceAll("\\d+", "");
String xfd_1 = ref2.replaceAll("\\d+", "");
xfd = fillChar(xfd, 3, '@', true);
xfd_1 = fillChar(xfd_1, 3, '@', true);
char[] letter = xfd.toCharArray();
char[] letter_1 = xfd_1.toCharArray();
int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]);
return res - 1;
}
private String fillChar(String str, int len, char let, boolean isPre) {
int len_1 = str.length();
if (len_1 < len) {
if (isPre) {
for (int i = 0; i < (len - len_1); i++) {
str = let + str;
}
} else {
for (int i = 0; i < (len - len_1); i++) {
str = str + let;
}
}
}
return str;
}
}