微软在桌面系统上的成功,令我们不得不大量使用它的办公产品,如:Word,Excel。时至今日,它的源代码仍然不公开已封锁了我们的进一步应用和开发。在我们实际开发企业办公系统的过程中,常常有客户这样子要求:你要把我们的报表直接用Excel打开。或者是:我们已经习惯用Excel打印。但是这种的客户需求在j2ee环境的环境下怎么实现?
一.Java用POI操作Excel文件
Apache的Jakata项目的POI子项目的HSSF接口可以处理MS Excel(97-2002)对象。它不象我们仅仅是用csv生成的没有格式的可以由Excel转换的东西,而是真正的Excel对象,你可以控制一些属性如sheet,cell等等。另外,无锡永中Office的实现方案也是纯java的解决方案,不过那也是完全商业的产品,并不是公开代码项目。其实,从开发历史的角度讲,在80年代中期starOffice的原作者在德国成立了StarOffice suite公司,然后到1999年夏天starOffice被sun收购,再到2000年6月starOffice5.2的发布;并且从starOffice6.0开始,starOffice建立在OpenOffice的api的基础上,这个公开代码的office项目已经进行了很长的时间。虽然那是由C++写的,但是POI的代码部分也是由openOffice改过来的。
HSSF提供给用户使用的对象在org.apache.poi.hssf.usermodel包中,主要部分包括Excell对象,样式和格式,还有辅助操作。有以下几种对象:
HSSFWorkbook excell的文档对象
HSSFSheet excell的表单
HSSFRow excell的行
HSSFCell excell的格子单元
HSSFFont excell字体
HSSFName 名称
HSSFDataFormat 日期格式
在poi1.7中才有以下2项:
HSSFHeader sheet头
HSSFFooter sheet尾
和这个样式
HSSFCellStyle cell样式
辅助操作包括
HSSFDateUtil 日期
HSSFPrintSetup 打印
HSSFErrorConstants 错误信息表
//利用Java 创建Excel文档 import org.apache.poi.hssf.usermodel.*; import java.io.FileOutputStream; import java.io.IOException; public class CreateCells { public static void main(String[] args) throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); //建立新HSSFWorkbook对象 HSSFSheet sheet = wb.createSheet("new sheet"); //建立新的sheet对象 // Create a row and put some cells in it. Rows are 0 based. HSSFRow row = sheet.createRow((short)0); //建立新行 // Create a cell and put a value in it. HSSFCell cell = row.createCell((short)0); //建立新cell cell.setCellValue(1); //设置cell的整数类型的值 // Or do it on one line. row.createCell((short)1).setCellValue(1.2); //设置cell浮点类型的值 row.createCell((short)2).setCellValue("test"); //设置cell字符类型的值 row.createCell((short)3).setCellValue(true); //设置cell布尔类型的值 HSSFCellStyle cellStyle = wb.createCellStyle(); //建立新的cell样式 cellStyle.setDataFormat(HSSFDataFormat.getFormat("m/d/yy h:mm")); //设置cell样式为定制的日期格式 HSSFCell dCell =row.createCell((short)4); dCell.setCellValue(new Date()); //设置cell为日期类型的值 dCell.setCellStyle(cellStyle); //设置该cell日期的显示格式 HSSFCell csCell =row.createCell((short)5); csCell.setEncoding(HSSFCell.ENCODING_UTF_16); //设置cell编码解决中文高位字节截断 csCell.setCellValue("中文测试_Chinese Words Test"); //设置中西文结合字符串 row.createCell((short)6).setCellType(HSSFCell.CELL_TYPE_ERROR); //建立错误cell // Write the output to a file FileOutputStream fileOut = new FileOutputStream("workbook.xls"); wb.write(fileOut); fileOut.close(); } }
通过这个例子,我们可以清楚的看到xls文件从大到小包括了HSSFWorkbook HSSFSheet HSSFRow HSSFCell这样几个对象。我们可以在cell中设置各种类型的值。尤其要注意的是如果你想正确的显示非欧美的字符时,尤其象中日韩这样的语言,必须设置编码为16位的即是HSSFCell.ENCODING_UTF_16,才能保证字符的高8位不被截断而引起编码失真形成乱码。
其他测试可以通过参考examples包中的测试例子掌握poi的详细用法,包括字体的设置,cell大小和低纹的设置等。需要注意的是POI是一个仍然在完善中的公开代码的项目,所以有些功能正在扩充、有些功能还在修改。如HSSFSheet的getFooter() getHeader()和setFooter(HSSFFooter hsf) setHeader(HSSFHeader hsh)是在POI1.7中才有的,而POI1.5中就没有。运行测试熟悉代码或者使用它做项目时请注意POI的版本。
因为POI还不是一个足够成熟的项目,所以有必要做进一步的开发和测试,确认生成的Excel是否能满足用户挑剔的需求。
二.Java用PageOffice操作Excel文件
PageOffice是一个实践了分布式计算设计思想的组件,提供的方案并不是纯服务器端的Excel解决方案,对文件的处理放到了客户端来进行。用PageOffice开发需要的引入的文件只需一个pageoffice4.4.0.3.jar(以具体的jar版的版本号为准)。毕竟是商业软件,提供的编程接口简洁高效。针对excel文件生成的类是:com.zhuozhengsoft.pageoffice.excelwriter
类摘要 | |
Border | Border 类,代表Excel中定义的边框对象。 |
Cell | Cell 类,代表Excel中定义的单元格对象,用来填充单元格数据及控制单元格格式。 |
DataField | DataField 类,代表PageOffice中定义的Excel表格对象里的字段对象。 |
DataFieldCollection | DataField 字段集合,代表 com.zhuozhengsoft.pageoffice.excelwriter.Table 中当前记录行。 |
Font | Font 类,代表Excel中定义的字体对象。 |
Sheet | Sheet 类,代表Excel中定义的工作表对象。 |
Table | Table 类,代表PageOffice中定义的Excel表格对象。 |
Workbook | Workbook 类代表一个Excel文档,用来动态输出数据到Excel文档并且控制其表格格式及编辑功能。 |
excelwriter类生成excel文件的效果可以在官方提供的Sample4包里找到。以下是PageOffice官方示例代码中提供的一个完全用程序生成预算表的demo的源代码,演示了针对Excel文档生成所提供的大部分接口。
Workbook wb = new Workbook(); // 设置背景 Table backGroundTable = wb.openSheet("Sheet1").openTable("A1:P200"); backGroundTable.getBorder().setLineColor(Color.white); // 设置标题 wb.openSheet("Sheet1").openTable("A1:H2").merge();//合并单元格 //打开表格并设置表格的行高 wb.openSheet("Sheet1").openTable("A1:H2").setRowHeight(30); Cell A1 = wb.openSheet("Sheet1").openCell("A1"); //设置单元格的水平对齐方式 A1.setHorizontalAlignment(XlHAlign.xlHAlignCenter); //设置单元格的垂直对齐方式 A1.setVerticalAlignment(XlVAlign.xlVAlignCenter); //设置单元格的前景色 A1.setForeColor(new Color(0, 128, 128)); //给单元格赋值 A1.setValue("出差开支预算"); //设置字体:加粗、大小 wb.openSheet("Sheet1").openTable("A1:A1").getFont().setBold(true); wb.openSheet("Sheet1").openTable("A1:A1").getFont().setSize(25); // 画表头 Border C4Border = wb.openSheet("Sheet1").openTable("C4:C4").getBorder(); //设置表格边框的宽度、颜色 C4Border.setWeight(XlBorderWeight.xlThick); C4Border.setLineColor(Color.yellow); Table titleTable = wb.openSheet("Sheet1").openTable("B4:H5"); //设置表格边框的样式、宽度、颜色 titleTable.getBorder().setBorderType(XlBorderType.xlAllEdges); titleTable.getBorder().setWeight(XlBorderWeight.xlThick); titleTable.getBorder().setLineColor(new Color(0, 128, 128)); // 画表体 Table bodyTable = wb.openSheet("Sheet1").openTable("B6:H15"); bodyTable.getBorder().setLineColor(Color.gray); bodyTable.getBorder().setWeight(XlBorderWeight.xlHairline); Border B7Border = wb.openSheet("Sheet1").openTable("B7:B7").getBorder(); B7Border.setLineColor(Color.white); Border B9Border = wb.openSheet("Sheet1").openTable("B9:B9").getBorder(); B9Border.setBorderType(XlBorderType.xlBottomEdge); B9Border.setLineColor(Color.white); Border C6C15BorderLeft = wb.openSheet("Sheet1").openTable("C6:C15").getBorder(); C6C15BorderLeft.setLineColor(Color.white); C6C15BorderLeft.setBorderType(XlBorderType.xlLeftEdge); Border C6C15BorderRight = wb.openSheet("Sheet1").openTable("C6:C15").getBorder(); C6C15BorderRight.setLineColor(Color.yellow); C6C15BorderRight.setLineStyle(XlBorderLineStyle.xlDot); C6C15BorderRight.setBorderType(XlBorderType.xlRightEdge); Border E6E15Border = wb.openSheet("Sheet1").openTable("E6:E15").getBorder(); E6E15Border.setLineStyle(XlBorderLineStyle.xlDot); E6E15Border.setBorderType(XlBorderType.xlAllEdges); E6E15Border.setLineColor(Color.yellow); Border G6G15BorderRight = wb.openSheet("Sheet1").openTable("G6:G15").getBorder(); G6G15BorderRight.setBorderType(XlBorderType.xlRightEdge); G6G15BorderRight.setLineColor(Color.white); Border G6G15BorderLeft = wb.openSheet("Sheet1").openTable("G6:G15").getBorder(); G6G15BorderLeft.setLineStyle(XlBorderLineStyle.xlDot); G6G15BorderLeft.setBorderType(XlBorderType.xlLeftEdge); G6G15BorderLeft.setLineColor(Color.yellow); Table bodyTable2 = wb.openSheet("Sheet1").openTable("B6:H15"); bodyTable2.getBorder().setWeight(XlBorderWeight.xlThick); bodyTable2.getBorder().setLineColor(new Color(0, 128, 128)); bodyTable2.getBorder().setBorderType(XlBorderType.xlAllEdges); // 画表尾 Border H16H17Border = wb.openSheet("Sheet1").openTable("H16:H17").getBorder(); H16H17Border.setLineColor(new Color(204, 255, 204)); Border E16G17Border = wb.openSheet("Sheet1").openTable("E16:G17").getBorder(); E16G17Border.setLineColor(new Color(0, 128, 128)); Table footTable = wb.openSheet("Sheet1").openTable("B16:H17"); footTable.getBorder().setWeight(XlBorderWeight.xlThick); footTable.getBorder().setLineColor(new Color(0, 128, 128)); footTable.getBorder().setBorderType(XlBorderType.xlAllEdges); // 设置行高列宽 wb.openSheet("Sheet1").openTable("A1:A1").setColumnWidth(1); wb.openSheet("Sheet1").openTable("B1:B1").setColumnWidth(20); wb.openSheet("Sheet1").openTable("C1:C1").setColumnWidth(15); wb.openSheet("Sheet1").openTable("D1:D1").setColumnWidth(10); wb.openSheet("Sheet1").openTable("E1:E1").setColumnWidth(8); wb.openSheet("Sheet1").openTable("F1:F1").setColumnWidth(3); wb.openSheet("Sheet1").openTable("G1:G1").setColumnWidth(12); wb.openSheet("Sheet1").openTable("H1:H1").setColumnWidth(20); wb.openSheet("Sheet1").openTable("A16:A16").setRowHeight(20); wb.openSheet("Sheet1").openTable("A17:A17").setRowHeight(20); // 设置表格中字体大小为10 for (int i = 0; i < 12; i++) {//excel表格行号 for (int j = 0; j < 7; j++) {//excel表格列号 wb.openSheet("Sheet1").openCellRC(4 + i, 2 + j).getFont().setSize(10); } } // 填充单元格背景颜色 for (int i = 0; i < 10; i++) { wb.openSheet("Sheet1").openCell("H" + (6 + i)).setBackColor(new Color(255, 255, 153)); } wb.openSheet("Sheet1").openCell("E16").setBackColor(new Color(0, 128, 128)); wb.openSheet("Sheet1").openCell("F16").setBackColor(new Color(0, 128, 128)); wb.openSheet("Sheet1").openCell("G16").setBackColor(new Color(0, 128, 128)); wb.openSheet("Sheet1").openCell("E17").setBackColor(new Color(0, 128, 128)); wb.openSheet("Sheet1").openCell("F17").setBackColor(new Color(0, 128, 128)); wb.openSheet("Sheet1").openCell("G17").setBackColor(new Color(0, 128, 128)); wb.openSheet("Sheet1").openCell("H16").setBackColor(new Color(204, 255, 204)); wb.openSheet("Sheet1").openCell("H17").setBackColor(new Color(204, 255, 204)); //填充单元格文本和公式 Cell B4 = wb.openSheet("Sheet1").openCell("B4"); B4.getFont().setBold(true); B4.setValue("出差开支预算"); Cell H5 = wb.openSheet("Sheet1").openCell("H5"); H5.getFont().setBold(true); H5.setValue("总计"); H5.setHorizontalAlignment(XlHAlign.xlHAlignCenter); Cell B6 = wb.openSheet("Sheet1").openCell("B6"); B6.getFont().setBold(true); B6.setValue("飞机票价"); Cell B9 = wb.openSheet("Sheet1").openCell("B9"); B9.getFont().setBold(true); B9.setValue("酒店"); Cell B11 = wb.openSheet("Sheet1").openCell("B11"); B11.getFont().setBold(true); B11.setValue("餐饮"); Cell B12 = wb.openSheet("Sheet1").openCell("B12"); B12.getFont().setBold(true); B12.setValue("交通费用"); Cell B13 = wb.openSheet("Sheet1").openCell("B13"); B13.getFont().setBold(true); B13.setValue("休闲娱乐"); Cell B14 = wb.openSheet("Sheet1").openCell("B14"); B14.getFont().setBold(true); B14.setValue("礼品"); Cell B15 = wb.openSheet("Sheet1").openCell("B15"); B15.getFont().setBold(true); B15.getFont().setSize(10); B15.setValue("其他费用"); wb.openSheet("Sheet1").openCell("C6").setValue("机票单价(往)"); wb.openSheet("Sheet1").openCell("C7").setValue("机票单价(返)"); wb.openSheet("Sheet1").openCell("C8").setValue("其他"); wb.openSheet("Sheet1").openCell("C9").setValue("每晚费用"); wb.openSheet("Sheet1").openCell("C10").setValue("其他"); wb.openSheet("Sheet1").openCell("C11").setValue("每天费用"); wb.openSheet("Sheet1").openCell("C12").setValue("每天费用"); wb.openSheet("Sheet1").openCell("C13").setValue("总计"); wb.openSheet("Sheet1").openCell("C14").setValue("总计"); wb.openSheet("Sheet1").openCell("C15").setValue("总计"); wb.openSheet("Sheet1").openCell("G6").setValue(" 张"); wb.openSheet("Sheet1").openCell("G7").setValue(" 张"); wb.openSheet("Sheet1").openCell("G9").setValue(" 晚"); wb.openSheet("Sheet1").openCell("G10").setValue(" 晚"); wb.openSheet("Sheet1").openCell("G11").setValue(" 天"); wb.openSheet("Sheet1").openCell("G12").setValue(" 天"); //设置单元格包含的公式 wb.openSheet("Sheet1").openCell("H6").setFormula("=D6*F6"); wb.openSheet("Sheet1").openCell("H7").setFormula("=D7*F7"); wb.openSheet("Sheet1").openCell("H8").setFormula("=D8*F8"); wb.openSheet("Sheet1").openCell("H9").setFormula("=D9*F9"); wb.openSheet("Sheet1").openCell("H10").setFormula("=D10*F10"); wb.openSheet("Sheet1").openCell("H11").setFormula("=D11*F11"); wb.openSheet("Sheet1").openCell("H12").setFormula("=D12*F12"); wb.openSheet("Sheet1").openCell("H13").setFormula("=D13*F13"); wb.openSheet("Sheet1").openCell("H14").setFormula("=D14*F14"); wb.openSheet("Sheet1").openCell("H15").setFormula("=D15*F15"); for (int i = 0; i < 10; i++) { //设置数据以货币形式显示 wb.openSheet("Sheet1").openCell("D" + (6 + i)).setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); wb.openSheet("Sheet1").openCell("H" + (6 + i)).setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); } Cell E16 = wb.openSheet("Sheet1").openCell("E16"); E16.getFont().setBold(true); E16.getFont().setSize(11); E16.setForeColor(Color.white); E16.setValue("出差开支总费用"); E16.setVerticalAlignment(XlVAlign.xlVAlignCenter); Cell E17 = wb.openSheet("Sheet1").openCell("E17"); E17.getFont().setBold(true); E17.getFont().setSize(11); E17.setForeColor(Color.white); E17.setFormula("=IF(C4>H16,\"低于预算\",\"超出预算\")"); E17.setVerticalAlignment(XlVAlign.xlVAlignCenter); Cell H16 = wb.openSheet("Sheet1").openCell("H16"); H16.setVerticalAlignment(XlVAlign.xlVAlignCenter); H16.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); H16.getFont().setName("Arial"); H16.getFont().setSize(11); H16.getFont().setBold(true); H16.setFormula("=SUM(H6:H15)"); Cell H17 = wb.openSheet("Sheet1").openCell("H17"); H17.setVerticalAlignment(XlVAlign.xlVAlignCenter); H17.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); H17.getFont().setName("Arial"); H17.getFont().setSize(11); H17.getFont().setBold(true); H17.setFormula("=(C4-H16)"); // 填充数据 Cell C4 = wb.openSheet("Sheet1").openCell("C4"); C4.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); C4.setValue("2500"); Cell D6 = wb.openSheet("Sheet1").openCell("D6"); D6.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); D6.setValue("1200"); wb.openSheet("Sheet1").openCell("F6").getFont().setSize(10); wb.openSheet("Sheet1").openCell("F6").setValue("1"); Cell D7 = wb.openSheet("Sheet1").openCell("D7"); D7.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); D7.setValue("875"); wb.openSheet("Sheet1").openCell("F7").setValue("1"); //打开文件 PageOfficeCtrl poCtrl1 = new PageOfficeCtrl(request); poCtrl1.setWriter(wb); poCtrl1.setServerPage("poserver.do"); poCtrl1.webOpen("doc/test.xls", OpenModeType.xlsNormalEdit, "");
可能有人会说上面的代码有点多,编程工作量大,但是当你对比一下方案一中POI生成的那个表格和这个demo中生成的表格用多大的差别,就不会有这种感觉。上面的代码生成的文件效果如下图所示:
就生成这个预算表来说,换用其他的任何组件来实现(包括开源和非开源的,当然也包括POI)都很难想象要写多少行代码。作为任何一个系统的开发人员工作的重点都应该放在具体的业务逻辑上,把大量的精力和编码工作放在用程序实现表格的绘制其实是很不明智,这个demo也只是展示了一下PageOffice是可以这样做的,但还有更好的、任何开源组件都没有或处理不好的方法,那就是使用现有的Excel文件模板。从上面的例子可以看出PageOffice 给excel单元格赋值,也可以赋值公式,所以最好的办法就是对用户现有的excel模板编程,直接对单元格赋值数据和公式,代码量会急速的减少,同样是生成上面的一个预算表,如果模板是下面这样的:
那么编写的代码就只剩下填充数据的代码了:
Workbook wb = new Workbook(); //填充单元格文本和公式 B6.setValue("飞机票价"); Cell B9 = wb.openSheet("Sheet1").openCell("B9"); B9.getFont().setBold(true); B9.setValue("酒店"); Cell B11 = wb.openSheet("Sheet1").openCell("B11"); B11.getFont().setBold(true); B11.setValue("餐饮"); Cell B12 = wb.openSheet("Sheet1").openCell("B12"); B12.getFont().setBold(true); B12.setValue("交通费用"); Cell B13 = wb.openSheet("Sheet1").openCell("B13"); B13.getFont().setBold(true); B13.setValue("休闲娱乐"); Cell B14 = wb.openSheet("Sheet1").openCell("B14"); B14.getFont().setBold(true); B14.setValue("礼品"); Cell B15 = wb.openSheet("Sheet1").openCell("B15"); B15.getFont().setBold(true); B15.getFont().setSize(10); B15.setValue("其他费用"); wb.openSheet("Sheet1").openCell("C6").setValue("机票单价(往)"); wb.openSheet("Sheet1").openCell("C7").setValue("机票单价(返)"); wb.openSheet("Sheet1").openCell("C8").setValue("其他"); wb.openSheet("Sheet1").openCell("C9").setValue("每晚费用"); wb.openSheet("Sheet1").openCell("C10").setValue("其他"); wb.openSheet("Sheet1").openCell("C11").setValue("每天费用"); wb.openSheet("Sheet1").openCell("C12").setValue("每天费用"); wb.openSheet("Sheet1").openCell("C13").setValue("总计"); wb.openSheet("Sheet1").openCell("C14").setValue("总计"); wb.openSheet("Sheet1").openCell("C15").setValue("总计"); wb.openSheet("Sheet1").openCell("G6").setValue(" 张"); wb.openSheet("Sheet1").openCell("G7").setValue(" 张"); wb.openSheet("Sheet1").openCell("G9").setValue(" 晚"); wb.openSheet("Sheet1").openCell("G10").setValue(" 晚"); wb.openSheet("Sheet1").openCell("G11").setValue(" 天"); wb.openSheet("Sheet1").openCell("G12").setValue(" 天"); // 填充数据 Cell C4 = wb.openSheet("Sheet1").openCell("C4"); C4.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); C4.setValue("2500"); Cell D6 = wb.openSheet("Sheet1").openCell("D6"); D6.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); D6.setValue("1200"); wb.openSheet("Sheet1").openCell("F6").getFont().setSize(10); wb.openSheet("Sheet1").openCell("F6").setValue("1"); Cell D7 = wb.openSheet("Sheet1").openCell("D7"); D7.setNumberFormatLocal("¥#,##0.00;¥-#,##0.00"); D7.setValue("875"); wb.openSheet("Sheet1").openCell("F7").setValue("1");
这就变成了一个简单的工作量的问题,不需要绞尽脑汁去计算怎么绘制表格才更合理,才能实现效果。
就视觉效果来说,PageOffice处理生成的表格,不管是表格边框的处理,还是单元格背景色,或者字体和对齐方式等等都处理的非常好,尤其对Excel公式的支持更是可圈可点,就这个功能,一般用户想要的结构比较复杂的报表都可以实现。
从性能上看,文件生成的速度飞快,几乎是瞬间完成,还同时实现了文件在线打开、是否只读和打印的控制。
两种方案的选择:对于客户端数量小的系统,如果也不需要文件的在线的打开查看或编辑打印(PageOffice生成文件时可以有界面,也可以没有界面,不要被我上面说的那个例子误导),那么可以使用POI。当可能会出现大量客户端并发访问或同时处理多个大文件时, POI需要大量占用服务器内存,由于Word文档固有的复杂度,POI会大量占用CPU时间,写入时长时间占用磁盘文件,会导致网页反应速度下降、阻塞现象的发生,还是选择使用PageOffice。毕竟PageOffice是商业软件是收费的,根据具体的项目需求选择不同的方案更实际。