筆者據(jù)使用經(jīng)驗以為:Apache的開源項目。當然POI HSSF也有缺點:不能直接支持EXCEL圖表,API文檔粗糙簡略,有些類和方法需要引用Apache項目中的其它一些包,包與包之間依賴關系比較復雜等等。
本文內(nèi)容不在于提供一個POI HSSF 的完整使用指南(上面那個Apache主頁上的Quick Guide已經(jīng)非常詳細),而是列出一些筆者在項目開發(fā)過程中找到的一些技巧、經(jīng)驗。
下圖是定義的模板PoiTest.xls:
通過POI生成的最終結(jié)果:
(單元格中的數(shù)據(jù)發(fā)生變化時,圖表中的柱狀、折線也會跟著變化)
處理圖表
目前POI版本為2.5.1,org.apache.poi.hssf.usermodel包里有一個HSSFChart類,里面只有一個空方法createBarChart(),表明POI還不直接支持EXCEL圖表。
替代方法還是有的:因為EXCEL圖表的源數(shù)據(jù)引用自EXCEL單元格。我們可以先新建一個EXCEL工作薄,作為模板,在里面創(chuàng)建圖表,指定它引用工作表中的一些特定單元格。然后我們用POI來讀取這個工作薄,把數(shù)據(jù)寫入到那些特定單元格。
首先要在模板里創(chuàng)建可以動態(tài)引用單元格的“名稱”,利用“名稱”來創(chuàng)建圖表中的“系列”。
一、打開模板PoiTest.xls,點擊[插入]>[名稱]>[定義],創(chuàng)建四個“名稱”sx,s1y,s2y,s3y:
sx=OFFSET(Sheet1!$A$17,0,2,1,COUNTA(Sheet1!$17:$17)-4)
s1y=OFFSET(Sheet1!$A$18,0,2,1,COUNTA(Sheet1!$18:$18)-4)
s2y=OFFSET(Sheet1!$A$19,0,2,1,COUNTA(Sheet1!$19:$19)-3)
s3y=OFFSET(Sheet1!$A$20,0,2,1,COUNTA(Sheet1!$20:$20)-3)
這里用到了兩個EXCEL函數(shù),OFFSET()和COUNTA()函數(shù)。
其中COUNTA()可以返回一行或一列的單元格總數(shù):
比如COUNTA(Sheet1!$A:$A),計算工作表Sheet1的A列的單元格數(shù)目。
又比如COUNTA(Sheet1!$17:$17),計算的是Sheet1的第17行的單元格數(shù)目。
當我們沒有在單元格里鍵入數(shù)據(jù)時,該單元格是不會被COUNTA()計算的。
OFFSET()函數(shù)用來引用一系列連續(xù)的單元格,它共有五個參數(shù):
參數(shù)一,作為位置參照的單元格。
參數(shù)二,行的起始偏移量(以參數(shù)一為參照)。
參數(shù)三,列的起始偏移量(以參數(shù)一為參照)。
參數(shù)四,跨行數(shù)。
參數(shù)五,跨列數(shù)。
比如:
OFFSET(Sheet1!$A:$1,1,2,3,4),表示引用范圍為:C2:F4。
二、在模板中創(chuàng)建圖表,在圖表上點右鍵,選擇[源數(shù)據(jù)]>[系列],如圖建立三個系列:
點[添加]創(chuàng)建新的系列:
[名稱]表示系列名,可以直接輸入字串,也可以引用EXCEL單元格。
在[值]中輸入我們在上一步中創(chuàng)建的“名稱”,格式為:模板名.xls!名稱。
在[分類(X)軸標志(T)]中輸入我們在上一步中創(chuàng)建的“名稱”sx,格式為:模板名.xls!名稱。它表示圖表區(qū)域的X軸將要顯示的內(nèi)容。
三、用POI把數(shù)據(jù)寫入到相應的單元格中,圖表將會自動顯示對應的信息。
注意:
上面這種方法適用數(shù)據(jù)集合行數(shù)固定而列數(shù)動態(tài)變化的情況。
對于行數(shù)也動態(tài)變化的情況,只能先在模板里預設盡可能多的“名稱”和“系列”。
對于行數(shù)和列數(shù)都固定的情形,沒必要這么復雜,只要在圖表的[源數(shù)據(jù)]里設置[數(shù)據(jù)區(qū)域],使之引用EXCEL模板中的一定范圍,如下圖:
設置單元格樣式
HSSFCellStyle類代表一種單元格樣式??梢酝ㄟ^這個類來設置單元格的邊框樣式、背景顏色、字體、水平和垂直對齊方式等等。
HSSFCellStyle titleStyle = workbook.createCellStyle();
titleStyle.setBorderBottom(HSSFCellStyle.BORDER_DOUBLE);
titleStyle.setBorderLeft((short)1);
titleStyle.setBorderRight((short)1);
titleStyle.setBorderTop(HSSFCellStyle.BORDER_DOUBLE);
titleStyle.setFillForegroundColor(HSSFColor.LIGHT_ORANGE.index);
titleStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
注意:如果我們定義了一種樣式,把它賦給一些單元格。然后基于新的需要,更改該樣式中的某個屬性,再賦給另一些單元格。那么之前單元格樣式的該屬性也會被同時更改。
比如我們定義了樣式,設置單元格背景色為紅色:
HSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFillForegroundColor(HSSFColor.RED.index);
cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
然后把它賦給一個單元格:
HSSFCell cell1 = row.createCell((short)1);
cell1.setCellStyle(cellStyle);
然后更改樣式中的背景色屬性為藍色:
cellStyle.setFillForegroundColor(HSSFColor.BLUE.index);
然后賦給另一個單元格:
HSSFCell cell2 = row.createCell((short)2);
cell2.setCellStyle(cellStyle);
想當然,我們預計在最終結(jié)果中cell1的背景色為紅色,cell2的背景色為藍色。但是結(jié)果是:兩個單元格的背景色都變成了藍色。
遇到這種情況,要預先定義兩種不同的單元格樣式。
當一個EXCEL文件同時需要很多大同小異的單元格樣式時,這樣一一定義很麻煩。POI HSSF提供了一個HSSFCellUtil類(在org.apache.poi.hssf.usermodel.contrib包),里面有幾個方法可以繞過HSSFCellStyle直接設定單元格的樣式,但這幾個方法會拋出NestableException異常,要處理這個異常,需要引用Apache的幾個Common包:
commons-beanutils.jar
commons-beanutils-bean-collections.jar
commons-beanutils-core.jar
commons-lang.jar
commons-logging-api.jar
合并單元格
HSSFSheet.addMergedRegion(new Region())方法可以合并單元格,Region()中的一個構(gòu)造函數(shù)含有四個參數(shù),分別代表起始行、起始列、結(jié)束行、結(jié)束列:
sheet.addMergedRegion(new Region(initRow, (short)(initCol-2), initRow + lists.size() - 1, (short)(initCol-2)));
處理公式
HSSFCell.setCellFormula()方法用來在EXCEL單元格中寫入公式。
cell = row.createCell((short)(dataFlag));
cell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
cell.setCellFormula("SUM(" + getColLetter(initCol) + (listFlag+1) +
":" + getColLetter(dataFlag-1) + (listFlag+1) + ")");
cell.setCellStyle(nameStyle);
處理鏈接
在POI中往單元格中寫鏈接,是用HYPERLINK函數(shù)搞定的。
HYPERLINK函數(shù)包含兩個參數(shù),第一個參數(shù)是指向的URL地址,第二個參數(shù)是顯示字串。
cell = row.createCell((short)(dataFlag));
cell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
cell.setCellFormula("HYPERLINK(\" cell.setCellStyle(linkStyle);
為了使鏈接效果更好,我們可以給鏈接所在單元格定義一種樣式,使鏈接顯示為有下劃線的藍色字串:
HSSFCellStyle linkStyle = workbook.createCellStyle();
linkStyle.setBorderBottom((short)1);
linkStyle.setBorderLeft((short)1);
linkStyle.setBorderRight((short)1);
linkStyle.setBorderTop((short)1);
linkStyle.setFillForegroundColor(HSSFColor.SKY_BLUE.index);
linkStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
HSSFFont font = workbook.createFont();
font.setFontName(HSSFFont.FONT_ARIAL);
font.setUnderline((byte)1);
font.setColor(HSSFColor.BLUE.index);
linkStyle.setFont(font);
中文處理:
要在通過POI生成的EXCEL中正常顯示中文,需要為單元格設置編碼:
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellValue("部門");
完整的PoiServlet類:
package org.eleaf.poi.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.hssf.util.Region;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
public class PoiServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=PoiTest.xls");
ServletOutputStream sos = response.getOutputStream();
HSSFWorkbook workbook = new HSSFWorkbook(getServletContext().getResourceAsStream("/PoiTest.xls"));
HashMap map = getDatas();
workbook = writeDatas(workbook, map);
workbook.write(sos);
sos.close();
}
/**
* 將數(shù)據(jù)寫入到EXCEL中。
* @param workbook
* @param map 數(shù)據(jù)集合
* @return
*/
private HSSFWorkbook writeDatas(HSSFWorkbook workbook, HashMap map)
{
HSSFCellStyle titleStyle = workbook.createCellStyle();
titleStyle.setBorderBottom(HSSFCellStyle.BORDER_DOUBLE);
titleStyle.setBorderLeft((short)1);
titleStyle.setBorderRight((short)1);
titleStyle.setBorderTop(HSSFCellStyle.BORDER_DOUBLE);
titleStyle.setFillForegroundColor(HSSFColor.LIGHT_ORANGE.index);
titleStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
HSSFCellStyle dataStyle = workbook.createCellStyle();
dataStyle.setBorderBottom((short)1);
dataStyle.setBorderLeft((short)1);
dataStyle.setBorderRight((short)1);
dataStyle.setBorderTop((short)1);
dataStyle.setFillForegroundColor(HSSFColor.LIGHT_GREEN.index);
dataStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
dataStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
HSSFCellStyle nameStyle = workbook.createCellStyle();
nameStyle.setBorderBottom((short)1);
nameStyle.setBorderLeft((short)1);
nameStyle.setBorderRight((short)1);
nameStyle.setBorderTop((short)1);
nameStyle.setFillForegroundColor(HSSFColor.LIGHT_CORNFLOWER_BLUE.index);
nameStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
HSSFCellStyle linkStyle = workbook.createCellStyle();
linkStyle.setBorderBottom((short)1);
linkStyle.setBorderLeft((short)1);
linkStyle.setBorderRight((short)1);
linkStyle.setBorderTop((short)1);
linkStyle.setFillForegroundColor(HSSFColor.SKY_BLUE.index);
linkStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
HSSFFont font = workbook.createFont();
font.setFontName(HSSFFont.FONT_ARIAL);
font.setUnderline((byte)1);
font.setColor(HSSFColor.BLUE.index);
linkStyle.setFont(font);
HSSFSheet sheet = workbook.getSheetAt(0);
final int initRow = 17;
final int initCol = 2;
HSSFRow rTitle = sheet.createRow(initRow - 1);
List lists = (List) map.get("list");
List titles = (List)map.get("title");
int titleFlag = initCol;
for (Iterator it = titles.iterator(); it.hasNext();)
{
String title = (String)it.next();
HSSFCell cell = rTitle.createCell((short)titleFlag);
cell.setCellStyle(titleStyle);
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setCellValue(title);
titleFlag++;
}
HSSFCell cell = rTitle.createCell((short)(titleFlag));
cell.setCellStyle(titleStyle);
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellValue("總計");
titleFlag++;
cell = rTitle.createCell((short)(titleFlag));
cell.setCellStyle(titleStyle);
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellValue("鏈接");
cell = rTitle.createCell((short)(initCol-1));
cell.setCellStyle(titleStyle);
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellValue("職員");
cell = rTitle.createCell((short)(initCol-2));
cell.setCellStyle(titleStyle);
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellValue("部門");
int listFlag = initRow;
for (Iterator it = lists.iterator(); it.hasNext();)
{
String name = (String)it.next();
List datas = (List)map.get(name);
HSSFRow row = sheet.createRow(listFlag);
cell = row.createCell((short)(initCol-1));
cell.setCellStyle(nameStyle);
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellValue(name);
cell = row.createCell((short)(initCol-2));
cell.setEncoding(HSSFCell.ENCODING_UTF_16);
cell.setCellStyle(dataStyle);
int dataFlag = initCol;
System.out.println("datas=" + datas);
for (Iterator ite = datas.iterator(); ite.hasNext();)
{
int data = ((Integer)ite.next()).intValue();
cell = row.createCell((short)dataFlag);
cell.setCellStyle(dataStyle);
cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
cell.setCellValue(data);
dataFlag++;
}
cell = row.createCell((short)(dataFlag));
cell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
cell.setCellFormula("SUM(" + getColLetter(initCol) + (listFlag+1) +
":" + getColLetter(dataFlag-1) + (listFlag+1) + ")");
cell.setCellStyle(nameStyle);
dataFlag++;
cell = row.createCell((short)(dataFlag));
cell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
cell.setCellFormula("HYPERLINK(\" cell.setCellStyle(linkStyle);
listFlag++;
}
sheet.getRow(initRow).getCell((short)(initCol-2)).setCellValue("武裝部");
sheet.addMergedRegion(new Region(initRow, (short)(initCol-2), initRow + lists.size() - 1, (short)(initCol-2)));
return workbook;
}
/**
* 將列的索引換算成ABCD字母,這個方法要在插入公式時用到。
* @param colIndex 列索引。
* @return ABCD字母。
*/
private String getColLetter(int colIndex)
{
String ch = "";
if (colIndex < 26)
ch = "" + (char)((colIndex) + 65);
else
ch = "" + (char)((colIndex) / 26 + 65 - 1) + (char)((colIndex) % 26 + 65);
return ch;
}
/**
* 獲得數(shù)據(jù),組織為HashMap. 這里為了演示方便,簡單生成了一些數(shù)據(jù)。在實際應用中,是從
* 數(shù)據(jù)庫中獲取數(shù)據(jù)的。
* @return 組織后的數(shù)據(jù)
*/
private HashMap getDatas()
{
HashMap map = new HashMap();
List lists = new ArrayList();
List title = new ArrayList();
List a = new ArrayList();
List b = new ArrayList();
List c = new ArrayList();
for (int i = 1; i <= 8; i++)
{
title.add(i + "月");
a.add(new Integer((int)(Math.random() * 10)));
b.add(new Integer((int)(Math.random() * 10)));
c.add(new Integer((int)(Math.random() * 10)));
}
map.put("荊軻", a);lists.add("荊軻");
map.put("專諸", b); lists.add("專諸");
map.put("聶政", c); lists.add("聶政");
map.put("list", lists);
map.put("title", title);
System.out.println("map=" + map);
return map;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}