五、通過eventusermodel讀取文件
通過eventusermodel讀取文件要比使用usermodel復雜得多,但效率也要高不少,因為它要求應用程序一邊讀取數據,一邊處理數據。
eventusermodel實際上模擬了DOM環境下SAX處理XML文檔的辦法,應用程序首先要注冊期望處理的數據,eventusermodel將在遇到匹配的數據結構時回調應用程序注冊的方法。使用eventusermodel最大的困難在於你必須熟悉Excel工作簿的內部結構。
在HSSF中,低層次的二進制結構稱為記錄(Record)。記錄有不同的類型,每一種類型由org.apache.poi.hssf.record包中的一個Java類描述。例如,BOFRecord記錄表示Workbook或Sheet區域的開始,RowRecord表示有一個行存在並保存其樣式信息。
所有具有CellValueRecordInterface接口的記錄表示Excel的單元格,包括NumericRecord、LabelSSTRecord和FormulaRecord(還有其他一些,其中部分已被棄置不用,部分用於優化處理,但一般而言,HSSF可以轉換它們)。
下面是一個注冊事件處理句柄的例子:
private EventRecordFactory factory = new EventRecordFactory();
factory.registerListener(new ERFListener() {
public boolean processRecord(Record rec) {
(got BOF Record);
return true;
}
}, new short[] {BOFRecord.sid});
factory.processRecords(someInputStream);
六、HSSF電子表格結構
如前所述,HSSF建立在POIFS的基礎上。具體地說,Excel 97+文件是OLE 2復合文檔( OLE 2 Compound Document),底層的OLE 2復合文檔保存了一個總是命名為Workbook(Excel 95除外,HSSF不支持Excel 95)的流。
然而,宏和圖片並不保存在Workbook流,它們有自己獨立的流,有時甚至會放到OLE 2 CDF文件之內的另一個目錄。理想情況下,宏也應該被保留,不過目前POI項目中還沒有合適的API來處理宏。
每一個流之內是一組記錄,一個記錄其實就是一個字節數組,可分為記錄頭、記錄體兩部分。記錄頭指明了記錄的類型(也即ID)以及後繼數據的長度,記錄體被分割成多個字段(FIEld),字段包含數值數據(包括對其他記錄的引用)、字符數據或標記。
Excel工作簿的頂級結構:
Bla.xls {
OLE2CDF headers
"Workbook" stream {
Workbook {
Static String Table Record..
Sheet names… and pointers
}
Sheet {
ROW
ROW
…
NUMBER RECORD (cell)
LABELSST Record (cell)
…
}
Sheet
}
}
… images, Macros, etc.
Document Summary
Summary
七、通過HPSF讀取文檔屬性
在Microsoft Word、Excel、PowerPoint等軟件中,用戶可以通過“文件”→“屬性”菜單給文檔添加附加信息,包括文檔的標題、主題、摘要、類別、關鍵詞等,同時應用軟件本身還會加入最後訪問的用戶、最後訪問和修改/打印的日期時間等信息。
文檔的屬性和正文是分開保存的。如前所述,OLE 2 CDF文件內部就象是一個容器,裡面包含許多類似目錄和文件的結構,而POIFS就是用來訪問其中的文件的工具。這些文件也稱為流,文檔的屬性就保存在POIFS文件系統中專用的流裡面。
以一個Word文檔為例:雖然在資源管理器中你只看到一個叫做MyFile.doc的文檔,其實在這個文檔的內部,又包含了一個WordDocument、一個SummaryInformation和一個DocumentSummaryInformation文檔;通常還會有其他的文檔,這裡暫且不管。
你能夠猜出這些文檔(流)分別包含什麼內容嗎?不錯,WordDocument包含了你在Word裡面編輯的文本,文檔的屬性保存在SummaryInformation和DocumentSummaryInformation流裡面。也許將所有屬性保存在單個文檔裡面看起來太簡單了,所以Microsoft決心要使用兩個流,為了使事情更復雜一點,這兩個流的名字前面還加上了八進制的\005字符??這是一個不可打印的字符,因此前面就把它省略了。
Microsoft定義的標准屬性有一個好處,它們並不在乎主文檔到底是什麼類型??不管是Word文檔、Excel工作簿還是PowerPoint幻燈。只要你知道如何讀取Excel文檔的屬性,就知道了如何讀取其他文檔的屬性。
讀取文檔屬性其實並不復雜,因為Java程序可以利用POI項目的HPSF包。HPSF是 Horrible Property Set Format的縮寫,譯成中文就是“討厭的屬性集格式”。HPSF包是POI項目實現的讀取屬性工具,目前還不支持屬性寫入。
對於讀取Microsoft定義的標准屬性,通過HPSF提供的API可以很方便地辦到;但如果要讀取任意屬性集就要用到更一般化的API,可以想象它要比讀取標准屬性的API復雜不少。本文只介紹讀取標准屬性的簡單API,因為對大多數應用程序來說這已經完全足夠了。
下面就是一個讀取OLE 2 CDF文檔的標題(title)屬性的Java程序:
import Java.io.*;
import org.apache.poi.hpsf.*;
import org.apache.poi.poifs.eventfilesystem.*;
/**
* 讀取OLE 2文檔標題的示例程序,
* 在命令行參數中指定文檔的文件名字。
*/
public class ReadTitle
{
public static void main(String[] args) throws IOException
{
final String filename = args[0];
POIFSReader r = new POIFSReader();
r.registerListener(new MyPOIFSReaderListener(),
"\005SummaryInformation");
r.read(new FileInputStream(filename));
}
static class MyPOIFSReaderListener
implements POIFSReaderListener
{
public void processPOIFSReaderEvent(POIFSReaderEvent event)
{
SummaryInformation si = null;
try
{
si = (SummaryInformation)
PropertySetFactory.create(event.getStream());
}
catch (Exception ex)
{
throw new RuntimeException
("屬性集流\"" + event.getPath() +
event.getName() + "\": " + ex);
}
final String title = si.getTitle();
if (title != null)
System.out.println("標題: \"" + title + "\"");
else
System.out.println("該文檔沒有標題.");
}
}
}