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