java使用freemarker生成word文档(.docx格式)

        网上大多教程都是将word另存为.xml格式的文件,然后再重命名为ftl的模板文件,经过freemarker填充后输出成.docx格式的文件。但实际上输出的文件本质还是配置文件,只不过能够被wps或word所识别,下面讲的是直接转成真正的.docx格式的方法。

1、重命名word文档

将需要作为模板的文档重命名,如xxxx.doc重命名为xxxx.zip

2、解压

将已经重命名的文档进行解压

重命名后的文档解压后的目录基本如下:

3、配置模板

打开word文件夹,里面有个document.xml文件,该文件就是word文档内容。

将document.xml文件复制出来,重命名为.ftl格式,打开文件(我这里用的是vscode打开,用idea打开会很卡,内容太多了)。打开的内容是被压缩过的xml文件内容。没有排版,所有可以将文件内容格式化(格式化后内容或样式可能会有些变化,但可以调整,大多是原本有空格,但格式化后就没有了,后期可以慢慢调整,可以直接百度xml格式化,直接在线格式化)。

格式化后的样子

在需要的地方进行模板数据填充(使用freemarker,不清楚的百度)

4、代码编写

maven依赖

		<dependency>
			<groupId>org.freemarker</groupId>
			<artifactId>freemarker</artifactId>
			<version>2.3.28</version>
		</dependency>

工具类

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;

import java.io.*;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class WordUtils {


    /**
     * 使用FreeMarker自动生成Word文档
     * @param dataMap word文档数据
     * @param fileName 生成文档的路径包括文件名称
     * @param ftlPath 模板路径
     * @param ftlName 模板文件名称
     * @throws Exception
     */
    public static void generateWord(Map<String, Object> dataMap, String fileName, String ftlPath, String ftlName) throws Exception {
        // 设置FreeMarker的版本和编码格式
        Configuration configuration = new Configuration(new Version("2.3.28"));
        configuration.setDefaultEncoding("UTF-8");

        // 设置FreeMarker生成Word文档所需要的模板的路径
        configuration.setDirectoryForTemplateLoading(new File(ftlPath));
        // 设置FreeMarker生成Word文档所需要的模板
        Template t = configuration.getTemplate(ftlName, "UTF-8");
        // 创建一个Word文档的输出流
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(fileName)), "UTF-8"));
        //FreeMarker使用Word模板和数据生成Word文档
        t.process(dataMap, out);
        out.flush();
        out.close();
    }



    /**
     * 压缩文件或文件夹(包括所有子目录文件)
     * @param sourceFile 源文件
     * @param format 格式(zip或rar)
     * @param outPath 输入路径
     * @throws IOException
     */
    public static void zipFileTree(File sourceFile, String format,String outPath) throws IOException {
        ZipOutputStream zipOutputStream = null;
        try {
            String zipFileName;
            if (sourceFile.isDirectory()) { // 目录
                zipFileName = sourceFile.getParent() + File.separator + sourceFile.getName() + "."
                        + format;
            } else { // 单个文件
                zipFileName = sourceFile.getParent()
                        + sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf("."))
                        + "." + format;
            }
            // 压缩输出流
            zipOutputStream = new ZipOutputStream(new FileOutputStream(outPath));
            zip(sourceFile, zipOutputStream, "");
        } finally {
            if (null != zipOutputStream) {
                // 关闭流
                try {
                    zipOutputStream.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }



    /**
     * 递归压缩文件
     *
     * @param file 当前文件
     * @param zipOutputStream 压缩输出流
     * @param relativePath 相对路径
     * @throws IOException IO异常
     */
    public static void zip(File file, ZipOutputStream zipOutputStream, String relativePath)
            throws IOException {

        FileInputStream fileInputStream = null;
        try {
            if (file.isDirectory()) { // 当前为文件夹
                // 当前文件夹下的所有文件
                File[] list = file.listFiles();
                if (null != list) {
                    // 计算当前的相对路径
                    relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
                    // 递归压缩每个文件
                    for (File f : list) {
                        zip(f, zipOutputStream, relativePath);
                    }
                }
            } else { // 压缩文件
                // 计算文件的相对路径
                relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
                // 写入单个文件
                String tmp = relativePath.substring(relativePath.indexOf("/")+1,relativePath.length());
                zipOutputStream.putNextEntry(new ZipEntry(tmp));
                fileInputStream = new FileInputStream(file);
                int readLen;
                byte[] buffer = new byte[1024];
                while ((readLen = fileInputStream.read(buffer)) != -1) {
                    zipOutputStream.write(buffer, 0, readLen);
                }
                zipOutputStream.closeEntry();
            }
        } finally {
            // 关闭流
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }




}

测试main

            String fileName = "报告解压后,document.xml用模板生成的document.xml替换掉里面的document.xml";//转换后的路径地址 如 docx重命名zip解压后的目录是 c:/tmp/xxx/word/document.xml
            String ftlPath = "模板地址";//模板地址 转成模板的地址 如document.ftl地址是 c:/tmp/document.ftl 则填写 c:/tmp
            String ftlName = "document.ftl";//模板名称 document.ftl
            //填充数据
            WordUtils.generateWord(objMap, fileName,ftlPath,ftlName);


            String needZipPath = "需要压缩的路径";// 如 docx重命名zip解压后的目录是 c:/tmp/xxx/word/document.xml 则填写 c:/tmp/xxx/
            String zipPath = "文件生成路径";//如 c:/tmp/xxx/生成的.docx
            //压缩
            WordUtils.zipFileTree(new File(needZipPath),"zip",zipPath);

4、思路

.docx文件重命名.zip,解压,将里面内容文件拷贝出来,再通过freemarker填充数据后覆盖原来的内容文件,再将整个文件压缩。

可以做到替换图片(覆盖media文件里的图片,如若要添加图片,就需要修改_rels里的配置文件并向media中添加图片,还要修改document里的内容)、页眉等,麻烦点在于要理清楚xml里面的格式对应的word的内容。

优点:不死板,如标题列表可以使用for循环进行生成,包括表格,也可以选择生成

缺点:需要理清xml格式的内容,对应的文档内容

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值