Java 对Word文件的生成(基于Apache POI)

Java 对Word文件的生成(基于Apache POI)

Apache POI 是一个开源的跨平台的对Microsoft Office格式档案具有读和写功能工具。
在Github上有一个开源的Word模版引擎poi-tl ,这个模版引擎是基于Apache POI。主要是为了解决下面的问题:

  • java操作word使用apache poi的复杂性
  • 使用freemarker,转化为xml操作word的难度
  • 依赖服务器上安装软件openoffice来调用转化
  • 依赖windows的word lib库,不具有跨平台性

注意!
HSSF - 提供读写Microsoft Excel XLS格式档案的功能。(*.doc),HWPFDocument类
XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。(*.docx),XWPFDocument类
因为这个模版引擎是只使用XWPFDocument类,所以只对*.docx文档生效。

poi-tl demo

调用方法

/*
* datas 是你要渲染的数据
* datas 可以是JavaBean,也可以是Map<String, Object>
*/
XWPFTemplate template = XWPFTemplate.compile("~/file.docx").render(datas);

除了传入模版文件路径,还可以传入模版文件输入流

public static XWPFTemplate compile(InputStream inputStream) {
    .....
    }

datas TO Map<String, Object>

看看数据类转Map的实现

private static Map<String, Object> convert2Map(Object dataSrouce) {
        Map<String, Object> ret = new HashMap<String, Object>();
        try {
            Class<?> clazz = dataSrouce.getClass();
            while (clazz != Object.class) {
                Field[] fields = clazz.getDeclaredFields();
                PropertyDescriptor pd = null;
                for (Field f : fields) {
                    pd = new PropertyDescriptor(f.getName(), dataSrouce.getClass());
                    Name annotation = f.getAnnotation(Name.class);
                    Object value = pd.getReadMethod().invoke(dataSrouce);
                    ret.put(null == annotation ? f.getName() : annotation.value(), value);
                }
                clazz = clazz.getSuperclass();
            }
        } catch (Exception e) {
            logger.error("Convert datasource failed.", e);
            throw new RenderException("Convert datasource failed.");
        }
        return ret;
    }
    

利用反射,把datas类和它的父类的字段属性转成Map<String, Object>,Object除外。所以我们传的参数是JavaBean或Map<String, Object>就可以了(传Map参数,会调用同名的的重载方法)。

语法

普通文本

渲染数据为String或者TextRenderData

模版文件中使用:{{template}}

...
Map<String, Object> datas = new HashMap<String, Object>();
datas.put("template", "我是渲染的数据");
// 参数1:颜色 9d55b8;参数2:文本内容
datas.put("title", new TextRenderData("9d55b8", "Deeply in love with the things you love,\n just deepoove."));

...         
  • 文本中可用\n 来进行换行

图片

渲染数据为:PictureRenderData

模版文件中使用:{{@picture}}

/*
* 参数1:宽度;参数2:高度;参数3:图片路径
*/
datas.put("picture", new PictureRenderData(100, 120, "src/test/resources/logo.png"));

表格

渲染数据为:TableRenderData

模版文件中使用:{{#table}}

/**
     * @param headers 表格头
     * @param datas 表格数据
     * @param noDatadesc 没有数据显示的文案
     * @param width 宽度
     */
    public TableRenderData(List<RenderData> headers, List<Object> datas,
            String noDatadesc, int width) {
        this.headers = headers;
        this.datas = datas;
        this.noDatadesc = noDatadesc;
        this.width = width;
    }
// 有表格头 有数据
datas.put("table", new TableRenderData(new ArrayList<RenderData>() {
        {
          add(new TextRenderData("1E915D", "province"));
          add(new TextRenderData("1E915D", "city"));
        }
      }, new ArrayList<Object>() {
        {
          add("beijing;beijing");
          add("zhejiang;hangzhou");
        }
      }, "no datas", 0));
    }

如果没有数据,表格会显示“no datas”
更加详细的请参考:poi-tl处理Word表格(Table)的最佳实践

列表

渲染数据为:NumbericRenderData

模版文件中使用:{{numbering}}*

    /**
     * @param numFmt 编号字符
     * @param fmtStyle 编号样式
     * @param numbers 列表内容
     */
    public NumbericRenderData(Pair<Enum, String> numFmt, Style fmtStyle, List<TextRenderData> numbers) {
        this.numFmt = numFmt;
        this.numbers = numbers;
        this.fmtStyle = fmtStyle;
    }
datas.put("unorderlist", new NumbericRenderData(new ArrayList<TextRenderData>(){{
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
            }}));
datas.put("orderlist", new NumbericRenderData(NumbericRenderData.FMT_DECIMAL, new ArrayList<TextRenderData>(){{
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
            }}));

NumbericRenderData类中有编号的符号常量

    /**
     * 1. 2. 3.
     */
    public static final Pair<Enum, String> FMT_DECIMAL = Pair.of(STNumberFormat.DECIMAL, "%1.");
    /**
     * 1) 2) 3)
     */
    public static final Pair<Enum, String> FMT_DECIMAL_PARENTHESES = Pair.of(STNumberFormat.DECIMAL,
            "%1)");
    /**
     * ● ● ●
     */
    public static final Pair<Enum, String> FMT_BULLET = Pair.of(STNumberFormat.BULLET, "●");
    /**
     * a. b. c.
     */
    public static final Pair<Enum, String> FMT_LOWER_LETTER = Pair.of(STNumberFormat.LOWER_LETTER,
            "%1.");
    /**
     * i ⅱ ⅲ
     */
    public static final Pair<Enum, String> FMT_LOWER_ROMAN = Pair.of(STNumberFormat.LOWER_ROMAN,
            "%1.");
    /**
     * A. B. C.
     */
    public static final Pair<Enum, String> FMT_UPPER_LETTER = Pair.of(STNumberFormat.UPPER_LETTER,
            "%1.");
    /**
     * Ⅰ Ⅱ Ⅲ
     */
    public static final Pair<Enum, String> FMT_UPPER_ROMAN = Pair.of(STNumberFormat.UPPER_ROMAN,
            "%1.");

样式

Style类

主要样式如下:

  • 颜色
  • 字体
  • 字号
  • 粗体
  • 斜体
  • 删除线
public class Style {
   //颜色
    private String color;
    //字体
    private String fontFamily;
    //字号
    private int fontSize;
    //粗体
    private Boolean isBold;
    //斜体
    private Boolean isItalic;
    //删除线
    private Boolean isStrike;

    public Style() {
    }

    public Style(String color) {
        this.color = color;
    }

    public Style(String fontFamily, int fontSize) {
        this.fontFamily = fontFamily;
        this.fontSize = fontSize;
    }
    
    ......
}

poi-tl 的 Change log

v1.2.0 2017-10-12

  1. 新增api:XWPFTemplate compile(InputStream inputStream)
  2. 不兼容升级:文本模板换行符由原先的\\n替换成更符合语言的\n

v1.1.0 2017-09-15

  1. 修复老版本office打开表格模板时出错
  2. 新增列表字符样式:设置编号颜色、字体、粗体、斜体等

v1.0.0

  1. 以插件的思想进行了重新设计
  2. 高度扩展性:语法即插件,像新增插件一样新增语法
  3. 新增工具类BytePictureUtils,便于操作图片的byte[]数据
  4. 新增Annotation @Name
  5. NiceXWPFDocument新增插入段落insertNewParagraph方法
  6. 新增代码生成工具类CodeGenUtils

V0.0.5

  1. bugfix: 解决0.0.4版本解析模板时CTSignedTwips类加载不到的问题
  2. new feature: 新增列表语法*,支持对有序列表和无序列表的插入

V0.0.4

  1. 增加新的api:XWPFTemplate.compile
  2. 渲染数据除了支持Map以外,还支持JavaBean渲染
  3. 升级poi组件至最新版本3.16

V0.0.3

  1. 新增表单语法#
  2. 支持表单插入
  3. 渲染器支持对table动态处理DynamicTableRenderPolicy
  4. 支持单元格的合并
  5. 丰富文本样式

Office Open XML -- OOXML

为了让微软的office用其他软件打开不会出现错乱的问题,所以出现了docx格式的文档(xlsx,pptx也是)。微软的OOXML文档格式已被批准为全球行业标准。
如果要了解比apache poi 更低层的ooxml,可以访问office open xml

在office open xml网站里,你会知道文件是怎样实现的,颜色、字体、粗体等是怎么设置的。

docx 文档转成xml后,普通文本就是这个样子的:

<w:r>
    <w:rPr>
        <w:b/>
        <w:i/>
    </w:rPr>
    <w:t>我是文本</w:t>
</w:r>

设置文本为粗体:

<w:r>
    <w:rPr>
        <w:b w:val="true"/>
        <w:i/>
    </w:rPr>
    <w:t>我是文本</w:t>
</w:r>

基本所有的属性,在office open xml都有详细的解析。

Word文档生成目录问题

我一直在找POI生成目录的方法,Jacob可以实现。如果你知道,麻烦告诉我!谢谢

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 首页 资讯 文章 资源 小组 相亲 登录 注册 首页 最新文章 IT 职场 前端 后端 移动端 数据库 运维 其他...
    Helen_Cat阅读 3,843评论 1 10
  • 记得2013年的时候,我还在深圳一家物联网公司上班,做项目销售,每天716式的工作。当时的工资并不高,2500一个...
    小坤职场阅读 913评论 0 0
  • 有一位佛友非常地优秀,经常受到老板的表扬,引起了同事的嫉妒,在背后捕风捉影说他坏话,这个佛友非常地苦恼。有一次他预...
    平缘阅读 332评论 0 0
  • 我抱着小小的玻璃罐, 走走停停。 走过灯红酒绿, 我将林立的高楼间, 那些浮躁与骄傲, 装进小小的罐子里。 走过沉...
    霖子酱阅读 125评论 0 2