以前寫過一篇利用Freemarker模板生成doc的博客,不過那個博客有點缺陷,生成的word佔用的空間很大,幾百頁的word有將近100M了。因此,後面需求必須是生成的docx文檔,結果導出後正常才幾M,昨天花了一天的時間實現。瀏覽器
1.把docx文檔修改成ZIP格式(修改.docx後綴名爲.zip)
2.獲取zip裏的document.xml文檔以及_rels文件夾下的document.xml.rels文檔
3.把內容填充到document.xml裏,以及圖片配置信息填充至document.xml.rels文檔裏
4.在輸入docx文檔的時候把填充過內容的的 document.xml、document.xml.rels用流的方式寫入zip(詳見下面代碼)。
5.把圖片寫入zip文件下word/media文件夾中
6.輸出docx文檔
docx模板修改爲zip格式後的信息以下(由於word文檔自己就是ZIP格式實現的)測試
注意:若是docx模板裏的圖片帶有具體路徑的話,則圖片的格式不受限制。編碼
若是docx模板裏裏圖片信息不帶路徑,則模板僅支持和模板圖片類型一致的圖片。spa
1.準備好docx模板設計
2.把docx文檔修改成ZIP格式(修改.docx後綴名爲.zip)3d
3.獲取zip文件裏的word文件夾下的document.xml以及_rels文件夾裏的document.xml.rels文件做爲模板。code
注意:這裏圖片配置信息是根據 rId來獲取的,爲了不重複,能夠根據本身具體的業務規則來實現xml
4.填充模板信息、寫入圖片信息。blog
//outputStream 輸出流能夠本身定義 瀏覽器或者文件輸出流 public static void createDocx(Map dataMap,OutputStream outputStream) { ZipOutputStream zipout = null; try { //圖片配置文件模板 ByteArrayInputStream documentXmlRelsInput =FreeMarkUtils.getFreemarkerContentInputStream(dataMap, documentXmlRels); //內容模板 ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, document); //最初設計的模板 File docxFile = new File(WordUtils.class.getClassLoader().getResource(template).getPath()); if (!docxFile.exists()) { docxFile.createNewFile(); } ZipFile zipFile = new ZipFile(docxFile); Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries(); zipout = new ZipOutputStream(outputStream); //開始覆蓋文檔------------------ int len = -1; byte[] buffer = new byte[1024]; while (zipEntrys.hasMoreElements()) { ZipEntry next = zipEntrys.nextElement(); InputStream is = zipFile.getInputStream(next); if (next.toString().indexOf("media") < 0) { zipout.putNextEntry(new ZipEntry(next.getName())); if (next.getName().indexOf("document.xml.rels") > 0) { //若是是document.xml.rels由咱們輸入 if (documentXmlRelsInput != null) { while ((len = documentXmlRelsInput.read(buffer)) != -1) { zipout.write(buffer, 0, len); } documentXmlRelsInput.close(); } } else if ("word/document.xml".equals(next.getName())) {//若是是word/document.xml由咱們輸入 if (documentInput != null) { while ((len = documentInput.read(buffer)) != -1) { zipout.write(buffer, 0, len); } documentInput.close(); } } else { while ((len = is.read(buffer)) != -1) { zipout.write(buffer, 0, len); } is.close(); } } } //寫入圖片 List<Map<String, Object>> picList = (List<Map<String, Object>>) dataMap.get("picList"); for (Map<String, Object> pic : picList) { ZipEntry next = new ZipEntry("word" + separator + "media" + separator + pic.get("name")); zipout.putNextEntry(new ZipEntry(next.toString())); InputStream in = (ByteArrayInputStream)pic.get("code"); while ((len = in.read(buffer)) != -1) { zipout.write(buffer, 0, len); } in.close(); } } catch (Exception e) { logger.error("word導出失敗:"+e.getStackTrace()); }finally { if(zipout!=null){ try { zipout.close(); } catch (IOException e) { logger.error("io異常"); } } if(outputStream!=null){ try { outputStream.close(); } catch (IOException e) { logger.error("io異常"); } } } }
/** * 獲取freemarker模板字符串 * @author lpf */ public class FreeMarkUtils { private static Logger logger = LoggerFactory.getLogger(FreeMarkUtils.class); public static Configuration getConfiguration(){ //建立配置實例 Configuration configuration = new Configuration(Configuration.VERSION_2_3_28); //設置編碼 configuration.setDefaultEncoding("utf-8"); configuration.setClassForTemplateLoading(FreeMarkUtils.class, "/template"); return configuration; } /** * 獲取模板字符串輸入流 * @param dataMap 參數 * @param templateName 模板名稱 * @return */ public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName) { ByteArrayInputStream in = null; try { //獲取模板 Template template = getConfiguration().getTemplate(templateName); StringWriter swriter = new StringWriter(); //生成文件 template.process(dataMap, swriter); in = new ByteArrayInputStream(swriter.toString().getBytes("utf-8"));//這裏必定要設置utf-8編碼 不然導出的word中中文會是亂碼 } catch (Exception e) { logger.error("模板生成錯誤!"); } return in; } }
5.輸出word測試圖片
就是這麼簡單,比較麻煩的就是若是word比較複雜的話寫freemarkr標籤要仔細一些,還有word中的字符儘可能對特殊字符有轉義,不然會引發word打不開的現象。
這裏最重要的一個思想是把文件輸出到ByteArrayOutputStream裏,下載的時候把這個字節寫出就好了。開發的時候注意編碼問題,用這種方式導出還有一個好處就是,你會發現,一樣一個word。導出doc格式的文件大小要比docx格式的文件大小大的多,因此仍是推薦用這種;