java 使用freemaker模版生成docx word文檔的解決方案

   公司前一段時間做的一個項目中應用到了這個技術,需要後臺獲取數據後在前臺直接下載word文檔,利用freemarker模版生成的doc文檔在電腦上可以正常打開,但是發送到手機上打開則全部變成“亂碼”。但實際上並不是亂碼,而是xml格式的代碼,在手機等移動端顯示不出正常的文檔信息。之後輾轉查詢使用了很多方案,比如利用poi操作,利用jacob進行格式轉換,但是效果都不盡如人意,而且處理過程非常複雜,浪費了大量時間。最終,通過不斷摸索,找到了非常完美的解決方案。不需要先生成xml格式的doc文檔再去轉換,而是戒指在後臺利用模版添加數據後,利用IO流進行替換,可以直接生成標準的docx文檔。

    首先需要理解的一點是,docx文檔本身是一個壓縮文件。在利用這個方案前要先準備好docx模版。在這個docx模版中,將要替換的數據用${}的方式替換掉,先佔好位置。如圖:

    將docx改後綴名爲.zip並解壓出來。在word文件夾下有一個文件爲、:document.xml

    利用notpad等工具打開此文件,將格式修改好,比如在xml文件中${xxx}分別氛圍${ ,  xxx 以及 } 三部分存貯在不同的標籤下,要將他們合併到一起。

    隨後將保存好的document.xml和之前定義好的zip文件放入工程的配置文件夾下,方便引用。

    核心代碼如下

  Configuration
             configuration = new Configuration();
     /** 設置編碼 **/
     /** 我的ftl文件是放在D盤的**/
     String fileDirectory = "D:/cache/qqChace/T1/xlsx";
     /** 加載文件 **/
     configuration.setDirectoryForTemplateLoading(new File(fileDirectory));
     /** 加載模板 **/
     Template template = configuration.getTemplate("document.xml");
     /** 準備數據 **/
     Map<String,String> dataMap = new HashMap<>();
     /** 在ftl文件中有${textDeal}這個標籤**/

     dataMap.put("id","哈士奇");
     dataMap.put("number","20");
     dataMap.put("language","java,php,python,c++.......");
     dataMap.put("example","Hello World!");

     /** 指定輸出word文件的路徑 **/
     String outFilePath = "D:/cache/qqChace/T1/xlsx/data.xml";
     File docFile = new File(outFilePath);
     FileOutputStream fos = new FileOutputStream(docFile);
     OutputStreamWriter oWriter = new OutputStreamWriter(fos);
     Writer out = new BufferedWriter(new OutputStreamWriter(fos),10240);
     template.process(dataMap,out);

     if(out != null){
         out.close();
     }
     // ZipUtils 是一個工具類,主要用來替換具體可以看github工程
     ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(new FileInputStream(new File("D:/cache/qqChace/T1/xlsx/test.zip")));
        ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(new FileOutputStream(new File("D:/cache/qqChace/T1/xlsx/test.docx")));
        String itemname = "word/document.xml";
        ZipUtils.replaceItem(zipInputStream, zipOutputStream, itemname, new FileInputStream(new File("D:/cache/qqChace/T1/xlsx/data.xml")));
        System.out.println("success");

    這段代碼是轉來的,https://blog.csdn.net/u013076044/article/details/79236000

   想要源碼可以去這個地址下載。

   上面主要解決的問題事文字的轉換,下面要說的關於圖片的轉換問題。

    將zip文件解壓後會發現,document.xml中沒有你原本保存在docx中的圖片,圖片是在word下的media文件夾下。document.xml中或許只是引用。但是仍可以通過這種方式,以流的形式替換圖片。只要保證圖片的順序不亂就可以了。

   下面是我在上面這段代碼上進行的改動

    String zipPath =  this.getClass().getResource("/freemarkerDoc.zip").getPath();
	        try {
	            ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(new FileInputStream(new File(zipPath)));
	            ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(new FileOutputStream(new File(basePath+patientInfo.getName()+"連續血氧檢測報告("+reportInfo.getId()+").docx")));//這個是要生成文檔的路徑和名稱
	            String itemname = "word/document.xml";
	            String itemname2 = "word/media/";
	            String picturePath = PropertiesUtil.getBinaryRoorPath("picturePath")+File.separator+patientInfo.getId()+File.separator+reportInfo.getId()+File.separator;
	            ZipUtils.replaceItem(zipInputStream, zipOutputStream, itemname, new FileInputStream(new File(url)), itemname2, picturePath);
	            System.out.println("success");

	        } catch (Exception e) {
	            System.out.println(e.toString());
	        }

我的docx文檔中工有四張圖片,所以在zipUtil中要傳入你要替換上去的圖片路徑。代碼如下

 public static void replaceItem(ZipInputStream zipInputStream,
                                   ZipOutputStream zipOutputStream,
                                   String itemName,
                                   InputStream itemInputStream
                                   ,
                                   String itemName2,
                                   String picturePath
    ){
        //
        if(null == zipInputStream){return;}
        if(null == zipOutputStream){return;}
        if(null == itemName){return;}
        if(null == itemInputStream){return;}
        InputStream itemIS = null;
        //
        ZipEntry entryIn;
        try {
            while((entryIn = zipInputStream.getNextEntry())!=null)
            {
                String entryName =  entryIn.getName();
                ZipEntry entryOut = new ZipEntry(entryName);
                // 只使用 name
                zipOutputStream.putNextEntry(entryOut);
                // 緩衝區
                byte [] buf = new byte[8*1024];
                int len;
                //image1.png等是media目錄下的圖片名稱。
                String ima1 = itemName2+"image1.png";
                String ima2 = itemName2+"image2.png";
                String ima3 = itemName2+"image3.png";
                String ima4 = itemName2+"image4.png";
                
                if(entryName.equals(itemName)){
                    // 使用替換流
                    while((len = (itemInputStream.read(buf))) > 0) {
                        zipOutputStream.write(buf, 0, len);
                    }
                }else if(entryName.equals(ima1)){
                    // 使用替換流,將需要替換上去的圖片路徑以流的形式獲取
                	itemIS = new FileInputStream(new File(picturePath+"spo2PiePicture.png"));
                    while((len = (itemIS.read(buf))) > 0) {
                        zipOutputStream.write(buf, 0, len);
                    }
                }else if(entryName.equals(ima2)){
                  // 使用替換流
            		itemIS = new FileInputStream(new File(picturePath+"prPiePicture.png"));
	                while((len = (itemIS.read(buf))) > 0) {
	                    zipOutputStream.write(buf, 0, len);
	                }
                }else if(entryName.equals(ima3)){
                // 使用替換流
                	itemIS = new FileInputStream(new File(picturePath+"spo2Picture.png"));
	                while((len = (itemIS.read(buf))) > 0) {
	                   zipOutputStream.write(buf, 0, len);
	                }
                }else if(entryName.equals(ima4)){
                    // 使用替換流
                	itemIS = new FileInputStream(new File(picturePath+"prPicture.png"));
	                while((len = (itemIS.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 {
            //e.printStackTrace();
            close(itemInputStream);
            close(zipInputStream);
            close(zipOutputStream);
            close(itemIS);
        }
    }

轉換完成後要講所有的流全部關閉,就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章