您的位置:首页 > 编程语言 > Java开发

秒懂POI解析excel,SAXParser解析大xlsx,XSSFReader处理包括被忽略的空单元格处理

2018-04-01 18:58 1616 查看
poi常用解析excel文件

excel分97-03格式xls和07格式xlsx,官网对于这两种的说明POI-HSSF and POI-XSSF

最新的是SXSSF

【原理】

poi先将excel转为xml,而后是使用SAXParser解析器,解析xml文件得到excel的数据

xml解析一般是先转dom树,然后操作,【方便随意遍历】,但是这需要将全部xml加载处理,适合小的xml,或者配置类xml

xml文件到数百M或上G的量,全部加载效率低,无法生成完整dom树操作,所以SAX解析器是循环读取一定长度处理,读到一个标签就会回调一个用户方法处理,这样减小内存。【适合大量数据导入,不能回头遍历以前的xml,需要自己实现处理xml内读取的数据关系】

excel转换后的完整xml例子,test.xml

<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac"
xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">
<dimension ref="A1:AB7"/>
<sheetViews>
<sheetView tabSelected="1" workbookViewId="0">
<selection activeCell="L3" sqref="L3"/>
</sheetView>
</sheetViews>
<sheetFormatPr defaultRowHeight="13.8" x14ac:dyDescent="0.25"/>
<cols>
<col min="12" max="12" width="9.109375" bestFit="1" customWidth="1"/>
</cols>
<sheetData>
<row r="1" spans="1:28" x14ac:dyDescent="0.25">
<c r="A1">
<v>1</v>
</c>
<c r="B1">
<v>2</v>
</c>
<c r="D1">
<v>4</v>
</c>
<c r="G1">
<v>7</v>
</c>
<c r="H1" t="s">
<v>0</v>
</c>
<c r="I1" t="s">
<v>4</v>
</c>
<c r="K1">
<v>32423</v>
</c>
<c r="U1">
<v>78979</v>
</c>
<c r="Y1" t="s">
<v>3</v>
</c>
</row>
<row r="2" spans="1:28" x14ac:dyDescent="0.25">
<c r="B2">
<v>22</v>
</c>
<c r="C2">
<v>33</v>
</c>
<c r="E2">
<v>55</v>
</c>
<c r="F2" t="s">
<v>1</v>
</c>
<c r="Q2" t="s">
<v>2</v>
</c>
</row>
<row r="3" spans="1:28" x14ac:dyDescent="0.25">
<c r="L3" s="1">
<v>201287</v>
</c>
</row>
<row r="7" spans="1:28" x14ac:dyDescent="0.25">
<c r="AB7">
<v>123131</v>
</c>
</row>
</sheetData>
<phoneticPr fontId="1" type="noConversion"/>
<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
<pageSetup paperSize="9" orientation="portrait" horizontalDpi="1200" verticalDpi="1200" r:id="rId1"/>
</worksheet>


【XSSFReader空单元格,空行问题,从上面的xml可以看出】poi转换excel为xml会忽略空单元格(不是单元格内容为空格,是单元格没有内容)和空行,导致转换后的【数据错位问题】,需要自己实现判断空单元格和空行处理(根据excel的行列号,比如B1, D1则表明C1是空单元格,行row的行列号由L3(L是列号,3是行号),到AB7,表明4,5,6是空行)

【SAXParser解析器DefaultHandler】

从上面的介绍可以大致了解poi处理excel的过程,我们要做的就是覆盖实现解析的方法,来达到自己的需求

自己的Handler继承DefaultHandler,覆盖一些方法

xml标签的成对的,有开始,有结束

startDocument是?xml标签的回调处理方法

startElement方法是读到一个xml开始标签时的回调处理方法

endElement是标签结束的回调处理方法

characters方法是处理xml中的v标签中间的内容的回调处理方法

【注意xml中的c与v标签】

c就是cell单元格,c的属性r是行列号,t是类型,当t是s,表示是SST(SharedStringsTable) 的索引,其他类型很多,不一一列举,打开调试看看完整xml内容,注意在自己的Handler中处理,比如单元格是日期格式等等

v是单元内容【或SST索引】,注意SST索引的取值方式

以下是一个基本的处理类,可以很好理解poi解析excel,可以根据需要完善一下,【包含空单元格处理,没有空行处理】

//单文件示例代码,转换结果在List<List<String>> container

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
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;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Test2 {

public static void main(String[] args) throws Exception{
File test = new File(".");
String file = test.getAbsolutePath()+"/src/main/resources/empty_cell 中文名.xlsx";

OPCPackage pkg = OPCPackage.open(file);
XSSFReader r = new XSSFReader( pkg );
InputStream in =  r.getSheet("rId1");
//查看转换的xml原始文件,方便理解后面解析时的处理,
// 注意:如果打开注释,下面parse()就读不到流的内容了
Test2.streamOut(in);

//下面是SST 的索引会用到的
SharedStringsTable sst = r.getSharedStringsTable();
//sst.writeTo(System.out);

XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
List<List<String>> container = new ArrayList<>();
parser.setContentHandler(new Myhandler(sst,container));

InputSource inputSource = new InputSource(in);
parser.parse(inputSource);

in.close();

Test2.printContainer(container);
}

public static void printContainer(List<List<String>> container) {
for(List<String> stringList:container)
{
for(String str:stringList)
{
System.out.printf("%15s",str+" | ");
}
System.out.println("");
}
}

//读取流,查看文件内容
public static void streamOut(InputStream in) throws Exception{
byte[] buf = new byte[1024];
int len;
while ((len=in.read(buf))!=-1){
System.out.write(buf,0,len);
}
}

}

class Myhandler extends DefaultHandler{

//取SST 的索引对应的值
private SharedStringsTable sst;

public void setSst(SharedStringsTable sst) {
this.sst = sst;
}

//解析结果保存
private List<List<String>> container;

public Myhandler(SharedStringsTable sst, List<List<String>> container) {
this.sst = sst;
this.container = container;
}

private String lastContents;

//有效数据矩形区域,A1:Y2
private String dimension;

//根据dimension得出每行的数据长度
private int longest;

//上个有内容的单元格id,判断空单元格
private String lastRowid;

//行数据保存
private List<String> currentRow;

//单元格内容是SST 的索引
private boolean isSSTIndex=false;

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//        System.out.println("startElement:"+qName);

if (qName.equals("dimension")){
dimension = attributes.getValue("ref");
longest = covertRowIdtoInt(dimension.substring(dimension.indexOf(":")+1) );
}
//行开始
if (qName.equals("row")) {
currentRow = new ArrayList<>();
}
if (qName.equals("c")) {
String rowId = attributes.getValue("r");

//空单元判断,添加空字符到list
if (lastRowid!=null)
{
int gap = covertRowIdtoInt(rowId)-covertRowIdtoInt(lastRowid);
for(int i=0;i<gap-1;i++)
{
currentRow.add("");
}
}else{
//第一个单元格可能不是在第一列
if (!"A1".equals(rowId))
{
for(int i=0;i<covertRowIdtoInt(rowId)-1;i++)
{
currentRow.add("");
}
}
}
lastRowid = rowId;

//判断单元格的值是SST 的索引,不能直接characters方法取值
if (attributes.getValue("t")!=null && attributes.getValue("t").equals("s"))
{
isSSTIndex = true;
}else{
isSSTIndex = false;
}
}

}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
//        System.out.println("endElement:"+qName);

//行结束,存储一行数据
if (qName.equals("row")) {

//判断最后一个单元格是否在最后,补齐列数
if(covertRowIdtoInt(lastRowid)<longest){
for(int i=0;i<longest- covertRowIdtoInt(lastRowid);i++)
{
currentRow.add("");
}
}

container.add(currentRow);
lastRowid=null;
}
//单元格内容标签结束,characters方法会被调用处理内容
if (qName.equals("v")) {
//单元格的值是SST 的索引
if (isSSTIndex){
String sstIndex = lastContents.toString();
try {
int idx = Integer.parseInt(sstIndex);
XSSFRichTextString rtss = new XSSFRichTextString(
sst.getEntryAt(idx));
lastContents = rtss.toString();
currentRow.add(lastContents);
} catch (NumberFormatException ex) {
System.out.println(lastContents);
}
}else {
currentRow.add(lastContents);
}

}

}

/**
* 获取element的文本数据
*/
public void characters(char[] ch, int start, int length)
throws SAXException {
lastContents = new String(ch, start, length);
}

/**
* 列号转数字   AB7-->28 第28列
* @param rowId
* @return
*/
public static int covertRowIdtoInt(String rowId){
int firstDigit = -1;
for (int c = 0; c < rowId.length(); ++c) {
if (Character.isDigit(rowId.charAt(c))) {
firstDigit = c;
break;
}
}
//AB7-->AB
//AB是列号, 7是行号
String newRowId = rowId.substring(0,firstDigit);
int num = 0;
int result = 0;
int length = newRowId.length();
for(int i = 0; i < length; i++) {
//先取最低位,B
char ch = newRowId.charAt(length - i - 1);
//B表示的十进制2,ascii码相减,以A的ascii码为基准,A表示1,B表示2
num = (int)(ch - 'A' + 1) ;
//列号转换相当于26进制数转10进制
num *= Math.pow(26, i);
result += num;
}
return result;

}

public static void main(String[] args) {
System.out.println(Myhandler.covertRowIdtoInt("AB7"));

}
}


需要用到的类,pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<groupId>com.dddd</groupId>
<artifactId>poisaxxls</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poi xml javaxls poixlsx