POI事件模式指北(二)-Excel2007
1、简介
Excel2007(.xlsx)是现在最常用的Excel格式,对这种文件的读取也是非常常见的需求;同样的POI也提供用户模式(User API) 和事件模式(Event API) 两种方式供大家使用。
POI的事件模式占用内存更小,它利用基础的XML数据进行处理,适用于愿意学习.xlsx文件结构以及在java中处理XML的开发人员。
那么事不宜迟,Let‘s go!
2、Excel文件的XML结构
了解文件结构之前先来看一下准备的文件,这个文件只有一个sheet页,结构也很简单。
Excel2007是用XMl格式储存,将要读取的文件后缀名改为.zip或者直接用解压缩工具打开,就可以看到这个Excel文件的结构了。
2.1、[Content_Types].xml
[Content_Types].xml文件描述了整个Excel文件的结构,也将根据这个文件组织后面的读取工作。
2.1、docProps文件夹
docProps文件夹下面有两个xml,里面记录的Excel的属性信息。
app.xml : 记录文档的创建时间和修改时间,以及标题、作者等。
core.xml: 记录文档的其他属性,包括版本、是否只读、安全等。
2.2、xl文件夹
xl文件夹包括了需要的数据和格式信息,是重点关注的对象。
workbook.xml: 记录了工作表基本信息,是我们重点关注的文件之一。
styles.xml: 记录了每个单元格的样式。
worksheets: 里面包括了我们的每个sheet的信息,储存在xml文件中。
2.3、workbook.xml
workbook.xml重点关注的就是sheets和sheet两个标签
- sheet标签中name属性记录的就是sheet的名称
- sheet标签中r:id属性记录了当前sheet和之前提到的记录sheet信息的xml之间的对应关系,储存在_rels文件夹下的xml文件中。
- sheet标签还有一个属性state标识来是否隐藏。
2.4、worksheets文件夹
一般一个Excel文件有几个sheet页,就会有几个XML文件与之对应。其中sheet页和xml文件就是根据【新建 Microsoft Excel 工作表xl\_relsworkbook.xml.rels】文件对应起来的。
2.5、sheet1.xml
现在回头看一下开始时展示的文件,实在是再清晰不过了。row标签代表一行、c标签代表一个单元格、v标签代表单元格中的值;但是B2的值是0,可实际的值明明是a,这是怎么回事呢?
其实Excel将所有字符值都储存在共享字符集中,每个用到的地方只用对应的索引来表示,a是第一个字符值,所以索引是0。
2.6、小结
至此Excel文件的基本结构就介绍的差不多了,其实数据基本都储存在sheet*.xml中了,只需要按照规则读取出来就行了。
还有诸如公式单元格,合并单元格,隐藏的单元格并没有介绍,不过了解上面这些,即使遇到其他情况也能从容应对了。
3、读取.xlsx文件实例
读取Excel2007文件的思路就是,先解压,然后依次读取各个xml文件,然后按需要的方式处理。
- 用OPCPackage以压缩文件的形式打开文件。
- 读取文件流
- 捕获开始和结束标签并分类处理
下面是POI官网上的示例,做了简单的修改。
想要更完整的示例,可以看官方SVN上的例子
import java.io.InputStream;
import java.util.Iterator;
import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
public class ExampleEventUserModel {
public void processOneSheet(String filename) throws Exception {
OPCPackage pkg = OPCPackage.open(filename);
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
//选择要处理的sheet页
//通常形式为rId#或rSheet#(这里选择了第一个)
InputStream sheet1 = r.getSheet("rId1");
InputSource sheetSource = new InputSource(sheet1);
parser.parse(sheetSource);
sheet2.close();
}
public void processAllSheets(String filename) throws Exception {
OPCPackage pkg = OPCPackage.open(filename);
XSSFReader r = new XSSFReader( pkg );
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
Iterator<InputStream> sheets = r.getSheetsData();
while(sheets.hasNext()) {
System.out.println("处理新sheet:\n");
InputStream sheet = sheets.next();
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
System.out.println("");
}
}
public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException, ParserConfigurationException {
XMLReader parser = SAXHelper.newXMLReader();
ContentHandler handler = new SheetHandler(sst);
parser.setContentHandler(handler);
return parser;
}
/**
* 请参阅 org.xml.sax.helpers.DefaultHandler javadocs
*/
private static class SheetHandler extends DefaultHandler {
private SharedStringsTable sst;
private String lastContents;
private boolean nextIsString;
private SheetHandler(SharedStringsTable sst) {
this.sst = sst;
}
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
// c代表单元格
if(name.equals("c")) {
// 打印单元格
System.out.print(attributes.getValue("r") + " - ");
// 确定该值是否为SST
String cellType = attributes.getValue("t");
if(cellType != null && cellType.equals("s")) {
nextIsString = true;
} else {
nextIsString = false;
}
}
// 清除缓存
lastContents = "";
}
public void endElement(String uri, String localName, String name)
throws SAXException {
// 获取对应的文本值
if(nextIsString) {
int idx = Integer.parseInt(lastContents);
lastContents = sst.getItemAt(idx).getString();
nextIsString = false;
}
// v代表单元格内容
// Output after we've seen the string contents
if(name.equals("v")) {
System.out.println(lastContents);
}
}
public void characters(char[] ch, int start, int length) {
lastContents += new String(ch, start, length);
}
}
public static void main(String[] args) throws Exception {
ExampleEventUserModel example = new ExampleEventUserModel();
example.processOneSheet(args[0]);
example.processAllSheets(args[0]);
}
}
这个例子只处理了文本值,数字处理起来还要更容易一些,但是实际使用的时候会发现,因为xml文件中空单元格和空行直接跳过了,所以要考虑填补空白格和行的事情,只要熟悉了Excel的结构,还是不难的。
4、后记
现在微软终于开窍,使用xml格式组织Excel文件,让更方便的读取Excel文件成为可能。更贴近实战的示例我会在下一篇展示,敬请期待。
参考链接
Apache POI官网: https://poi.apache.org/index.html