7.3 使用POI來處理Excel和Word文件格式
Microsoft的Office系列產品擁有大量的用戶,Word、Excel也成為辦公文件的首選。在Java中,已經有很多對于Word、Excel的開源的解決方案,其中比較出色的是Apache的Jakata項目的POI子項目。該項目的官方網站是http://jakarta.apache.org/poi/。
POI包括一系列的API,它們可以操作基于MicroSoft OLE 2 Compound Document Format的各種格式文件,可以通過這些API在Java中讀寫Excel、Word等文件。POI是完全的Java Excel和Java Word解決方案。POI子項目包括:POIFS、HSSF、HDF、HPSF。表7-2對它們進行了簡要介紹。
表7-2 POI子項目介紹
子項目名
|
說明
|
POIFS(POI File System)
|
POIFS是POI項目中最早的最基礎的一個模塊,是Java到OLE 2 Compound Document Format的接口,支持讀寫功能,所有的其他項目都依賴與該項目。
|
HSSF(Horrible Spreadsheet Format)
|
HSSF是Java到Microsoft Excel 97(-2002)文件的接口,支持讀寫功能
|
HWPF(Horrible Word Processing Format)
|
HWPF是Java到Microsoft Word 97文件的接口,支持讀寫功能,但目前該模塊還處于剛開始開發階段,只能實現一些簡單文件的操作,在后續版本中,會提供更強大的支持
|
HPSF(Horrible Property Set Format)
|
HPSF 是Java到OLE 2 Compound Document Format文件的屬性設置的接口,屬性設置通常用來設置文檔的屬性(標題,作者,最后修改日期等),還可以設置用戶定義的屬性。HPSF支持讀寫功能,當前發布版本中直支持讀功能。
|
7.3.1 對Excel的處理類
下面通過HSSF提供的接口對Excel文件經行處理。首先需要下載POI的包,可以到apache的官方網站下載,地址為:http://apache.justdn.org/jakarta/poi/,本書采用的是poi-2.5.1-final-20040804.jar,讀者可以下載當前的穩定版本。把下載的包按照前面介紹的方式加入Build Path,然后新建一個ch7.poi包,并創建一個ExcelReader類。
ExcelReader類可以讀取一個XLS文件,然后將其內容逐行提取出來,寫入文本文件。其代碼如下。
代碼7.6
public class ExcelReader {
// 創建文件輸入流
private BufferedReader reader = null;
// 文件類型
private String filetype;
// 文件二進制輸入流
private InputStream is = null;
// 當前的Sheet
private int currSheet;
// 當前位置
private int currPosition;
// Sheet數量
private int numOfSheets;
// HSSFWorkbook
HSSFWorkbook workbook = null;
// 設置Cell之間以空格分割
private static String EXCEL_LINE_DELIMITER = " ";
// 設置最大列數
private static int MAX_EXCEL_COLUMNS = 64;
// 構造函數創建一個ExcelReader
public ExcelReader(String inputfile) throws IOException, Exception {
// 判斷參數是否為空或沒有意義
if (inputfile == null || inputfile.trim().equals("")) {
throw new IOException("no input file specified");
}
// 取得文件名的后綴名賦值給filetype
this.filetype = inputfile.substring(inputfile.lastIndexOf(".") + 1);
// 設置開始行為0
currPosition = 0;
// 設置當前位置為0
currSheet = 0;
// 創建文件輸入流
is = new FileInputStream(inputfile);
// 判斷文件格式
if (filetype.equalsIgnoreCase("txt")) {
// 如果是txt則直接創建BufferedReader讀取
reader = new BufferedReader(new InputStreamReader(is));
}
else if (filetype.equalsIgnoreCase("xls")) {
// 如果是Excel文件則創建HSSFWorkbook讀取
workbook = new HSSFWorkbook(is);
// 設置Sheet數
numOfSheets = workbook.getNumberOfSheets();
}
else {
throw new Exception("File Type Not Supported");
}
}
// 函數readLine讀取文件的一行
public String readLine() throws IOException {
// 如果是txt文件則通過reader讀取
if (filetype.equalsIgnoreCase("txt")) {
String str = reader.readLine();
// 空行則略去,直接讀取下一行
while (str.trim().equals("")) {
str = reader.readLine();
}
return str;
}
// 如果是XLS文件則通過POI提供的API讀取文件
else if (filetype.equalsIgnoreCase("xls")) {
// 根據currSheet值獲得當前的sheet
HSSFSheet sheet = workbook.getSheetAt(currSheet);
// 判斷當前行是否到但前Sheet的結尾
if (currPosition > sheet.getLastRowNum()) {
// 當前行位置清零
currPosition = 0;
// 判斷是否還有Sheet
while (currSheet != numOfSheets - 1) {
// 得到下一張Sheet
sheet = workbook.getSheetAt(currSheet + 1);
// 當前行數是否已經到達文件末尾
if (currPosition == sheet.getLastRowNum()) {
// 當前Sheet指向下一張Sheet
currSheet++;
continue;
} else {
// 獲取當前行數
int row = currPosition;
currPosition++;
// 讀取當前行數據
return getLine(sheet, row);
}
}
return null;
}
// 獲取當前行數
int row = currPosition;
currPosition++;
// 讀取當前行數據
return getLine(sheet, row);
}
return null;
}
// 函數getLine返回Sheet的一行數據
private String getLine(HSSFSheet sheet, int row) {
// 根據行數取得Sheet的一行
HSSFRow rowline = sheet.getRow(row);
// 創建字符創緩沖區
StringBuffer buffer = new StringBuffer();
// 獲取當前行的列數
int filledColumns = rowline.getLastCellNum();
HSSFCell cell = null;
// 循環遍歷所有列
for (int i = 0; i < filledColumns; i++) {
// 取得當前Cell
cell = rowline.getCell((short) i);
String cellvalue = null;
if (cell != null) {
// 判斷當前Cell的Type
switch (cell.getCellType()) {
// 如果當前Cell的Type為NUMERIC
case HSSFCell.CELL_TYPE_NUMERIC: {
// 判斷當前的cell是否為Date
if (HSSFDateUtil.isCellDateFormatted(cell)) {
// 如果是Date類型則,取得該Cell的Date值
Date date = cell.getDateCellValue();
// 把Date轉換成本地格式的字符串
cellvalue = cell.getDateCellValue().toLocaleString();
}
// 如果是純數字
else {
// 取得當前Cell的數值
Integer num = new Integer((int) cell
.getNumericCellValue());
cellvalue = String.valueOf(num);
}
break;
}
// 如果當前Cell的Type為STRIN
case HSSFCell.CELL_TYPE_STRING:
// 取得當前的Cell字符串
cellvalue = cell.getStringCellValue().replaceAll("'", "''");
break;
// 默認的Cell值
default:
cellvalue = " ";
}
} else {
cellvalue = "";
}
// 在每個字段之間插入分割符
buffer.append(cellvalue).append(EXCEL_LINE_DELIMITER);
}
// 以字符串返回該行的數據
return buffer.toString();
}
// close函數執行流的關閉操作
public void close() {
// 如果is不為空,則關閉InputSteam文件輸入流
if (is != null) {
try {
is.close();
} catch (IOException e) {
is = null;
}
}
// 如果reader不為空則關閉BufferedReader文件輸入流
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
reader = null;
}
}
}
}
7.3.2 ExcelReader的運行效果
下面創建一個main函數,用來測試上面的ExcelReader類,代碼如下。
代碼7.7
public static void main(String[] args) {
try{
ExcelReader er=new ExcelReader("c:\\xp.xls");
String line=er.readLine();
while(line != null){
System.out.println(line);
line=er.readLine();
}
er.close();
}catch(Exception e){
e.printStackTrace();
}
}
main函數先創建一個ExcelReader類,然后調用它提供的接口readLine,對XLS文件進行讀取,打印到控制臺,處理前的XLS文件如圖7-12所示。

圖7-12 處理前的XLS文件內容
運行main函數進行內容提取后,Eclipse的控制臺輸出如圖7-13所示。

圖7-13 輸出結果
可以看到,Excel文件中的內容已經被成功的輸出了出來。
7.3.3 POI中Excel文件Cell的類型
在讀取每一個Cell的值的時候,通過getCellType方法獲得當前Cell的類型,在Excel中Cell有6種類型,如表7-3所示。
表7-3 Cell的類型
CellType
|
說明
|
CELL_TYPE_BLANK
|
空值
|
CELL_TYPE_BOOLEAN
|
布爾型
|
CELL_TYPE_ERROR
|
錯誤
|
CELL_TYPE_FORMULA
|
公式型
|
CELL_TYPE_STRING
|
字符串型
|
CELL_TYPE_NUMERIC
|
數值型
|
本例采用了CELL_TYPE_STRING和CELL_TYPE_NUMERIC類型,因為在Excel文件中只有字符串和數字。如果Cell的Type為CELL_TYPE_NUMERIC時,還需要進一步判斷該Cell的數據格式,因為它有可能是Date類型,在Excel中的Date類型也是以Double類型的數字存儲的。Excel中的Date表示當前時間與1900年1月1日相隔的天數,所以需要調用HSSFDateUtil的isCellDateFormatted方法,判斷該Cell的數據格式是否是Excel Date類型。如果是,則調用getDateCellValue方法,返回一個Java類型的Date。
實際上Excel的數據格式有很多,還支持用戶自定義的類型,在Excel中,選擇一個單元格然后右鍵選擇“設置單元格格式”,在彈出的單元格格式中選中“數字”,如圖7-14所示。

圖7-14 Excel的單元格格式
圖中的數據有數值、貨幣、時間、日期、文本等格式。這些數據格式在POI中的HSSFDataFormat類里都有相應的定義。
HSSFDataFormat是HSSF子項目里面定義的一個類。類HSSFDataFormat允許用戶新建數據格式類型。HSSFDataFormat類包含靜態方法static java.lang.String getBuiltinFormat(short index),它可以根據編號返回內置數據類型。另外static short getBuiltinFormat(java.lang.String format)方法則可以根據數據類型返回其編號,static java.util.List getBuiltinFormats()可以返回整個內置的數據格式列表。
在HSSFDataFormat里一共定義了49種內置的數據格式,如表7-4所示。
表7-4 HSSFDataFormat的數據格式
內置數據類型
|
編號
|
"General"
|
0
|
"0"
|
1
|
"0.00"
|
2
|
"#,##0"
|
3
|
"#,##0.00"
|
4
|
"($#,##0_);($#,##0)"
|
5
|
"($#,##0_);[Red]($#,##0)"
|
6
|
"($#,##0.00);($#,##0.00)"
|
7
|
"($#,##0.00_);[Red]($#,##0.00)"
|
8
|
"0%"
|
9
|
"0.00%"
|
0xa
|
"0.00E+00"
|
0xb
|
"# ?/?"
|
0xc
|
"# ??/??"
|
0xd
|
"m/d/yy"
|
0xe
|
"d-mmm-yy"
|
0xf
|
"d-mmm"
|
0x10
|
"mmm-yy"
|
0x11
|
"h:mm AM/PM"
|
0x12
|
"h:mm:ss AM/PM"
|
0x13
|
"h:mm"
|
0x14
|
"h:mm:ss"
|
0x15
|
"m/d/yy h:mm"
|
0x16
|
保留為過國際化用
|
0x17 - 0x24
|
"(#,##0_);(#,##0)"
|
0x25
|
"(#,##0_);[Red](#,##0)"
|
0x26
|
"(#,##0.00_);(#,##0.00)"
|
0x27
|
"(#,##0.00_);[Red](#,##0.00)"
|
0x28
|
"_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)"
|
0x29
|
"_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)"
|
0x2a
|
"_($*#,##0.00_);_($*(#,##0.00);_($*\"-\"??_);_(@_)"
|
0x2b
|
"_($*#,##0.00_);_($*(#,##0.00);_($*\"-\"??_);_(@_)"
|
0x2c
|
"mm:ss"
|
0x2d
|
"[h]:mm:ss"
|
0x2e
|
"mm:ss.0"
|
0x2f
|
"##0.0E+0"
|
0x30
|
"@" - This is text format
|
0x31
|
在上面表中,字符串類型所對應的是數據格式為"@"(最后一行),也就是HSSFDataFormat中定義的值為0x31(49)的那行。Date類型的值的范圍是0xe-0x11,本例子中的Date格式為""m/d/yy"",在HSSFDataFormat定義的值為0xe(14)。
需要注意的一點是,所創建的Excel必須是在Microsoft Excel 97到Excel XP的版本上的,如果在Excel 2003中創建文件后,在使用POI進行解析時,可能會出現問題。它會把Date類型當作自定義類型。POI目前只提供對Microsoft Excel XP以下的版本的支持,在以后的版本中,希望會提供對Microsoft Excel 2003更好的支持。
7.3.4 對Word的處理類
除了支持對Excel文件的讀取外,POI還提供對Word的DOC格式文件的讀取。但在它的發行版本中沒有發布對Word支持的模塊,需要另外下載一個POI的擴展的Jar包。用戶可以到http://www.ibiblio.org/maven2/org/textmining/tm-extractors/0.4/下載,本書采用的是tm-extractors-0.4_zip。
下載后,把該包加入工程的Build Path中,然后在ch7.poi包下新建一個類WordReader,該類提供一個靜態方法readDoc,讀取一個DOC文件并返回文本。函數內容很簡單,就是調用WordExtractor的API來提取DOC的內容到字符串,該函數的代碼如下。
代碼7.8
public static String readDoc(String doc) throws Exception {
// 創建輸入流讀取DOC文件
FileInputStream in = new FileInputStream(new File(doc));
WordExtractor extractor = null;
String text = null;
// 創建WordExtractor
extractor = new WordExtractor();
// 對DOC文件進行提取
text = extractor.extractText(in);
return text;
}
在同一個類里創建一個main函數,測試WordReader,該main函數代碼如下。
代碼7.9
public static void main(String[] args) {
try{
String text = WordReader.readDoc("c:/test.doc");
System.out.println(text);
}catch(Exception e){
e.printStackTrace();
}
}
處理前的Doc文件如圖7-15所示。

圖7-15 處理前的Word文檔
使用代碼處理后的文本如圖7-16所示。

圖7-16 處理后的結果
可以看到Word文檔內的文本已經全部被提取了出來。