1. 程式人生 > 其它 >Freemarker根據模板生成docx

Freemarker根據模板生成docx

需求:

使用Freemarker模板靜態化技術根據word模板生成docx的word文件,生成doc的文章請看這裡

注意點:

生成doc檔案和生成docx檔案的方式有所不同,原因為docx是一個特殊的zip檔案

模板配置步驟:

  • 1、首先將自己的模板重新命名為xxx.zip的檔案,zip保留再解壓一份,解壓好以後的第一層目錄有3個資料夾和一個檔案我們只用到了word資料夾中的檔案,然後word中的檔案中我們只使用document.xml檔案、media資料夾和_rels資料夾

    目錄解釋:

    ​ document.xml檔案就類似與我們做doc生成文件時候另存為的那個xml檔案,這個xml中就是我們需要替換${}資料的部分

    ​ _rels資料夾中的document.xml.rels檔案中的程式碼是用來關聯document.xml和media資料夾中的圖片的,如果word某處的圖 片不確定有多少個就需要用迴圈來操作這個檔案中的配置了

    ​ media資料夾中放置的就是word中需要的圖片了,他們的關聯關係由document.xml.rels來決定

  • 2、將document.xml單獨拿出來然後在編輯器中開啟,(這裡注意這個docx的xml檔案不能格式化,一旦格式化以後雖然資料可以正常填充但是在生成word之後會出現 Word 在xxx.docx中發現無法讀取的內容。是否恢復此文件的內容?如果您信任此文件的來源,請單擊"是"。

    的問題 ,這個問題在生成doc檔案的時候也出現了,但是它有解決辦法,那就是在另存為的時候選擇2003的xml,但是docx是直接重新命名的zip檔案,如果格式化了就一定會出現上面的問題,暫時還沒找到格式化後的這個問題怎麼解決,所以只能在做docx的xml時候麻煩點、仔細一點,將就這替換變量了,如果看到這篇文章的小夥伴有解決方案還請您評論區告訴我 感謝)

  • 3、如果你的文件中有圖片且圖片個數是固定的那就沒有必要配置document.xml.rels,否則就需要配置,這篇文章是固定的

編碼步驟:

  • 1、這裡需要的是document.xml檔案和將自己的模板重新命名為xxx.zip的檔案
  • 2、使用freemarker將資料替換到document.xml中,然後使用java的zip工具類將document.xml替換掉模板zip中的document.xml檔案,如果有圖片就替換到media資料夾下,再然後將zip的輸入流輸出成xxx.docx檔案即可。

編碼:

freemarker生成Docx工具類:


/**
 * freemarker生成Docx工具類
 */
public class FreemarkerDocxUtil {

    /**
     * freemarker公用配置
     *
     * @return
     * @throws IOException
     */
    public static Configuration getConfiguration(String rootPath) throws IOException {
        //建立配置例項
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        //設定編碼
        configuration.setDefaultEncoding("utf-8");
        configuration.setDirectoryForTemplateLoading(new File(rootPath));
        return configuration;
    }

    /**
     * 根據freemarker生成docx文件
     *
     * @param templateRootPath    word模板的根路徑 例如:D:/xxx/xxx 最後一個名稱只到目錄
     * @param xmlTemplateName     word模板的名稱(docx的是xml格式)例如:test.xml
     * @param docxFileName        生成docx檔名稱 例如:test.docx
     * @param docxZipName         模板壓縮包名稱  例如:test.zip
     * @param dataMap             生成檔案需要的資料
     * @param itemNameList        需要替換的圖片資源地址 例如:word/media/xxx.xxx 去你的zip模板的word/media路徑去找
     * @param itemInputStreamList 根據itemNameList中的圖片地址替換掉word/media中的圖片,這個圖片的順序要和itemNameList中的一致
     * @throws Exception
     */
    public static void freemarkerDocxTest(String templateRootPath, String xmlTemplateName, String docxFileName, String docxZipName, Map<String, Object> dataMap,
                                          List<String> itemNameList, List<InputStream> itemInputStreamList) throws Exception {
        //配置freemarker模板
        Template template = getConfiguration(templateRootPath).getTemplate(xmlTemplateName);
        //通過模板生成的xml臨時檔案 方法結束後刪除該臨時檔案
        String outFilePath = templateRootPath + UUID.randomUUID().toString().replace("-", "") + ".xml";
        //指定輸出word xml檔案的路徑
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFilePath), StandardCharsets.UTF_8));
        template.process(dataMap, out);
        out.close();

        //包裝輸入流 模板zip包
        ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(templateRootPath + File.separator + docxZipName));
        //包裝輸出流 輸出docx文件流
        ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(templateRootPath + File.separator + docxFileName));
        //如果為空 建立需要替換zip中的檔案集合
        if (itemNameList == null) {
            itemNameList = new ArrayList<>();
        } else {
            itemNameList = itemNameList.stream().map(item -> item.replaceAll("\\\\", "/")).collect(Collectors.toList());
        }

        if (itemInputStreamList == null) {
            itemInputStreamList = new ArrayList<>();
        }
        itemNameList.add(0, "word/document.xml");
        // 建立需要替換的檔案資源  根據xml模板生成的xml檔案
        itemInputStreamList.add(0, new FileInputStream(outFilePath));

        replaceItemList(zipInputStream, zipOutputStream, itemNameList, itemInputStreamList);
        new File(outFilePath).delete();
    }

    /**
     * @param zipInputStream      zip模板
     * @param zipOutputStream     輸出docx文件流
     * @param itemNameList        替換的檔案路徑
     * @param itemInputStreamList
     */
    public static void replaceItemList(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream, List<String> itemNameList, List<InputStream> itemInputStreamList) {
        if (null == zipInputStream) {
            return;
        }
        if (null == zipOutputStream) {
            return;
        }
        ZipEntry entryIn;
        try {
            while ((entryIn = zipInputStream.getNextEntry()) != null) {
                String entryName = entryIn.getName();
                ZipEntry entryOut = new ZipEntry(entryName);
                // 這個entryName路徑結構是什麼,解壓後的檔案結構就是什麼,ZipEntry的大概意思就是隻設定這個檔案在zip中的路徑
                // 並不涉及到位元組的寫入 putNextEntry 設定每一個ZipEntry物件
                zipOutputStream.putNextEntry(entryOut);
                byte[] buf = new byte[8 * 1024];
                int len;

                // 這裡的判斷意思是如果有需要替換的檔案就使用替換檔案寫入,沒有就寫入原來的到新的docx檔案中
                if (itemNameList.contains(entryName)) {
                    // 使用替換流
                    while ((len = (itemInputStreamList.get(itemNameList.indexOf(entryName)).read(buf))) > 0) {
                        zipOutputStream.write(buf, 0, len);
                    }
                } else {
                    // 輸出普通Zip流
                    while ((len = (zipInputStream.read(buf))) > 0) {
                        zipOutputStream.write(buf, 0, len);
                    }
                }
                // 關閉此 entry
                zipOutputStream.closeEntry();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            for (InputStream itemInputStream : itemInputStreamList) {
                close(itemInputStream);
            }
            close(zipInputStream);
            close(zipOutputStream);
        }
    }

    private static void close(InputStream inputStream) {
        if (null != inputStream) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void close(OutputStream outputStream) {
        if (null != outputStream) {
            try {
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

獲取網路圖片的輸入流

/**
 * 獲取網路圖片的輸入流
 * @param imgURL
 * @return
 */
public static InputStream getImageInputStream(String imgURL) {
    try {
        URL url = new URL(imgURL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        return conn.getInputStream();
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

測試:

public static void main(String[] args) throws Exception {
    Map<String, Object> map = new HashMap<>();
    // 城市資訊
    List<Map<String, String>> citys = new ArrayList<>();
    Map<String, String> map1 = new HashMap<>();
    map1.put("cityName", "測試城市名稱1");
    map1.put("distanceEpicenter", "測試震中1");
    Map<String, String> map2 = new HashMap<>();
    map2.put("cityName", "測試城市名稱2");
    map2.put("distanceEpicenter", "測試震中2");
    citys.add(map1);
    citys.add(map2);

    map.put("citys", citys);
    map.put("level", "20");
    map.put("time", "3002年12月01日21時05分");
    map.put("latitude", "35.58");
    map.put("longitude", "112.05");
    map.put("depth", "10");
    map.put("intensity", "120");
    map.put("scope", "15");
    String templateRootPath = "word模板的根路徑";
    String xmlTemplateName = "document.xml", docxFileName = "314159265875658.docx", docxZipName = "zipTemplate.zip";
    // 這裡需要注意的就是imageName中的個數和順序要和imageNew對應
    List<String> imageName =new ArrayList<>();
    imageName.add("word\\media\\image2.jpeg");
    imageName.add("word\\media\\image3.jpeg");
    imageName.add("word\\media\\image4.jpeg");
    List<InputStream> imageNew =new ArrayList<>();
    imageNew.add(new FileInputStream("圖片1"));
    imageNew.add(new FileInputStream("圖片2"));
    imageNew.add(getImageInputStream("http://jy.sccnn.com/zb_users/upload/2015/2/2015021757532565.jpg"));
    FreemarkerDocxUtil.freemarkerDocxTest(templateRootPath, xmlTemplateName, docxFileName, docxZipName, map, imageName, imageNew);
}

結語:

到這裡關於Freemarker模板靜態化的文章就寫完了,我個人覺得需要注意的就是在替換變數的xml檔案時候不要將xml格式化。