java使用freemarker导出word文档

1 篇文章 0 订阅
1 篇文章 0 订阅

使用freemarker导出word文档

最近的项目中,在导出word文档中遇见了一些问题,由于之前的导出word是导出的doc格式的word文档,但是由于使用的freemarker技术,导出的word文档其实就是xml文件,只是将文件后缀名改为.doc其实实际上还是xml,所以在使用某些工具打开时,遇到无法解析,或者打开后直接显示xml的问题。
所以这里解决这个问题的办法,是导出docx格式的文档,因为docx格式实际上就是zip格式,使用word生成的docx文档,可以直接使用解压缩工具打开,可以直接编辑里面的内容。
这里也展示一下怎么导出doc格式的word文档。

导出doc文件

先新建一个doc文件,制作好模板,这里假设需要导出的是一个表格。制作好表格的格式,然后另存为时保存为xml文件。
在这里插入图片描述
另存为之后,编辑xml文件,由于导出表格,这里只有一行数据,需要修改为freemarker的<#list>标签来生成列表。将xml文件格式化之后,找到对应的李明这一行数据:

<w:tr>
    ......
    <w:tc>
        ......
        <w:p>
            ......
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="fareast"/>
                    <w:vertAlign w:val="baseline"/>
                    <w:lang w:fareast="ZH-CN"/>
                </w:rPr>
                <w:t>李明</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        ......
        <w:p>
           	......
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="fareast"/>
                    <w:vertAlign w:val="baseline"/>
                    <w:lang w:fareast="ZH-CN"/>
                </w:rPr>
                <w:t></w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        ......
        <w:p>
            ......
            <w:r>
                <w:rPr>
                    <w:rFonts w:hint="fareast"/>
                    <w:vertAlign w:val="baseline"/>
                    <w:lang w:val="EN-US" w:fareast="ZH-CN"/>
                </w:rPr>
                <w:t>19</w:t>
            </w:r>
        </w:p>
    </w:tc>
</w:tr>

这里省略了部分代码,使用freemarker语法修改这一段xml文本:

<#list persons as person>    
    <w:tr>
        ......
        <w:tc>
            ......
            <w:p>
                ......
                <w:r>
                    <w:rPr>
                        <w:rFonts w:hint="fareast"/>
                        <w:vertAlign w:val="baseline"/>
                        <w:lang w:fareast="ZH-CN"/>
                    </w:rPr>
                    <w:t>${person.name}</w:t>
                </w:r>
            </w:p>
        </w:tc>
        <w:tc>
            ......
            <w:p>
               	......
                <w:r>
                    <w:rPr>
                        <w:rFonts w:hint="fareast"/>
                        <w:vertAlign w:val="baseline"/>
                        <w:lang w:fareast="ZH-CN"/>
                    </w:rPr>
                    <w:t>${person.gender}</w:t>
                </w:r>
            </w:p>
        </w:tc>
        <w:tc>
            ......
            <w:p>
                ......
                <w:r>
                    <w:rPr>
                        <w:rFonts w:hint="fareast"/>
                        <w:vertAlign w:val="baseline"/>
                        <w:lang w:val="EN-US" w:fareast="ZH-CN"/>
                    </w:rPr>
                    <w:t>${person.age}</w:t>
                </w:r>
            </w:p>
        </w:tc>
    </w:tr>
</#list>

修改完成之后,将文件后缀改为ftl,将模板文件复制到项目中。之后就可以使用freemarker生成出xml文件,生成之后可以直接讲文件名后缀名改为doc。
这里附上代码(这里是一个maven项目,模板文件在resources目录下):
Person.class用来生成word文档中的列表对象

public class Person {
    private String name;
    private String gender;
    private Integer age;

    public Person() {
    }

    public Person(String name, String gender, Integer age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    //......Getter、Setter方法省略
}    

数据生成的类(在生成docx的方法中依然使用该类)

public final class DataGenerate {
    private DataGenerate(){}

    public static List<Person> getData() {
        List<Person> persons = new ArrayList<>();
        persons.add(new Person("小李", "男", 31));
        persons.add(new Person("小王", "男", 18));
        persons.add(new Person("小红", "女", 21));
        return persons;
    }
}

生成word文档,这里直接执行main方法即可

public class XmlToDoc {
    public static void main(String[] args) throws IOException, TemplateException {
        Configuration configuration = new Configuration(Configuration.getVersion());
        configuration.setDefaultEncoding("UTF-8");

        URL resource = XmlToDoc.class.getClassLoader().getResource("");
        if(resource != null){
            File resourceDir = new File(resource.getFile());
            configuration.setDirectoryForTemplateLoading(resourceDir);
            Template template = configuration.getTemplate("docTemplate.ftl");

            File docFile = new File(resource.getFile() + "out/导出.doc");
            File docDir = docFile.getParentFile();
            if(!docDir.exists()) {
                docDir.mkdirs();
            }
            FileOutputStream docFileOut = new FileOutputStream(docFile);
            BufferedWriter docWriter = new BufferedWriter(new OutputStreamWriter(docFileOut));
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "标题");
            dataMap.put("persons", DataGenerate.getData());
            template.process(dataMap, docWriter);
            docWriter.flush();
            docWriter.close();
        }
    }
}

执行完成之后,生成的doc文档在maven项目的target/classes/out目录下。

导出docx文件

因为项目之前使用的word导出都是使用的该技术,项目中已经做了很多freemarker模板,在这时替换其他技术的话,会产生很大的工作量。所以在这里依然使用freemarker技术来导出word,用freemarker生成docx文件。使用解压缩工具打开docx文件可以看到里面有个word目录,进去之后可以看到一个document.xml文件,这个文件就是word文档的内容。
我们依然使用之前的word文档,保存为docx格式,解压缩打开内部的word/document.xml文件。会发现文件与上面的导出xml文件有些不同,但是内档内容部分<w:body>标签中的内容基本一致。所以就尝试这讲wordxml的文件的<w:body>部分来替换docx的document.xml<w:body>部分。发现替换之后依然可以正常打开docx文件,所以这里就直接讲以前的模板保留<w:body>中的内容,其余替换成docx的document.xml的内容来使用。至于导出word的代码也需要修改,由于之前是直接导出xml格式,而这里需要将生成的xml替换docx文件的document.xml文件,所以这里多了一步zip文件操作。
与导出doc不同的是,这里不仅需要一个ftl的freemarker模板,同时需要一个空的docx的文件(需要使用该文件来生成word)。
代码如下:

public class XmlToDocx {
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void main(String[] args) throws IOException, TemplateException {
        //使用模板生成document.xml文件
        Configuration configuration = new Configuration(Configuration.getVersion());
        configuration.setDefaultEncoding("UTF-8");

        URL resource = XmlToDoc.class.getClassLoader().getResource("");
        if(resource != null){
            File resourceDir = new File(resource.getFile());
            configuration.setDirectoryForTemplateLoading(resourceDir);
            Template template = configuration.getTemplate("docxTemplate.ftl");

            File tempFile = new File(resource.getFile() + "temp/docxTemp.xml");
            File tempDir = tempFile.getParentFile();
            if(!tempDir.exists()) {
                tempDir.mkdirs();
            }
            FileOutputStream tempFileOut = new FileOutputStream(tempFile);
            BufferedWriter tempWriter = new BufferedWriter(new OutputStreamWriter(tempFileOut));
            Map<String, Object> dataMap = new HashMap<>();
            dataMap.put("title", "标题");
            dataMap.put("persons", DataGenerate.getData());
            template.process(dataMap, tempWriter);
            tempWriter.flush();
            tempWriter.close();

            //使用生成的xml文件来替换docx中的document.xml
            ZipFile docxTempFile = new ZipFile(resource.getFile() + "template.docx");
            File docxFile = new File(resource.getFile() + "out/导出.docx");
            File docxDir = docxFile.getParentFile();
            if(!docxDir.exists()){
                docxDir.mkdirs();
            }
            if(!docxFile.exists()) {
                docxFile.createNewFile();
            }
            ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(docxFile));

            Enumeration<? extends ZipEntry> entries = docxTempFile.entries();
            int len = 1;
            byte[] buffer = new byte[1024];
            while (entries.hasMoreElements()) {
                ZipEntry next = entries.nextElement();
                InputStream inputStream = docxTempFile.getInputStream(next);
                zipOut.putNextEntry(new ZipEntry(next.toString()));
                if("word/document.xml".equals(next.toString())) {
                    FileInputStream in = new FileInputStream(tempFile);
                    while ((len = in.read(buffer)) != -1){
                        zipOut.write(buffer, 0, len);
                    }
                    in.close();
                }else {
                    while ((len = inputStream.read(buffer)) != -1){
                        zipOut.write(buffer, 0, len);
                    }
                    inputStream.close();
                }
            }
            zipOut.close();

        }
    }
}

注意: 这里使用doc另存为的xml文本中的<w:body>内容来替换docx文件下word/document.xml文件中<w:body>内容,在我目前使用模板中是可以直接替换,但是不清楚是否有其他不兼容的情况,大家如果要这样使用的话,自行验证。但是直接用docx文件中的document.xml来直接制作模板,是可以直接使用上面的导出docx文件的方法的。

文中代码: https://gitee.com/hello_ji/xml2word

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值