最近使用poi做Excel导出较多,应用场景也各种各样,导致基本都是按需求自己手动拼的Excel,网上的通用工具类大多不适合业务需要,简单记录下来以免下次用到。
通用版(根据jsp上的表格内容动态生成Excel)
工具类
@SuppressWarnings("deprecation") public static void fromHtmlTable(String tableHtml,String fileName,OutputStream outputStream){ HSSFWorkbook wb = null; try{ wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet("sheet1"); TableRow[] rows = parserTable(tableHtml); int rowIndex = 0; Boolean hasCheckbox = false; Integer checkboxTdIndex = -1; HSSFCellStyle thStyle = createCellStyle(wb, true); HSSFCellStyle tdStyle = createCellStyle(wb, false); String[][] aa = new String[rows.length][MAX_COL]; for(int trIndex = 0 ;trIndex < rows.length;trIndex++){ TableRow tableRow = rows[trIndex]; HSSFRow row = sheet.createRow((short)rowIndex); int cellIndex = 0; if(tableRow.hasHeader()) { TableHeader[] heads = tableRow.getHeaders(); for(int tdIndex = 0 ;tdIndex< heads.length;tdIndex++){ if(heads[tdIndex].getStringText() != null && heads[tdIndex].getStringText().contains("checkbox")){ hasCheckbox = true; checkboxTdIndex = tdIndex; continue; } String rowspanstr = heads[tdIndex].getAttribute("rowspan"); Integer rowspan = Integer.parseInt(StringUtils.isBlank(rowspanstr)?"1":rowspanstr); String colspanstr = heads[tdIndex].getAttribute("colspan"); Integer colspan = Integer.parseInt(StringUtils.isBlank(colspanstr)?"1":colspanstr); while(aa[trIndex][cellIndex]!=null) { cellIndex ++; } HSSFCell cell = row.createCell((short) cellIndex); cell.setCellStyle(thStyle); if ( rowspan != 1) { sheet.addMergedRegion(new Region (rowIndex,(short)cellIndex, rowIndex+rowspan-1,(short)cellIndex)); for (int i=rowIndex+1 ; i<=rowIndex+rowspan-1 ; i++) { aa[i][cellIndex] = "1"; } } if (colspan != 1) { sheet.addMergedRegion(new Region (rowIndex, (short)cellIndex,rowIndex, (short)(cellIndex+colspan-1))); } cellIndex = cellIndex+colspan-1; cell.setCellValue(heads[tdIndex].getStringText()); cellIndex++; } } else { TableColumn[] cls=tableRow.getColumns(); for(int tdIndex = 0 ;tdIndex< cls.length;tdIndex++) { if(hasCheckbox && tdIndex == checkboxTdIndex){ continue; } HSSFCell cell = row.createCell((short) cellIndex); TableColumn tc = cls[tdIndex]; String cellValue = tc.getStringText(); cellValue = cellValue.replaceAll("<[^>]*>", ""); cell.setCellValue(cellValue); cell.setCellStyle(tdStyle); cellIndex++; } } rowIndex++; } } catch (Exception e) { e.printStackTrace(); } if(wb != null){ try { wb.write(outputStream); } catch (IOException e) { e.printStackTrace(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
private static TableRow[] parserTable(String tableHtml) throws ParserException { Parser ps = new Parser(tableHtml); NodeFilter nodeFilter=new TagNameFilter("table"); NodeList list=ps.extractAllNodesThatMatch(nodeFilter); TableTag node=(TableTag)list.elementAt(0) ; TableRow[] rows= node.getRows(); return rows; }
jsp
除了应有的表格内容外
<form id='form1_1' name="form1_1" action="${ctx}/chartTools/swf/saveToExecl3.jsp" method='post' style="display: hidden"> <input type='hidden' name='type' id='type'/> <input type='hidden' name='sfhm' id='sfhm'/> <input type='hidden' name='objectid' id='objectid' value=""/></form>
saveToExecl3.jsp
<%!private String toUtf8(String s){ StringBuffer sb = new StringBuffer(); for (int i=0;i<s.length();i++){ char c = s.charAt(i); if (c >= 0 && c <= 255){sb.append(c);} else{ byte[] b; try { b = Character.toString(c).getBytes("utf-8");} catch (Exception ex) { System.out.println(ex); b = new byte[0]; } for (int j = 0; j < b.length; j++) { int k = b[j]; if (k < 0) k += 256; sb.append("%" + Integer.toHexString(k).toUpperCase()); } } } return sb.toString(); }%><% request.setCharacterEncoding("utf-8"); String reqString =URLDecoder.decode(request.getParameter("content"),"UTF-8"); String filename = request.getParameter("filename");try{ out.clear(); out = pageContext.pushBody(); OutputStream outputStream = response.getOutputStream(); response.setContentType("application/vnd.ms-excel;utf-8"); response.addHeader("Content-Disposition", "attachment; filename=\"" + toUtf8(filename) + ".xls\""); ExportToExcel.fromHtmlTable2(reqString,filename,outputStream); }catch(Exception e){}%>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
主要就这两个部分,请求方式按自己的来,表名需要转化编码,th和td会对应拼到Excel上。
手动拼装复杂表头
有项目需要的Excel表头为多个单元格合并显示的情况,这种情况因为表头固定,所以也不会要求动态生成表头,因此我都是直接在后台拼装好表头,内容部分按通用方式生成这样的处理方式。
基本操作
HSSFCell title = sheet.createRow(0).createCell(0)title.setCellType(HSSFCell.CELL_TYPE_STRING)title.setCellStyle(cs)title.setCellValue(filename.trim())sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 10))
createRow为创建行,createCell为创建列,建议一行一行拼接,不然很容易出问题。
setCellType为设置样式,具体样式对应代码可上网查询,setCellValue为设置单元格内容。
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 10));为合并单元格操作方式,
四个参数分别是:起始行,起始列,结束行,结束列 。
这里将第一行前11列合并,用来显示表名。
难处理的地方
基本操作其实就能满足合并单元格的要求了,可如果表头较多,有时不止是列需要合并,行也需要合并,这种时候后台写起合并来就容易转的晕乎乎的,行和列本就容易弄错,这里介绍一些自己的经验。
- 优先一行一行处理
尤其是生成时,先生成行,一行一行处理,包括赋值和合并单元格
//第三行 列标题 HSSFRow row2 = sheet.createRow(2)HSSFCell title = row2.createCell(1)title.setCellType(HSSFCell.CELL_TYPE_STRING)title.setCellStyle(cs)title.setCellValue("人数1")HSSFCell title2 = row2.createCell(7)title2.setCellType(HSSFCell.CELL_TYPE_STRING)title2.setCellStyle(cs)title2.setCellValue("人数2")sheet.addMergedRegion(new CellRangeAddress(2, 2, 1, 5))sheet.addMergedRegion(new CellRangeAddress(2, 2, 7, 10))
合并单元格的赋值要对应未合并前的单元格位置
简单说,之前合并时将第3行2-6列合并,8-11合并,那么赋值便应该按照列1和列7来赋值,不论是合并操作前还是合并操作后,建议可以先合并,对着Excel看往哪个格子赋值,有时真的想着有点绕。。。
行之间的合并单元格要先生成才能合并
我这里的第二行的第1列和第7列是合并了第三行的,因此需要先进行sheet.createRow(3)操作。
HSSFRow row3 = sheet.createRow(3)sheet.addMergedRegion(new CellRangeAddress(2, 3, 0, 0))sheet.addMergedRegion(new CellRangeAddress(2, 3, 6, 6))row2.createCell(0).setCellValue("单位1")row2.createCell(6).setCellValue("5")
生成Excel表头效果大概如下
动态生成多页和表头
有的项目因数据原因,不会在页面上生成表格,也不方便生成表格,这时用通用的工具类生成Excel反倒复杂化,而业务需要又是要求动态生成多页,每页的表头还不一样,虽然不用复杂表头了,但是写死的方式更是行不通,这时我们便需要后台组装数据,动态拼接页数和表头。
@SuppressWarnings("deprecation") public static void fromHtmlTable2(List<SimDescDto> dtos, List<Map<String, String>> maps, HttpServletResponse response){ HSSFWorkbook wb = new HSSFWorkbook() HSSFSheet sheet = wb.createSheet("结果") HSSFRow row = sheet.createRow(0) HSSFRow row2 = sheet.createRow(1) HSSFCellStyle cs = wb.createCellStyle() //表头样式 HSSFCellStyle thStyle = createCellStyle(wb, true) //表体样式 HSSFCellStyle tdStyle = createCellStyle(wb, false) //第一页 HSSFCell c = row.createCell(0) c.setCellType(HSSFCell.CELL_TYPE_STRING) c.setCellValue("序号") c.setCellStyle(thStyle) HSSFCell c2 = row2.createCell(0) c2.setCellType(HSSFCell.CELL_TYPE_STRING) c2.setCellValue(1) c2.setCellStyle(tdStyle) HSSFCell cell = row.createCell(1) cell.setCellType(HSSFCell.CELL_TYPE_STRING) cell.setCellValue("身份证号") cell.setCellStyle(thStyle) HSSFCell cell2 = row2.createCell(1) cell2.setCellType(HSSFCell.CELL_TYPE_STRING) cell2.setCellValue(dtos.get(0).getSfhm()) cell2.setCellStyle(tdStyle) sheet.setColumnWidth(1, 35*150) for (int k = 0 HSSFSheet sheet2 = wb.createSheet(dtos.get(k).getTablename() + "结果") HSSFRow row3 = sheet2.createRow(0) HSSFRow row4 = sheet2.createRow(1) //后几页 Map<String, String> map = maps.get(k) int j = 0 if (null != map) { for (String key : map.keySet()) { HSSFCell cell3 = row3.createCell(j) cell3.setCellType(HSSFCell.CELL_TYPE_STRING) cell3.setCellValue(key) cell3.setCellStyle(tdStyle) sheet2.setColumnWidth(j, 35*150) HSSFCell cell4 = row4.createCell(j) cell4.setCellType(HSSFCell.CELL_TYPE_STRING) cell4.setCellValue(map.get(key)) cell4.setCellStyle(tdStyle) j++ } }else { HSSFCell cell3 = row3.createCell(j) cell3.setCellType(HSSFCell.CELL_TYPE_STRING) cell3.setCellValue("无") cell3.setCellStyle(tdStyle) } } OutputStream outputStream = null try { outputStream = response.getOutputStream() response.setContentType("application/vnd.ms-excel;utf-8") response.addHeader("Content-Disposition", "attachment; filename=\"" + toUtf8("结果") + ".xls\"") } catch (IOException e1) { e1.printStackTrace() } if(wb != null){ try { wb.write(outputStream) } catch (IOException e) { e.printStackTrace() } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
动态拼装实际上和手动拼装差不多,麻烦的是数据难处理的话就比较绕,比较多页面的循环里需要嵌套表头的循环,然后又要匹配数据里的内容,这里贴的代码是处理的较为简单的两层循环,弄清楚数据循环出来和Excel表格对应的地方就行了。
需要注意的地方
HSSFSheet sheet2 = wb.createSheet()即为创建多个页面的方法,sheet承载的就是整个页面的内容,在拼装时千万别用混了,还有给值的话便是页面名,但是千万不能同名,同名会报错,Excel导出时报错会影响后面内容不会显示。
create是必不可少的操作,不管是createSheet还是createRow,可是经常会出现页面内容和行内容为空的情况,这种时候就是因为多次创建了同一页面和行,即使你没有使用,只是HSSFRow row3 = sheet2.createRow(0);这样生成,可是这和Java的new可不一样,它会默认你重新创建了这一页或这一行,因此你的内容为空了。所以在循环拼装的时候,create操作放在哪里一定要仔细,我就是错了很多次才发现原因的。。。
有人在使用poi导出Excel时会有各种编码问题,包括表名,单元格内容,页名等等,建议在给单元格赋值时给上cell3.setCellType(HSSFCell.CELL_TYPE_STRING);的处理方式,虽然我测试的时候并没有什么影响….然后表名部分是一定要进行utf-8转换处理的,这里单独贴出来通用处理方式。
private static String toUtf8(String s){ StringBuffer sb = new StringBuffer(); for (int i=0;i<s.length();i++){ char c = s.charAt(i); if (c >= 0 && c <= 255){sb.append(c);} else{ byte[] b; try { b = Character.toString(c).getBytes("utf-8");} catch (Exception ex) { System.out.println(ex); b = new byte[0]; } for (int j = 0; j < b.length; j++) { int k = b[j]; if (k < 0) k += 256; sb.append("%" + Integer.toHexString(k).toUpperCase()); } } } return sb.toString(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
表名设置时
response.addHeader("Content-Disposition", "attachment; filename=\"" + toUtf8("比对结果") + ".xls\"");