- 使用POI技术生成word格式的月报,实现功能根据日期进行预览,导出。
- 使用template.docx作为word模板,参数使用特殊符号标识,封装数据(Map<String,String>); 通过IO读取模板替换参数,从而动态获取数据。
- 预览的实现,由于web页面展示通过html或pdf来进行。将word转为pdf后基本都会出现样式的不兼容问题,所以放弃。 最终我根据word的模板又手写了一个html对应的模板,参数使用的jsp的el表达式动态替换参数,从而动态获取数据。暂时没有考虑word模板过多的情况。
- 由于我做的系统的前端是easyui的框架,预览时的翻页使用了panel插件功能,批量导出使用的ZipEntry。
相关代码
1 package com.bocsh.base.util; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Map.Entry; 9 import java.util.Set; 10 11 12 //import org.apache.poi.POIXMLDocument; 13 import org.apache.poi.xwpf.usermodel.*; 14 15 /** 16 * 通过word模板生成新的word工具类 17 * 18 * @author zhiheng 19 * 20 * 21 * XWPFDocument代表一个docx文档,其可以用来读docx文档,也可以用来写docx文档 22 * XWPFParagraph代表文档、表格、标题等种的段落,由多个XWPFRun组成 23 * XWPFRun代表具有同样风格的一段文本 24 * XWPFTable代表一个表格 25 * XWPFTableRow代表表格的一行 26 * XWPFTableCell代表表格的一个单元格 27 * XWPFChar 表示.docx文件中的图表 28 * XWPFHyperlink 表示超链接 29 * XWPFPicture 代表图片 30 * 31 * 32 * 33 */ 34 public class WorderToNewWordUtils { 35 36 /** 37 * 判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入 38 * @param inputUrl 模板存放地址 39 * @param textMap 需要替换的信息集合 40 * @param excelDataBytes 生成了新的数据流 word格式, 存放容器 41 * @return 成功返回true,失败返回false 42 */ 43 public static boolean changWord(String inputUrl, 44 Map<String, String> textMap, Map<String,byte[]> excelDataBytes) { 45 46 //模板转换默认成功 47 boolean changeFlag = true; 48 ByteArrayOutputStream writeToBytes = null; 49 try { 50 //获取docx解析对象 51 XWPFDocument document = new XWPFDocument(new FileInputStream(inputUrl)); 52 //解析替换文本段落对象 53 WorderToNewWordUtils.changeText(document, textMap); 54 //解析替换表格对象 55 WorderToNewWordUtils.changeTable(document, textMap); 56 57 //生成了新的数据流 word 格式 58 writeToBytes = new ByteArrayOutputStream(); 59 document.write(writeToBytes); 60 excelDataBytes.put(textMap.get("year") + textMap.get("month"), writeToBytes.toByteArray()); 61 62 } catch (IOException e) { 63 e.printStackTrace(); 64 changeFlag = false; 65 }finally{ 66 try { 67 if(writeToBytes!=null) 68 writeToBytes.close(); 69 } catch (IOException e) { 70 e.printStackTrace(); 71 } 72 } 73 74 return changeFlag; 75 76 } 77 78 79 /** 80 * 替换段落文本 81 * @param document docx解析对象 82 * @param textMap 需要替换的信息集合 83 */ 84 public static void changeText(XWPFDocument document, Map<String, String> textMap){ 85 //获取段落集合 86 List<XWPFParagraph> paragraphs = document.getParagraphs(); 87 88 for (XWPFParagraph paragraph : paragraphs) { 89 //判断此段落时候需要进行替换 90 String text = paragraph.getText(); 91 if(checkText(text)){ 92 List<XWPFRun> runs = paragraph.getRuns(); 93 for (XWPFRun run : runs) { 94 //替换模板原来位置 95 run.setText(changeValue(run.toString(), textMap),0); 96 } 97 } 98 } 99 100 } 101 102 /** 103 * 替换表格对象方法 104 * @param document docx解析对象 105 * @param textMap 需要替换的信息集合 106 */ 107 private static void changeTable(XWPFDocument document, Map<String, String> textMap){ 108 //获取表格对象集合 109 List<XWPFTable> tables = document.getTables(); 110 for (int i = 0; i < tables.size(); i++) { 111 //只处理行数大于等于2的表格,且不循环表头 112 XWPFTable table = tables.get(i); 113 if(table.getRows().size()>1){ 114 //判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入 115 if(checkText(table.getText())){ 116 List<XWPFTableRow> rows = table.getRows(); 117 //遍历表格,并替换模板 118 eachTable(rows, textMap); 119 } 120 } 121 } 122 123 124 } 125 126 127 /** 128 * 遍历表格 129 * @param rows 表格行对象 130 * @param textMap 需要替换的信息集合 131 */ 132 private static void eachTable(List<XWPFTableRow> rows ,Map<String, String> textMap){ 133 for (XWPFTableRow row : rows) { 134 List<XWPFTableCell> cells = row.getTableCells(); 135 for (XWPFTableCell cell : cells) { 136 //判断单元格是否需要替换 137 if(checkText(cell.getText())){ 138 List<XWPFParagraph> paragraphs = cell.getParagraphs(); 139 for (XWPFParagraph paragraph : paragraphs) { 140 List<XWPFRun> runs = paragraph.getRuns(); 141 for (XWPFRun run : runs) { 142 run.setText(changeValue(run.toString(), textMap),0); 143 } 144 } 145 } 146 } 147 } 148 } 149 150 151 /** 152 * 判断文本中时候包含$ 153 * @param text 文本 154 * @return 包含返回true,不包含返回false 155 */ 156 private static boolean checkText(String text){ 157 boolean check = false; 158 if(text.indexOf("$")!= -1){ 159 check = true; 160 } 161 return check; 162 163 } 164 165 /** 166 * 匹配传入信息集合与模板 167 * @param value 模板需要替换的区域 168 * @param textMap 传入信息集合 169 * @return 模板需要替换区域信息集合对应值 170 */ 171 private static String changeValue(String value, Map<String, String> textMap){ 172 Set<Entry<String, String>> textSets = textMap.entrySet(); 173 for (Entry<String, String> textSet : textSets) { 174 //匹配模板与替换值 格式${key} 175 String key = "${"+textSet.getKey()+"}"; 176 if(value.indexOf(key)!= -1){ 177 value = textSet.getValue(); 178 } 179 } 180 //模板未匹配到区域替换为空 181 if(checkText(value)){ 182 value = "0"; 183 } 184 return value; 185 } 186 187 188 }
注意:
- html模板和word模板共用的数据模型
- 写这个功能前翻阅了大量的关于java导出word的博客,有大量的将word转为ftl格式再去操作,感觉不是很方便和直观。
- 上面的代码参考了 https://www.cnblogs.com/sun-flower1314/p/10128796.html,这里有使用POI对word大量的操作。