一:doc和docx文件
首先我们要了解doc和docx两种word文件的相同点和不同点,为什么ftl可以直接生成doc而生成docx就会报错。
1.doc和docx文件构成
- 我们可以把doc格式的word文件当成是一个单独的xml文件,而docx当成一个zip压缩包。将一个docx文件的后缀名改成.zip,然后用压缩工具打开,显示的即为docx的目录结构。
2.为什么ftl能直接转成doc而不能直接转成docx
- ftl生成word文件的过程是通过java选定一个*.ftl模板文件(该ftl模板是通过doc文件另存为xml格式,然后把后缀改为ftl得到),加入map参数集合,调用freemaker组件,生成一个doc格式的word文件。
- 综合前边概念,我们可以得出结论,freemaker只能通过模板生成一个doc格式的独立文件,而无法得到docx所需要的zip格式,所以ftl无法直接生成docx文件。
3.docx文件格式解析
-
选一个word文件作为输出模板,然后java代码输出需要提取word压缩包里两个文件:
1.word路径下的document.xml
2.word路径下_rels下的document.xml.rels
-
将docx文件的后缀改为.zip之后,把word路径下的document.xml作为模板,可通过格式化工具把xml文件里的数据格式化,点开后编写word的xml内容,下图以一个段落为例,其他类型请自行参考openxml语法或自己在word中对应
-
编写完xml模板之后,进入代码开发阶段,
1.同ftl生成doc一样,第一步要先封装数据到一个map中
2.指定输出docx的文件路径
3.指定两个从word压缩包中提取的文件路径
4.执行输出
代码示例如下:
//封装数据
Map<String,Object> dataMap=new HashMap<>();
dataMap.put("title","示例");//key名对应xml模板里的freemaker标签
//输出docx的模板
String originalTemplate = tempPath+ File.separator+"needsOriginalTemplate.docx";
//word中提取的两个文件,代码中可以根据实际情况更改名字
String documentXmlRels = "document.xml.rels";
String document = "document.xml";
//执行输出
Xml2DocxUtil.Xml2Docx(outFile,dataMap,document,documentXmlRels,originalTemplate);
下方附上 Xml2DocxUtil.java 以及FreeMarkUtils.java
Xml2DocxUtil.java
import cn.hutool.core.io.FileUtil;
import java.io.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Xml2DocxUtil {
/**
*
* @param outFile 输出文件
* @param dataMap 数据
* @param document 模板文件
* @param documentXmlRels 模板关联文件
* @param originalTemplate 模板原始docx文件
* @return retMap succ 是否成功 true & false ; msg返回异常信息
*/
public static Map<String,Object> Xml2Docx(File outFile, Map<String,Object> dataMap, String document, String documentXmlRels, String originalTemplate){
Map<String,Object> retMap = new HashMap<>();
retMap.put("succ",true);
ZipOutputStream zipout = null;
OutputStream outputStream = FileUtil.getOutputStream(outFile);
try {
//图片配置文件模板
ByteArrayInputStream documentXmlRelsInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, documentXmlRels);
//内容模板
ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, document);
ZipFile zipFile = new ZipFile(originalTemplate);
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
zipout = new ZipOutputStream(outputStream);
//开始覆盖文档------------------
int len = -1;
byte[] buffer = new byte[1024];
while (zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();
InputStream is = zipFile.getInputStream(next);
if (next.toString().indexOf("media") < 0) {
zipout.putNextEntry(new ZipEntry(next.getName()));
if (next.getName().indexOf("document.xml.rels") > 0) { //如果是document.xml.rels由我们输入
if (documentXmlRelsInput != null) {
while ((len = documentXmlRelsInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
documentXmlRelsInput.close();
}
} else if ("word/document.xml".equals(next.getName())) {//如果是word/document.xml由我们输入
if (documentInput != null) {
while ((len = documentInput.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
documentInput.close();
}
} else {
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
is.close();
}
}
}
} catch (Exception e) {
e.getStackTrace();
retMap.put("succ",false);
retMap.put("msg",e.getMessage());
}
finally {
if(zipout!=null){
try {
zipout.close();
} catch (IOException e) {
e.getStackTrace();
retMap.put("succ",false);
retMap.put("msg",e.getMessage());
}
}
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.getStackTrace();
retMap.put("succ",false);
retMap.put("msg",e.getMessage());
}
}
}
return retMap;
}
}
FreeMarkUtils.java
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class FreeMarkUtils {
private static Logger logger = LoggerFactory.getLogger(FreeMarkUtils.class);
public static Configuration getConfiguration(){
//创建配置实例
Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
//设置编码
configuration.setDefaultEncoding("utf-8");
configuration.setClassForTemplateLoading(FreeMarkUtils.class, "/templates/needsFileTemplates/");
return configuration;
}
/**
* 获取模板字符串输入流
* @param dataMap 参数
* @param templateName 模板名称
* @return
*/
public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName) {
ByteArrayInputStream in = null;
try {
//获取模板
Template template = getConfiguration().getTemplate(templateName);
StringWriter swriter = new StringWriter();
//生成文件
template.process(dataMap, swriter);
in = new ByteArrayInputStream(swriter.toString().getBytes(StandardCharsets.UTF_8));//这里一定要设置utf-8编码 否则导出的word中中文会是乱码
} catch (Exception e) {
e.printStackTrace();
logger.error("模板生成错误!");
}
return in;
}
}
欢迎大家评论区进行交流,之后会发表一篇如何通过java在docx的word文件中插入一个docx格式的word文件的文章。