在项目中使用freemaker生成word、excel、pdf文档是比较方便的实现方式,但是也存在一个问题,我们的产品最终是要给客户使用的,而freemaker使用的是ftl模板,而不是word、excel这样的原始文件模板,但对于客户来讲,手动去调整生成ftl模板并不现实,所以我实现了一个word文档自动导出的功能,提供一种解决问题的思路。
一、导包
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
二、实现对word模板的参数解析,参数校正,列表参数处理,图片参数处理等,以及ftl模板生成
1、解析参数,因为word本质上是以xml文档的格式实现文档的数据存储和处理的,所以我们通过对xml文档的处理实现自动化,第一步是将模板中出现的参数解析出来
import com.tss.mangosercivea.manager.template.DocumentModel;
import com.tss.mangosercivea.manager.template.enums.ParamCategory;
import com.tss.mangosercivea.manager.template.TemplateParam;
import com.tss.mangosercivea.manager.template.XMLHandler;
import org.dom4j.Element;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* 解析xml文档中的参数值
* <p>
* Created by yangxiangjun on 2021/1/22.
*/
public class AnalysisParamHandler extends XMLHandler {
@Override
public void run(DocumentModel document) {
List<TemplateParam> params = new ArrayList<>();
List<Element> nodes = document.getNodes();
//获取所有的文本信息节点
Stack stack = new Stack<>();
boolean isAnalysis = false;
boolean isStack = false;
for (Element node : nodes) {
String path = node.getUniquePath();
//获取节点的文本信息
String text = node.getText();
//如果文本信息包含占位符,则该文本信息后面讲出现与之对应的参数
for (ParamCategory value : ParamCategory.values()) {
isAnalysis = isAnalysis || text.contains(value.getParamCate()[0]) || isStack;
}
// 如果flag变量为ture,代表当前正在寻找被xml格式转换打乱的参数,将接下来的文本信息入栈,
// 并且判断是否找到了占位符结束标志,如果找到,代表找到参数,isAnalysis设置为false
// 此时开启对文本信息的逐个字符读取
if (isAnalysis) {
ParamCategory paramCate = null;
for (int i = 0; i < text.length(); i++) {
if (!isStack) {
char[] chars = new char[2];
chars[0] = text.charAt(i);
chars[1] = text.charAt(i + 1);
String str = new String(chars);
//判断字符串在遍历中是否找到了占位符的开始标志
for (ParamCategory value : ParamCategory.values()) {
if (str.equals(value.getParamCate()[0])) {
if (!stack.isEmpty()) {
throw new RuntimeException("word模板参数解析失败");
}
//标注当前找到的占位符
paramCate = value;
isStack = true;
}
}
}
//如果找到了占位符开始标志,后续的字符需要入栈
if (isStack) {
stack.push(text.charAt(i));
//已经遍历到当前占位符的尾部,表示占位符的所有字符都已入栈,需要从栈中获取参数
if (paramCate.getParamCate()[1].charAt(0) == text.charAt(i)) {
isStack = false;
isAnalysis = false;
params.add(getParam(stack, paramCate, path));
}
}
}
}
}
document.setParams(params);
}
private TemplateParam getParam(Stack stack, ParamCategory paramCategory, String path) {
int size = stack.size();
StringBuilder tempParam = new StringBuilder();
for (int i = 0; i < size; i++) {
tempParam.append(stack.pop());
}
tempParam = tempParam.reverse();
int startIndex = tempParam.indexOf(paramCategory.getParamCate()[0]) + 2;
int endIndex = tempParam.indexOf(paramCategory.getParamCate()[1]);
String param = tempParam.substring(startIndex, endIndex);
boolean matches = Pattern.matches(RULE, param);
if (!matches) {
throw new RuntimeException("word模板参数格式错误");
}
TemplateParam templateParam = new TemplateParam();
switch (paramCategory.getParamType()) {
case LOOP:
templateParam = TemplateParam.buildCollParam(param, path);
break;
case IMAGE:
templateParam = TemplateParam.buildImageParam(param,path);
break;
case SINGLE:
templateParam = TemplateParam.buildSingleParam(param, path);
break;
case CHECKBOX:
break;
default:
templateParam = null;
break;
}
return templateParam;
}
}
import org.dom4j.Document;
import org.dom4j.Element;
import java.util.List;
/**
* 对模板文档的封装,会在文档加载的时候初始化,并用于处理的各个环节
* Created by yangxiangjun on 2021/1/26.
*/
public class DocumentModel {
/**
* xml文档对象
*/
private Document document;
/**
* 源文件路径
*/
private String filePath;
/**
* 生成ftl模板的路径
*/
private String ftlPath;
/**
* 文档中的参数集合
*/
protected List<TemplateParam> params;
/**
* xml文档中需要处理的节点
*/
protected List<Element> nodes;
...
}
import com.tss.mangosercivea.manager.template.enums.ParamType;
/**
* 模板中的参数
* Created by yangxiangjun on 2021/1/8.
*/
public class TemplateParam {
/**
* 参数名
*/
private String name;
/**
* 参数的前缀,代表列表参数属于哪个对象
*/
private String ascription;
/**
* 参数出现在xml文档的路径
*/
private String path;
/**
* 参数类型
*/
private ParamType paramType;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAscription() {
return ascription;
}
public void setAscription(String ascription) {
this.ascription = ascription;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public ParamType getParamType() {
return paramType;
}
public void setParamType(ParamType paramType) {
this.paramType = paramType;
}
public static TemplateParam buildParam(String text, String path){
TemplateParam templateParam = new TemplateParam();
templateParam.setAscription("");
templateParam.setName(text);
templateParam.setPath(path);
return templateParam;
}
public static TemplateParam buildSingleParam(String text, String path){
TemplateParam templateParam = buildParam(text, path);
templateParam.setParamType(ParamType.SINGLE);
return templateParam;
}
public static TemplateParam buildImageParam(String text, String path){
TemplateParam templateParam = buildParam(text, path);
templateParam.setParamType(ParamType.IMAGE);
return templateParam;
}
public static TemplateParam buildCollParam(String text,String path){
TemplateParam templateParam = new TemplateParam();
String[] split = text.split("\\.");
if (split.length != 2) {
throw new RuntimeException("word模板循环参数格式错误");
}
templateParam.setAscription(split[0]);
templateParam.setName(split[1]);
templateParam.setParamType(ParamType.LOOP);
templateParam.setPath(path);
return templateParam;
}
@Override
public String toString() {
return "TemplateParam{" +
"name='" + name + '\'' +
", ascription='" + ascription + '\'' +
", path='" + path + '\'' +
", paramType=" + paramType +
'}';
}
}
/**
* 对模板中参数占位符的定义
*/
public enum ParamCategory {
/**
* 普通单次使用的参数占位符
*/
SINGLE(ParamType.SINGLE, new String[]{"${", "}"}),
/**
* 列表参数占位符
*/
LOOP(ParamType.LOOP, new String[]{"$[", "]"}),
/**
* 图片参数占位符
*/
IMAGE(ParamType.IMAGE, new String[]{"@{", "}"}),
/**
* 复选框参数占位符
*/
CHECKBOX(ParamType.CHECKBOX, new String[]{"#{", "}"}),
;
private ParamType paramType;
private String[] paramCate;
ParamCategory(ParamType paramType, String[] paramCate) {
this.paramType = paramType;
this.paramCate = paramCate;
}
public ParamType getParamType() {
return paramType;
}
public void setParamType(ParamType paramType) {
this.paramType = paramType;
}
public String[] getParamCate() {
return paramCate;
}
public void setParamCate(String[] paramCate) {
this.paramCate = paramCate;
}
}
2、实现对参数的校正,word格式转换为xml的过程很可能会导致参数在xml中被打乱,所以我们解析出来参数之后,需要去校验参数是否符合预期,被打乱的参数需要校正,除此之外,在配置模板时,普通参数、列表参数、图片参数等的占位符是做了区别的,分别是${} $[]和@{},这里会替换成freemaker规范的占位符
/**
* 对参数进行占位符的检测,如果占位符和参数被分开,不在同一个标签下,进行校正
*
* Created by yangxiangjun on 2021/1/22.
*/
public class NormalizationHandler extends XMLHandler {
@Override
public void run(DocumentModel document) {
List<Element> nodes = document.getNodes();
List<TemplateParam> params = document.getParams();
//获取所有的文本信息节点
for (Element node : nodes) {
//获取节点的文本信息
String text = node.getText();
//如果文本信息包好占位符,但是占位符不全,则首先将占位符去除
for (ParamCategory value : ParamCategory.values()) {
if (text.contains(value.getParamCate()[0]) ^ text.contains(value.getParamCate()[1])) {
text.replace(value.getParamCate()[0],"");
text.replace(value.getParamCate()[1],"");
node.setText(text);
}
}
//如果文本信息中包含参数,判断参数是否有正确的占位符,没有正确的占位符,则添加正确的占位符
String finalText = text;
params.forEach((templateParam) -> {
String param = templateParam.getParamType().equals(ParamType.LOOP) ? templateParam.getAscription() + "." + templateParam.getName() : templateParam.getName();
String relParam = "";
String paramCategory[] = new String[2];
switch (templateParam.getParamType()){
case LOOP:
paramCategory = ParamCategory.LOOP.getParamCate();
break;
case IMAGE:
paramCategory = ParamCategory.IMAGE.getParamCate();
break;
case SINGLE:
paramCategory = ParamCategory.SINGLE.getParamCate();
break;
case CHECKBOX:
paramCategory = ParamCategory.CHECKBOX.getParamCate();
break;
default:
break;
}
relParam = paramCategory.length != 2 ? param : paramCategory[0] + param + paramCategory[1];
node.setText(finalText.replace(param,relParam));
});
}
}
}
3、列表参数处理,我们想要输出列表数据,就要在需要循环的数据外层加上list标签,这样freemaker在生成文档的时候才能循环数据
import com.tss.mangosercivea.manager.template.*;
import com.tss.mangosercivea.manager.template.enums.ParamCategory;
import com.tss.mangosercivea.manager.template.enums.ParamType;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* 实现word模板中列表参数的处理
* Created by yangxiangjun on 2021/1/25.
*/
public class WordCollectionHandler extends XMLHandler {
@Override
public void run(DocumentModel document) {
List<TemplateParam> params = document.getParams();
//首先对参数进行筛选和分类,同一个集合的参数放到同一个列表里,剔除单参数
Map<String, List<TemplateParam>> collParams = params.stream()
.filter(param -> ParamType.LOOP.equals(param.getParamType()))
.collect(Collectors.groupingBy(TemplateParam::getAscription));
for (String asc : collParams.keySet()) {
List<TemplateParam> templateParams = collParams.get(asc);
AtomicReference<Element> ancestorTr = new AtomicReference<>();
templateParams.forEach(templateParam -> {
//根据参数所在标签的路径获取标签
List<Element> nodes = document.getDocument().selectNodes(templateParam.getPath());
nodes.get(0).setText(ParamCategory.SINGLE.getParamCate()[0] + templateParam.getAscription() + "." + templateParam.getName() + ParamCategory.SINGLE.getParamCate()[1]);
//获取当前w:t的w:tr父节点
List<Element> ancestorTrList = nodes.get(0).selectNodes("ancestor::w:tr[1]");
if (!ancestorTrList.isEmpty()) {
Element element = ancestorTrList.get(0);
if (null != ancestorTr.get() && !element.equals(ancestorTr.get())) {
throw new RuntimeException("word文档关于循环数据的配置不符合规定");
}
ancestorTr.set(element);
} else {
throw new RuntimeException("处理集合数据时,模板的配置只能借助表格");
}
});
Element parent = ancestorTr.get().getParent();
List<Element> elements = parent.elements();
//获取w:tr标签的位置下标,用于添加循环标签后对其进行重写
int index = elements.indexOf(ancestorTr.get());
//创建一个#list标签,并设置循环数据的属性,这里的属性添加了name属性名,但实际上是不需要他的
//这里只是为了能回写xml文件,后续会做处理
Element foreach = DocumentHelper.createElement("#list");
foreach.addAttribute("name", asc +" as "+asc);
Element copy = ancestorTr.get().createCopy();
foreach.add(copy);
//添加if标签用于对循环数据的空判断
Element iflist = DocumentHelper.createElement("#if");
iflist.addAttribute("name",String.format("%s ??&& (%s?size>0)",asc,asc));
iflist.add(foreach);
elements.set(index,iflist);
}
}
}
4、图片参数处理,图片的配置方式是在模板中使用一张占位的图片,生成文档会使用新的图片数据将其替换,对于普通的图片,可以换行加上@{}占位符参数,但是有其他展示效果,如悬浮的,可能占位符无法生效,不见占位符的会用img1,2,3代替,图片处理目前验证不够充分,可能难以实现复杂场景
import cn.hutool.core.collection.CollectionUtil;
import com.tss.mangosercivea.manager.template.DocumentModel;
import com.tss.mangosercivea.manager.template.XMLHandler;
import com.tss.mangosercivea.util.XMLUtil;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Element;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* word的图片参数处理
* Created by yangxiangjun on 2021/2/4.
*/
public class WordImageHandler extends XMLHandler {
private final static String REGEX = "\\@\\{(\\w+)\\}";
@Override
public void run(DocumentModel document) {
//图片索引下表
Integer index = 1;
//获取根路径
Element root = document.getDocument().getRootElement();
//获取图片标签
List<Element> imgTagList = root.selectNodes("//w:binData");
for (Element element : imgTagList) {
element.setText(String.format("${img%s!''}",index++));
//获取当前图片所在的wp标签
List<Element> wpList = element.selectNodes("ancestor::w:p");
if (CollectionUtil.isEmpty(wpList)) {
throw new RuntimeException("未知异常");
}
Element imgWpElement = wpList.get(0);
while (imgWpElement != null) {
try {
imgWpElement = XMLUtil.selectNextElement(imgWpElement);
} catch (Exception de) {
break;
}
//获取对应图片字段
List<Element> imgFiledList = imgWpElement.selectNodes("w:r/w:t");
if (CollectionUtil.isEmpty(imgFiledList)) {
continue;
}
String imgFiled = getImgFiledTrimStr(imgFiledList);
Pattern compile = Pattern.compile(REGEX);
Matcher matcher = compile.matcher(imgFiled);
String imgFiledStr = "";
while (matcher.find()) {
imgFiledStr = matcher.group(1);
boolean remove = imgWpElement.getParent().elements().remove(imgWpElement);
System.out.println(remove);
}
if (StringUtils.isNotEmpty(imgFiledStr)) {
element.setText(String.format("${%s!''}",imgFiledStr));
break;
}
}
}
}
private String getImgFiledTrimStr(List<Element> imgFiledList) {
StringBuilder stringBuilder = new StringBuilder();
if (CollectionUtil.isNotEmpty(imgFiledList)) {
for (Element element : imgFiledList) {
stringBuilder.append(element.getTextTrim().toString());
}
}
return stringBuilder.toString();
}
}
5、回写xml文档,将处理后的xml回写,注意回写xml文档是需要重写XMLWriter,避免在回写的过程中特殊字符被转换
import com.tss.mangosercivea.manager.template.core.ConvertXmlWriter;
import com.tss.mangosercivea.manager.template.DocumentModel;
import com.tss.mangosercivea.manager.template.XMLHandler;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
/**
* 将处理后的document对象重新写入文件
* Created by yangxiangjun on 2021/1/27.
*/
public class XMLWriterHandler extends XMLHandler {
private static Logger log = LoggerFactory.getLogger(XMLWriterHandler.class);
@Override
public void run(DocumentModel document) {
OutputStreamWriter outputStreamWriter = null;
XMLWriter writer = null;
try {
outputStreamWriter = new OutputStreamWriter(new FileOutputStream(document.getFilePath()));
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8"); // 指定XML编码
writer = new ConvertXmlWriter(outputStreamWriter,format);
writer.write(document.getDocument());
} catch (FileNotFoundException e) {
log.error("找不到指定文件",e);
} catch (IOException e) {
log.error("重新写xml文件失败",e);
} finally {
try {
if (outputStreamWriter != null) {
outputStreamWriter.close();
}
if (writer!= null) {
writer.close();
}
} catch (IOException e) {
}
}
}
}
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
/**
* 重写writer方法,对xml文件中新添加的特殊字符进行转义
*
* Created by yangxiangjun on 2021/1/27.
*/
public class ConvertXmlWriter extends XMLWriter {
public ConvertXmlWriter(OutputStreamWriter out, OutputFormat format) throws UnsupportedEncodingException {
super(out, format);
}
@Override
protected void writeEscapeAttributeEntities(String txt) throws IOException {
if (txt != null) {
this.writer.write(txt);
}
}
}
6、将xml文档转换为ftl模板,并且在这个转换过程中需要去除list标签的name属性的key,只留下value
import com.tss.mangosercivea.manager.template.DocumentModel;
import com.tss.mangosercivea.manager.template.XMLHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.compile;
/**
* 将xml文档转换为ftl模板
* Created by yangxiangjun on 2021/2/4.
*/
@Slf4j
public class XmlToFtlHandler extends XMLHandler {
@Override
public void run(DocumentModel document) {
this.replacePartTextContent(document);
}
/**
* @Description 目前需要修改的集合属性,因为xml不支持ftl的list标签。这里需要我们修改
* @Param
* @return void
*/
public synchronized void replacePartTextContent(DocumentModel documentModel) {
String filePath = documentModel.getFilePath();
// 读
try {
File file = new File(filePath);
File targetFile = new File(filePath.substring(0,filePath.lastIndexOf("."))+".ftl");
BufferedReader bufIn = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
// 内存流, 作为临时流
CharArrayWriter tempStream = new CharArrayWriter();
// 替换
String line = null;
while ((line = bufIn.readLine()) != null) {
if (StringUtils.isBlank(line)) {
continue;
}
// 替换每行中, 符合条件的字符串
line = replacePart(line);
// 将该行写入内存
tempStream.write(line);
// 添加换行符
//tempStream.append(System.getProperty("line.separator"));
}
// 关闭 输入流
bufIn.close();
// 将内存中的流 写入 文件
BufferedWriter out = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(targetFile), "UTF-8"));
tempStream.writeTo(out);
out.close();
documentModel.setFtlPath(targetFile.getPath());
} catch (Exception e) {
log.error("xml转ftl失败",e);
}
}
public String replacePart(String line) {
StringBuffer sb = new StringBuffer();
Pattern p = compile("(\\#(\\w+) name=\"([ \\>\\?\\&\\!\\'\\$\\(\\)\\{\\}\\,\\.\\=\\f\\n\\r\\t\\vA-Za-z0-9_]+)\")");
Matcher m = p.matcher(line) ;
while( m.find() ){
String labelName = m.group(2);
String group = m.group(3);
String v = "#"+labelName+" "+group;
//注意,在替换字符串中使用反斜线 (\) 和美元符号 ($) 可能导致与作为字面值替换字符串时所产生的结果不同。
//美元符号可视为到如上所述已捕获子序列的引用,反斜线可用于转义替换字符串中的字面值字符。
v = v.replace("\\", "\\\\").replace("$", "\\$");
//替换掉查找到的字符串
m.appendReplacement(sb, v) ;
}
//别忘了加上最后一点
m.appendTail(sb) ;
return sb.toString();
}
}
到这里就完成了几个基本的处理过程,生成了ftl模板文件,可以用于freemaker生成文档了,下面要做的,就是利用责任链模式,将上面这些步骤依次执行。
这是比较核心的一个类,他用于构建责任链模式,并且控制从docx格式的word文档模板到生成word文档结果的全过程。
import com.tss.mangosercivea.manager.template.DocumentModel;
import com.tss.mangosercivea.manager.template.XMLHandler;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import java.io.*;
import java.util.Map;
/**
* Created by yangxiangjun on 2021/1/26.
*/
public class DocExecutor {
public static final String LABEL_TEXT = "//w:t";
private Map<String, Object> dataMap;
private String sourceFilePath;
private String targetFilePath;
private DocumentModel documentModel;
private XMLHandler header;
private XMLHandler tail;
public DocExecutor(Map<String, Object> dataMap, String sourceFilePath, String targetFilePath) {
this.dataMap = dataMap;
this.sourceFilePath = sourceFilePath;
this.targetFilePath = targetFilePath;
}
public DocExecutor addChain(XMLHandler chain) {
if (header == null) {
this.header = this.tail = chain;
return this;
}
this.tail.next(chain);
this.tail = chain;
return this;
}
public File build() {
try {
init();
} catch (DocumentException e) {
throw new RuntimeException("文档初始化失败", e);
}
this.header.execute(documentModel);
File generateFile;
try {
generateFile = generate();
} catch (IOException e) {
throw new RuntimeException("文档生成失败", e);
}
destroy();
return generateFile;
}
/**
* 初始化document对象
*
* @throws DocumentException
*/
private void init() throws DocumentException {
if (StringUtils.isBlank(sourceFilePath)) {
throw new RuntimeException("源模板文件为空");
}
convertToXml();
SAXReader reader = new SAXReader();
File file = new File(sourceFilePath);
Document document = reader.read(file);
DocumentModel documentModel = new DocumentModel();
documentModel.setDocument(document);
documentModel.setFilePath(sourceFilePath);
documentModel.setNodes(document.selectNodes(LABEL_TEXT));
this.documentModel = documentModel;
}
private void convertToXml() {
File file = new File(sourceFilePath);
if (file.isFile()) {
String name = sourceFilePath.substring(0, sourceFilePath.lastIndexOf(".")) + ".xml";
file.renameTo(new File(name));
this.sourceFilePath = name;
} else {
throw new RuntimeException("找不到源文件");
}
}
private File generate() throws IOException {
if (StringUtils.isBlank(documentModel.getFtlPath())) {
throw new RuntimeException("模板文件为空");
}
if (StringUtils.isBlank(targetFilePath)) {
throw new RuntimeException("目标文件为空");
}
File file = new File(documentModel.getFtlPath());
String directoryPath;
String fileName;
if (file.isFile()) {
directoryPath = file.getParent();
fileName = file.getName();
} else {
throw new RuntimeException("找不到ftl模板文件");
}
Configuration configuration = FreeMarkerConfig.getInstance();
// 设置FreeMarker生成文档所需要的模板的路径
try {
configuration.setDirectoryForTemplateLoading(new File(directoryPath));
} catch (IOException e) {
throw new RuntimeException("找不到ftl模板文件所在的路径");
}
// 设置FreeMarker生成Word文档所需要的模板
Template t = configuration.getTemplate(fileName);
// 创建一个Word文档的输出流
File targetFile = new File(targetFilePath);
try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), "UTF-8"));) {
//FreeMarker使用Word模板和数据生成Word文档
t.process(dataMap, out);
return file;
} catch (TemplateException e) {
throw new RuntimeException("文档生成失败", e);
}
}
private void destroy() {
//删除处理过程中产生的文件
// deleteFile(DIR_PATH + sourceFileName);
deleteFile(documentModel.getFtlPath());
//处理生成的最终文档,上传文件服务器或构建流进行传输
}
/**
* 删除单个文件
*
* @param fileName 要删除的文件的文件名
* @return 单个文件删除成功返回true,否则返回false
*/
private static boolean deleteFile(String fileName) {
File file = new File(fileName);
// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除
if (file.exists() && file.isFile()) {
if (file.delete()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
/**
* freemaker导出excel的公共配置类
* Created by yangxiangjun on 2021/1/27.
*/
public class FreeMarkerConfig {
private static Logger log = LoggerFactory.getLogger(FreeMarkerConfig.class);
public static Configuration getInstance(){
return ConfigEnum.CONFIG_INSTANCE.getInstance();
}
public enum ConfigEnum{
/**
*
*/
CONFIG_INSTANCE;
private Configuration instance;
ConfigEnum() {
// 设置FreeMarker的版本和编码格式
Configuration configuration = new Configuration(new Version("2.3.28"));
configuration.setDefaultEncoding("UTF-8");
this.instance = configuration;
}
public Configuration getInstance() {
return instance;
}
}
}
/**
* 文档生成器,可继承对word、excel、pdf等各种文档的实现
* Created by yangxiangjun on 2021/1/29.
*/
public interface DocumentGenerator {
DocumentType getType();
File generateFile(Map<String, Object> dataMap, String sourceFilePath, String targetFilePath);
}
/**
* word文档生成的实现
* Created by yangxiangjun on 2021/2/3.
*/
@Component
public class WordDocument implements DocumentGenerator {
@Override
public DocumentType getType() {
return DocumentType.WORD;
}
@Override
public File generateFile(Map<String, Object> dataMap, String sourceFilePath, String targetFilePath) {
DocExecutor builder = new DocExecutor(dataMap,sourceFilePath,targetFilePath);
builder.addChain(new AnalysisParamHandler())
.addChain(new NormalizationHandler())
.addChain(new WordCollectionHandler())
.addChain(new WordImageHandler())
.addChain(new XMLWriterHandler())
.addChain(new XmlToFtlHandler());
File build = builder.build();
return build;
}
}
import com.tss.mangosercivea.manager.template.enums.DocumentType;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 文档生成器的工厂,这里会根据文件类型的不同,产生不同的处理器
* Created by yangxiangjun on 2021/2/3.
*/
@Component
public class DocumentFactory implements ApplicationContextAware {
private List<DocumentGenerator> generators;
/**
* 根据类型从DocumentGenerator的实现类中选取对应的处理器
* @param documentType
* @return
*/
public DocumentGenerator createGenerator(DocumentType documentType){
Optional<DocumentGenerator> generatorOptional = generators.stream().filter(generator -> documentType.equals(generator.getType())).findFirst();
if (generatorOptional.isPresent()) {
return generatorOptional.get();
} else {
throw new RuntimeException("找不到对应的文档处理器");
}
}
/**
* 系统初始化时将所有DocumentGenerator的实现类加载
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
generators = new ArrayList<>();
Map<String, DocumentGenerator> beansOfType = applicationContext.getBeansOfType(DocumentGenerator.class);
generators.addAll(beansOfType.values());
}
}
借助设计模式的设计,我们在调用功能的时候,可以变得简单明了,传入数据和文件即可。
/**
* Created by yangxiangjun on 2021/2/3.
*/
@SpringBootTest
public class WordExportTest {
@Autowired
DocumentFactory documentFactory;
@Test
public void test(){
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("title","测试");
Map<String,Object> map = new HashMap<>();
List<Map<String,Object>> list = new ArrayList<>();
map.put("name", "用户1");
map.put("sex", "男");
map.put("age", 20);
map.put("birth","2018-12-12");
list.add(map);
Map<String,Object> map1 = new HashMap<>();
map1.put("name", "用户2");
map1.put("sex", "女");
map1.put("age", 18);
map1.put("birth","2020-12-12");
list.add(map1);
dataMap.put("user",list);
dataMap.put("image","图片文件base64");
DocumentGenerator generator = documentFactory.createGenerator(DocumentType.WORD);
generator.generateFile(dataMap,"C:\\Users\\mango\\Documents\\项目资料\\demo.docx","C:\\Users\\mango\\Documents\\项目资料\\123.docx");
}
}