功能需求是根據客戶提供的excel模板,程序動態填充其中的一些數據。該模板包含大量的宏,剛拿到的時候頭都要暈了,本人雖天天和電腦打交道,但是excel咱不是高手啊,什么宏,之前光聽過,聽着都覺得是高級的東西,就暈,但作為富有責任心的程序員,咱得攻克,也真的攻克了,現在鄙人去搞財務也沒問題啊。
之前看各種評論說poi普遍對excel的支持更多一些,比較好用,具體jxl不好用在哪里,沒有具體的說法,我就以為無傷大雅,而且項目之前的一些功能都是用jxl的,我就繼續沿用jxl。首先來說下流程:
1.根據excel模板創建新工作簿,本質就是copy一份新的,在新的上面進行后續操作。
2.讀取其中兩個sheet頁,對其中內容進行修改。
3.設置密碼保護。
4.給第一個sheet頁設置選中狀態。
5.寫出excel文件,關閉流之類的。
下面直接貼代碼,這都是瀝心之作啊,一筆一畫都是心血,程序猿虧心虧腦啊,天氣炎熱,得好好休養。
首先是變量,這玩意是我自己項目需要,貼出來只為大家看代碼看的明白,沒啥意思的哈。
int colRefNo = 8; //RefNo所在列,列數是從0開始
int colVari = 9; //Variables所在列
int colChannel = 10; //Channel所在列
int colAFYP= 11; //AFYP所在列
int colNBSP= 12; //NBSP所在列
int colVONB= 14; //VONB所在列
int channelNum = 8; //excel模板channel的個數
int channelPart = 35; //每個channel的部分占多少行
int startRow = 18; //excel模板上數據的起始行,行數從0開始的
先貼jxl,斷斷續續搞了近一個月,中間端午回家,要不是因為它,我完全可以毫無牽掛地休上半個月到一個月的,說起都是淚啊。
public String modifyExcel(){
String infilepath = "c:/AA.xls";//模板文件位置
String filePath = "c:/test/";
String fileName = "jxlFile.xls";
String targetfile = filePath + fileName;//生成文件位置
File file = new File(targetfile);
//判斷文件夾是否存在,不存在就創建
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
//創建新工作簿
Workbook rw = null;
WritableWorkbook wwb = null;
try {
rw = Workbook.getWorkbook(new File(infilepath)); //讀入excel模板
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
try {
wwb = Workbook.createWorkbook(file, rw); //根據現有只讀文件創建一個可寫入的Excel工作薄對象
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
//讀取第一張隱藏的工作表,取消隱藏屬性
WritableSheet ws0 = wwb.getSheet(0);
ws0.setHidden(true);
System.out.println("===ws0.isHidden()="+ws0.isHidden()+"==ws0.isProtected()==="+ws0.isProtected());
WritableSheet wsPP = wwb.getSheet(2); //讀取第三張工作表,PP
WritableSheet wsNP = wwb.getSheet(3); //讀取第四張工作表,NP
wsPP = modifySheet(wsPP,"PP"); //對PP修改內容
wsNP = modifySheet(wsNP,"NP"); //對NP修改內容
SheetSettings ssPP = wsPP.getSettings();//設置PP的密碼保護
ssPP.setPassword("ABCDEFG");
ssPP.setProtected(true);
SheetSettings ssNP = wsNP.getSettings();//設置NP的密碼保護
ssNP.setPassword("ABCDEFG");
ssNP.setProtected(true);
ssNP.setSelected(false);
//讀取第二張的工作表,設置選中狀態
WritableSheet ws1 = wwb.getSheet(1);
ws1.getSettings().setSelected(true);
//寫入Excel對象
try {
wwb.write();
} catch (Exception e) {
return e.getMessage();
}
//關閉可寫入的Excel對象
try {
wwb.close();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
//關閉只讀的Excel對象
rw.close();
return "文件"+fileName+"已生成在"+filePath+"下!";
}
private WritableSheet modifySheet(WritableSheet ws,String pnp) {
int start = startRow;
DailySalesBean dsb = new DailySalesBean();
dsb.setPnp(pnp);
for (int j= 0 ; j< channelNum; j++){
int end = start+channelPart;
for(int i= start;i< end;i++){
Cell cellRefNo = ws.getCell(colRefNo, i);
Cell cellVari = ws.getCell(colVari, i);
Cell cellChannel = ws.getCell(colChannel, i);
//獲取excel中某些列的值作為查詢條件
dsb.setRefno(ObjectUtil.isEmpty(cellRefNo.getContents())?"":cellRefNo.getContents());
dsb.setVari(ObjectUtil.isEmpty(cellVari.getContents())?"":cellVari.getContents());
dsb.setChannel(ObjectUtil.isEmpty(cellChannel.getContents())?"":cellChannel.getContents());
//根據條件得出數據結果
DailySalesBean dsb2 = getDailyData(dsb);
WritableCell afypCell = ws.getWritableCell(colAFYP,i);
if(afypCell.getType() == CellType.NUMBER ){//單元格的數據格式,保證是number類型,涉及到自動計算
Number afyp = (Number)afypCell;
afyp.setValue(Double.parseDouble(dsb2.getAfyp()));//修改單元格的數值
}
WritableCell nbspCell = ws.getWritableCell(colNBSP,i);
if(nbspCell.getType() == CellType.NUMBER ){
Number nbsp = (Number)nbspCell;
nbsp.setValue(Double.parseDouble(dsb2.getNbsp()));
}
WritableCell vonbCell = ws.getWritableCell(colVONB,i);
if(vonbCell.getType() == CellType.NUMBER ){
Number vonb = (Number)vonbCell;
vonb.setValue(Double.parseDouble(dsb2.getVonb()));
}
//上面是獲取單元格,直接修改值。下面注釋掉的是另外一種方法,是new一個新的單元格,設置完值之后,add到sheet頁里面去,個人覺得有種多余,應該浪費資源會多一
//點。但是在將數據單元格改為字符串單元格,涉及到數據類型更改的時候,后一種方法就派上用場了。
/* CellFormat cf1 = null;
CellFormat cf2 = null;
CellFormat cf3 = null;
if(j==0 && k==1 ){
Cell afypCell_old = ws.getCell(colAFYP,i);
Cell nbspCell_old = ws.getCell(colNBSP,i);
Cell vonbCell_old = ws.getCell(colVONB,i);
cf1 = afypCell_old.getCellFormat();
cf2 = nbspCell_old.getCellFormat();
cf3 = vonbCell_old.getCellFormat();//為保持原有單元格的樣式,譬如邊框,顏色,字體之類的,需要獲取原有樣式賦到新單元格上
}
Number afypCell = new Number(colAFYP, i, Double.parseDouble(dsb2.getAfyp()));
afypCell.setCellFormat(cf1);
try {
ws.addCell(afypCell);
} catch (Exception e) {
e.printStackTrace();
}
Number nbspCell = new Number(colNBSP, i, Double.parseDouble(dsb2.getNbsp()));
nbspCell.setCellFormat(cf2);
try {
ws.addCell(nbspCell);
} catch (Exception e) {
e.printStackTrace();
}
Number vonbCell = new Number(colVONB, i, Double.parseDouble(dsb2.getVonb()));
vonbCell.setCellFormat(cf3);
try {
ws.addCell(vonbCell);
} catch (Exception e) {
e.printStackTrace();
}
*/
}
start += channelPart;
}
return ws;
}
再貼個簡潔的POI代碼:
public String modifyExcel(){
String infilepath = "c:/AA.xls";
String filePath = "c:/Sales_Daily_java/";
String fileNane = "InputTemplate-Sales_Daily_Draft.xls";
String targetfile = filePath + fileNane;
File file = new File(targetfile);
//判斷文件夾是否存在,不存在就創建
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
//創建新工作簿
HSSFWorkbook wb = null;//創建97-03版excel,如果是07-10版,則XSSFWorkbook wb = null;
NPOIFSFileSystem fs = null;
try {
fs = new NPOIFSFileSystem(new File(infilepath));
} catch (Exception e1) {
e1.printStackTrace();
return "讀取excel模板文件失敗!";
}
try {
wb = new HSSFWorkbook(fs.getRoot(),true);
} catch (Exception e1) {
e1.printStackTrace();
return "創建excel文件失敗!";
}
//讀取第三張工作表,PP_DailyInput
Sheet spp = wb.getSheetAt(2);
//讀取第四張工作表,NP_DailyInput
Sheet snp = wb.getSheetAt(3);
spp = modifySheet(spp,"PP"); //對PP_DailyInput修改內容
snp = modifySheet(snp,"NP"); //對NP_DailyInput修改內容
spp.protectSheet("XDFZALDEXCAVPGS");//設置PP_DailyInput Sheet的密碼保護
snp.protectSheet("XDFZALDEXCAVPGS");//設置NP_DailyInput Sheet的密碼保護
snp.setSelected(false);
//讀取第二張的工作表,設置選中狀態
Sheet s1 = wb.getSheetAt(1);
s1.setSelected(true);
//輸出流文件,關閉流
FileOutputStream fileOut = null;
try {
fileOut = new FileOutputStream(targetfile);
wb.write(fileOut);
fileOut.flush();
fileOut.close();
} catch (Exception e) {
e.printStackTrace();
return "excel文件生成失敗!";
}
try {
fs.close(); //一定要比上面的后關閉,不然write的時候會拋異常,文件寫出不了
} catch (IOException e1) {
e1.printStackTrace();
}
return "文件"+fileNane+"已生成在"+filePath+"下!";
}
/**
* 獲取單元格的值
* @param cell
* @return
*/
private String getCellValue(Cell cell){
String result = null;
switch (cell.getCellType()) {
case Cell.CELL_TYPE_STRING:
result = cell.getRichStringCellValue().getString();
break;
case Cell.CELL_TYPE_NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
result = cell.getDateCellValue().toString();
} else {
Double cellValue = cell.getNumericCellValue();
DecimalFormat df = new DecimalFormat("#.#");
result = df.format(cellValue);
}
break;
case Cell.CELL_TYPE_BOOLEAN:
result = Boolean.toString(cell.getBooleanCellValue());
break;
case Cell.CELL_TYPE_FORMULA:
result = cell.getCellFormula();
break;
default:
result ="";
}
if(StringUtils.isEmpty(result)){
result = "";
}
return result;
}
private Sheet modifySheet(Sheet ws,String pnp) {
int start = startRow;
DailySalesBean dsb = new DailySalesBean();
dsb.setPnp(pnp);
for (int j= 0 ; j< channelNum; j++){
int end = start+channelPart;
for(int i= start;i< end;i++){
Row row = ws.getRow(i);
Cell cellRefNo = row.getCell(colRefNo);
Cell cellVari = row.getCell(colVari);
Cell cellChannel = row.getCell(colChannel);
dsb.setRefno(getCellValue(cellRefNo));
dsb.setVari(getCellValue(cellVari));
dsb.setChannel(getCellValue(cellChannel));
DailySalesBean dsb2 = getDailyData(dsb);
Cell afypCell = row.getCell(colAFYP);
afypCell.setCellValue(Double.parseDouble(dsb2.getAfyp()));
Cell nbspCell = row.getCell(colNBSP);
nbspCell.setCellValue(Double.parseDouble(dsb2.getNbsp()));
Cell vonbCell = row.getCell(colVONB);
vonbCell.setCellValue(Double.parseDouble(dsb2.getVonb()));
}
start += channelPart;
}
ws.setForceFormulaRecalculation(true);//excel中有寫計算公式,數據修改之后強制重新調用里面的公式自動計算
return ws;
}
現在來總結下吧,之所以最后UAT發布關頭改變方法,是因為jxl生成出來的excel在用office2010或07打開的時候總彈出來倆數據丟失和數據格式丟失的alert框,這玩意給客戶感覺得是多不好啊,而且客戶給的excel模板比較惡心,貌似因為宏的原因隱藏了個sheet頁,一打開的時候下面的sheet頁切換不靈光,而且原有的sheet設置的tab顏色也丟了。如果用office2003打開,那就是直接都打不開了,一打開就直接excel停止工作。琢磨了三四天,各種資料查下來都說是excel各種版本不兼容之類的問題,大家混亂的解決方案下來我也沒解決問題。痛心之下抱着死馬當活馬醫的心態,在發布UAT的前一天晚上查詢poi的相關資料,對比了下jxl和poi的不同寫法,第二天一早動筆修改程序,換血用poi重寫代碼,那邊在如火如荼地發布,解決發布過程中的各種問題,我這邊淡定地寫着代碼,終於趕在他們解決完問題之前寫完了代碼,中午寫完,測試的時候,心情那個忐忑啊,要是還不行,我估計就得撞牆去了。顫抖着手,咽着快餐,啟動服務器,測試,生成,生成的excel完美,和模板一模一樣,激動的我呀,小心肝都快飛出來了。沒有alert框,sheet切換正常,tab顏色也在,perfect!!!經此一事,忠實擁戴poi,看了下poi的api,齊全的,各種office操作的東西,poi都有相應的方法可供調用。
參考文獻:http://poi.apache.org/spreadsheet/quick-guide.html#Iterator,著名的Busy Developers' Guide to HSSF and XSSF Features,新手必看,比看API塊