上一篇介绍了POI组件操作Excel时如何对单元格和行进行设置,合并单元格等操作,最后给出一个综合实例,就是制作复杂报表,原理就是涉及合并行和列的计算。 
    本篇就来详细分析一下复杂报表的分析与设计问题,并用POI通过程序来生成Excel报表。首先说一点文档相关内容。使用POI组件可以生成Office文档,而Office文档也有一些属性,比如作者,分类,公司等信息。我们若通过程序生成时,这一步就直接略过了,但有时我们会需要这些信息,要写入一些文档信息,那么该如何实现呢? 
    我们分2003和2007两个版本说明,因为操作是不太一样的。看下面的代码:

  1. // 设置核心属性
  2. POIXMLProperties.CoreProperties props = workbook2007.getProperties()
  3. .getCoreProperties();
  4. props.setCreator("Nanlei");
  5. props.setCategory("POI程序测试");
  6. props.setTitle("学生信息表");
  7. // 设置扩展属性
  8. POIXMLProperties.ExtendedProperties extProps = workbook2007
  9. .getProperties().getExtendedProperties();
  10. // 设置自定义属性
  11. POIXMLProperties.CustomProperties customProps = workbook2007
  12. .getProperties().getCustomProperties();

生成2007的Excel时,只需上述步骤便可加入我们需要的属性了,具体的属性含义可以参考官方文档,这里仅仅添加作者,分类和标题,生成Excel文档后,我们可以查看到入校内容: 
 
    那么这里就是我们设置的一些信息了。而对于2003,则需要如下的步骤:

  1. // 创建工作簿对象
  2. HSSFWorkbook workbook2003 = new HSSFWorkbook();
  3. workbook2003.createInformationProperties();
  4. SummaryInformation si = workbook2003.getSummaryInformation();
  5. si.setAuthor("Nanlei");
  6. si.setTitle("学生信息表");
  7. si.setComments("POI程序测试");
  8. DocumentSummaryInformation dsi = workbook2003
  9. .getDocumentSummaryInformation();
  10. dsi.setCompany("Pioneer");

要注意的是第二行,必须执行createInformationProperties()方法,之后才可以设置属性,这和2007的做法是不同的。这里只是给出示例,就不深入讨论每个设置项了。 
    回头来看报表。中国式的复杂报表基本上是合计,合计再合计,就是数值分析到一个阶段后出一次合计,这个阶段可以按照业务的不同元素来划分。本例是根据经销商,省份最终到达事业部。那么设计数据库时就要唯一区分开这些元素,根据这些标识来实现划分,合并等,首先来准备一些数据。

  1. static {
  2. cruiseServiceLocationList = new ArrayList<CruiseServiceLocation>();
  3. csl[0] = new CruiseServiceLocation("T001", "北京市", "北京总部", "bj", "清华大学",
  4. 20);
  5. csl[1] = new CruiseServiceLocation("T001", "北京市", "北京总部", "bj", "北京大学",
  6. 30);
  7. csl[2] = new CruiseServiceLocation("T001", "北京市", "海淀经销商", "bjhd",
  8. "西直门", 15);
  9. csl[3] = new CruiseServiceLocation("T001", "北京市", "海淀经销商", "bjhd",
  10. "首都机场", 50);
  11. csl[7] = new CruiseServiceLocation("T001", "辽宁省", "大连经销商", "lndl",
  12. "河口软件园", 15);
  13. csl[8] = new CruiseServiceLocation("T001", "辽宁省", "大连经销商", "lndl",
  14. "七贤岭腾飞软件园", 13);
  15. csl[9] = new CruiseServiceLocation("T001", "辽宁省", "大连经销商", "lndl",
  16. "高新园区信达街", 11);
  17. csl[19] = new CruiseServiceLocation("T003", "河北省", "石家庄经销商", "hbsjz",
  18. "火车站", 4);
  19. csl[20] = new CruiseServiceLocation("", "", "", "", "", 0);// 合并算法捕捉最后一行有问题,增补一行无效数据,计算时去除
  20. cruiseServiceLocationList.addAll(Arrays.asList(csl));
  21. }

(具体数据请参考源码,附件中下载) 
    注意在最后增补一条无效数据,因为算法的限制,读取最后一行比较时可能会将其略过,所以这样保证所有数据都能被正常读出。这个数据结构根据事业部代号,省份和经销商名称来区分各个元素。 
    算法就是根据标识位的不同来区分是否该进行特殊处理了,这之前数据要排好顺序,就可以分门别类进行了,来看一下合计算法:

  1. // 合计量的计算
  2. CruiseServiceLocation cslTotal = null;
  3. List<CruiseServiceLocation> cslList = new ArrayList<CruiseServiceLocation>();
  4. // 合并计算控制
  5. double totalDealer = 0;
  6. double totalProvince = 0;
  7. double totalDivision = 0;
  8. int locationNum = 0;
  9. // 循环遍历List
  10. for (int i = 0; i < cruiseServiceLocationList.size(); i++) {
  11. cslList.add(cruiseServiceLocationList.get(i));
  12. // 是否是最后一条记录的开关
  13. boolean last = (i == cruiseServiceLocationList.size() - 1);
  14. // 取出相邻的两条记录进行比较
  15. CruiseServiceLocation csl1 = null;
  16. CruiseServiceLocation csl2 = 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 = new CruiseServiceLocation();
  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 = new CruiseServiceLocation();
  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 = new CruiseServiceLocation();
  56. cslTotal.setTotalDivision(totalDivision);
  57. cslList.add(cslTotal);
  58. totalDivision = 0;
  59. }
  60. }

其中Bean的设计如下:

  1. package org.ourpioneer.excel.bean;
  2. /**
  3. * 巡航服务地点bean
  4. *
  5. * @author Nanlei
  6. *
  7. */
  8. public class CruiseServiceLocation {
  9. private String division;// 事业部
  10. private String province;// 省份
  11. private String dealerName;// 经销商名称
  12. private String dealerCode;// 经销商代码
  13. private String location;// 巡航服务地点
  14. private double miles;// 巡航服务里程
  15. private int locationNum;// 地点条数
  16. private double totalDealer;// 经销商合计
  17. private double totalProvince;// 省份合计
  18. private double totalDivision;// 事业部合计
  19. public CruiseServiceLocation() {
  20. super();
  21. }
  22. public CruiseServiceLocation(String division, String province,
  23. String dealerName, String dealerCode, String location, double miles) {
  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里,这样一个合计行就加好了,后面的省份和事业部处理也是这个思路。循环完成,我们就得到了按顺序排好的最终列表,剩下的就是遍历这个列表来生成表格了。 
    要注意我们之前填补了一条无效记录,那么在合并的时候也会多处三行合计,要把这四项排除在外,不能忘记了。 
    计算合并行是需要一些辅助变量,比如:

  1. // 省份合计和事业部合计时跨行的计算数据
  2. int comboProvince = 0;
  3. int comboDivision = 0;
  4. List<Integer> indexComboProvice = new ArrayList<Integer>();
  5. List<Integer> indexComboDivision = new ArrayList<Integer>();

它们是用来计算合并数量的,最后用在跨行数量上,因为每出一条合计,整体跨行数就要增加,那么需要将这些数据记录到List中,方便处理。我们就引入了这些辅助变量。 
    先来看经销商合并算法:

  1. if (csl.getTotalDealer() != 0) {
  2. row.createCell(0).setCellStyle(style);
  3. row.createCell(1).setCellStyle(style);
  4. XSSFCell t_dealer = row.createCell(2);
  5. t_dealer.setCellValue("经销商合计");
  6. t_dealer.setCellStyle(biStyle);
  7. sheet.addMergedRegion(new CellRangeAddress(i + 1, i + 1, 2, 4));
  8. XSSFCell t_dealer_value = row.createCell(5);
  9. t_dealer_value.setCellValue(csl.getTotalDealer());
  10. t_dealer_value.setCellStyle(biStyle);
  11. sheet.addMergedRegion(new CellRangeAddress(i
  12. - csl.getLocationNum() + 1, i, 2, 2));
  13. sheet.addMergedRegion(new CellRangeAddress(i
  14. - csl.getLocationNum() + 1, i, 3, 3));
  15. }

这个就很简单了,因为不涉及到跨行,只是跨列而已,这都是预先已经设定好的,不难。而省份和经销商的跨行计算就略微复杂:

  1. if (csl.getTotalProvince() != 0) {
  2. XSSFCell t_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(new CellRangeAddress(i + 1, i + 1, 1, 4));
  9. XSSFCell t_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(new CellRangeAddress(1, i, 1, 1));
  16. } else if (comboProvince == 1) {
  17. sheet.addMergedRegion(new CellRangeAddress(
  18. indexComboProvice.get(comboProvince - 1)
  19. + comboProvince + 1, i, 1, 1));
  20. } else {
  21. sheet.addMergedRegion(new CellRangeAddress(
  22. indexComboProvice.get(comboProvince - 1)
  23. + comboProvince, i, 1, 1));
  24. }
  25. comboProvince++;
  26. }

这里为了将所有单元格都加入样式,所以没有数据填充的单元格仅做样式处理即可。合并行的时候要看这是第几次合并,因为算法不同。第一次时比较简单,只需数出有第一个合计中有多少经销商即可。而后续的就需要记录上次合并出现的位置,然后再加上第二个经销商的数量,之后进行合并。那么事业部的算法也是如此。

  1. if (csl.getTotalDivision() != 0) {
  2. XSSFCell t_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(new CellRangeAddress(i + 1, i + 1, 0, 4));
  10. XSSFCell t_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(new CellRangeAddress(1, i, 0, 0));
  17. } else if (comboDivision == 1) {
  18. sheet.addMergedRegion(new CellRangeAddress(
  19. indexComboDivision.get(comboDivision - 1)
  20. + comboDivision + 1, i, 0, 0));
  21. } else {
  22. sheet.addMergedRegion(new CellRangeAddress(
  23. indexComboDivision.get(comboDivision - 1)
  24. + comboDivision, i, 0, 0));
  25. }
  26. comboDivision++;
  27. }

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

 

Apache POI组件操作Excel,制作报表(三)的更多相关文章

  1. Apache POI组件操作Excel,制作报表(四)

    Apache POI组件操作Excel,制作报表(四) 博客分类: 探索实践 ExcelApacheSpringMVCServlet      上一篇我们介绍了如何制作复杂报表的分析和设计,本篇结合S ...

  2. Apache POI组件操作Excel,制作报表(二)

    本文接上一篇继续探究POI组件的使用.     现在来看看Excel的基本设置问题,以2007为例,先从工作簿来说,设置列宽,因为生成表格列应该固定,而行是遍历生成的,所以可以在工作簿级别来设置列宽, ...

  3. Apache POI组件操作Excel,制作报表(一)

    Apache的POI组件是Java操作Microsoft Office办公套件的强大API,其中对Word,Excel和PowperPoint都有支持,当然使用较多的还是Excel,因为Word和Po ...

  4. java用org.apache.poi包操作excel

    一.POI简介 Jakarta POI 是apache的子项目,目标是处理ole2对象.它提供了一组操纵Windows文档的Java API 目前比较成熟的是HSSF接口,处理MS Excel(97- ...

  5. C#操作Excel开发报表系列整理(转)

    C#操作Excel进行报表开发系列共写了七篇,也已经有很久没有新东西了,现在整理一下,方便以后查阅,如果有写新的,会同时更新.需要注意的是因为Office的版本不同,实际的代码可能会有所不同,但是都是 ...

  6. C#操作Excel开发报表系列整理

    C#操作Excel进行报表开发系列共写了八篇,也已经有很久没有新东西了,现在整理一下,方便以后查阅,如果有写新的,会同时更新.需要注意的是因为Office的版本不同,实际的代码可能会有所不同,但是都是 ...

  7. 使用JXL组件操作Excel和导出文件

    这段时间参与的项目要求做几张Excel报表,由于项目框架使用了jxl组件,所以把jxl组件的详细用法归纳总结一下.本文主要讲述了以下内容: JXL及相关工具简介 如何安装JXL JXL的基本操作 创建 ...

  8. 导入org.apache.poi.xssf 读取excel

    POI 操作 excel  用XSSF 方式时,如果不能自动导入 org.apache.poi.xssf 对应jar 包,则可以Apache 官网进行下载,自行导入. step1: 访问 http:/ ...

  9. Apache POI 实现对 Excel 文件读写

    1. Apache POI 简介 Apache POI是Apache软件基金会的开放源码函式库. 提供API给Java应用程序对Microsoft Office格式档案读和写的功能. 老外起名字总是很 ...

随机推荐

  1. python mock模块使用(一)

    什么是mock unittest.mock是一个用于在Python中进行单元测试的库,Mock翻译过来就是模拟的意思,顾名思义这个库的主要功能是模拟一些东西. 它的主要功能是使用mock对象替代掉指定 ...

  2. robotframework使用requestsLibrary进行接口测试

    一.定义 接口测试:接口测试通常是系统之间交互的接口,或者某个系统对外提供的一些接口服务 分类:RESTful.webservice接口 二.安装 进入C:\Pyhon27\scripts 先要安装r ...

  3. Flask---ajax(jquery)交互

    目录结构如下: |--| |--run.py |--static |--test.txt |--templates |--index.html 前端代码如下: index.html <!DOCT ...

  4. hdu 3732

    #include<stdio.h> #include<string.h> int n,m,dp[10001]; int max(int a,int b) {  return a ...

  5. hdu 1528 二分匹配

    #include<stdio.h> #include<string.h> int map[100][100],mark[100],link[100],max2,k; int f ...

  6. SDWebImage实现分析

    该博文来自南峰子的技术博客,文章从下载和缓存俩个大的组件分析到里面一些核心方法的实现,条理清晰,相对于一些一上来就通篇分析实现思路的技术文章, 这篇的讲解思路明确,框架架构也讲的比较清楚.看完这篇再去 ...

  7. android去除标题栏和状态栏(全屏)

    转--http://www.eoeandroid.com/thread-66555-1-1.html 在开发中我们经常需要把我们的应用设置为全屏,这里我所知道的有俩中方法,一中是在代码中设置,另一种方 ...

  8. hdu1588:Gauss Fibonacci

    对每个0<=i<n求f(g(i))的和,其中f(x)为斐波那契数列第x项,g(i)=k*i+b,k,b,n给定,模数给定. 斐波那契数有一种用矩阵乘法求的方法,这个矩阵A自己写,令F[i] ...

  9. T1992 聚会 codevs

    http://codevs.cn/problem/1992/  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description 小S 想要从某地 ...

  10. Android 学习路线图(转载自https://blog.csdn.net/lixuce1234/article/details/77947405)

    程序设计 一.java (a)基本语法(如继承.异常.引用.泛型等) Java核心技术 卷I(适合入门) 进阶 Effective Java中文版(如何写好的Java代码) Java解惑 (介绍烂Ja ...