1 package com.cib.common.excelUnils;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.util.ArrayList;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.regex.Matcher;
9 import java.util.regex.Pattern;
10
11 import org.apache.commons.lang.StringUtils;
12 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
13 import org.apache.poi.openxml4j.opc.OPCPackage;
14 import org.apache.poi.ss.usermodel.BuiltinFormats;
15 import org.apache.poi.ss.usermodel.DataFormatter;
16 import org.apache.poi.xssf.eventusermodel.XSSFReader;
17 import org.apache.poi.xssf.model.SharedStringsTable;
18 import org.apache.poi.xssf.model.StylesTable;
19 import org.apache.poi.xssf.usermodel.XSSFCellStyle;
20 import org.apache.poi.xssf.usermodel.XSSFRichTextString;
21 import org.xml.sax.Attributes;
22 import org.xml.sax.InputSource;
23 import org.xml.sax.SAXException;
24 import org.xml.sax.XMLReader;
25 import org.xml.sax.helpers.DefaultHandler;
26 import org.xml.sax.helpers.XMLReaderFactory;
27
28 /**
29 * 名称: ExcelXlsxReader.java<br>
30 * 描述: <br>
31 * 类型: JAVA<br>
32 * 最近修改时间:2017年11月09日 上午10:00:52<br>
33 *
34 * @since 2017年11月09日
35 * @author “小涛”
36 */
37 public abstract class ExcelXlsxReader extends DefaultHandler {
38
39 /**
40 * 共享字符串表
41 */
42 private SharedStringsTable sst;
43
44
45 /**
46 * 上一次的内容
47 */
48 private String lastContents;
49
50
51 /**
52 * 字符串标识
53 */
54 private boolean nextIsString;
55
56
57 /**
58 * 工作表索引
59 */
60 private int sheetIndex = -1;
61
62
63 /**
64 * 行集合
65 */
66 private List<String> rowlist = new ArrayList<String>();
67
68
69 /**
70 * 当前行
71 */
72 private int curRow = 0;
73
74
75 /**
76 * 当前列
77 */
78 private int curCol = 0;
79
80
81 /**
82 * T元素标识
83 */
84 private boolean isTElement;
85
86
87 /**
88 * 异常信息,如果为空则表示没有异常
89 */
90 private String exceptionMessage;
91
92
93 /**
94 * 单元格数据类型,默认为字符串类型
95 */
96 private CellDataType nextDataType = CellDataType.SSTINDEX;
97
98
99 private final DataFormatter formatter = new DataFormatter();
100
101
102 private short formatIndex;
103
104
105 private String formatString;
106
107
108 // 定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等
109 private String preRef = null, ref = null;
110
111
112 // 定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格
113 private String maxRef = null;
114
115
116 /**
117 * 单元格
118 */
119 private StylesTable stylesTable;
120
121
122 /**
123 * 遍历工作簿中所有的电子表格
124 *
125 * @param filename
126 * @throws IOException
127 * @throws OpenXML4JException
128 * @throws SAXException
129 * @throws Exception
130 */
131 public void process(String filename) throws IOException, OpenXML4JException, SAXException {
132 OPCPackage pkg = OPCPackage.open(filename);
133 XSSFReader xssfReader = new XSSFReader(pkg);
134 stylesTable = xssfReader.getStylesTable();
135 SharedStringsTable sst = xssfReader.getSharedStringsTable();
136 XMLReader parser = this.fetchSheetParser(sst);
137 Iterator<InputStream> sheets = xssfReader.getSheetsData();
138 while (sheets.hasNext()) {
139 curRow = 0;
140 sheetIndex++;
141 InputStream sheet = sheets.next();
142 InputSource sheetSource = new InputSource(sheet);
143 parser.parse(sheetSource);
144 sheet.close();
145 }
146 }
147
148
149 public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
150 XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
151 this.sst = sst;
152 parser.setContentHandler(this);
153 return parser;
154 }
155
156
157 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
158 // c => 单元格
159 if ("c".equals(name)) {
160 // 前一个单元格的位置
161 if (preRef == null) {
162 preRef = attributes.getValue("r");
163 } else {
164 preRef = ref;
165 }
166 // 当前单元格的位置
167 ref = attributes.getValue("r");
168 // 设定单元格类型
169 this.setNextDataType(attributes);
170 // Figure out if the value is an index in the SST
171 String cellType = attributes.getValue("t");
172 if (cellType != null && cellType.equals("s")) {
173 nextIsString = true;
174 } else {
175 nextIsString = false;
176 }
177 }
178
179
180 // 当元素为t时
181 if ("t".equals(name)) {
182 isTElement = true;
183 } else {
184 isTElement = false;
185 }
186
187
188 // 置空
189 lastContents = "";
190 }
191
192
193 /**
194 * 单元格中的数据可能的数据类型
195 */
196 enum CellDataType {
197 BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, DATE, NULL
198 }
199
200
201 /**
202 * 处理数据类型
203 *
204 * @param attributes
205 */
206 public void setNextDataType(Attributes attributes) {
207 nextDataType = CellDataType.NUMBER;
208 formatIndex = -1;
209 formatString = null;
210 String cellType = attributes.getValue("t");
211 String cellStyleStr = attributes.getValue("s");
212 String columData = attributes.getValue("r");
213
214 if ("b".equals(cellType)) {
215 nextDataType = CellDataType.BOOL;
216 } else if ("e".equals(cellType)) {
217 nextDataType = CellDataType.ERROR;
218 } else if ("inlineStr".equals(cellType)) {
219 nextDataType = CellDataType.INLINESTR;
220 } else if ("s".equals(cellType)) {
221 nextDataType = CellDataType.SSTINDEX;
222 } else if ("str".equals(cellType)) {
223 nextDataType = CellDataType.FORMULA;
224 }
225
226
227 if (cellStyleStr != null) {
228 int styleIndex = Integer.parseInt(cellStyleStr);
229 XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
230 formatIndex = style.getDataFormat();
231 formatString = style.getDataFormatString();
232
233
234 if ("m/d/yy" == formatString) {
235 nextDataType = CellDataType.DATE;
236 formatString = "yyyy-MM-dd hh:mm:ss";
237 }
238
239
240 if (formatString == null) {
241 nextDataType = CellDataType.NULL;
242 formatString = BuiltinFormats.getBuiltinFormat(formatIndex);
243 }
244 }
245 }
246
247
248 /**
249 * 对解析出来的数据进行类型处理
250 *
251 * @param value
252 * 单元格的值(这时候是一串数字)
253 * @param thisStr
254 * 一个空字符串
255 * @return
256 */
257 public String getDataValue(String value, String thisStr) {
258 switch (nextDataType) {
259 // 这几个的顺序不能随便交换,交换了很可能会导致数据错误
260 case BOOL:
261 char first = value.charAt(0);
262 thisStr = first == '0' ? "FALSE" : "TRUE";
263 break;
264 case ERROR:
265 thisStr = "\"ERROR:" + value.toString() + '"';
266 break;
267 case FORMULA:
268 thisStr = '"' + value.toString() + '"';
269 break;
270 case INLINESTR:
271 XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
272
273 thisStr = rtsi.toString();
274 rtsi = null;
275 break;
276 case SSTINDEX:
277 String sstIndex = value.toString();
278 try {
279 int idx = Integer.parseInt(sstIndex);
280 XSSFRichTextString rtss = new XSSFRichTextString(sst.getEntryAt(idx));
281 thisStr = rtss.toString();
282 rtss = null;
283 } catch (NumberFormatException ex) {
284 thisStr = value.toString();
285 }
286
287 break;
288 case NUMBER:
289 if (formatString != null) {
290 thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString).trim();
291 } else {
292 thisStr = value;
293 }
294 thisStr = thisStr.replace("_", "").trim();
295
296 break;
297 case DATE:
298 //获取的日期是一串数字、需要使用此HasDigit方法来过滤第一行的表头、日期类型
299 if (HasDigit(value)) {
300 thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString);
301 // 对日期字符串作特殊处理
302 thisStr = thisStr.replace(" ", " ");
303 break;
304 }
305 default:
306 thisStr = "null";
307 break;
308 }
309 return thisStr;
310 }
311
312
313 @Override
314 public void endElement(String uri, String localName, String name) throws SAXException {
315 // 根据SST的索引值的到单元格的真正要存储的字符串
316 // 这时characters()方法可能会被调用多次
317 if (nextIsString && StringUtils.isNotEmpty(lastContents) && StringUtils.isNumeric(lastContents)) {
318 int idx = Integer.parseInt(lastContents);
319 lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
320 }
321
322
323 // t元素也包含字符串
324 if (isTElement) {
325 // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符
326 String value = lastContents.trim();
327 rowlist.add(curCol, value);
328 curCol++;
329 isTElement = false;
330 } else if ("v".equals(name)) {
331 // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引
332 String value = this.getDataValue(lastContents.trim(), "");
333 // 补全单元格之间的空单元格
334 if (!ref.equals(preRef)) {
335 int len = countNullCell(ref, preRef);
336 for (int i = 0; i < len; i++) {
337 rowlist.add(curCol, "null");
338 curCol++;
339 }
340 }
341 rowlist.add(curCol, value);
342 curCol++;
343 } else {
344 // 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法
345 if (name.equals("row")) {
346 // 默认第一行为表头,以该行单元格数目为最大数目
347 if (curRow == 0) {
348 maxRef = ref;
349 }
350 // 补全一行尾部可能缺失的单元格
351 if (maxRef != null) {
352 int len = countNullCell(maxRef, ref);
353 for (int i = 0; i <= len; i++) {
354 rowlist.add(curCol, "");
355 curCol++;
356 }
357 }
358 getRows(sheetIndex, curRow, rowlist);
359 rowlist.clear();
360 curRow++;
361 curCol = 0;
362 preRef = null;
363 ref = null;
364 }
365 }
366 }
367
368
369 /**
370 * 计算两个单元格之间的单元格数目(同一行)
371 * 此方法用来判断单元格是空的、如不加、则不会读取空的单元格
372 * @param ref
373 * @param preRef
374 * @return
375 */
376 public int countNullCell(String ref, String preRef) {
377 // excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD
378 String xfd = ref.replaceAll("\\d+", "");
379 String xfd_1 = preRef.replaceAll("\\d+", "");
380
381 xfd = fillChar(xfd, 3, '@', true);
382 xfd_1 = fillChar(xfd_1, 3, '@', true);
383
384 char[] letter = xfd.toCharArray();
385 char[] letter_1 = xfd_1.toCharArray();
386 int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]);
387 return res - 1;
388 }
389
390
391 /**
392 * 字符串的填充
393 *
394 * @param str
395 * @param len
396 * @param let
397 * @param isPre
398 * @return
399 */
400 String fillChar(String str, int len, char let, boolean isPre) {
401 int len_1 = str.length();
402 if (len_1 < len) {
403 if (isPre) {
404 for (int i = 0; i < (len - len_1); i++) {
405 str = let + str;
406 }
407 } else {
408 for (int i = 0; i < (len - len_1); i++) {
409 str = str + let;
410 }
411 }
412 }
413 return str;
414 }
415
416
417 @Override
418 public void characters(char[] ch, int start, int length) throws SAXException {
419 // 得到单元格内容的值
420 lastContents += new String(ch, start, length);
421 }
422
423
424 /**
425 * @return the exceptionMessage
426 */
427 public String getExceptionMessage() {
428 return exceptionMessage;
429 }
430
431 /**
432 * 获取行数据回调
433 *
434 * @param sheetIndex
435 * @param curRow
436 * @param rowList
437 */
438 public abstract void getRows(int sheetIndex, int curRow,
439 List<String> rowList);
440
441 //判断一个字符串是否含有数字
442 public boolean HasDigit(String content) {
443 boolean flag = false;
444 Pattern p = Pattern.compile("[1-9]\\d*\\.?\\d*");
445 Matcher m = p.matcher(content);
446 if (m.matches()) {
447 flag = true;
448 }
449 return flag;
450 }
451
452 }