7、FreeMarker

FreeMarker

FreeMarker 是一款免费的模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件,也可以通过模板来生成Word、pdf、Excel

依赖

普通项目

<dependency>
  <groupId>org.freemarker</groupId>
  <artifactId>freemarker</artifactId>
  <version>2.3.23</version>
</dependency>

SpringBoot项目

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

语法

一般FreeMarker的模板文件后缀名为:.ftl,模板文件中有四种元素:

  1. 文本,直接输出的部分
  2. 注释,即<#-- … -->格式不会输出
  3. 插值(Interpolation):即${…}部分,将使用数据模型中的部分替代输出
  4. FTL指令:FreeMarker指令,和HTML标记类似,名字前加#予以区分,不会输出
<#-- 注释 -->
<body>${name}</body>

常见指令

assign

assign指令用于在页面上定义一个变量

<#-- 定义变量 -->
<#assign linkman="周先生">
<#-- 使用变量 -->
联系人:${linkman}

<#-- 定义变量 -->
<#assign info={"mobile":"13812345678",'address':'北京市昌平区'} >
<#-- 使用变量 -->
电话:${info.mobile}  地址:${info.address}

include

模板嵌套,在指定位置引入指定模板

<#include "head.ftl"/>

if

在模板文件中使用if指令进行判断

<#if isSuccess=true>
  你已通过实名认证
<#else>  
  你未通过实名认证
</#if>

list

在模板文件中使用list指令进行遍历

<#list goodsList as goods>
  第${goods?index}个商品,商品名称:${goods.name},价格:${goods.price}<br>
</#list>

特殊数据类型

null

Freemarker不支持null值,会报错,可以对空值进行处理

<#--不存在则什么都不显示-->
${abc!}

<#--如果为空,则显示没有字符-->
${abc!'没有字符'}

boolean

${flag?string("Yes","No")}

Date

<#--格式化-->
${date?string("yyyy-MM-dd HH:mm:ss")}

<#--格式化,为null则不显示-->
${date?string("yyyy-MM-dd HH:mm:ss")!}

使用

生成Word

  1. 将 word 中需要填充的数据用占位符${变量名}替换。
  2. 将该 word 另存为.xml的格式,并检查看格式是否有误(主要看占位符有没被分割开来)。
  3. 将后缀.xml改成.ftl后(可以不改)
  4. 再调用相关 API 即可生成 word 文档。
    public static void generateWord(Map<String,Object> map) throws Exception {

        // 设置FreeMarker的版本和编码格式
        Configuration configuration = new Configuration(Configuration.getVersion());
        configuration.setDefaultEncoding("UTF-8");

        // 设置FreeMarker生成Word文档所需要的模板的 绝对路径
        // configuration.setDirectoryForTemplateLoading(new File("/Users/yanggang/Desktop/"));
        // 设置FreeMarker生成Word文档所需要的模板 ClassPath下
        configuration.setClassForTemplateLoading(XsReportGenerator.class, "/templates");

        // 设置FreeMarker生成Word文档所需要的模板
        Template tem = configuration.getTemplate("模板.xml", "UTF-8");
        // 创建一个Word文档的输出流,生成到本地
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("/Users/yanggang/Desktop/生成结果.docx")), StandardCharsets.UTF_8));

        tem.process(map, out);
        out.flush();
        out.close();
    }

占位符被分割的问题

在我们编辑好Word模板,转成xml时,有的占位符会被分割,那么我们可以使用如下的程序对xml模板文件进行整理

注意:使用下面程序整理xml模板文件时,不要格式化

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.11</version>
</dependency>
package top.ygang;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileAppender;
import cn.hutool.core.io.file.FileReader;

import java.util.List;

/**
 * Author: yanggang
 * Date: 2023-12-11 16:41:51
 */
public class Main {
    public static void main(String[] args) {
        //文件读取-FileReader
        //默认UTF-8编码,可以在构造中传入第二个参数作为编码
        FileReader fileReader = new FileReader("/Users/yanggang/Desktop/模板.xml");
        //从文件中读取每一行数据
        List<String> strings = fileReader.readLines();
        //文件追加-FileAppender
        //destFile – 目标文件
        //capacity – 当行数积累多少条时刷入到文件
        //isNewLineMode – 追加内容是否为新行
        FileAppender appender = new FileAppender(FileUtil.newFile("/Users/yanggang/Desktop/模板new.xml"), 16, true);
        //遍历得到每一行数据
        for (String string : strings) {
            //判断每一行数据中不包含'$'的数据先添加进新文件
            if (!string.contains("$")) {
                appender.append(string);
                continue;
            }
            //如果一行数据中包含'$'变量符将替换为'#$'
            string = string.replaceAll("\\$", "#\\$");
            //然后以'#'切割成每一行(数组),这样一来'$'都将在每一行的开头
            String[] ss = string.split("#");
            // 同一行的数据写到同一行,文件追加自动换行了(最后的完整数据)
            StringBuilder sb = new StringBuilder();
            //遍历每一行(数组ss)
            for (int i = 0; i < ss.length; i++) {
                //暂存数据
                String s1 = ss[i];
                //将不是以'$'开头的行数据放进StringBuilder
                if (!s1.startsWith("$")) {
                    sb.append(s1);
                    continue;
                }
                //被分离的数据一般都是'${'这样被分开
                //匹配以'$'开头的变量找到'}' 得到索引位置
                int i1 = s1.lastIndexOf("}");
                //先切割得到这个完整体
                String substr = s1.substring(0, i1 + 1);
                //把变量追加到StringBuilder
                sb.append(substr.replaceAll("<[^>]+>", ""));
                //再将标签数据追加到StringBuilder包裹变量
                sb.append(s1.substring(i1 + 1));
            }
            appender.append(sb.toString());
        }
        appender.flush();
        appender.toString();
    }
}