1. 程式人生 > >word 文件匯出 (freemaker+jacob)--java開發

word 文件匯出 (freemaker+jacob)--java開發

工作中終於遇到了 需要匯出word文旦的需求了。由於以前沒有操作過,所以就先百度下了,基本上是:部落格園,簡書,CDSN,這幾大機構的相關帖子比較多,然後花了2周時間 才初步弄懂。

 學習順序:

第一階段

1,。首先 是 先了解 java 通過什麼方式 來操作word的匯出工作。就有了下面這個帖子了:

  java 操作 word 的方法 :https://www.cnblogs.com/lcngu/p/5247179.html  。新手可以先看看了解下。

2. 根據需求:操作word很複雜: 1.有圖片(圖片數量 動態變化,可能沒有),2.有複選框需要展示 【一個文件裡有好幾個 複選框。框框打鉤還是不打勾,不確定】,3.文件樣式和模板客戶已經給我們提供好了。

因此; 選著了使用freemaker輔助工具來操作,word文件生成和匯出。

 整個流程簡單來說:

        對於支援低版本的ofifce  word 文件格式 應該是 doc格式的:

                1.先生成模板:就是word文件 先標記好要填充資料的位置,(一般建議用 實體類的屬性名 站位 ,可以的話用花括號括起來)。小技巧(對於圖片,複選框。這些特別的東西。先不做處理)。

                2. 生成 xml檔案:word文件 另存為 xml 格式的--(一定要是word.xml,其他格式的不行,這裡選著 word-2003.xml)。低版本要求

                3. 生成ftl檔案: 這個沒啥說的,freemaker只能能識別它,因此需要這幾個步驟1. 把生成好的xml檔案,把字尾名改成 ftl 就可以了;2.所有佔位符的地方 加上$標誌;

                4. 使用freemaker 工具 整合 模板和資料了。

                5. 輸出生成好的 word文件。

         參考教程: java使用freemarker生成word文件步驟  帶圖片   

                             Java之利用FreeMarker匯出Word例項      

         對於高版本的ofifce  word 文件格式 是 docx格式的:

              有兩種方法:

           1.解壓--替換--壓縮。好處word是原生的不是xml格式的。

           2. 和上述doc方式一樣,只不不過匯出xml 選擇 word.xml。 後續操作一模一樣。

   參考教程 : 

                          第一種方式: JAVA通過模板生成DOCX文件    一般不推薦,除非圖片數量固定。

                       第二種方式:1.使用freemarker匯出word並動態插入多張圖片。

                                            2.使用freemarker匯出word文件包含多張圖片。

 

對於複選框(打鉤和不打勾)的操作:請參考這個文件,非常直觀的告訴你怎麼操作:freemarker匯出word文件中的複選框打鉤功能

對於圖片操作請參考 上面給的教程。 上面的教程主要是圖片的處理。

第一階段到此結束了,然後開始寫程式碼和程式設計了。下面給的是資料獲取的幾個操作方法。資料物件Map<string ,object> .

WordPhotoUtil: 對圖片的處理

  1 package com.chiyun.peersocialworker.synchroWork.util;
  2 
  3 import com.chiyun.peersocialworker.files.entity.EmplPictureInforEntity;
  4 import com.chiyun.peersocialworker.synchroWork.vo.Picther2Vo;
  5 import com.chiyun.peersocialworker.synchroWork.vo.PictherVo;
  6 import sun.misc.BASE64Encoder;
  7 
  8 import java.io.File;
  9 import java.io.FileInputStream;
 10 import java.io.IOException;
 11 import java.io.InputStream;
 12 import java.util.ArrayList;
 13 import java.util.HashMap;
 14 import java.util.List;
 15 import java.util.Map;
 16 
 17 /**
 18  *  word-圖片資訊處理-工具類
 19  * 
 20  */
 21 public class WordPhotoUtil {
 22 
 23 
 24 
 25     /**
 26      * 獲得圖片的base64碼
 27      * @param filepash
 28      * @return
 29      * @throws Exception
 30      */
 31     public static String getImageBase(String filepash) throws Exception {
 32         if (filepash == null || filepash == "") {
 33             return "";
 34         }
 35         System.out.println("圖片路徑"+filepash);
 36         File file = new File(filepash);
 37         if (!file.exists()) {
 38             System.out.println("圖片不存在");
 39             return "";
 40         }
 41         InputStream in = null;
 42         byte[] data = null;
 43         try {
 44             in = new FileInputStream(file);
 45             data = new byte[in.available()];
 46             in.read(data);
 47             in.close();
 48         }catch(IOException e){
 49             e.printStackTrace();
 50         }
 51         System.out.println("圖片大小:"+data.length);
 52         BASE64Encoder encoder = new BASE64Encoder();
 53         return encoder.encode(data);
 54     }
 55 
 56 
 57 
 58 
 59     /**
 60      * 圖片資訊獲取 【肯定是偶數個數】 雙圖片成對儲存
 61      * @param images
 62      * @return
 63      * @throws Exception
 64      */
 65     public static List<PictherVo> getImage(List<EmplPictureInforEntity> images ,int numbers,int photosize )throws Exception{
 66         List<PictherVo> pictvoList=new ArrayList<>();
 67         List<EmplPictureInforEntity> List1=new ArrayList<>();
 68         List<EmplPictureInforEntity> List2=new ArrayList<>();
 69         int i=1;
 70         for(EmplPictureInforEntity image:images ) {
 71             if( i%2==0){
 72                 List2.add(image);
 73             }else{
 74                 List1.add(image);
 75             }
 76             i++;
 77         }
 78            int bb=numbers;
 79         for(int j=0;j<(images.size()/2);j++){
 80             PictherVo pvo =new PictherVo();
 81             pvo.setTpnr1(getImageBase(List1.get(j).getTpzslj()));
 82             pvo.setTphz1(List1.get(j).getTpmc());
 83             pvo.setRidnumber1(String.valueOf(bb));
 84             pvo.setTpgs1(List1.get(j).getTpgslx());
 85             bb++;
 86             pvo.setTpnr2(getImageBase(List2.get(j).getTpzslj()));
 87             pvo.setTphz2(List2.get(j).getTpmc());
 88             pvo.setRidnumber2(String.valueOf(bb));
 89             pvo.setTpgs2(List2.get(j).getTpgslx());
 90             pictvoList.add(pvo);
 91             bb++;
 92         }
 93         return pictvoList;
 94     }
 95 
 96 
 97     /**
 98      * 圖片資訊獲取 【不一定是偶數個數】 單圖片儲存
 99      * @param images
100      * @return numbers
101      * @throws Exception
102      */
103     public static  List<Picther2Vo>  getImage2(List<EmplPictureInforEntity> images,int numbers,int photosize )throws Exception{
104         List<Picther2Vo> pictvoList=new ArrayList<>();
105              int RidNumber=numbers;
106         for(EmplPictureInforEntity imag:images){
107             Picther2Vo pvo =new Picther2Vo();
108             pvo.setTpnr(getImageBase(imag.getTpzslj()));
109             pvo.setTphz(imag.getTpmc());
110             pvo.setTpgs(imag.getTpgslx());
111             pvo.setRidnumber(String.valueOf(RidNumber));
112             pictvoList.add(pvo);
113             RidNumber++;
114         }
115 
116 
117         return pictvoList;
118     }
119 
120     /**
121      *  輸出圖片資訊集合-成雙圖片資訊
122      * @param images 圖片內容
123      * @param numbers Rid 起始編號
124      * @return
125      * @throws Exception
126      */
127    public static  List<Map<String, Object>> getphotoInfo(  List<EmplPictureInforEntity>  images ,int numbers,int photosize )throws Exception{
128          System.out.println(images.size());
129        List<Map<String, Object>> info = new ArrayList<>();
130        List<PictherVo> picvoList=null;
131        if(images.size()==0){
132            return info;
133        }else {//必須有圖片
134            String str = images.get(0).getTpzslj();
135            String defoTpPath = str.substring(0, str.indexOf("upload\\"));
136            if ( images.size()% 2 == 0) { //偶數張
137                picvoList =getImage(images,numbers,photosize);
138 
139            } else { //奇數張
140                EmplPictureInforEntity defoimage = new EmplPictureInforEntity();
141                defoimage.setTpmc("back.png");
142                defoimage.setTpzslj(defoTpPath + "upload\\" + "back.png");
143                defoimage.setTpgslx("png");
144                images.add(defoimage);
145                picvoList = getImage(images,numbers,photosize);
146 
147            }
148            for (PictherVo vo : picvoList) {
149                Map<String, Object> map = new HashMap<>();
150                map.put("photo1", vo.getTpnr1());
151                map.put("mc1", vo.getTphz1());
152                map.put("hz1",vo.getTpgs1());
153                map.put("rId1",vo.getRidnumber1());
154                map.put("photo2", vo.getTpnr2());
155                map.put("mc2", vo.getTphz2());
156                map.put("hz2",vo.getTpgs2());
157                map.put("rId2",vo.getRidnumber2());
158                info.add(map);
159            }
160        }
161 
162         return info;
163    }
164 
165     /**
166      * 輸出圖片資訊集合-單張圖片資訊
167      * @param images 圖片內容
168      * @param numbers Rid 起始編號
169      * @param photosize 圖片總數
170      * @return
171      * @throws Exception
172      */
173     public static  List<Map<String, Object>> getphotoInfo2(  List<EmplPictureInforEntity>  images ,int numbers,int photosize )throws Exception{
174         List<Map<String, Object>> info = new ArrayList<>();
175         List<Picther2Vo> picvoList=null;
176 
177         if(images.size()==0){
178             return info;
179         }else {
180             picvoList = getImage2(images,numbers,photosize);
181         }
182         for(Picther2Vo vo: picvoList){
183             Map<String, Object> map = new HashMap<>();
184             map.put("photo",vo.getTpnr());
185             map.put("tpmc",vo.getTphz());
186             map.put("hz",vo.getTpgs());
187             map.put("rId",vo.getRidnumber());
188             info.add(map);
189         }
190         return info;
191     }
192 
193 
194 }
View Code

getObjectToMap():  把 實體類轉換成map

    //物件轉Map-主表資訊排除時間格式的
    private static  Map<String, Object> getObjectToMap2(Object object)throws Exception{
        Map<String, Object> map = new HashMap<>();
        for (Field f : object.getClass().getDeclaredFields()) {
            f.setAccessible(true);
            if(f.getType().toString().equals("class java.lang.String")){
                map.put(f.getName(),StringUtil.getNullorString((String)f.get(object)));
            }else if(f.getType().toString().equals("class java.util.Date")){

            }else if(f.getType().toString().equals("class java.lang.Integer")){
                map.put(f.getName(),StringUtil.getNullorString(((String)f.get(object))));
            }else{
                System.out.println("這是新的屬性:"+f.getType().toString());
                map.put(f.getName(),StringUtil.getNullorString(((String)f.get(object))));
            }

        }
        return map;
    }
View Code

WordCheckboxUtil: 操作複選框

package com.chiyun.peersocialworker.synchroWork.util;

import com.chiyun.peersocialworker.utils.StringUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * word-複選框操作-工具類
 *
 */
public class WordCheckboxUtil {

    /**
     * 單選-複選框資訊處理
     * @param dictdm 打鉤值
     * @param bjname  標記名稱-模板佔位符
     * @param list  所有詞條資訊
     * @return
     */
    public static Map<String, Object> getCheckbox1(String dictdm,String bjname,List<DictionarylistEntity> list){
        Map<String, Object> dataMap = new HashMap<>();
        int index =0;
        for(DictionarylistEntity entity:list){
            index=index+1;
            if(entity.getCtdm().equals(dictdm)){
                dataMap.put(bjname+index,true);

            }else {
                dataMap.put(bjname+index,false);

            }
        }
        return  dataMap;
    }


    /**
     *
     * 多選-複選框資訊處理
     * @param dictdm 打鉤值
     * @param bjname  標記名稱-模板佔位符
     * @param list  所有詞條資訊
     * @return
     */
    public static Map<String, Object> getCheckbox2(String dictdm,String bjname,List<DictionarylistEntity> list){
        String[] zsxqdms=null;
        if(StringUtil.isNull(dictdm)){

        }else{
         zsxqdms=dictdm.split(","); //用英文逗號分隔

        }
        Map<String, Object> dataMap = new HashMap<>();
        int index =0;
        for(DictionarylistEntity entity:list){
            index=index+1;
            boolean flag=false;
            if(zsxqdms!=null){
             for(String str1 :zsxqdms){
                 if(entity.getCtdm().equals(str1)) {
                    flag=true;
                 }
             }
            }else {

            }
            dataMap.put(bjname+index,flag);

        }
        return  dataMap;
    }

    public static void main(String[] args) {
        String dictdm="123,03,45";
        String[] zsxqdms=dictdm.split(","); //用英文逗號分隔
        System.out.println(zsxqdms.length);
        System.out.println(zsxqdms[0]);
    }

}
View Code

word 工具類:WordUtil

package com.chiyun.peersocialworker.utils;

import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.util.Map;

/**
 * 
 */
public class WordUtil {

    /**
     * 生成word檔案
     * @param dataMap word中需要展示的動態資料,用map集合來儲存
     * @param templateName word模板名稱,例如:test.ftl
     * @param filePath 檔案生成的目標路徑,例如:D:/wordFile/
     * @param fileName 生成的檔名稱,例如:test.doc
     */
    @SuppressWarnings("unchecked")
    public static void createWord(Map dataMap,String templateName,String filePath,String fileName){
        try {
            //建立配置例項
            Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);

            //設定編碼
            configuration.setDefaultEncoding("UTF-8");

            //ftl模板檔案
            configuration.setClassForTemplateLoading(WordUtil.class,"/template/new");

            //獲取模板
            Template template = configuration.getTemplate(templateName);

            //輸出檔案
            File outFile = new File(filePath+File.separator+fileName);

            //如果輸出目標資料夾不存在,則建立
            if (!outFile.getParentFile().exists()){
                outFile.getParentFile().mkdirs();
            }

            //將模板和資料模型合併生成檔案
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));


            //生成檔案
            template.process(dataMap, out);

            //關閉流
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成word檔案
     * @param dataMap word中需要展示的動態資料,用map集合來儲存
     * @param templateName word模板名稱,例如:test.ftl
     * @param filePath 檔案生成的目標路徑,例如:D:/wordFile/
     * @param fileName 生成的檔名稱,例如:test.doc
     */
    @SuppressWarnings("unchecked")
    public static void createWord2(Map dataMap,String templateName,String filePath,String fileName){
        try {
            //建立配置例項
            Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);

            //設定編碼
            configuration.setDefaultEncoding("UTF-8");

            //ftl模板檔案
            configuration.setClassForTemplateLoading(WordUtil.class,"/template/new");

            //獲取模板
            Template template = configuration.getTemplate(templateName);

            //輸出檔案
            File outFile = new File(filePath+File.separator+fileName);

            //如果輸出目標資料夾不存在,則建立
            if (!outFile.getParentFile().exists()){
                outFile.getParentFile().mkdirs();
            }

            //將模板和資料模型合併生成檔案
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));


            //生成檔案
            template.process(dataMap, out);

            //關閉流
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
View Code

 

總結: 

    當然上面的幾個方法 肯定不是全部 ,只是其中對資料處理的幾個手段而已;值得注意的是:freemaker 操作,不能返回 null ,已返回就報錯。需要轉化成 “ ”才行。

這裡遇到的問題:

可以參考一下帖子:

         java使用freemarker動態生成world文件及常見錯誤解決。

     通過freemarker生成一個word 解決生成的word用wps開啟有問題的問題,解決出word時中文檔名亂碼問題。

上面給的建議,基本上是屬於匯出前屬於執著模板的時候才會出現。

第二階段: 要相容 office和 WPS    

    1.需求變更: 客戶 使用群體,有的是office,有的是WPS;PC端。可以不用支援低版本的office (因為他們最低都是用的是office2010版本的)

    現在就遇到了問題:  用office 開啟能夠正常顯示  ,而用 WPS開啟 ,能容都能出來,就是格式不對,【在office 裡方框 和文字不會重疊,而WPS 卻會重疊】。這是相容性問題,還是WPS和office 這個樣式的標號不一樣。

那麼久只能另想其他方法了。

   首先分析: 我使用模板格式是: 第一種方式相容2003版本的也就是  匯出xml是 2003word.xml。  

    然後就會發現: office 有這個格式,而WPS沒有,只有一個 word.xml 格式的。 然後百度了下,才知道 word.xml是高版本的,也就是說WPS 不支援低版本的了。

然後嘗試完善:

匯出文件 名稱的字尾改成 docx.。然後神奇的發現 ,匯出的文件,office 和WPS 都不能打開了。 這個方式失敗,放棄。

    第二種方式:

        那我就導 docx 格式的吧,使用docx 第一種方式 。我直接放棄了,就是因為那個該死的圖片數量不定,以及生成的文件型別多個,而且還要重寫呼叫方式 太費時間了。

就採用第二種方式了,整個過程和doc 一樣,只是模板裡的排版有些變化,呼叫方式基本上不變。

     當採用 docx的第二種方式時,你會發現,生成的xml檔案 幾乎一模一樣,唯一的區別就是 圖片了;word-2003.xml  圖片的配置資訊就在一個地方。,而word.xml 圖片分散開來了,在三個標籤裡了,一個圖片展示位置,一個圖片對應對映關係,一個圖片內容存放位置。這個也簡單,由原來的一個list集合改成 3個list集合 分別處理就行了。(具體操作請看上面提供的帖子)

   一番操作後。

匯出檔案 ,檔案字尾 寫的是 docx ;然後開啟 才發現:office 打不開,但是wps能夠正常開啟,而且顯示正常。

 最後卡在這裡大概3天 ,網上各種找資料; 結果在無意間 解決了。

    這個問題的解決辦法:

 解決方法 是: 匯出檔案的字尾名改成 doc 。這樣就行了,匯出的文件是doc格式的。不論是office還是WPS都能正常打開了。萬事大吉了

 這就然我們充分認識到了,如果使用的電腦是XP系統的,那麼對不起,就不要用WPS了; 如果是高版本的系統,請使用2007版級以上的office吧。這樣就不怕相容性的問題了;

  同時也說明了一個問題:模板原文是doc還是docx 都不是問題,只要另存為 word,xml就是一樣的了。 生成的檔案 格式 是doc還是docx ,取決於你給檔案定的字尾。 

第三階段: 要相容移動端了   

       後面由於客戶覺得我們的系統不錯,就叫我們順便也做個移動端吧,移動端吧。(差價的錢已到位)。  然後拿手機開啟生成的word文件。

靠,居然打不開,顯示的東西和我們生成的xml模板一模一樣的調調。

      也就是說我們用freemaker 生成的word文件 (以到xml模板的方式)其實內容是xml格式的,不是標準的word了。【也就是: 生成的模板xml檔案 只是從命名一下(把字尾名改成doc或者docx)】?

所以,移動端當然就打不開了啊。

    回去試了下,把 xml模板 字尾改成 doc或者docx ,用office和WPS開啟,結果也能正常開啟和顯示; 

    我有繼續試了下這個,把系統生成好的word文件開啟另存為 docx或者doc .然後把另存好的檔案發到手機上,開啟下,結果就能正常顯示了。說明,我們的word文件就差一個把非常規word轉化成正規的word文件操作了。(也就是像 office 軟體那個另存為 的強大功能)。

在這裡深深的譴責下那些  :提出相同問題,並解決了的,但就是不告訴你怎麼解決的坑爹樓主們了。害的各位同胞線上急等。

  解決方法: 使用jacob 外掛,把你生成好的word文件轉成 正規的wordw文件。

 參考文件:

              1.  Java 將xml模板動態填充資料轉換為word文件

             2. jacob 官網 下載 相關jar包和dll檔案。  

             3.jacob 安裝手冊 1     安裝配置2

&n