- 使用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大量的操作。