Apache POI组件操作Excel,制作报表(三)

上一篇介绍了POI组件操作Excel时如何对单元格和行进行设置,合并单元格等操作,最后给出一个综合实例,就是制作复杂报表,原理就是涉及合并行和列的计算。
本篇就来详细分析一下复杂报表的分析与设计问题,并用POI通过程序来生成Excel报表。首先说一点文档相关内容。使用POI组件可以生成Office文档,而Office文档也有一些属性,比如作者,分类,公司等信息。我们若通过程序生成时,这一步就直接略过了,但有时我们会需要这些信息,要写入一些文档信息,那么该如何实现呢?
我们分2003和2007两个版本说明,因为操作是不太一样的。看下面的代码:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. //设置核心属性
  2. POIXMLProperties.CorePropertiesprops=workbook2007.getProperties()
  3. .getCoreProperties();
  4. props.setCreator("Nanlei");
  5. props.setCategory("POI程序测试");
  6. props.setTitle("学生信息表");
  7. //设置扩展属性
  8. POIXMLProperties.ExtendedPropertiesextProps=workbook2007
  9. .getProperties().getExtendedProperties();
  10. //设置自定义属性
  11. POIXMLProperties.CustomPropertiescustomProps=workbook2007
  12. .getProperties().getCustomProperties();

生成2007的Excel时,只需上述步骤便可加入我们需要的属性了,具体的属性含义可以参考官方文档,这里仅仅添加作者,分类和标题,生成Excel文档后,我们可以查看到入校内容:
Apache POI组件操作Excel,制作报表(三)
那么这里就是我们设置的一些信息了。而对于2003,则需要如下的步骤:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. //创建工作簿对象
  2. HSSFWorkbookworkbook2003=newHSSFWorkbook();
  3. workbook2003.createInformationProperties();
  4. SummaryInformationsi=workbook2003.getSummaryInformation();
  5. si.setAuthor("Nanlei");
  6. si.setTitle("学生信息表");
  7. si.setComments("POI程序测试");
  8. DocumentSummaryInformationdsi=workbook2003
  9. .getDocumentSummaryInformation();
  10. dsi.setCompany("Pioneer");

要注意的是第二行,必须执行createInformationProperties()方法,之后才可以设置属性,这和2007的做法是不同的。这里只是给出示例,就不深入讨论每个设置项了。
回头来看报表。中国式的复杂报表基本上是合计,合计再合计,就是数值分析到一个阶段后出一次合计,这个阶段可以按照业务的不同元素来划分。本例是根据经销商,省份最终到达事业部。那么设计数据库时就要唯一区分开这些元素,根据这些标识来实现划分,合并等,首先来准备一些数据。
Java代码Apache POI组件操作Excel,制作报表(三)
  1. static{
  2. cruiseServiceLocationList=newArrayList<CruiseServiceLocation>();
  3. csl[0]=newCruiseServiceLocation("T001","北京市","北京总部","bj","清华大学",
  4. 20);
  5. csl[1]=newCruiseServiceLocation("T001","北京市","北京总部","bj","北京大学",
  6. 30);
  7. csl[2]=newCruiseServiceLocation("T001","北京市","海淀经销商","bjhd",
  8. "西直门",15);
  9. csl[3]=newCruiseServiceLocation("T001","北京市","海淀经销商","bjhd",
  10. "首都机场",50);
  11. csl[7]=newCruiseServiceLocation("T001","辽宁省","大连经销商","lndl",
  12. "河口软件园",15);
  13. csl[8]=newCruiseServiceLocation("T001","辽宁省","大连经销商","lndl",
  14. "七贤岭腾飞软件园",13);
  15. csl[9]=newCruiseServiceLocation("T001","辽宁省","大连经销商","lndl",
  16. "高新园区信达街",11);
  17. csl[19]=newCruiseServiceLocation("T003","河北省","石家庄经销商","hbsjz",
  18. "火车站",4);
  19. csl[20]=newCruiseServiceLocation("","","","","",0);//合并算法捕捉最后一行有问题,增补一行无效数据,计算时去除
  20. cruiseServiceLocationList.addAll(Arrays.asList(csl));
  21. }

(具体数据请参考源码,附件中下载)
注意在最后增补一条无效数据,因为算法的限制,读取最后一行比较时可能会将其略过,所以这样保证所有数据都能被正常读出。这个数据结构根据事业部代号,省份和经销商名称来区分各个元素。
算法就是根据标识位的不同来区分是否该进行特殊处理了,这之前数据要排好顺序,就可以分门别类进行了,来看一下合计算法:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. //合计量的计算
  2. CruiseServiceLocationcslTotal=null;
  3. List<CruiseServiceLocation>cslList=newArrayList<CruiseServiceLocation>();
  4. //合并计算控制
  5. doubletotalDealer=0;
  6. doubletotalProvince=0;
  7. doubletotalDivision=0;
  8. intlocationNum=0;
  9. //循环遍历List
  10. for(inti=0;i<cruiseServiceLocationList.size();i++){
  11. cslList.add(cruiseServiceLocationList.get(i));
  12. //是否是最后一条记录的开关
  13. booleanlast=(i==cruiseServiceLocationList.size()-1);
  14. //取出相邻的两条记录进行比较
  15. CruiseServiceLocationcsl1=null;
  16. CruiseServiceLocationcsl2=null;
  17. if(!last){
  18. csl1=cruiseServiceLocationList.get(i);
  19. csl2=cruiseServiceLocationList.get(i+1);
  20. }else{
  21. //防止最后一条记录无法加入集合
  22. csl1=cruiseServiceLocationList.get(i);
  23. if(cruiseServiceLocationList.size()!=1)
  24. csl2=cruiseServiceLocationList.get(i-1);
  25. else
  26. csl2=cruiseServiceLocationList.get(i);
  27. }
  28. //开始处理
  29. if(csl1.getDealerName().equals(csl2.getDealerName())){
  30. locationNum++;
  31. totalDealer+=csl1.getMiles();
  32. }else{
  33. locationNum++;
  34. totalDealer+=csl1.getMiles();
  35. cslTotal=newCruiseServiceLocation();
  36. cslTotal.setTotalDealer(totalDealer);
  37. cslTotal.setLocationNum(locationNum);
  38. cslList.add(cslTotal);
  39. totalDealer=0;
  40. locationNum=0;
  41. }
  42. if(csl1.getProvince().equals(csl2.getProvince())){
  43. totalProvince+=csl1.getMiles();
  44. }else{
  45. totalProvince+=csl1.getMiles();
  46. cslTotal=newCruiseServiceLocation();
  47. cslTotal.setTotalProvince(totalProvince);
  48. cslList.add(cslTotal);
  49. totalProvince=0;
  50. }
  51. if(csl1.getDivision().equals(csl2.getDivision())){
  52. totalDivision+=csl1.getMiles();
  53. }else{
  54. totalDivision+=csl1.getMiles();
  55. cslTotal=newCruiseServiceLocation();
  56. cslTotal.setTotalDivision(totalDivision);
  57. cslList.add(cslTotal);
  58. totalDivision=0;
  59. }
  60. }

其中Bean的设计如下:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. packageorg.ourpioneer.excel.bean;
  2. /**
  3. *巡航服务地点bean
  4. *
  5. *@authorNanlei
  6. *
  7. */
  8. publicclassCruiseServiceLocation{
  9. privateStringdivision;//事业部
  10. privateStringprovince;//省份
  11. privateStringdealerName;//经销商名称
  12. privateStringdealerCode;//经销商代码
  13. privateStringlocation;//巡航服务地点
  14. privatedoublemiles;//巡航服务里程
  15. privateintlocationNum;//地点条数
  16. privatedoubletotalDealer;//经销商合计
  17. privatedoubletotalProvince;//省份合计
  18. privatedoubletotalDivision;//事业部合计
  19. publicCruiseServiceLocation(){
  20. super();
  21. }
  22. publicCruiseServiceLocation(Stringdivision,Stringprovince,
  23. StringdealerName,StringdealerCode,Stringlocation,doublemiles){
  24. super();
  25. this.division=division;
  26. this.province=province;
  27. this.dealerName=dealerName;
  28. this.dealerCode=dealerCode;
  29. this.location=location;
  30. this.miles=miles;
  31. }
  32. //省略getter和setter方法
  33. }

下面来分析一下这个算法,思路很简单,就是逐条记录进行比较,发现不同后立即处理,按照从小到大的顺序,一次处理经销商,省份和事业部,取出两条相邻元素之后,首先比较的是经销商是否一致,如果一致,经销商数量加1,里程累加,不一致时,这两个量也要相应计算并放入新的对象中,加入list里,这样一个合计行就加好了,后面的省份和事业部处理也是这个思路。循环完成,我们就得到了按顺序排好的最终列表,剩下的就是遍历这个列表来生成表格了。
要注意我们之前填补了一条无效记录,那么在合并的时候也会多处三行合计,要把这四项排除在外,不能忘记了。
计算合并行是需要一些辅助变量,比如:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. //省份合计和事业部合计时跨行的计算数据
  2. intcomboProvince=0;
  3. intcomboDivision=0;
  4. List<Integer>indexComboProvice=newArrayList<Integer>();
  5. List<Integer>indexComboDivision=newArrayList<Integer>();

它们是用来计算合并数量的,最后用在跨行数量上,因为每出一条合计,整体跨行数就要增加,那么需要将这些数据记录到List中,方便处理。我们就引入了这些辅助变量。
先来看经销商合并算法:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. if(csl.getTotalDealer()!=0){
  2. row.createCell(0).setCellStyle(style);
  3. row.createCell(1).setCellStyle(style);
  4. XSSFCellt_dealer=row.createCell(2);
  5. t_dealer.setCellValue("经销商合计");
  6. t_dealer.setCellStyle(biStyle);
  7. sheet.addMergedRegion(newCellRangeAddress(i+1,i+1,2,4));
  8. XSSFCellt_dealer_value=row.createCell(5);
  9. t_dealer_value.setCellValue(csl.getTotalDealer());
  10. t_dealer_value.setCellStyle(biStyle);
  11. sheet.addMergedRegion(newCellRangeAddress(i
  12. -csl.getLocationNum()+1,i,2,2));
  13. sheet.addMergedRegion(newCellRangeAddress(i
  14. -csl.getLocationNum()+1,i,3,3));
  15. }

这个就很简单了,因为不涉及到跨行,只是跨列而已,这都是预先已经设定好的,不难。而省份和经销商的跨行计算就略微复杂:
Java代码Apache POI组件操作Excel,制作报表(三)
  1. if(csl.getTotalProvince()!=0){
  2. XSSFCellt_province=row.createCell(1);
  3. row.createCell(0).setCellStyle(style);
  4. row.createCell(3).setCellStyle(style);
  5. row.createCell(4).setCellStyle(style);
  6. t_province.setCellValue("省份合计");
  7. t_province.setCellStyle(biStyle);
  8. sheet.addMergedRegion(newCellRangeAddress(i+1,i+1,1,4));
  9. XSSFCellt_province_value=row.createCell(5);
  10. t_province_value.setCellValue(csl.getTotalProvince());
  11. t_province_value.setCellStyle(biStyle);
  12. indexComboProvice.add(i);
  13. //合并行
  14. if(comboProvince==0){
  15. sheet.addMergedRegion(newCellRangeAddress(1,i,1,1));
  16. }elseif(comboProvince==1){
  17. sheet.addMergedRegion(newCellRangeAddress(
  18. indexComboProvice.get(comboProvince-1)
  19. +comboProvince+1,i,1,1));
  20. }else{
  21. sheet.addMergedRegion(newCellRangeAddress(
  22. indexComboProvice.get(comboProvince-1)
  23. +comboProvince,i,1,1));
  24. }
  25. comboProvince++;
  26. }

这里为了将所有单元格都加入样式,所以没有数据填充的单元格仅做样式处理即可。合并行的时候要看这是第几次合并,因为算法不同。第一次时比较简单,只需数出有第一个合计中有多少经销商即可。而后续的就需要记录上次合并出现的位置,然后再加上第二个经销商的数量,之后进行合并。那么事业部的算法也是如此。
Java代码Apache POI组件操作Excel,制作报表(三)
  1. if(csl.getTotalDivision()!=0){
  2. XSSFCellt_division=row.createCell(0);
  3. row.createCell(1).setCellStyle(style);
  4. row.createCell(2).setCellStyle(style);
  5. row.createCell(3).setCellStyle(style);
  6. row.createCell(4).setCellStyle(style);
  7. t_division.setCellValue("事业部合计");
  8. t_division.setCellStyle(biStyle);
  9. sheet.addMergedRegion(newCellRangeAddress(i+1,i+1,0,4));
  10. XSSFCellt_division_value=row.createCell(5);
  11. t_division_value.setCellValue(csl.getTotalDivision());
  12. t_division_value.setCellStyle(biStyle);
  13. indexComboDivision.add(i);
  14. //合并行
  15. if(comboDivision==0){
  16. sheet.addMergedRegion(newCellRangeAddress(1,i,0,0));
  17. }elseif(comboDivision==1){
  18. sheet.addMergedRegion(newCellRangeAddress(
  19. indexComboDivision.get(comboDivision-1)
  20. +comboDivision+1,i,0,0));
  21. }else{
  22. sheet.addMergedRegion(newCellRangeAddress(
  23. indexComboDivision.get(comboDivision-1)
  24. +comboDivision,i,0,0));
  25. }
  26. comboDivision++;
  27. }

最后生成文件,就得到了我们要的报表了。
Apache POI组件操作Excel,制作报表(三)
综合实例就介绍完了,下一篇将结合Spring MVC来说明在Web应用程序中如何生成Excel文件并进行下载操作。