前言
前因
公司項目電子化項目部的新功能添加了根據施工項目文檔模板,用戶輸入可變變量,生成用戶所需文件的功能
方案記錄
- 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系統暫時無法解決
歡迎加入:前端開發羣