1. 概述

poi-tl 是基于 Apache POI 的开源 Java 库,核心功能是简化基于模板的 Word 文档生成。它作为 Word 模板引擎,通过 Word 模板和数据生成新文档。

我们可以在模板中预定义样式,生成文档时自动保留这些样式。模板采用声明式设计,完全基于标签实现,不同标签对应图片、文本、表格等不同元素。poi-tl 还支持自定义插件,实现灵活的文档结构定制。

本文将深入探讨模板中的各类标签用法及自定义插件开发。

2. 依赖配置

使用 poi-tl 需添加 Maven 依赖:

<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.12.2</version>
</dependency>

最新版本可在 Maven Central 查询。

3. 核心配置

通过 ConfigureBuilder 构建配置对象:

ConfigureBuilder builder = Configure.builder();
...
XWPFTemplate template = XWPFTemplate.compile(...).render(templateData);
template.writeAndClose(...);

模板文件需为 Word 的 .docx 格式。核心流程分为三步:

  1. 使用 XWPFTemplate.compile() 编译模板
  2. 通过 render()templateData 渲染到新文档
  3. 调用 writeAndClose() 生成文件并关闭资源

其中 templateDataHashMap<String, Object> 实例。以下是关键配置项:

3.1. 标签分隔符

默认使用 {{}} 作为标签标记,可自定义其他形式:

builder.buildGramer("${", "}");

3.2. 标签类型标识符

默认使用特定符号标识标签类型(如 @ 表示图片,# 表示表格),支持自定义:

builder.addPlugin('@', new TableRenderPolicy());
builder.addPlugin('#', new PictureRenderPolicy());

3.3. 标签命名规则

标签名默认支持字母、数字、下划线组合,可通过正则表达式自定义规则:

builder.buildGrammerRegex("[\\w]+(\\.[\\w]+)*");

后续章节将介绍插件和错误处理等高级配置。本文剩余部分均采用默认配置。

4. 模板标签详解

poi-tl 模板不包含变量赋值或循环控制,完全依赖标签驱动。数据通过 MapDataModel 与标签关联。标签基本语法:

{{tagName}}

下面分类说明常用标签:

4.1. 文本标签

普通文本使用双花括号包裹标签名:

{{authorname}}

template.docx 中添加此标签后,通过 Map 设置数据:

this.templateData.put("authorname", Texts.of("John")
  .color("000000")
  .bold()
  .create());

渲染时将替换为 "John" 并应用指定的样式(黑色加粗)。

4.2. 图片标签

图片标签以 @ 开头:

{{@companylogo}}

在数据映射中指定图片路径:

templateData.put("companylogo", "logo.png");

4.3. 编号列表标签

编号列表标签以 * 开头:

{{*bulletlist}}

通过 NumberingBuilder 构建列表数据:

List<String> list = new ArrayList<String>();
list.add("Plug-in grammar");
// ...

NumberingBuilder builder = Numberings.of(NumberingFormat.DECIMAL);
for(String s:list) {
    builder.addItem(s);
}
NumberingRenderData renderData = builder.create();    
this.templateData.put("list", renderData);

支持多种编号格式:

  • NumberingFormat.DECIMAL(十进制数字)
  • NumberingFormat.LOWER_LETTER(小写字母)
  • NumberingFormat.LOWER_ROMAN(小写罗马数字)

4.4. 区块标签

区块由开始/结束标签对构成,开始标签带 ? 前缀,结束标签带 / 前缀:

{{?students}} {{name}} {{/students}}

添加区块数据:

Map<String, Object> students = new LinkedHashMap<String, Object>();
students.put("name", "John");
students.put("name", "Mary");
students.put("name", "Disarray");
this.templateData.put("students", students);

4.5. 表格标签

表格标签以 # 开头:

{{#table0}}

使用 Tables 类构建表格数据:

templateData.put("table0", Tables.of(new String[][] { 
    new String[] { "00", "01" }, 
    new String[] { "10", "11" } 
  })
  .border(BorderStyle.DEFAULT)
  .create());

高级用法:

  • 通过 Rows.of() 单独定义行样式:
    RowRenderData row01 = Rows.of("Col0", "col1", "col2")
      .center()
      .bgColor("4472C4")
      .create();
    RowRenderData row11 = Rows.of("Col10", "col11", "col12")
      .center()
      .bgColor("4472C4")
      .create();
    templateData.put("table3", Tables.of(row01, row11)
      .create());
    
  • 使用 MergeCellRule 合并单元格:
    MergeCellRule rule = MergeCellRule.builder()
      .map(Grid.of(1, 0), Grid.of(1, 2))
      .build();
    templateData.put("table3", Tables.of(row01, row11)
      .mergeRule(rule)
      .create());
    

4.6. 嵌套模板

嵌套模板标签以 + 开头:

{{+nested}}

设置嵌套模板数据:

List<Address> subData = new ArrayList<>();
subData.add(new Address("Florida,USA"));
subData.add(new Address("Texas,USA"));
templateData.put("nested", Includes.ofStream(
  WordDocumentEditor.class.getClassLoader()
    .getResourceAsStream("nested.docx"))
  .setRenderModel(subData)
  .create());

引擎会将 nested.docx 渲染结果作为标签值插入主文档。

4.7. 使用 DataModel 渲染

支持通过 DataModel 绑定数据。创建 Person 类:

public class Person {
    private String name;
    private int age;
    // ...
}

绑定数据模型:

templateData.put("person", new Person("Jimmy", 35));

模板中使用 . 运算符访问属性:

{{person.name}}
{{person.age}}

5. 插件系统

插件允许在标签位置执行预定义逻辑,实现复杂操作。poi-tl 提供三类插件:

  1. 默认插件(无需配置)
  2. 内置插件(需配置)
  3. 自定义插件

5.1. 使用内置插件

以 Word 注释插件为例,通过 CommentRenderPolicy 配置:

builder.bind("comment", new CommentRenderPolicy());

使用插件:

CommentRenderData comment = Comments.of("SampleExample")
  .signature("John", "S", LocaleUtil.getLocaleCalendar())
  .comment("Authored by John")
  .create();
templateData.put("comment", comment);

其他可用插件见官方文档

5.2. 自定义插件开发

需实现 RenderPolicy 接口或继承 AbstractRenderPolicy

public class SampleRenderPolicy implements RenderPolicy {
    @Override
    public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
        XWPFRun run = ((RunTemplate) eleTemplate).getRun(); 
        String text = "Sample plugin " + String.valueOf(data);
        run.setText(text, 0);
    }
}

注册自定义插件:

ConfigureBuilder builder = Configure.builder();
builder.bind("sample", new SampleRenderPolicy());
templateData.put("sample", "custom-plugin");

渲染结果:将 {{sample}} 替换为 "Sample plugin custom-plugin"。

6. 日志配置

通过 Logback 启用日志,在 logback.xml 添加:

<logger name="com.deepoove.poi" level="debug" additivity="false">
    <appender-ref ref="STDOUT" />
</logger>

典型日志输出:

18:01:15.488 [main] INFO  c.d.poi.resolver.TemplateResolver - Resolve the document start...
18:01:15.503 [main] DEBUG c.d.poi.resolver.RunningRunBody - {{title}}
18:01:15.503 [main] DEBUG c.d.poi.resolver.RunningRunBody - [Start]:The run position of {{title}} is 0, Offset in run is 0
...
18:01:19.685 [main] INFO  c.deepoove.poi.render.DefaultRender - Successfully Render template in 4126 millis

通过日志可追踪标签解析和数据渲染过程。

7. 错误处理机制

poi-tl 支持自定义错误处理策略:

常见错误场景:

  • 引用不存在的变量
  • 级联属性为 null(如 {{student.name}}student 为 null)

处理策略配置:

  1. 默认行为:返回 null(清除标签)
    builder.useDefaultEL(true);
    
  2. 静默忽略标签:
    builder.setValidErrorHandler(new DiscardHandler());
    
  3. 严格模式(抛异常):
    builder.setValidErrorHandler(new AbortHandler());
    

8. 动态模板生成

模板引擎不仅能生成文档,还能创建新模板。示例:将文本标签拆分为文本+表格标签:

Configure config = Configure.builder()
  .bind("title", new DocumentRenderPolicy())
  .build();

Map<String, Object> data = new HashMap<>();

DocumentRenderData document = Documents.of()
  .addParagraph(Paragraphs.of("{{title}}").create())
  .addParagraph(Paragraphs.of("{{#table}}").create())
  .create();
data.put("title", document);

引擎会将 title 标签替换为文档结构模板。

9. 总结

本文系统讲解了 poi-tl 的核心功能: ✅ 模板标签系统(文本/图片/表格/列表等) ✅ 插件开发机制(内置+自定义) ✅ 日志与错误处理配置 ✅ 动态模板生成技巧

⚠️ 实际开发中建议优先采用模板标签实现基础功能,复杂场景再结合插件扩展。遇到样式问题可检查 Word 模板中的样式定义是否正确。

完整示例代码见 GitHub


原始标题:Generate MS Word Documents Using poi-tl Template | Baeldung