Freemarker根據模板生成docx
需求:
使用Freemarker模板靜態化技術根據word模板生成docx的word文件,生成doc的文章請看這裡
注意點:
生成doc檔案和生成docx檔案的方式有所不同,原因為docx是一個特殊的zip檔案
模板配置步驟:
-
1、首先將自己的模板重新命名為xxx.zip的檔案,zip保留再解壓一份,解壓好以後的第一層目錄有3個資料夾和一個檔案我們只用到了word資料夾中的檔案,然後word中的檔案中我們只使用document.xml檔案、media資料夾和_rels資料夾
目錄解釋:
document.xml檔案就類似與我們做doc生成文件時候另存為的那個xml檔案,這個xml中就是我們需要替換${}資料的部分
_rels資料夾中的document.xml.rels檔案中的程式碼是用來關聯document.xml和media資料夾中的圖片的,如果word某處的圖 片不確定有多少個就需要用迴圈來操作這個檔案中的配置了
media資料夾中放置的就是word中需要的圖片了,他們的關聯關係由document.xml.rels來決定
-
2、將document.xml單獨拿出來然後在編輯器中開啟,(這裡注意這個docx的xml檔案不能格式化,一旦格式化以後雖然資料可以正常填充但是在生成word之後會出現 Word 在xxx.docx中發現無法讀取的內容。是否恢復此文件的內容?如果您信任此文件的來源,請單擊"是"。
-
3、如果你的文件中有圖片且圖片個數是固定的那就沒有必要配置document.xml.rels,否則就需要配置,這篇文章是固定的
編碼步驟:
- 1、這裡需要的是document.xml檔案和將自己的模板重新命名為xxx.zip的檔案
- 2、使用freemarker將資料替換到document.xml中,然後使用java的zip工具類將document.xml替換掉模板zip中的document.xml檔案,如果有圖片就替換到media資料夾下,再然後將zip的輸入流輸出成xxx.docx檔案即可。
編碼:
freemarker生成Docx工具類:
/**
* freemarker生成Docx工具類
*/
public class FreemarkerDocxUtil {
/**
* freemarker公用配置
*
* @return
* @throws IOException
*/
public static Configuration getConfiguration(String rootPath) throws IOException {
//建立配置例項
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
//設定編碼
configuration.setDefaultEncoding("utf-8");
configuration.setDirectoryForTemplateLoading(new File(rootPath));
return configuration;
}
/**
* 根據freemarker生成docx文件
*
* @param templateRootPath word模板的根路徑 例如:D:/xxx/xxx 最後一個名稱只到目錄
* @param xmlTemplateName word模板的名稱(docx的是xml格式)例如:test.xml
* @param docxFileName 生成docx檔名稱 例如:test.docx
* @param docxZipName 模板壓縮包名稱 例如:test.zip
* @param dataMap 生成檔案需要的資料
* @param itemNameList 需要替換的圖片資源地址 例如:word/media/xxx.xxx 去你的zip模板的word/media路徑去找
* @param itemInputStreamList 根據itemNameList中的圖片地址替換掉word/media中的圖片,這個圖片的順序要和itemNameList中的一致
* @throws Exception
*/
public static void freemarkerDocxTest(String templateRootPath, String xmlTemplateName, String docxFileName, String docxZipName, Map<String, Object> dataMap,
List<String> itemNameList, List<InputStream> itemInputStreamList) throws Exception {
//配置freemarker模板
Template template = getConfiguration(templateRootPath).getTemplate(xmlTemplateName);
//通過模板生成的xml臨時檔案 方法結束後刪除該臨時檔案
String outFilePath = templateRootPath + UUID.randomUUID().toString().replace("-", "") + ".xml";
//指定輸出word xml檔案的路徑
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFilePath), StandardCharsets.UTF_8));
template.process(dataMap, out);
out.close();
//包裝輸入流 模板zip包
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(templateRootPath + File.separator + docxZipName));
//包裝輸出流 輸出docx文件流
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(templateRootPath + File.separator + docxFileName));
//如果為空 建立需要替換zip中的檔案集合
if (itemNameList == null) {
itemNameList = new ArrayList<>();
} else {
itemNameList = itemNameList.stream().map(item -> item.replaceAll("\\\\", "/")).collect(Collectors.toList());
}
if (itemInputStreamList == null) {
itemInputStreamList = new ArrayList<>();
}
itemNameList.add(0, "word/document.xml");
// 建立需要替換的檔案資源 根據xml模板生成的xml檔案
itemInputStreamList.add(0, new FileInputStream(outFilePath));
replaceItemList(zipInputStream, zipOutputStream, itemNameList, itemInputStreamList);
new File(outFilePath).delete();
}
/**
* @param zipInputStream zip模板
* @param zipOutputStream 輸出docx文件流
* @param itemNameList 替換的檔案路徑
* @param itemInputStreamList
*/
public static void replaceItemList(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream, List<String> itemNameList, List<InputStream> itemInputStreamList) {
if (null == zipInputStream) {
return;
}
if (null == zipOutputStream) {
return;
}
ZipEntry entryIn;
try {
while ((entryIn = zipInputStream.getNextEntry()) != null) {
String entryName = entryIn.getName();
ZipEntry entryOut = new ZipEntry(entryName);
// 這個entryName路徑結構是什麼,解壓後的檔案結構就是什麼,ZipEntry的大概意思就是隻設定這個檔案在zip中的路徑
// 並不涉及到位元組的寫入 putNextEntry 設定每一個ZipEntry物件
zipOutputStream.putNextEntry(entryOut);
byte[] buf = new byte[8 * 1024];
int len;
// 這裡的判斷意思是如果有需要替換的檔案就使用替換檔案寫入,沒有就寫入原來的到新的docx檔案中
if (itemNameList.contains(entryName)) {
// 使用替換流
while ((len = (itemInputStreamList.get(itemNameList.indexOf(entryName)).read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
} else {
// 輸出普通Zip流
while ((len = (zipInputStream.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}
// 關閉此 entry
zipOutputStream.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
for (InputStream itemInputStream : itemInputStreamList) {
close(itemInputStream);
}
close(zipInputStream);
close(zipOutputStream);
}
}
private static void close(InputStream inputStream) {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void close(OutputStream outputStream) {
if (null != outputStream) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
獲取網路圖片的輸入流
/**
* 獲取網路圖片的輸入流
* @param imgURL
* @return
*/
public static InputStream getImageInputStream(String imgURL) {
try {
URL url = new URL(imgURL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
return conn.getInputStream();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
測試:
public static void main(String[] args) throws Exception {
Map<String, Object> map = new HashMap<>();
// 城市資訊
List<Map<String, String>> citys = new ArrayList<>();
Map<String, String> map1 = new HashMap<>();
map1.put("cityName", "測試城市名稱1");
map1.put("distanceEpicenter", "測試震中1");
Map<String, String> map2 = new HashMap<>();
map2.put("cityName", "測試城市名稱2");
map2.put("distanceEpicenter", "測試震中2");
citys.add(map1);
citys.add(map2);
map.put("citys", citys);
map.put("level", "20");
map.put("time", "3002年12月01日21時05分");
map.put("latitude", "35.58");
map.put("longitude", "112.05");
map.put("depth", "10");
map.put("intensity", "120");
map.put("scope", "15");
String templateRootPath = "word模板的根路徑";
String xmlTemplateName = "document.xml", docxFileName = "314159265875658.docx", docxZipName = "zipTemplate.zip";
// 這裡需要注意的就是imageName中的個數和順序要和imageNew對應
List<String> imageName =new ArrayList<>();
imageName.add("word\\media\\image2.jpeg");
imageName.add("word\\media\\image3.jpeg");
imageName.add("word\\media\\image4.jpeg");
List<InputStream> imageNew =new ArrayList<>();
imageNew.add(new FileInputStream("圖片1"));
imageNew.add(new FileInputStream("圖片2"));
imageNew.add(getImageInputStream("http://jy.sccnn.com/zb_users/upload/2015/2/2015021757532565.jpg"));
FreemarkerDocxUtil.freemarkerDocxTest(templateRootPath, xmlTemplateName, docxFileName, docxZipName, map, imageName, imageNew);
}
結語:
到這裡關於Freemarker模板靜態化的文章就寫完了,我個人覺得需要注意的就是在替換變數的xml檔案時候不要將xml格式化。