java利用Freemarker模板生成docx格式的word文檔

以前寫過一篇利用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格式實現的)測試

  • document.xml裏存放主要數據
  • media存放圖片信息
  • _rels裏存放配置信息

注意:若是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格式的文件大小大的多,因此仍是推薦用這種;

相關文章
相關標籤/搜索