利用FreeMarker动态生成Word报告:打造灵活的信息表格实例
一、引言
- 背景介绍:
- 项目背景及需求,需要按实际数据生成多行表格报告,并且每条数据生成一个具体表格信息。比如20条数据,生成一个20行数据,并且生成20个表格。
二、FreeMarker基础概览
-
FreeMarker简介
-
FreeMarker的基本概念和作用
FreeMarker 是一款强大的、基于Java的模板引擎,主要用于生成各种类型的文本输出,尤其是适用于Web开发领域,用于分离程序逻辑和页面展示层。它是由Apache软件基金会维护的一个开源项目,允许开发者编写简单的、易于理解和修改的模板文件,并结合预先准备好的数据模型,动态生成HTML、XML、邮件正文、配置文件等各种格式的文本内容。
-
FreeMarker在文档自动化生成领域的应用优势
- 模板化能力:
- FreeMarker强大的模板语言允许创建高度可复用的文档结构模板,只需要替换模板内的变量就可以生成不同内容的文档,这大大简化了文档生产流程,提高了工作效率。
- 可以设计复杂的模板,通过嵌套结构、循环和条件语句来处理多变的数据结构,适应各类复杂报告和文档的布局需求。
- 分离关注点:
- FreeMarker遵循“模板+数据模型=输出”的原则,确保了文档内容与样式、数据与呈现逻辑之间的分离,使得文档生成过程更为模块化和组件化,更便于管理和维护。
- 灵活性与兼容性:
- 不仅限于HTML,FreeMarker可以生成多种格式的文本文件,包括XML、PDF、Word文档(通过转换服务)、纯文本文件等,适应不同的文档格式要求。
- 对于企业级应用,它可以轻松与Java应用程序集成,支持从数据库、REST API等多种数据源获取实时数据,实现文档的动态更新和定制化生成。
- 安全性与稳定性:
- FreeMarker有严格的沙箱模式,限制了模板执行的权限,防止潜在的安全风险,确保了在大规模自动化文档生成过程中系统的安全稳定。
- 跨平台和可扩展性:
- 由于FreeMarker是Java编写的,因此具有良好的跨平台特性,可在任何支持Java环境的应用中部署和使用。
- 具备良好的扩展性,可以根据实际需求定制额外的标签库或函数,增强模板的功能性。
总之,FreeMarker在文档自动化生成领域的应用能帮助企业提高生产力,减少重复工作,同时保证文档的一致性和准确性,尤其适合诸如报表、合同、发票等大量标准化但又需个性化定制的文档生成任务。
- 模板化能力:
-
-
FreeMarker模板语言(FTL)基础
-
变量替换与循环控制结构
-
变量替换:在FTL中,变量替换是通过插值表达式完成的,通常使用
${}
符号包裹变量名。例如:Hello, ${user.name}!
在这个例子中,
user.name
是一个从数据模型(Data Model)中提取的变量,当模板被处理时,它会被对应的实际值替换。如果变量可能不存在,可以使用条件修饰符检查其存在性:或者设置默认值:
${user.address?if_exists} ${user.title?default('Unknown User')}
-
-
条件判断和其他常用指令
-
循环控制结构(Looping Constructs): FreeMarker 提供了
list
和foreach
标签来进行迭代和循环输出集合或序列中的元素。例如,遍历一个名为
items
的列表:<ul> <#list items as item> <li>${item.name}</li> </#list> </ul>
-
-
三、FreeMarker模板设计与实现
- Word模板文件构建
-
在Word中设计可复用的表格模板
- 首先需要根据需求设计出word模板和动态生成的模板,如下:
-
插入FreeMarker标签实现动态填充
- 将需要动态填充的数据,设置为标签,如下:
-
五、核心代码片段解析
-
模板
-
将word模板调整以后,另存格式为xml,然后重名为.ftl格式,放入idea中格式化
-
格式化以后需要查看各个占位符在不在一行中或者出现乱码的情况,进行调整
<w:r> <w:rPr> <w:rFonts w:h-ansi="宋体" w:hint="default"/> <w:b w:val="off"/> <w:sz w:val="28"/> <w:sz-cs w:val="28"/> <w:u w:val="single"/> </w:rPr> <w:t>${planYear}</w:t> </w:r>
-
设置需要动态替换的表格数据,插入循环控制结构
list
标签 -
<#list list1 as item> //省略部分代码数据 <w:r> <w:rPr> <w:rFonts w:ascii="Times New Roman" w:h-ansi="Times New Roman" w:fareast="等线" w:cs="Times New Roman" w:hint="default"/> <w:color w:val="000000"/> <w:kern w:val="0"/> <w:sz-cs w:val="21"/> <w:lang w:bidi="AR-SA"/> </w:rPr> <w:t>${item.index}</w:t> </w:r> </w:p> </w:tc> //省略部分代码数据 </#list>
-
需要将循环填充的表格也插入
list
标签,但是需要找到<w:tbl>
或者整个表格标签的上面 -
<#list itemsList as item> <w:tbl> <w:tblPr> <w:tblpPr w:leftFromText="180" w:rightFromText="180" w:vertAnchor="text" w:horzAnchor="page" w:tblpX="1534" w:tblpY="501"/> <w:tblOverlap w:val="Never"/> <w:tblW w:w="11199" w:type="dxa"/> <w:tblInd w:w="0" w:type="dxa"/> <w:shd w:val="clear" w:color="auto" w:fill="auto"/> <w:tblLayout w:type="Fixed"/> <w:tblCellMar> <w:top w:w="0" w:type="dxa"/> <w:left w:w="108" w:type="dxa"/> <w:bottom w:w="0" w:type="dxa"/> <w:right w:w="108" w:type="dxa"/> </w:tblCellMar> </w:tblPr> <w:tblGrid> <w:gridCol w:w="1596"/> <w:gridCol w:w="2283"/> <w:gridCol w:w="1852"/> <w:gridCol w:w="1733"/> <w:gridCol w:w="2040"/> <w:gridCol w:w="1695"/> </w:tblGrid> <w:tr> <w:tblPrEx> <w:shd w:val="clear" w:color="auto" w:fill="auto"/> <w:tblCellMar> <w:top w:w="0" w:type="dxa"/> <w:left w:w="108" w:type="dxa"/> <w:bottom w:w="0" w:type="dxa"/> <w:right w:w="108" w:type="dxa"/> </w:tblCellMar> </w:tblPrEx> <w:trPr> <w:cantSplit w:val="on"/> <w:trHeight w:val="600" w:h-rule="atLeast"/> </w:trPr> <w:tc> <w:tcPr> <w:tcW w:w="11199" w:type="dxa"/> <w:gridSpan w:val="6"/> <w:tcBorders> <w:top w:val="single" w:sz="8" wx:bdrwidth="20" w:space="0" w:color="000000"/> <w:left w:val="single" w:sz="8" wx:bdrwidth="20" w:space="0" w:color="000000"/> <w:bottom w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> <w:right w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> </w:tcBorders> <w:shd w:val="clear" w:color="auto" w:fill="auto"/> <w:noWrap/> <w:vAlign w:val="center"/> </w:tcPr> <w:p> <w:pPr> <w:keepNext w:val="off"/> <w:keepLines w:val="off"/> <w:widowControl/> <w:supressLineNumbers w:val="off"/> <w:jc w:val="center"/> <w:textAlignment w:val="center"/> <w:rPr> <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:cs="宋体" w:hint="fareast"/> <w:b/> <w:b-cs/> <w:i w:val="off"/> <w:i-cs w:val="off"/> <w:color w:val="000000"/> <w:sz w:val="28"/> <w:sz-cs w:val="28"/> <w:u w:val="none"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:cs="宋体" w:hint="fareast"/> <w:b/> <w:b-cs/> <w:i w:val="off"/> <w:i-cs w:val="off"/> <w:color w:val="000000"/> <w:kern w:val="0"/> <w:sz w:val="28"/> <w:sz-cs w:val="28"/> <w:u w:val="none"/> <w:lang w:val="EN-US" w:fareast="ZH-CN" w:bidi="AR-SA"/> </w:rPr> <w:t>拜访记录表</w:t> </w:r> </w:p> </w:tc> </w:tr> <w:tr> <w:tblPrEx> <w:shd w:val="clear" w:color="auto" w:fill="auto"/> <w:tblCellMar> <w:top w:w="0" w:type="dxa"/> <w:left w:w="108" w:type="dxa"/> <w:bottom w:w="0" w:type="dxa"/> <w:right w:w="108" w:type="dxa"/> </w:tblCellMar> </w:tblPrEx> <w:trPr> <w:cantSplit w:val="on"/> <w:trHeight w:val="600" w:h-rule="atLeast"/> </w:trPr> <w:tc> <w:tcPr> <w:tcW w:w="1596" w:type="dxa"/> <w:tcBorders> <w:top w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> <w:left w:val="single" w:sz="8" wx:bdrwidth="20" w:space="0" w:color="000000"/> <w:bottom w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> <w:right w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> </w:tcBorders> <w:shd w:val="clear" w:color="auto" w:fill="auto"/> <w:noWrap/> <w:vAlign w:val="center"/> </w:tcPr> <w:p> <w:pPr> <w:keepNext w:val="off"/> <w:keepLines w:val="off"/> <w:widowControl/> <w:supressLineNumbers w:val="off"/> <w:jc w:val="center"/> <w:textAlignment w:val="center"/> <w:rPr> <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:cs="宋体" w:hint="fareast"/> <w:i w:val="off"/> <w:i-cs w:val="off"/> <w:color w:val="000000"/> <w:sz w:val="22"/> <w:sz-cs w:val="22"/> <w:u w:val="none"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:cs="宋体" w:hint="fareast"/> <w:i w:val="off"/> <w:i-cs w:val="off"/> <w:color w:val="000000"/> <w:kern w:val="0"/> <w:sz w:val="22"/> <w:sz-cs w:val="22"/> <w:u w:val="none"/> <w:lang w:val="EN-US" w:fareast="ZH-CN" w:bidi="AR-SA"/> </w:rPr> <w:t>日期</w:t> </w:r> </w:p> </w:tc> <w:tc> <w:tcPr> <w:tcW w:w="2283" w:type="dxa"/> <w:tcBorders> <w:top w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> <w:left w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> <w:bottom w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> <w:right w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="000000"/> </w:tcBorders> <w:shd w:val="clear" w:color="auto" w:fill="auto"/> <w:noWrap/> <w:vAlign w:val="center"/> </w:tcPr> <w:p> <w:pPr> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:cs="宋体" w:hint="fareast"/> <w:i w:val="off"/> <w:i-cs w:val="off"/> <w:color w:val="000000"/> <w:sz w:val="22"/> <w:sz-cs w:val="22"/> <w:u w:val="none"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:cs="宋体" w:hint="default"/> <w:color w:val="000000"/> <w:sz w:val="22"/> <w:sz-cs w:val="22"/> </w:rPr> <w:t>${item.visitDate}</w:t> </w:r> </w:p> </w:tc> </#list>
-
-
Java代码示例
-
初始化FreeMarker环境
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency>
-
加载模板文件
//封装map Map<String, Object> dataMap = new HashMap<>(); Configuration configuration = new Configuration(Configuration.VERSION_2_3_0); configuration.setDefaultEncoding("utf-8"); configuration.setClassForTemplateLoading(this.getClass(), "/templates"); Template template = configuration.getTemplate("销售报告模板.ftl");
-
将数据模型绑定到模板,
dataMap
替换基本信息,list
去替换表格信息 -
//省略具体获取数据步骤, 这个根据实际情况查询就可 dataMap.put("levelOneVisits", levelOneVisits); dataMap.put("levelTwoVisits", levelTwoVisits); dataMap.put("levelThreeVisits", levelThreeVisits); dataMap.put("managerCoVisits", managerCoVisits); dataMap.put("pharmacyVisits", pharmacyVisits); dataMap.put("list1", list2); dataMap.put("list2", list1); dataMap.put("itemsList", itemsList); // 输出 File outFile = new File(path); Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8")); try { template.process(dataMap, out); out.flush(); out.close(); } catch (TemplateException e) { e.printStackTrace(); }
-
输出生成的Word文档
-
附录
- 相关资源链接:FreeMarker官方文档:https://freemarker.apache.org/docs/