前言
前因
公司项目电子化项目部的新功能添加了根据施工项目文档模板,用户输入可变变量,生成用户所需文件的功能
方案记录
- XDOC http://www.xdocin.com/xdoc.html
- FreeMarker :freemarker,一个基于模板和源数据来生成目标文本的引擎工具
- **http://blog.sina.cn/dpool/blog/s/blog_14d29a9ba0102ws8h.html?vt=4
- https://blog.csdn.net/qq_35042227/article/details/78666362
- https://www.cnblogs.com/duanrantao/p/9377818.html
- **<https://www.cnblogs.com/w-yu-chen/p/11402098.html
- **https://www.cnblogs.com/Mywyr/p/10307515.html
空指针问题:https://www.cnblogs.com/Weagle/p/5417947.html,似乎没用用
FreeMarker生成Word笔记整理
流程
1 模板处理
,将文档需要手动生成的地方用变量替换,并将变量名记录下来!!!!
!!一定要检查好没有遗漏问题在进行下一步
变量起名风格:最好将文本,表格,单图片,多图片等不同格式的文本起名做标记
如:${textxxxxx},${imgxxxxx},${tablexxxxxx},${tablexxxx}
1.3 将xml文件后缀名直接修改为ftl,【.ftl】类型的文件是FreeMarker文件
1.4 将.ftl文件用开发软件打开,并将代码格式化(Intellij IDEA快捷键Ctrl+Alt+L)
- 格式化以前
- 格式化以后
纠正错误
需要遍历的内容添加<#list></#list>标签,如表格,图片等
图片变量替换(在文档中无法用变量替换图片,只能在代码替换)
- 纠正错误
用查找自己定义的变量的方式快速定位自己定义的变量,检查转换过程中是否语法出错。Intellij IDEA ,查找快捷键 Ctrl+F,替换快捷键 Ctrl+R
找到有错误的代码,将被分割开的变量中间的代码全部删除
- 需要遍历的内容添加<#list></#list>标签,如表格,图片等
- 图片变量替换(在文档中无法用变量替换图片,只能在代码替换)图片放入表格进行遍历更方便
-
替换BASE64编码的图片
-
- 中遍历list获取索引值
<#list currentPathList as path>
<#if path_index == 0>
</#if>
</#list>
- 中遍历list获取索引值
需要遍历的图片有三部分的代码需要逐一修改(https://www.cnblogs.com/w-yu-chen/p/11402098.html)
- 替换BASE64编码的图片,添加<#list></#list>标签,占位变量${img_projectManagerCerti}
- #list img_projectManagerCertiList as img_projectManagerCerti>
- <pkg:part pkg:name="/word/media/manager${img_projectManagerCerti_index+1}.png" pkg:contentType="image/png">
<pkg:binaryData>${img_projectManagerCerti}</pkg:binaryData>
</pkg:part>
</#list>
- <pkg:part pkg:name="/word/media/manager${img_projectManagerCerti_index+1}.png" pkg:contentType="image/png">
-
-
-
- 第二大部分:根据之前图片的名字快捷查找
- 第二大部分:根据之前图片的名字快捷查找
-
-
-
-
-
-
- 修改第二大部分
-
-
-
- 根据修改前的rid找到第三部分需要修改的内容
-
此处是存放图片的格式信息,找到存放图片的表格标签的开头和结尾
- 第三部分修改后
-
2 编写程序,封装为工具类调用
package com.zzdy.project.manager.system.gen.modular.program.util;
import com.alibaba.fastjson.JSONObject;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import sun.misc.BASE64Encoder;
import java.io.*;
/**
* WordUtil.java
* User: Monica Jia
* Email: kyrd@qq.com
* Date: 2020/1/14 Time: 16:49
* Description:
*/
@Component
public class WordUtil {
// freemarker 版本为2.3.28,Configuration对象不推荐直接new Configuration()
// 仔细看Configuration.class文件会发现,推荐的是 Configuration(Version incompatibleImprovements) 这个构造方法,具体这个构造方法里面传的就是Version版本类,而且版本号不能低于2.3.0
private Configuration configuration = new Configuration(new Version("2.3.0"));
@Value("${logging.path}")
private String basePath;
/**
* @method readWord
* @description 装载文件模板供文件生成
* @date: 2020/3/18 19:32
* @author: Monica J
* @param fileNme
* @return freemarker.template.Template
*/
private Template readWord(String fileNme,String tempFilePath){
//web工程还可以使用加载方法configuration.setServletContextForTemplateLoading(Object servletContext, String path);
configuration.setDefaultEncoding("UTF-8");
Template tempWord = null;
try {
// 加载文档模板FTL文件所存在的位置
configuration.setDirectoryForTemplateLoading(new File( tempFilePath));
// 获取模板信息
tempWord = configuration.getTemplate(fileNme+".ftl");
} catch (IOException e) {
e.printStackTrace();
}
return tempWord;
}
// 填充模板参数
private JSONObject getFillData() {
JSONObject dataMap = new JSONObject();
// 根据模板中的参数填充内容,可以不按顺序,参数名称要对上
dataMap.put("companyName", "国家电网");
dataMap.put("projectName", "10Kv");
// list的内容对应表格,表格行数与list的size对应,正常应用中list数据从数据库获取,本示例设置一个size=5的list
/* <List> wordList = new ArrayList >();
for (int i = 0; i < 5; i++) {
Map map = new HashMap();
map.put('para', i);
map.put('type', '参数' + i);
if(4 == i){
map.put('empty', '可空');
}else{
map.put('empty', '不可空');
}
wordList.add(map);
}
dataMap.put('wordList', wordList);*/
return dataMap;
}
/**
* @method createWord
* @description 根据用户传入的模板文件路径,生成word / rtf 文件
* @date: 2020/3/18 19:20
* @author: Monica J
* @param data, fileNme, fileSuffix, outPath
* @return com.alibaba.fastjson.JSONObject
*/
public JSONObject createWord(JSONObject data,String fileNme,String tempPath,String outPath) {
//文件后缀
// String fileSuffix="rtf";
String fileSuffix="doc";
String pdfFileUrl;
String docFileUrl;
JSONObject dataRet;
JSONObject result;
//文件输出路径
String fileUrl=outPath+fileNme+"/";
MyFileUtil.makeDir(Constants.BASE_PATH+fileUrl);
// 组装填充模板数据
// JSONObject dataMap= getFillData();
// 文档输出目录
File outFile = new File(Constants.BASE_PATH+fileUrl+fileNme+"."+fileSuffix);
Writer out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(outFile)));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
try {
// 读取模板内容并填充变量值,生成文件
readWord(fileNme,Constants.BASE_PATH+tempPath).process(data, out);
dataRet=new JSONObject();
//存储生成文件的路径
docFileUrl=fileUrl+fileNme+"."+fileSuffix;
//放到响应体中
dataRet.put("docFileUrl",docFileUrl);
pdfFileUrl=fileUrl+fileNme+"."+"pdf";
//将文件转换为PDF
this.doc2pdf2(docFileUrl,pdfFileUrl);
dataRet.put("pdfFileUrl",pdfFileUrl);
result=JUtil.getJson(true,dataRet,"文档生成成功!");
return result;
} catch (Exception e) {
JUtil.exceptionPrint("文档生成工具(WordUtil)",e);
result=JUtil.getJson(false,null,"文档生成失败,请联系研发人员处理!");
return result;
}
}
/**
* @method getImageStr
* @description 根据图片的本地绝对路径将图片转换为base64编码形式
* @date: 2020/3/18 19:31
* @author: Monica J
* @param imagePath
* @return java.lang.String
*/
public String getImageStr(String imagePath) {
InputStream in ;
byte[] data = null;
try {
in = new FileInputStream(imagePath);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
}
public void doc2pdf2(String inPath,String outPath){
ActiveXComponent app = null;
System.out.println("DOC-PDF开始转换...");
// 开始时间
long start = System.currentTimeMillis();
try {
// 打开word
app = new ActiveXComponent("Word.Application");
// 设置word不可见,很多博客下面这里都写了这一句话,其实是没有必要的,因为默认就是不可见的,如果设置可见就是会打开一个word文档,对于转化为pdf明显是没有必要的
//app.setProperty("Visible", false);
// 获得word中所有打开的文档
Dispatch documents = app.getProperty("Documents").toDispatch();
System.out.println("打开文件: " +Constants.BASE_PATH+ inPath);
// 打开文档
Dispatch document = Dispatch.call(documents, "Open", Constants.BASE_PATH+inPath, false, true).toDispatch();
// 如果文件存在的话,不会覆盖,会直接报错,所以我们需要判断文件是否存在
File target = new File(Constants.BASE_PATH+outPath);
if (target.exists()) {
target.delete();
}
System.out.println("另存为: " +Constants.BASE_PATH+ outPath);
// 另存为,将文档报错为pdf,其中word保存为pdf的格式宏的值是17
Dispatch.call(document, "SaveAs", Constants.BASE_PATH+outPath, 17);
// 关闭文档
Dispatch.call(document, "Close", false);
// 结束时间
long end = System.currentTimeMillis();
System.out.println("转换成功,用时:" + (end - start) + "ms");
}catch(Exception e) {
e.getMessage();
System.out.println("转换失败"+e.getMessage());
}finally {
// 关闭office
app.invoke("Quit", 0);
}
}
}
可调用的接口(根据实际情况优化)
@RequestMapping("/genDocB")
@ResponseBody
public JSONObject genDocB(){
JSONObject data=new JSONObject();
//数据填充,格式参考MyJsonUtil.getWordTemplateJson()
data.put("text_year","2019");
data.put("text_fileNo3_1","12");
data.put("text_fileNo3_2","13");
data.put("text_fileNo3_3","3");
data.put("text_projectName","息县配电网2019年第六批工程新开工项目");
data.put("text_proDeptName","息县10千伏配电网工程施工项目部");
data.put("text_dateCh","二○一九年十月十五日");
data.put("text_date","2019年10月15日");
data.put("text_proManName","陈伟");
data.put("text_safetyName","[安全员名字]");
data.put("text_techName","[技术员名字]");
data.put("text_QCName","[质检员名字]");
data.put("text_costName","罗冲");
data.put("text_infoName","万久梅");
List table_managerEmpList=new ArrayList();
JSONObject managerEmp1=new JSONObject();
managerEmp1.put("text_position","项目经理");
managerEmp1.put("text_name","陈伟");
managerEmp1.put("text_certiName","二级建造师");
managerEmp1.put("text_certiNo","豫2411414590887");
managerEmp1.put("text_certiUsefulLife","2015--2019");
table_managerEmpList.add(managerEmp1);
JSONObject managerEmp2=new JSONObject();
managerEmp2.put("text_position","项目总工");
managerEmp2.put("text_name","万久梅");
managerEmp2.put("text_certiName","工程师");
managerEmp2.put("text_certiNo","C17910970900086");
managerEmp2.put("text_certiUsefulLife","长期有效");
table_managerEmpList.add(managerEmp2);
data.put("table_managerEmpList",table_managerEmpList);
data.put("img_contractorStamp", wordUtil.getImageStr("E:\\epms_files\\file_template\\华祥公章.png"));
List img_projectManagerCertiList=new ArrayList();
img_projectManagerCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\项目经理_陈伟1.png"));
img_projectManagerCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\项目经理_陈伟2.png"));
img_projectManagerCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\项目经理_陈伟3.png"));
data.put("img_projectManagerCertiList",img_projectManagerCertiList);
List img_chiefEngineerList=new ArrayList();
img_chiefEngineerList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\项目总工_万久梅1.png"));
data.put("img_chiefEngineerList",img_chiefEngineerList);
List img_technicianCertiList=new ArrayList();
img_technicianCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\技术员_王刚1.png"));
img_technicianCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\技术员_王刚2.png"));
data.put("img_technicianCertiList",img_technicianCertiList);
List img_QCCertiList=new ArrayList();
img_QCCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\质检员_丁凯1.png"));
data.put("img_QCCertiList",img_QCCertiList);
List img_safetyCertiList=new ArrayList();
img_safetyCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\安全员_王军1.png"));
data.put("img_safetyCertiList",img_safetyCertiList);
List img_budgeterCertiList=new ArrayList();
img_budgeterCertiList.add(wordUtil.getImageStr("E:\\epms_files\\file_template\\项目预算员_罗冲1.png"));
data.put("img_budgeterCertiList",img_budgeterCertiList);
JSONObject dataMap= MyJsonUtil.getWordTemplateJson(data);
JSONObject result=wordUtil.createWord(dataMap,"施工项目部组织机构成立文件", Constants.TEMPLATE_FILE_PATH,Constants.PRO_FILE_BASE_PATH+"XM001/");
return result;
}
附录:java:WORD转换PDF
https://www.cnblogs.com/mh-study/p/10342246.html?tdsourcetag=s_pcqq_aiomsg
所用文件位置:
链接:1网盘 https://pan.baidu.com/s/143HqfThKw1nWO7KrH-fA-g
提取码:9epf
1蓝奏云地址:https://www.lanzous.com/ianh9nc
jdk环境:jdk8.0.1310.1164 (64位)
1.引入pom文件(或者推荐直接在项目中添加依赖,不然协同开发同事配置麻烦)
<!-- word转pdf(依赖windows本地的wps) -->
<dependency>
<groupId>com.jacob</groupId>
<artifactId>jacob</artifactId>
<version>1.18</version>
</dependency>
2.下载jar文件,手动添加至maven仓库(无法直接拉取)
cmd进入dos:
进行以下命令操作,路径进行对应修改
$ mvn install:install-file -Dfile=C:\Users\MingHao\Downloads\jacob-1.18\jacob-1.18\jacob.jar -DgroupId=com.jacob -DartifactId=jacob -Dversion=1.18 -Dpackaging=jar
解析: -Dfile:本地jar包位置(未引入前) -DgroupId:项目名 对应 com.jacob -DartifactId:文件名 对应 jacob -Dversion:版本号 对应 1.18
3.在jdk/bin目录下引入.dll文件(64位:jacob-1.18-x64.dll 32位:jacob-1.18-x86.dll)
资源文件云盘备份:
4.准备java代码
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import java.io.File;
public class Word2Pdf {
public static void main(String args[]) {
ActiveXComponent app = null;
String wordFile = "e:/测试word.docx";
String pdfFile = "e:/测试pdf.pdf";
System.out.println("开始转换...");
// 开始时间
long start = System.currentTimeMillis();
try {
// 打开word
app = new ActiveXComponent("Word.Application");
// 设置word不可见,很多博客下面这里都写了这一句话,其实是没有必要的,因为默认就是不可见的,如果设置可见就是会打开一个word文档,对于转化为pdf明显是没有必要的
//app.setProperty("Visible", false);
// 获得word中所有打开的文档
Dispatch documents = app.getProperty("Documents").toDispatch();
System.out.println("打开文件: " + wordFile);
// 打开文档
Dispatch document = Dispatch.call(documents, "Open", wordFile, false, true).toDispatch();
// 如果文件存在的话,不会覆盖,会直接报错,所以我们需要判断文件是否存在
File target = new File(pdfFile);
if (target.exists()) {
target.delete();
}
System.out.println("另存为: " + pdfFile);
// 另存为,将文档报错为pdf,其中word保存为pdf的格式宏的值是17
Dispatch.call(document, "SaveAs", pdfFile, 17);
// 关闭文档
Dispatch.call(document, "Close", false);
// 结束时间
long end = System.currentTimeMillis();
System.out.println("转换成功,用时:" + (end - start) + "ms");
}catch(Exception e) {
e.getMessage();
System.out.println("转换失败"+e.getMessage());
}finally {
// 关闭office
app.invoke("Quit", 0);
}
}
}
5.准备word文档 (格式:.docx)
路径:e:/测试word.docx
6.windows环境准备
windows电脑安装wps office,并且设置wps office为默认启动 。(最好不要使用microsoft word 微软的需要**,很麻烦,还不成功!)
注:jacb只能在windows系统使用,linux系统暂时无法解决