1、导入依赖
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency>
2、准备word模版文件
2.1、新建word,并且编辑占位符,使用 “${ }”包裹占位符,如: ${name}、${age}
2.2、另存为 Word XML 格式,该格式可以与 word文件格式、xml格式无损转换,文件不会变形
2.3、打开XML文件,把图片编码替换为图片占位符
2.3.1 图片编码:非常长的这一串就是图片编码,搜索 binaryData就可以找到,找到后删除
2.3.2、删除图片编码后,在原位置添加占位符,如:${icon}
2.4、把修改好的模版复制到/resrouce目录下,并且修改后缀为 .ftl
ftl格式为FreeMarker可编辑的文本类型,并且与xml格式文件可以无损转换
3、批量替换动态数据
package shzu.wangbao.autoGenerateWordFile.util;
import freemarker.core.ParseException;
import freemarker.log.Logger;
import freemarker.template.*;
import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: FreeMarkerUtil
* @Description: 使用 FreeMarker 批量修改替换动态文本和图片
* @Date: 2023/3/20 6:35 PM
* @Author: wang-bao / uuid-07c6
**/
public class FreeMarkerUtil {
/**
* 1、新建配置类对象 config , 用来管理 FreeMarker 的配置信息,非常重要!!!
*/
private Configuration config = null;
/**
* 2、新建无参构造器,用于初始化 FreeMarker 的配置信息
*/
public FreeMarkerUtil() {
// 设置 FreeMarker 的版本 ==> 2.3.29 ==> 这个要根据你引入 FreeMarker 的依赖版本来切换!!!
config = new Configuration(Configuration.VERSION_2_3_29);
// 设置 FreeMarker 的默认编码格式 ==> utf-8
config.setDefaultEncoding("utf-8");
}
/**
* 3、新建日志 log ,用来打印各种日志信息,比如错误信息和异常信息,便于查找问题和修正代码
*/
private Logger log = Logger.getLogger(FreeMarkerUtil.class.toString());
/**
* 4、使用 FreeMarker 生成 Word 文件
* @param dataMap 数据
* @param templateName 目标名(模板名称)
* @param saveFilePath 要保存的文件存放地址:全路径+文件名
*/
public File createWord(Map<String, Object> dataMap, String templateName, String saveFilePath) {
// 一、设置配置信息
// 4-1、设置 word 模板文件存放的位置,默认寻找 /resources 目录下的位置
// 此处我选择的是 "/resources/templates/" ,因为默认省去 "/resources" 的前缀,直接填写参数 "/templates/"
config.setClassForTemplateLoading(this.getClass(), "/templates/");
// 4-2、设置异常处理器 这样的话 即使没有属性也不会出错 如:${list.name}...不会报错
config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
// 二、设置提供原始数据的源文件
// 4-3、新建模版类对象 template,用来管理模版文件,完成动态数据的替换
Template template = null;
// 4-4、查找后缀为 ".ftl" 的文件,截取其文件名
if (templateName.endsWith(".ftl")) {
templateName = templateName.substring(0, templateName.indexOf(".ftl"));
}
// 4-5、通过文件名 templateName ,获取模版文件的数据流 template,此后调用输入流的时候会直接从 template 中获取并填充
try {
template = config.getTemplate(templateName + ".ftl");
} catch (TemplateNotFoundException e) {
log.error("模板文件未找到", e);
e.printStackTrace();
} catch (MalformedTemplateNameException e) {
log.error("模板类型不正确", e);
e.printStackTrace();
} catch (ParseException e) {
log.error("解析模板出错,请检查模板格式", e);
e.printStackTrace();
} catch (IOException e) {
log.error("IO读取失败", e);
e.printStackTrace();
}
// 三、设置被写入数据的目标文件的写入流 BufferedWriter
// 4-6、新建我们需要生成的文件 outFile,指定其存储位置 saveFilePath
File outFile = new File(saveFilePath);
// 4-7、如果父节点的文件夹不存在,则新建该文件夹
if (!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
// 4-8、新建文件输出流对象 fos,用于对 word 文件写入数据
// Stream 是按照字节逐个读取,读取汉字时会出现乱码( 1个汉字 = 2个字节 )
FileOutputStream fos = null;
// 4-9、指定被写入数据流的文件对象为 outFile
try {
fos = new FileOutputStream(outFile);
} catch (FileNotFoundException e) {
log.error("输出文件时未找到文件", e);
e.printStackTrace();
}
// 4-10、新建写入流对象 out,用于对 word 文件写入数据流
// Writer 是按照字符逐个读取,可以保证汉字不会乱码( 1个汉字 = 2个字节 )
Writer out = null;
// 4-11、使用 BufferedWriter 包装文件流输出对象
// BufferedWriter 是 Writer 的子类,以缓冲区为单位逐次写入数据,可以提高写入数据的效率
out = new BufferedWriter(new OutputStreamWriter(fos));
// 四、生成目标文件
// 4-12、读取源文件数据,执行目标文件的写入流,完成动态数据的批量替换
try {
template.process(dataMap, out);
} catch (TemplateException e) {
log.error("填充模板时异常", e);
e.printStackTrace();
} catch (IOException e) {
log.error("IO读取时异常", e);
e.printStackTrace();
}
log.info("由模板文件:" + templateName + ".ftl" + " 生成文件 :" + saveFilePath + " 成功!!");
// 4-13、关闭写入流
try {
out.close();//web项目不可关闭
} catch (IOException e) {
log.error("关闭Write对象出错", e);
e.printStackTrace();
}
// 4-14、返回目标文件
return outFile;
}
/**
* 5、获取图片对应的 base64 编码(图片在xml文件中存储为 base64 格式的图片编码)
* @param imgFile 图片
* @return 图片对应的base64码
* @throws IOException
* @date 2019/4/16 17:05
*/
public static String getImageBase64String(File imgFile) throws IOException {
InputStream inputStream = new FileInputStream(imgFile);
byte[] data = new byte[inputStream.available()];
int totalNumberBytes = inputStream.read(data);
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
}
/**
* 6、主方法,测试工具类是否生效
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// 1、设置常用的地址前缀
String imagePath = "/Users/never/Desktop/gitbase/autoGenerateWordFile/src/main/resources/static/images";
String docPath = "/Users/never/Desktop/gitbase/autoGenerateWordFile/src/main/resources/static/docs";
// 2、新建工具类
FreeMarkerUtil emw = new FreeMarkerUtil();
// 3、使用 map 存储动态数据与占位符的映射关系
Map<String, Object> dataMap = new HashMap<String, Object>();
// 4、设置字符串占位符与字符串数据的映射关系
dataMap.put("name", "王某某");
dataMap.put("sex", "男");
dataMap.put("age","23");
// 5、获取 Base64 格式的图片编码
String imageStr = getImageBase64String(new File(imagePath+"/322.jpeg"));
// 6、设置图片占位符与图片编码的映射关系
dataMap.put("icon", imageStr);
// 7、使用createWord方法,生成 (.doc 格式) word 文件
// 第一个参数是map,第二个参数是源文件(ftl文件),第三个参数是新生文件(产生的目标文件的存储路径)
emw.createWord(dataMap, "demo666.ftl", docPath+"/demo666.doc");
}
}
4、运行效果
5、可能出现的问题
如:格式解析失败
解决方法:打开xml文件,查看${}占位符是否与动态数据(如:name) 分隔开
问题原因:复制粘贴导致的文本变形,${name}被分割成${}和name
解决方案:删除占位符,重新手打一遍
经验小结:不要复制粘贴!不要复制粘贴!不要复制粘贴!动态数据一定要手打,防止格式变形。