Java通過freemarker生成Word文檔導(dǎo)出的方式詳解
1.制作ftl模板
在Word中制作模板,制作完后以XML格式導(dǎo)出
新建WORD文件,將所需參數(shù)用特殊符號(hào) ${} 配置,如下圖:
注意:freemarker中的占位符是${},例如:這里使用的是【${name}】的形式,那么傳遞的數(shù)據(jù)中就需要有一個(gè)叫做【name】的字段。
導(dǎo)出時(shí)選擇.xml格式導(dǎo)出
用編譯器(notepad++)打開看一下動(dòng)態(tài)參數(shù)是否正確,并修改后綴名為ftl
導(dǎo)出XML文件之后,打開這個(gè)文件,此時(shí)你會(huì)看到里面都是XML標(biāo)簽,首先格式化一下,這樣看起來會(huì)舒服些,可以檢查一下你的占位符內(nèi)容是否滿足freemarker語法。因?yàn)橛行r(shí)候,我們導(dǎo)出的XML文件中,可能會(huì)將【${xxx}】分隔成兩行,從而導(dǎo)致占位符失效,所以有時(shí)候需要手動(dòng)修改一下占位符。導(dǎo)出的Word XML文件內(nèi)容大致如下所示:
2.pom文件引入freemarke依賴,安裝依賴
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency>
3.java代碼實(shí)現(xiàn)
3.1 將ftl模板移入項(xiàng)目中
3.2 創(chuàng)建Freemarker工具類
引入freemarker依賴之后,就可以使用Freemarker編寫一個(gè)工具類,專門用于處理文件的導(dǎo)出和數(shù)據(jù)渲染。
import freemarker.template.Configuration; import freemarker.template.Template; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Map; /** * @version 1.0.0 * @Date: 2025/2/14 15:05 * @Author SYG * @Description: Freemarker 工具類 */ public class FreemarkerUtil { /** * 使用 Freemarker 生成 Word 文件并導(dǎo)出 * @param templateName 模板文件路徑名稱 * @param fileName 生成的文件路徑以及名稱 * @param dataModel 填充的數(shù)據(jù)對(duì)象 * @param response HttpServletResponse 對(duì)象 */ public static void exportWord(String templateName, String fileName, Map<String, Object> dataModel, Object obj, HttpServletResponse response) { generateFile(templateName, fileName, dataModel, obj, response); } /** * 使用 Freemarker 生成指定文件并導(dǎo)出 * @param templateName 模板文件路徑名稱 * @param fileName 生成的文件路徑以及名稱 * @param dataModel 填充的數(shù)據(jù)對(duì)象 * @param obj 基準(zhǔn)類對(duì)象 * @param response HttpServletResponse 對(duì)象 */ private static void generateFile(String templateName, String fileName, Map<String, Object> dataModel, Object obj, HttpServletResponse response) { try { // 1、創(chuàng)建配置對(duì)象 Configuration config = new Configuration(Configuration.VERSION_2_3_30); config.setDefaultEncoding("utf-8"); //設(shè)置了模板文件的加載類和路徑,使得Freemarker可以從指定的類路徑下的/templates目錄中加載模板文件。 config.setClassForTemplateLoading(obj.getClass(), "/templates"); // 2、獲取模板文件 Template template = config.getTemplate(templateName); // 3、設(shè)置響應(yīng)頭 response.setContentType("application/msword"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // 4、創(chuàng)建寫入器 Writer writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)); // 5、渲染模板文件 template.process(dataModel, writer); // 6、關(guān)閉寫入器 writer.close(); } catch (Exception e) { e.printStackTrace(); } } }
3.3 測(cè)試案例代碼
public class ExportWordDemo { public static void main(String[] args) { String templateName = "opinionCollect.ftl"; String fileName = "opinionCollect.docx"; Map<String, Object> dataModel = new HashMap<>(); dataModel.put("orgName", "測(cè)試機(jī)構(gòu)02141151"); dataModel.put("data", "2025-02-14"); // 執(zhí)行導(dǎo)出 FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response); } }
案例所使用的ftl文件共有兩個(gè)變量data和orgName,若map集合里只給data或orgName一個(gè)變量賦值(例如我們只給data賦值
dataModel.put("data", "2025-02-14");
),則會(huì)報(bào)錯(cuò):FreeMarker template error:
The following has evaluated to null or missing:==> orgName [in template "payment.ftl" at line 3, column 23752]
為解決當(dāng)前異常,這里有倆種方案:
- 給orgName賦一個(gè)空值,即dataModel.put("orgName", "");
- (推薦)修改模板,根據(jù)數(shù)據(jù)結(jié)構(gòu)字段,將需要填充數(shù)據(jù)的地方,修改為EL表達(dá)式。${()!} 加括號(hào)!號(hào)表示可以為null ,也可以解決上述問題,如圖所示:
3.4 解析
config.setClassForTemplateLoading(obj.getClass(), "/templates"); 中的第一個(gè)參數(shù) obj.getClass() 是用于指定模板加載的基準(zhǔn)類。具體作用如下:
確定類路徑:FreeMarker 根據(jù)傳入的類來確定模板文件所在的類路徑。這里使用 obj.getClass() 表示以 obj 所屬類的類路徑為基準(zhǔn)。加載模板資源:FreeMarker 會(huì)從該類所在的類路徑下查找并加載模板文件,路徑為 /templates。簡(jiǎn)而言之,第一個(gè)參數(shù)決定了 FreeMarker 在哪里查找模板文件。
此處ftl文件和OpinionCollectController是在同一個(gè)工程目錄下,同一個(gè)target目錄下,所以可以直接傳OpinionCollectController的對(duì)象本身,如果ftl文件和FreemarkerUtil工具類在同一個(gè)工程目錄下,我們也可以將config.setClassForTemplateLoading(obj.getClass(), "/templates");改成config.setClassForTemplateLoading(FreemarkerUtil.class, "/templates");
3.5 運(yùn)行代碼
運(yùn)行測(cè)試案例的代碼,可以看到文件正常導(dǎo)出,且變量已經(jīng)被替換
4.使用模板導(dǎo)出word勾選框
實(shí)際業(yè)務(wù)需求需要根據(jù)業(yè)務(wù)勾選上對(duì)應(yīng)的項(xiàng)目類型,同時(shí)導(dǎo)出的文件需要有點(diǎn)擊方框自動(dòng)勾選的功能,我們可以先在word模板里實(shí)現(xiàn)該功能,然后導(dǎo)出成xml格式的文件
打開xml文件,為了方便查看,我們需要安裝XMLTools的插件
安裝完插件之后,我們可以通過notepad查看xml文件
如圖所示,找到方框的位置,將方框替換為變量
添加代碼對(duì)test1和test2進(jìn)行賦值
public class ExportWordDemo { public static void main(String[] args) { String templateName = "opinionCollect.ftl"; String fileName = "opinionCollect.docx"; Map<String, Object> dataModel = new HashMap<>(); dataModel.put("orgName", "測(cè)試機(jī)構(gòu)02141151"); dataModel.put("data", "2025-02-14"); //? □ dataModel.put("test1", "□"); dataModel.put("test2", "?"); // 執(zhí)行導(dǎo)出 FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response); } }
執(zhí)行代碼我們可以看到導(dǎo)出的文件如下圖,同時(shí)方框也可以點(diǎn)擊勾選或取消勾選
5.生成動(dòng)態(tài)列表格
模板如下圖所示,具體業(yè)務(wù)需求如下:
1、一個(gè)任務(wù)編號(hào)下綁定的有多個(gè)直屬單位
2、每個(gè)直屬單位下需要匯總供應(yīng)商管理部門、質(zhì)量部門、技術(shù)部門、檢測(cè)部門、其他相關(guān)意見的審核意見
5.1 list標(biāo)簽
//whyc 是集合(collection)的表達(dá)式,yc是循環(huán)變量的名字,不能是表達(dá)式 <#list whyc as yc> 需要循環(huán)的部分 變量用${(yc.supplierReasonDesc)!} </#list>
首先修改ftl文件,找到我們需要循環(huán)的部門,進(jìn)行添加<#list></#list>標(biāo)簽
此處我們要循環(huán)的是companyName及其其對(duì)應(yīng)的征求部門的意見,我們?cè)趂tl文件找到“備注”這一行結(jié)束的位置,${companyName}這一行開始的位置,添加 <#list whyc as yc>
如下圖所示:
注意: list一定要放在要循環(huán)開始的位置,即前一行結(jié)束的位置
然后我們需要找到循環(huán)結(jié)束的位置,本案例是到相關(guān)意見的 ${otherReasonDesc}處結(jié)束,找到這一行結(jié)束的位置,添加 </#list> 標(biāo)簽,如圖所示:
添加完<#list></#list>標(biāo)簽后,我們需要調(diào)整變量名,將要?jiǎng)討B(tài)生成的變量改為 循環(huán)變量.變量
本案例中,以供應(yīng)商管理部門為例,我們需要將 ${supplierOpinion}改為{yc.supplierOpinion}, ${supplierSituationDesc}改為{yc.supplierSituationDesc}, ${supplierReasonDesc}改為{yc.supplierReasonDesc},質(zhì)量部門、技術(shù)部門等的變量相同方式修改,如圖所示:
5.2 相關(guān)代碼實(shí)現(xiàn)
public void exportWord(@RequestBody OpinionCollectFeedbackQueryVO query, HttpServletResponse response) throws Exception { String templateName = "problemSummary.ftl"; String fileName = "problemSummary.docx"; Map<String, Object> dataModel = new HashMap<>(); Map<String, Object> dataModel1 = new HashMap<>(); Map<String, Object> dataModel2 = new HashMap<>(); Map<String, Object> dataModel3 = new HashMap<>(); List<Map<String, Object>> list = new ArrayList<>(); dataModel.put("aptitudeTaskCode","202502211035"); dataModel.put("companyName","com20250221"); dataModel.put("supplierOpinion","供應(yīng)商管理部門審核意見"); dataModel.put("supplierSituationDesc","供應(yīng)商管理部門情況描述"); dataModel1.put("companyName","com20250221001"); dataModel1.put("supplierReasonDesc","供應(yīng)商管理部門原因11111"); dataModel2.put("companyName","com20250221002"); dataModel2.put("supplierReasonDesc","供應(yīng)商管理部門原因22222"); dataModel3.put("companyName","com20250221003"); dataModel3.put("supplierReasonDesc","供應(yīng)商管理部門原因33333"); list.add(dataModel1); list.add(dataModel2); list.add(dataModel3); dataModel.put("whyc",list); // 執(zhí)行導(dǎo)出 FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response); }
測(cè)試導(dǎo)出結(jié)果如下:
由圖可見,我們實(shí)現(xiàn)了動(dòng)態(tài)生成且數(shù)據(jù)和我們測(cè)試類里填寫的數(shù)據(jù)一致
5.3 合并單元格
要做到合并單元格,我們首先需要了解一下ftl文件一些屬性的含義,我們先看下圖:
單元格屬性設(shè)置:
<w:tcPr>:定義單元格的屬性。w:tcW:設(shè)置單元格寬度為1992單位(dxa類型)。w:vMerge:設(shè)置單元格為垂直合并的起始單元格(restart)。w:noWrap:設(shè)置單元格內(nèi)容不換行(0表示不啟用)。w:vAlign:設(shè)置單元格內(nèi)容垂直居中對(duì)齊(center)。
<w:vMerge w:val="continue"/>:設(shè)置單元格為垂直合并的延續(xù)部分(continue),表示該單元格與上方單元格合并。
如果生成的ftl文件里沒有上述屬性,我們可以 在循環(huán)的部門的第一行放 <w:vMerge w:val="restart"/> 從第二行開始,所有要合并的單元格放<w:vMerge w:val="continue"/>
5.3.1 編輯ftl文件:
start和end的位置,一定要放在循環(huán)開始的<w:tcPr>中:
代碼可以這么處理:
public List<Map<String, Object>> checkList(List<Map<String, Object>> list){ String start = "<w:vMerge w:val=\"restart\"/>"; String end = "<w:vMerge w:val=\"continue\"/>"; list.get(0).put("start",start); for (int i = 1; i < list.size(); i++) { list.get(i).put("end",end); } return list; }
以上便可實(shí)現(xiàn)合并單元格
以上就是Java通過freemarker生成Word文檔導(dǎo)出的方式講解的詳細(xì)內(nèi)容,更多關(guān)于Java freemarker生成Word導(dǎo)出的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中new與clone操作對(duì)象的比較方法舉例
這篇文章主要給大家介紹了關(guān)于Java中new與clone操作對(duì)象的比較方法,在java中對(duì)象的誕生是我們開發(fā)人員new出來的,對(duì)象的使用也是我們開發(fā)人員進(jìn)行操作的,需要的朋友可以參考下2024-07-07SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測(cè)好用)
這篇文章主要介紹了SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測(cè)好用),在Spring?Boot?2.x中,整合了這兩個(gè)JTA的實(shí)現(xiàn)分別是Atomikos和Bitronix,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07springboot獲取properties屬性值的多種方式總結(jié)
這篇文章主要介紹了springboot獲取properties屬性值的多種方式總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Spring Cloud OAuth2中/oauth/token的返回內(nèi)容格式
Spring Cloud OAuth2 生成access token的請(qǐng)求/oauth/token的返回內(nèi)容就需要自定義,本文就詳細(xì)介紹一下,感興趣的可以了解一下2021-07-07springboot+vue項(xiàng)目從第一行代碼到上線部署全流程
本文詳細(xì)介紹了如何從零開始搭建一個(gè)基于Spring Boot和Vue.js的前后端分離項(xiàng)目,并涵蓋項(xiàng)目需求分析、技術(shù)選型、項(xiàng)目結(jié)構(gòu)設(shè)計(jì)、前后端交互、部署上線等全過程,感興趣的朋友跟隨小編一起看看吧2024-11-11