前一篇文章介紹了后台將前台html轉為word文檔的一種方式,但卻有一個問題是沒法講圖片放置在生成的word報告中。我在網上找了很多方法,甚至將圖片轉換成base64編碼的方式也不成功。效果如下:

由於本人的水平有限,因此使用其他的實現方式。
首先介紹一下前台呈現圖片的原理:前台ueditor編輯框呈現的圖片實際上是一個img變遷,呈現的圖片的原始文件是存在服務器上的(甚至在udeitor中直接粘貼圖片也是想服務器上傳了該圖片)。

對應服務器文件為:
因此,我的視線思路是先將頁面傳回后台的html內容中的img標簽使用特定的字符替換,直接生成一個文本文檔,然后在在對應img標簽的位置替換為相應的圖片。因為在看網上說不能直接操作doc文檔插入圖片,需要生成docx文檔,於是我又參考網上實現了生成docx的后台代碼。
這次生成word文檔使用的是docx4j,插入圖片需要引入poi的另一個依賴,所需依賴如下:
<dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>3.3.6</version> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-ImportXHTML</artifactId> <version>3.3.6</version> </dependency>
代碼實現如下:
@RequestMapping("/defectV2/defect/analysis/tranformHtmlToWord")
@ResponseBody
public MessageBean tranformHtmlToWordDocx(@RequestParam Map params,HttpServletRequest request, HttpServletResponse response) {
try {
// params包含前台傳回的html內容
analysisService.tranformHtmlToWordDocx(params,request,response);
return new MessageBean("success", "生成報告成功!", null);
} catch (Exception e) {
e.printStackTrace();
utils.WriteSystemLog(sls, "ERROR", "生成報告", "生成報告失敗!" + e.getCause());
return new MessageBean("error", "生成報告失敗!", null);
}
}
public String tranformHtmlToWordDocx(Map params, HttpServletRequest request, HttpServletResponse response) throws Exception {
String body = (String) params.get("editorValue"); List<String> imgList = new ArrayList<String>(); String imgTag = "";//img標簽 String imgRegex = "<img[^>]+/>"; Pattern imgPattern = Pattern.compile(imgRegex,Pattern.CASE_INSENSITIVE); Matcher imgMatcher = imgPattern.matcher(body); int count = 1; while (imgMatcher.find()){ imgTag = imgMatcher.group(); //獲取img標簽 String srcStr = ""; //獲取匹配img標簽中的src內容 Matcher srcMatcher = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)").matcher(imgTag); while (srcMatcher.find()){ srcStr = srcMatcher.group(1); } String contextPath = request.getContextPath(); String imagePath = ExportWord.getRealPath() + srcStr.substring(srcStr.indexOf(contextPath) + contextPath.length() +1); imgList.add(imagePath); body = body.replace(imgTag,"${img" + count + "}"); count ++; } //html內容( 需要被替換,否則導出會報錯)
String unescaped ="<!DOCTYPE html><html><head><title>導出word</title></head><body>" + body.replaceAll(" ","    ")
+"</body></html>";
if (unescaped.contains("</") ) {
unescaped = StringEscapeUtils.unescapeHtml(unescaped);
}
// 創建一個空的docx對象
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
XHTMLImporter importer = new XHTMLImporterImpl(wordMLPackage);
importer.setTableFormatting(FormattingOption.IGNORE_CLASS);
importer.setParagraphFormatting(FormattingOption.IGNORE_CLASS);
NumberingDefinitionsPart ndp = new NumberingDefinitionsPart();
wordMLPackage.getMainDocumentPart().addTargetPart(ndp);
ndp.unmarshalDefaultNumbering();
// 轉換XHTML,並將其添加到我們制作的空docx中
XHTMLImporterImpl XHTMLImporter = new XHTMLImporterImpl(wordMLPackage);
XHTMLImporter.setHyperlinkStyle("Hyperlink");
wordMLPackage.getMainDocumentPart().getContent().addAll(
XHTMLImporter.convert(unescaped,null));
//ExportWord.getWordPath()是獲取項目文件路徑
String wordPath = ExportWord.getWordPath() + new Date().getTime() + "問題分析統計.docx";
wordMLPackage.save(new File(wordPath));
Map<String, Object> testMap = new HashMap<String, Object>();
WordUtils wordUtil=new WordUtils();
if (imgList!=null && imgList.size()>0){
for (int i=0;i<imgList.size();i++){
String imagePath = imgList.get(i);
Map map = new HashMap();
//封裝的單個圖片信息
map = WordUtils.packageImgMessage(imagePath);
testMap.put("${img" + (i+1) + "}",map);
}
}
wordUtil.getWord(wordPath,testMap,new ArrayList<String[]>(),"質量分析統計報告.docx",response);
return null;
}
WordUtil.java實現代碼(參考了網上的)
public class WordUtils { /** * 根據模板生成word * @param path 模板的路徑 * @param params 需要替換的參數 * @param tableList 需要插入的參數 * @param fileName 生成word文件的文件名 * @param response */ public void getWord(String path, Map<String, Object> params, List<String[]> tableList, String fileName, HttpServletResponse response) throws Exception { File file = new File(path); InputStream is = new FileInputStream(file); CustomXWPFDocument doc = new CustomXWPFDocument(is); this.replaceInPara(doc, params); //替換文本里面的變量 this.replaceInTable(doc, params, tableList); //替換表格里面的變量 OutputStream os = response.getOutputStream(); response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName,"utf-8")); doc.write(os); this.close(os); this.close(is); } /** * 替換段落里面的變量 * @param doc 要替換的文檔 * @param params 參數 */ private void replaceInPara(CustomXWPFDocument doc, Map<String, Object> params) { Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator(); XWPFParagraph para; while (iterator.hasNext()) { para = iterator.next(); this.replaceInPara(para, params, doc); } } /** * 替換段落里面的變量 *(網絡上找到的) * @param para 要替換的段落 * @param params 參數 */ private void replaceInParaYuan(XWPFParagraph para, Map<String, Object> params, CustomXWPFDocument doc) { List<XWPFRun> runs; Matcher matcher; if (this.matcher(para.getParagraphText()).find()) { runs = para.getRuns(); int start = -1; int end = -1; String str = ""; for (int i = 0; i < runs.size(); i++) { XWPFRun run = runs.get(i); String runText = run.toString(); //找出“${”開始的XWPFRun if ('$' == runText.charAt(0) && '{' == runText.charAt(1)) { start = i; } //若有“${”,則記錄開始的XWPFRun if ((start != -1)) { str += runText; } //若有“}”,記錄結束的XWPFRun if ('}' == runText.charAt(runText.length() - 1)) { if (start != -1) { end = i; break; } } } //${變量}之間的XWPFRun for (int i = start; i <= end; i++) { para.removeRun(i); i--; end--; } for (Map.Entry<String, Object> entry : params.entrySet()) { String key = entry.getKey(); if (str.indexOf(key) != -1) { Object value = entry.getValue(); if (value instanceof String) { str = str.replace(key, value.toString()); para.createRun().setText(str, 0); break; } else if (value instanceof Map) { str = str.replace(key, ""); Map pic = (Map) value; int width = Integer.parseInt(pic.get("width").toString()); int height = Integer.parseInt(pic.get("height").toString()); int picType = getPictureType(pic.get("type").toString()); byte[] byteArray = (byte[]) pic.get("content"); ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteArray); try { //int ind = doc.addPicture(byteInputStream,picType); //doc.createPicture(ind, width , height,para); doc.addPictureData(byteInputStream, picType); doc.createPicture(doc.getAllPictures().size() - 1, width, height, para); para.createRun().setText(str, 0); break; } catch (Exception e) { e.printStackTrace(); } } } } } } /** * 替換段落里面的變量 * (優化后的) * @param para 要替換的段落 * @param params 參數 */ private void replaceInPara(XWPFParagraph para, Map<String, Object> params, CustomXWPFDocument doc) { List<XWPFRun> runs; Matcher matcher; String paraText = para.getParagraphText(); if(matcher(paraText).find()){ runs = para.getRuns(); for (int i=0;i<runs.size();i++){ XWPFRun run = runs.get(i); String runText = run.toString(); matcher = matcher(runText); List<String> $StrList = new ArrayList<String>(); while (matcher.find()){ $StrList.add(matcher.group()); } if ($StrList.size()>0){ // para.removeRun(i); for (String $Str : $StrList){ if (params.containsKey($Str)){ Object value = params.get($Str); if (value instanceof String) { runText = runText.replace($Str, value.toString()); // para.createRun().setText(runText, 0); // break; } else if (value instanceof Map) { runText = runText.replace($Str, ""); Map pic = (Map) value; int width = Integer.parseInt(pic.get("width").toString()); int height = Integer.parseInt(pic.get("height").toString()); int picType = getPictureType(pic.get("type").toString()); byte[] byteArray = (byte[]) pic.get("content"); ByteArrayInputStream byteInputStream = new ByteArrayInputStream(byteArray); try { //int ind = doc.addPicture(byteInputStream,picType); //doc.createPicture(ind, width , height,para); doc.addPictureData(byteInputStream, picType); doc.createPicture(doc.getAllPictures().size() - 1, width, height, para); // para.createRun().setText(runText, 0); // break; } catch (Exception e) { e.printStackTrace(); } } } } // para.createRun().setText(runText, 0); run.setText(runText,0); } } } } /** * 為表格插入數據,行數不夠添加新行 * * @param table 需要插入數據的表格 * @param tableList 插入數據集合 */ private static void insertTable(XWPFTable table, List<String[]> tableList) { //創建行,根據需要插入的數據添加新行,不處理表頭 for (int i = 0; i < tableList.size(); i++) { XWPFTableRow row = table.createRow(); } //遍歷表格插入數據 List<XWPFTableRow> rows = table.getRows(); int length = table.getRows().size(); for (int i = 1; i < length - 1; i++) { XWPFTableRow newRow = table.getRow(i); List<XWPFTableCell> cells = newRow.getTableCells(); for (int j = 0; j < cells.size()&&tableList!=null&&tableList.size()>0; j++) { XWPFTableCell cell = cells.get(j); String s = tableList.get(i - 1)[j]; cell.setText(s); } } } /** * 替換表格里面的變量 * @param doc 要替換的文檔 * @param params 參數 */ private void replaceInTable(CustomXWPFDocument doc, Map<String, Object> params, List<String[]> tableList) { Iterator<XWPFTable> iterator = doc.getTablesIterator(); XWPFTable table; List<XWPFTableRow> rows; List<XWPFTableCell> cells; List<XWPFParagraph> paras; while (iterator.hasNext()) { table = iterator.next(); if (table.getRows().size() > 1) { //判斷表格是需要替換還是需要插入,判斷邏輯有$為替換,表格無$為插入 if (this.matcher(table.getText()).find()) { rows = table.getRows(); for (XWPFTableRow row : rows) { cells = row.getTableCells(); for (XWPFTableCell cell : cells) { paras = cell.getParagraphs(); for (XWPFParagraph para : paras) { this.replaceInPara(para, params, doc); } } } } else { insertTable(table, tableList); //插入數據 } } } } /** * 正則匹配字符串 * * @param str * @return */ private Matcher matcher(String str) { Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(str); return matcher; } /** * 根據圖片類型,取得對應的圖片類型代碼 * * @param picType * @return int */ private static int getPictureType(String picType) { int res = CustomXWPFDocument.PICTURE_TYPE_PICT; if (picType != null) { if (picType.equalsIgnoreCase("png")) { res = CustomXWPFDocument.PICTURE_TYPE_PNG; } else if (picType.equalsIgnoreCase("dib")) { res = CustomXWPFDocument.PICTURE_TYPE_DIB; } else if (picType.equalsIgnoreCase("emf")) { res = CustomXWPFDocument.PICTURE_TYPE_EMF; } else if (picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")) { res = CustomXWPFDocument.PICTURE_TYPE_JPEG; } else if (picType.equalsIgnoreCase("wmf")) { res = CustomXWPFDocument.PICTURE_TYPE_WMF; } } return res; } // /** // * 將輸入流中的數據寫入字節數組 // * // * @param in // * @return // */ // public static byte[] inputStream2ByteArray(InputStream in, boolean isClose) { // byte[] byteArray = null; // try { // int total = in.available(); // byteArray = new byte[total]; // in.read(byteArray); // } catch (IOException e) { // e.printStackTrace(); // } finally { // if (isClose) { // try { // in.close(); // } catch (Exception e2) { // e2.getStackTrace(); // } // } // } // return byteArray; // } /** * 封裝圖片信息 * @return */ public static Map packageImgMessage(String imagePath){ Map map = new HashMap(); InputStream ips = null; try { ips = new FileInputStream(imagePath); BufferedImage image = ImageIO.read(ips); map.put("width", image.getWidth()); map.put("height", image.getHeight()); ips.close(); ips = new FileInputStream(imagePath); map.put("type", imagePath.substring(imagePath.lastIndexOf('.')+1)); int total = ips.available(); byte[] byteArray = new byte[total]; ips.read(byteArray); map.put("content", byteArray); } catch (Exception e) { e.printStackTrace(); }finally { if (ips != null){ try { ips.close(); } catch (IOException e) { e.printStackTrace(); } } } return map; } /** * 關閉輸入流 * * @param is */ private void close(InputStream is) { if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 關閉輸出流 * * @param os */ private void close(OutputStream os) { if (os != null) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } }
這樣就實現了前台html轉換為docx的word文檔,並且將前台呈現的圖片插入到生成的文檔中。
我想解決文檔中的圖片的初衷是因為導出的文檔需要有餅圖,如下所示:

這個餅圖是word文檔做出來的,並不是圖片,我使用了簡單的方法使用圖片展現的方式呈現出來。下篇文章將介紹生成餅圖的實現方式。
params包含前台傳回的html內容
