使用freemarker引擎动态生成word文件
- 0 前言
- 1 工具与环境配置
- 1.1 sublime
- 1.2 依赖包
- 1.3 word模版ftl文件生成步骤
- 2 Word Xml基本语法介绍
- 3 动态数据填充
- 单元测试
- ftl模板
- 4 动态表格生成
- 单元测试
- ftl模板
- 5 动态目录与动态标题结构
- 单元测试
- ftl模板
- 6 插入图片
- 单元测试
- ftl模板
0 前言
本文使用freemarker模板引擎动态建立word文件,可以实现数据填充,文档结构和目录的变化,表格生成等功能,使用WPS2019,jdk版本1.8.0_141,maven版本3.1.1,freemarker版本2.3.23,idea版本Community Edition 2018.1.1。
1 工具与环境配置
1.1 sublime
安装sublime,选择Preferences–>Package Control–>Install Package–>安装HTML/CSS/JS Prettify。
1.2 依赖包
以maven工程为例,新建项目,在pom.xml文件中引入freemarker、junit、lombok依赖
<dependency>
<groupId>freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
</dependency>
1.3 word模版ftl文件生成步骤
freemarker使用专门的模板文件ftl(FreeMarker Template Language)格式,为了快速建立word模板文件,可以采用反编译的方式。
- 新建一个doc格式的word文档;
- 另存为xml格式文件,注意并非word xml文件;
- 修改后缀名为ftl;
- 使用sublime打开和编辑,在空白处右键,HTML/CSS/JS Prettify–>Prettify Code可以格式化代码。
2 Word Xml基本语法介绍
ftl文件采用word xml格式的语法。以这种格式存储的文件可以通过其他软件的编译创建word文档,不需要用专门的文稿软件打开。一个简单ftl文件如下所示
<?xml version="1.0"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">
<w:body>
<w:p>
<w:r>
<w:t>tyler</w:t>
</w:r>
</w:p>
</w:body>
</w:wordDocument>
文档内容包含在<w:body>
标签之间。<w:p>
、<w:r>
、<w:t>
是三个基本标签。<w:p>
标签表示一个段落,段落属性用<w:pPr>
来指定,一个段落中可以包含很多“执行段”<w:r>
。在执行段中,包含文本、制表符、回车符等特殊符号。<w:rPr>
可以指定该执行段的属性。<w:t>
之间包含真实的文本。
参考资料officeopenxml
3 动态数据填充
很多时候,人们会制定一些模板,如会议通知等,仅在会议时间、地点处留出空白,这样下次使用的时候只需要填写时间、地点就可以快速生成一个会议通知。因此我们首先实现文档内数据内容的动态填充。新建maven工程,工程目录如下
建立word工具类WordUtil,按照路径读取已有的ftl模板
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WordUtil {
private Configuration configure;
public WordUtil() {
configure = new Configuration(Configuration.VERSION_2_3_23);
configure.setDefaultEncoding("utf-8");
}
public boolean createWord(Map<String, Object> dataMap, String inputFilePath, String outFilePath) {
boolean flag = false;
try {
TemplateLoader templateLoader;
templateLoader=new FileTemplateLoader(new File("./"));
configure.setTemplateLoader(templateLoader);
Template template=configure.getTemplate(inputFilePath,"UTF-8");
Writer out = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(
new File(outFilePath)
), "utf-8"
), 10240
);
template.process(dataMap, out);
out.close();
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
}
建立Person模型
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class Person {
String name;
String age;
String gender;
public Person(String name, String age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
单元测试
建立单元测试,调用WordUtil的createWord方法。其入参为存储待填充的数据的map、ftl的存储路径、生成文档的路径。文件名以时间戳命名,避免每次生成文件覆盖。
import com.tyler.freemarker.model.Person;
import com.tyler.freemarker.WordUtil;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.*;
public class WordGenerateTest {
@Test
public void variableTest() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
Date date = new Date(System.currentTimeMillis());
String outputFileName = formatter.format(date);
String inputFilePath = "./src/main/resources/template.ftl";
String outFilePath = "./src/main/resources/"+outputFileName+".doc";
WordUtil wu = new WordUtil();
Map<String, Object> map = new HashMap<>();
Person person = new Person("Tyler", "18", "male");
map.put("person", person);
wu.createWord(map, inputFilePath, outFilePath);
}
}
ftl模板
ftl模板中留出了Person的属性的位置,可以通过读取map的数据填充进去。在freemarker中,${variable}
可以获取变量variable
的值,person
是传进来Person类实例的一个key值,通过key值访问到这个实例。和Java一样,类的属性可以通过.
操作符获取。这样就建立了Java类与ftl中数据的映射,实现了对同一个模板的复用,给定不同的数据可以生成很多相同格式的word文档。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
w:macrosPresent="no" w:embeddedObjPresent="no"
w:ocxPresent="no" xml:space="preserve">
<w:body>
<w:p>
<w:r>
<w:t>姓名:${person.name}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>年龄:${person.age}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>性别:${person.gender}</w:t>
</w:r>
</w:p>
</w:body>
</w:wordDocument>
生成结果展示
4 动态表格生成
表格是数据展示的重要方式。在可以填充数据的基础上,可以将这些数据以表格的形式展示。比如用一个表格统计公司员工的基本信息,姓名、年龄、性别。只需要在map中存储一个List<Person>
,再设计一个与之对应的表格并留出空白就好。
但是另一个公司也想用这套模板,但是两个公司的员工数量不同,因此需要修改表格的行数。接下来实现表格的动态生成,行数与List
的尺寸对应。这就需要用到循环语句。
freemarker可以设定计数循环,如
<#list 1..5 as i>
<w:t>${i}</w:t>
</#list>
也可以直接循环List
的内容
<#list personList as pl>
<w:t>${pl.name}</w:t>
</#list>
此处选择第二种,直接循环personList
的元素。建立单元测试,生成一个List<Person>
作为数据传输给ftl模板。
单元测试
import com.tyler.freemarker.model.Person;
import com.tyler.freemarker.WordUtil;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.*;
public class WordGenerateTest {
@Test
public void tableTest() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
Date date = new Date(System.currentTimeMillis());
String outputFileName = formatter.format(date);
String inputFilePath = "./src/main/resources/template.ftl";
String outFilePath = "./src/main/resources/"+outputFileName+".doc";
WordUtil wu = new WordUtil();
Map<String, Object> map = new HashMap<>();
List<Person> personList = new ArrayList<>();
personList.add(new Person("Tyler", "18", "male"));
personList.add(new Person("Tom", "20", "male"));
personList.add(new Person("Marry", "19", "female"));
map.put("personList", personList);
wu.createWord(map, inputFilePath, outFilePath);
}
}
ftl模板
freemarker中,表格内容在<w:tbl> </w:tbl>
中间,此处的ftl模板包含了很多表格的属性设置,在应用过程中,可以按照想要的表格样式先建立一个带有空表格的word文档,再反编译成ftl模板,避免手动设置属性。这里只实现了动态的行数,列数同理。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
w:macrosPresent="no" w:embeddedObjPresent="no"
w:ocxPresent="no" xml:space="preserve">
<w:body>
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="a63" />
<w:tblW w:w="9243" w:type="dxa" />
<w:tblInd w:w="0" w:type="dxa" />
<w:tblBorders>
<w:top w:val="single"
w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:left w:val="single"
w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:bottom w:val="single"
w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:right w:val="single"
w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:insideH w:val="single"
w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:insideV w:val="single"
w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto" />
</w:tblBorders>
<w:tblLayout w:type="Fixed" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="3499" />
<w:gridCol w:w="5744" />
</w:tblGrid>
<!-->以下开始循环内容,list的每个元素为一行,总共三行<-->
<#list personList as pl>
<w:tr>
<w:tblPrEx>
<w:tblBorders>
<w:top w:val="single" w:sz="4"
wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:left w:val="single" w:sz="4"
wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:bottom w:val="single" w:sz="4"
wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:right w:val="single" w:sz="4"
wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:insideH w:val="single" w:sz="4"
wx:bdrwidth="10" w:space="0" w:color="auto" />
<w:insideV w:val="single" w:sz="4"
wx:bdrwidth="10" w:space="0" w:color="auto" />
</w:tblBorders>
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
</w:tblPrEx>
<w:trPr>
<w:trHeight w:val="361" w:h-rule="atLeast" />
</w:trPr>
<w:tc>
<w:tcPr>
<w:tcW w:w="3499" w:type="dxa" />
<w:shd w:val="clear" w:color="auto"
w:fill="auto" />
<w:noWrap w:val="0" />
<w:vAlign w:val="center" />
</w:tcPr>
<w:p>
<w:pPr>
<w:spacing w:before-lines="50"
w:after-lines="50" w:line="240" w:line-rule="auto" />
<w:jc w:val="left" />
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体"
w:fareast="宋体" w:hint="default" />
<w:color w:val="auto" />
<w:sz-cs w:val="21" />
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体"
w:h-ansi="宋体" w:fareast="宋体"
w:cs="宋体" w:hint="fareast" />
<w:color w:val="auto" />
<w:kern w:val="0" />
<w:sz-cs w:val="21" />
</w:rPr>
<w:t>${pl.name}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="5744" w:type="dxa" />
<w:shd w:val="clear" w:color="auto"
w:fill="auto" />
<w:noWrap w:val="0" />
<w:vAlign w:val="center" />
</w:tcPr>
<w:p>
<w:pPr>
<w:spacing w:before-lines="50"
w:after-lines="50" w:line="240"
w:line-rule="auto" />
<w:jc w:val="left" />
<w:rPr>
<w:rFonts w:ascii="宋体"
w:h-ansi="宋体" w:fareast="宋体"
w:hint="default" />
<w:sz-cs w:val="21" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体"
w:h-ansi="宋体" w:fareast="宋体"
w:hint="fareast" />
<w:sz-cs w:val="21" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
<w:t>${pl.age}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="5744" w:type="dxa" />
<w:shd w:val="clear" w:color="auto"
w:fill="auto" />
<w:noWrap w:val="0" />
<w:vAlign w:val="center" />
</w:tcPr>
<w:p>
<w:pPr>
<w:spacing w:before-lines="50"
w:after-lines="50" w:line="240"
w:line-rule="auto" />
<w:jc w:val="left" />
<w:rPr>
<w:rFonts w:ascii="宋体"
w:h-ansi="宋体" w:fareast="宋体"
w:hint="default" />
<w:sz-cs w:val="21" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体"
w:fareast="宋体" w:hint="fareast" />
<w:sz-cs w:val="21" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
<w:t>${pl.gender}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
</w:tbl>
</w:body>
</w:wordDocument>
生成结果展示
5 动态目录与动态标题结构
使用循环结构,我们还可以建立动态的文档框架。一个文档的框架由一级标题、二级标题等确定。如果各级标题的数量和内容也由List
控制,那么文档就可以实现动态结构。同理也可以实现动态的目录。
首先建立一个Company类做为一级标题,每个Company包含一个长度不等的List<Person>
。
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class Company {
String name;
List<Person> personList;
public Company(String name, List<Person> personList) {
this.name = name;
this.personList = personList;
}
}
单元测试
建立单元测试
import com.tyler.freemarker.FigureUtil;
import com.tyler.freemarker.WordUtil;
import com.tyler.freemarker.model.Company;
import com.tyler.freemarker.model.Person;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.*;
public class WordGenerateTest {
@Test
public void titleTest() {
Random r = new Random();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
Date date = new Date(System.currentTimeMillis());
String outputFileName = formatter.format(date);
String inputFilePath = "./src/main/resources/template.ftl";
String outFilePath = "./src/main/resources/"+outputFileName+".doc";
WordUtil wu = new WordUtil();
Map<String, Object> map = new HashMap<>();
List<Company> companyList = new ArrayList<>();
for (int i = 0; i < r.nextInt(5)+2; i++) {
List<Person> personList = new ArrayList<>();
for (int j = 0; j <r.nextInt(4)+2 ; j++) {
Person person = new Person("name"+r.nextInt(15), ""+r.nextInt(50), "male");
personList.add(person);
}
Company company = new Company("company"+r.nextInt(13), personList);
companyList.add(company);
}
map.put("companyList", companyList);
wu.createWord(map, inputFilePath, outFilePath);
}
}
ftl模板
ftl模版文件,此处使用了嵌套的双层循环,外层循环一级标题,内层循环二级标题。目录与标题之间需要建立超链接,点击目录可以跳转正文标题。目录处<w:instrText> HYPERLINK \l ${cl.name} </w:instrText>
是建立超链接,<w:instrText> PAGEREF ${cl.name} </w:instrText>
是将超链接与页码关联。这两个地方的索引 ${cl.name}
必须和标题的<aml:annotation aml:id="0" w:type="Word.Bookmark.Start" w:name="${cl.name}" />
一致,才能建立超链接。因此为了避免索引冲突,这里选取名字作为索引值,也可以自行设定其他不会冲突的字符串,保证唯一性就可以。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
w:macrosPresent="no" w:embeddedObjPresent="no" w:ocxPresent="no"
xml:space="preserve">
<w:body>
<wx:sect>
<w:p>
<w:pPr>
<w:spacing w:before="0" w:before-lines="0" w:after="0" w:after-lines="0" w:line="240" w:line-rule="auto" />
<w:ind w:left="0" w:left-chars="0" w:right="0" w:right-chars="0" w:first-line="0" w:first-line-chars="0" />
<w:jc w:val="center" />
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:fareast="宋体" w:hint="default" />
<w:sz w:val="21" />
</w:rPr>
<w:t>目录</w:t>
</w:r>
</w:p>
<#list companyList as cl>
<w:p>
<w:pPr>
<w:pStyle w:val="a4" />
<w:tabs>
<w:tab w:val="right" w:leader="dot" w:pos="8306" />
</w:tabs>
</w:pPr>
<w:r>
<w:fldChar w:fldCharType="begin" />
</w:r>
<w:r>
<w:instrText>TOC \o "1-3" \h \u </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate" />
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin" />
</w:r>
<w:r>
<w:instrText> HYPERLINK \l ${cl.name} </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate" />
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast" />
<w:lang w:fareast="ZH-CN" />
</w:rPr>
<w:t>${cl.name}</w:t>
</w:r>
<w:r>
<w:tab />
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin" />
</w:r>
<w:r>
<w:instrText> PAGEREF ${cl.name} </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate" />
</w:r>
<w:r>
<w:t>1</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end" />
</w:r>
<w:r>
<w:fldChar w:fldCharType="end" />
</w:r>
</w:p>
<#list cl.getPersonList() as pl>
<w:p>
<w:pPr>
<w:pStyle w:val="a5" />
<w:tabs>
<w:tab w:val="right" w:leader="dot" w:pos="8306" />
</w:tabs>
</w:pPr>
<w:r>
<w:fldChar w:fldCharType="begin" />
</w:r>
<w:r>
<w:instrText> HYPERLINK \l ${pl.name} </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate" />
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
<w:t>${pl.name}</w:t>
</w:r>
<w:r>
<w:tab />
</w:r>
<w:r>
<w:fldChar w:fldCharType="begin" />
</w:r>
<w:r>
<w:instrText> PAGEREF ${pl.name} </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate" />
</w:r>
<w:r>
<w:t>1</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end" />
</w:r>
<w:r>
<w:fldChar w:fldCharType="end" />
</w:r>
</w:p>
</#list>
</#list>
<w:p>
<w:r>
<w:fldChar w:fldCharType="end" />
</w:r>
</w:p>
<#list companyList as cl>
<w:p>
<w:pPr>
<w:pStyle w:val="2" />
<w:rPr>
<w:rFonts w:hint="fareast" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
</w:pPr>
<aml:annotation aml:id="0" w:type="Word.Bookmark.Start" w:name="${cl.name}" />
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast" />
<w:lang w:fareast="ZH-CN" />
<w:sz w:val="50" />
</w:rPr>
<w:t>${cl.name}</w:t>
</w:r>
<aml:annotation aml:id="0" w:type="Word.Bookmark.End" />
</w:p>
<#list cl.getPersonList() as pl>
<w:p>
<w:pPr>
<w:pStyle w:val="3" />
<w:rPr>
<w:rFonts w:hint="fareast" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
</w:pPr>
<aml:annotation aml:id="1" w:type="Word.Bookmark.Start" w:name="${pl.name}" />
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
<w:sz w:val="30" />
</w:rPr>
<w:t>${pl.name}</w:t>
</w:r>
<aml:annotation aml:id="1" w:type="Word.Bookmark.End" />
</w:p>
</#list>
</#list>
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838" />
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0" />
<w:cols w:space="720" />
<w:docGrid w:type="lines" w:line-pitch="312" />
</w:sectPr>
</wx:sect>
</w:body>
</w:wordDocument>
生成结果展示
注:上述生成的文件中,暂时无法做到更新页码,只能手动打开word文件,右键–>更新域–>更新整个目录。
目录更新的实现思路可以参考:代码操作Word时,目录自动更新的两种方法
6 插入图片
插入word的图片要转码成base64码,才能写入ftl文件中,因此先建立转码处理的接口。生成的base64码以字符串形式存储。
import java.util.Base64;
import java.util.Base64.Encoder;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FigureUtil {
public static String getImageStr(String imgFilePath) {
InputStream in = null;
byte[] data = null;
try {
in = new FileInputStream(imgFilePath);
int avai = in.available();
data = new byte[avai];
in.read(data);
in.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
if(in !=null){
try{
in.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
Encoder encoder = Base64.getEncoder();;
return encoder.encodeToString(data);
}
}
单元测试
建立单元测试
import com.tyler.freemarker.FigureUtil;
import com.tyler.freemarker.WordUtil;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.*;
public class WordGenerateTest {
@Test
public void figureTest() {
String imgFilePath = "./src/main/resources/1.jpg";
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
Date date = new Date(System.currentTimeMillis());
String outputFileName = formatter.format(date);
String inputFilePath = "./src/main/resources/template.ftl";
String outFilePath = "./src/main/resources/"+outputFileName+".doc";
WordUtil wu = new WordUtil();
Map<String, Object> map = new HashMap<>();
String img = FigureUtil.getImageStr(imgFilePath);
map.put("img", img);
wu.createWord(map, inputFilePath, outFilePath);
}
}
ftl模板
ftl模版文件,base64码非常长,这里使用img来代替base64码字符串
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
w:macrosPresent="no" w:embeddedObjPresent="no"
w:ocxPresent="no" xml:space="preserve">
<w:body>
<wx:sect>
<w:p>
<w:pPr>
<w:rPr>
<w:rFonts w:hint="default" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="default" />
<w:lang w:val="EN-US" w:fareast="ZH-CN" />
</w:rPr>
<w:pict>
<w:binData w:name="wordml://1.png">${img}</w:binData>
<v:shape id="_x0000_s1026" o:spt="75" alt="1" type="#_x0000_t75"
style="height:140.9pt;width:211.35pt;" filled="f"
o:preferrelative="t" stroked="f" coordsize="21600,21600">
<v:path />
<v:fill on="f" focussize="0,0" />
<v:stroke on="f" />
<v:imagedata src="wordml://1.png" o:title="1" />
<o:lock v:ext="edit" aspectratio="t" />
<w10:wrap type="none" />
<w10:anchorlock />
</v:shape>
</w:pict>
</w:r>
</w:p>
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838" />
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0" />
<w:cols w:space="720" />
<w:docGrid w:type="lines" w:line-pitch="312" />
</w:sectPr>
</wx:sect>
</w:body>
</w:wordDocument>
生成结果展示
建议预先生成模版的时候随便插入一个图片占位,并设置好格式和位置。