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可以实现。如果你知道,麻烦告诉我!谢谢

推荐阅读更多精彩内容