使用Java + Freemarker 导出word文档

关于使用java导出word文档,网上有很多资料,但基本上来说使用freemarker模板导出的教程居多。所以这次基于网上查到的资料和自己的实践,记录下自己的实践过程,以便日后查阅,也希望能帮到一些人。

下面是基本的例子,以实现简单的word导出:

1. 组织word对应ftl模板

要导出的word模板的内容,启动拼音部分为要在代码种替换的部分。

模板

编辑好word后将文件另存为.xml文件,然后再将.xml文件后缀改为.ftl。打开ftl文件,依次将变量替换为用${}包裹。注意:替换的内容需要包裹在<w:t> </w:t>之中。

另外,最好使用全中文作为占位符。因为使用英文的话,转为xml时,word可能会将一个单词拆分成两个,比如我使用Title作为占位符,转化为xml后,搜索的时候一直找不到。然后你会发现,其实word将其拆分成T和itle。这种事也不是绝对的(同一个单词如果有不同的样式就会保存在不同的<w:r>中),所以只是建议,即便同一个单词被拆分了,也不用急等到后面就有解决方案。

word文档的结构

对于List类型的内容来说需要进行遍历。对于上面的数据结构来说,我们需要对list进行遍历。在这之前,我们首先了解一下word xml的大概结构

<w:wordDocument>
    <w:body>
        <w:p>
            <w:pPr>
            </w:pPr>    
            <w:r>
                <w:rPr>属性:加粗,倾斜,字体颜色等</w:rPr>
                <w:t> 文本内容</w:t>
            </w:r>      
        </w:p>
    </w:body>
</<w:wordDocument>
  • <w:p> 会包裹一段数据,(段落)

    • <w:pPr> 段落的属性,可选元素。 段落属性的一些示例包括对齐方式、边框、断字覆盖、缩进、行距、底纹、文本方向和孤行控制
  • <w:r> 它是具有一组共同属性(如格式设置)的文本区域。它可以包含多个<w:t>元素。如果示例文本中只有一个字是粗体,粗体将会分离到一个<w:r>中

    • <w:rPr>用于指定<w:r>属性。 连续文本属性的一些示例包括粗体、边框、字符样式、颜色、字体、字号、斜体、字距调整、禁用拼写/语法检查、底纹、小号大写字母、删除线、文字方向和下划线
  • <w:t> 实际的文本内容

    下面我们用一个例子来说明,写了一些内容,并配置了颜色


    示例

    另存为xml文件后的部分代码

    <w:p wsp:rsidR="0084377C" wsp:rsidRPr="002827FA" wsp:rsidRDefault="009C2113">
        <w:pPr>
            <w:rPr>
                <w:color w:val="000000"/>   
            </w:rPr>
        </w:pPr>
        <w:r>
            <w:rPr><w:rFonts w:hint="fareast"/></w:rPr>
            <w:t>哈哈</w:t>
        </w:r>
        <w:r wsp:rsidRPr="009C2113">
            <w:rPr>
                <w:rFonts w:hint="fareast"/>
                <w:color w:val="FF0000"/>   
            </w:rPr>
            <w:t>嗝</w:t>
        </w:r>
        <w:r wsp:rsidRPr="002827FA">
            <w:rPr>
                <w:rFonts w:hint="fareast"/>
                <w:color w:val="000000"/>
            </w:rPr>
            <w:t>哈哈</w:t>
        </w:r>
    </w:p>
    

从上面可以清楚的看到,上面的内容在一个段落里包裹。同时在一个段落里可以设置多个不同的文字样式,这部分数据就会存放在 <w:r> 中,样式数据就存放在<w:rPr> 里面。

所以说如果我们需要遍历,首先要找到需要遍历的位置在哪里?找好以后就完成了一半的工作。例如上面的小案例,我们需要遍历学号和内容。 所以首先定位到 “xuehao” 所在的<w:p> 然后查找 “选项”所在的</w:p>。 然后将这么内容使用<#list> </#list>包裹就可以了。

    <#list list as stu>
    <w:tr wsp:rsidR="00B362B3" wsp:rsidRPr="00B55103" wsp:rsidTr="00B55103">
        <w:trPr><w:trHeight w:val="563"/></w:trPr>
        <w:tc>
            <w:tcPr><w:tcW w:w="4148" w:type="dxa"/><w:shd w:val="clear" w:color="auto" w:fill="auto"/></w:tcPr>
            <w:p wsp:rsidR="00B362B3" wsp:rsidRPr="00B55103" wsp:rsidRDefault="00B362B3">
                <w:proofErr w:type="spellStart"/>
                <w:r wsp:rsidRPr="00B55103"><w:t>${stu.xuehao}</w:t></w:r>
                <w:proofErr w:type="spellEnd"/>
            </w:p>
        </w:tc>
        <w:tc>
            <w:tcPr><w:tcW w:w="4148" w:type="dxa"/><w:shd w:val="clear" w:color="auto" w:fill="auto"/></w:tcPr>
            <w:p wsp:rsidR="00B362B3" wsp:rsidRPr="00B55103" wsp:rsidRDefault="00B362B3">
                <w:proofErr w:type="spellStart"/>
                <w:r wsp:rsidRPr="00B55103"><w:rPr><w:rFonts w:hint="fareast"/></w:rPr></w:r>
                <w:r wsp:rsidRPr="00B55103"><w:t>${stu.neirong}</w:t></w:r>
                <w:proofErr w:type="spellEnd"/>
            </w:p>
        </w:tc>
    </w:tr>
    </#list>
2. 添加freemarker依赖
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
        <version>2.3.30</version>
</dependency>
3. 测试代码
package demo;

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

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WordTest {

    private Configuration configuration = null;

    public WordTest() {
        configuration = new Configuration();
        configuration.setDefaultEncoding("UTF-8");
    }

    public static void main(String[] args) {
        WordTest test = new WordTest();
        test.createWord();
    }

    public void createWord() {

        Map<String, Object> dataMap = new HashMap<String, Object>();
        getData(dataMap);
        configuration.setClassForTemplateLoading(this.getClass(), "/");//模板文件所在路径,此处我是存放在resource目录下
        try {
            Template t = configuration.getTemplate("wordtemplate.ftl"); //获取模板文件
            File outFile = new File("D:/outFile" + Math.random() * 10000 + ".doc"); //导出文件
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
            t.process(dataMap, out); //将填充数据填入模板文件并输出到目标文件
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void getData(Map<String, Object> dataMap) {
        dataMap.put("title", "标题");
        dataMap.put("nian", "2020");
        dataMap.put("yue", "09");
        dataMap.put("ri", "08");
        dataMap.put("shenheren", "李小龙");

        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < 10; i++) {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("xuehao", i);
            map.put("neirong", "内容" + i);
            list.add(map);
        }
        dataMap.put("list", list);
    }
}

4. 文件结构
文件结构
5. 导出文件效果
效果图
6. 参考文档

https://blog.csdn.net/yamadeee/article/details/82771035
https://www.cnblogs.com/lcngu/p/5247179.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350