好的!下面给你一份基于 poi-tl(Java POI模板引擎)的详细教程,重点讲解如何实现动态表格填充单元格合并功能,方便你快速用poi-tl处理Word模板生成复杂文档。


poi-tl 详细教程 — 动态表格 & 单元格合并


1. 简介

  • poi-tl 是基于 Apache POI 的模板引擎,支持Word模板(.docx)中占位符渲染。
  • 支持动态表格渲染、图片、单元格合并、多级嵌套等高级功能。

2. 引入依赖(Maven)

<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.13.1</version> <!-- 最新版本请到官网确认 -->
</dependency>

3. 基础示例:动态表格渲染


3.1 准备Word模板

  • 在Word中创建表格,表格内填写占位符,格式例如:
序号姓名年龄
{{#employees}}{{index}}{{name}}
  • {{#employees}}...{{/employees}} 是循环开始和结束标记。
  • {{index}} 是循环中自动生成的索引(从1开始)。

3.2 Java代码实现动态填充表格

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.*;
import java.util.*;

public class DynamicTableExample {
    public static void main(String[] args) throws Exception {
        // 模板路径
        String template = "template.docx";
        // 输出路径
        String out = "out.docx";

        // 构建数据列表
        List<Map<String, Object>> employees = new ArrayList<>();
        employees.add(new HashMap<String, Object>() {{
            put("index", 1);
            put("name", "张三");
            put("age", 28);
        }});
        employees.add(new HashMap<String, Object>() {{
            put("index", 2);
            put("name", "李四");
            put("age", 30);
        }});

        // 或使用 RenderData 和 MiniTableRenderData 更高级表格渲染(后面介绍)

        // 构建渲染数据
        Map<String, Object> data = new HashMap<>();
        data.put("employees", employees);

        // 渲染模板
        XWPFTemplate templateDoc = XWPFTemplate.compile(template).render(data);
        templateDoc.writeToFile(out);
        templateDoc.close();
    }
}

4. 动态表格高级渲染(使用 MiniTableRenderData)


4.1 准备Word模板

  • 在Word模板中写占位符:{{table}}

4.2 Java代码示例

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.*;
import com.deepoove.poi.policy.MiniTableRenderPolicy;

import java.util.*;

public class MiniTableExample {
    public static void main(String[] args) throws Exception {
        String template = "template.docx";
        String out = "out_mini_table.docx";

        // 表头
        RowRenderData header = RowRenderData.build("序号", "姓名", "年龄");

        // 构建数据行
        List<RowRenderData> rows = new ArrayList<>();
        rows.add(RowRenderData.build("1", "张三", "28"));
        rows.add(RowRenderData.build("2", "李四", "30"));

        // 构建表格数据
        MiniTableRenderData tableData = new MiniTableRenderData(header, rows);

        // 渲染数据
        Map<String, Object> data = new HashMap<>();
        data.put("table", tableData);

        XWPFTemplate templateDoc = XWPFTemplate.compile(template).render(data);
        templateDoc.writeToFile(out);
        templateDoc.close();
    }
}

5. 单元格合并


5.1 Word中模板准备

  • 合并的单元格在模板中先合并好,但如果需要动态控制,需通过代码处理。

5.2 使用 poi-tl 动态合并单元格

poi-tl 本身没有直接的合并API,但你可以在表格渲染完成后,使用 Apache POI 的API对目标表格单元格进行合并:

import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;

import java.io.FileOutputStream;
import java.util.List;

public class MergeCellExample {
    public static void main(String[] args) throws Exception {
        XWPFTemplate template = XWPFTemplate.compile("template.docx").render(null);
        XWPFDocument doc = template.getXWPFDocument();

        // 获取第一个表格
        XWPFTable table = doc.getTables().get(0);

        // 合并第一行第1列到第2列(水平合并)
        mergeCellsHorizontal(table, 0, 0, 1);

        // 合并第1列第2行到第3行(垂直合并)
        mergeCellsVertically(table, 0, 1, 2);

        // 输出文档
        FileOutputStream out = new FileOutputStream("merged_output.docx");
        doc.write(out);
        out.close();
        template.close();
    }

    // 水平合并单元格
    public static void mergeCellsHorizontal(XWPFTable table, int row, int fromCell, int toCell) {
        for (int cellIndex = fromCell; cellIndex <= toCell; cellIndex++) {
            XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
            if (cellIndex == fromCell) {
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal("restart");
            } else {
                cell.getCTTc().addNewTcPr().addNewHMerge().setVal("continue");
            }
        }
    }

    // 垂直合并单元格
    public static void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
        for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
            XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
            if (rowIndex == fromRow) {
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal("restart");
            } else {
                cell.getCTTc().addNewTcPr().addNewVMerge().setVal("continue");
            }
        }
    }
}

6. 总结

功能说明
动态表格渲染使用循环占位符 或 MiniTableRenderData 实现
单元格合并先渲染模板,再用Apache POI API对表格合并
推荐做法模板设计时尽量预留合并单元格位置,代码灵活合并

明白!我帮你系统整理了一份 poi-tl全方位教程,包含:

  1. 动态表格渲染(基础循环+MiniTableRenderData)
  2. 单元格合并(水平合并+垂直合并)
  3. 复杂多级表格嵌套渲染示例
  4. poi-tl图片、条码、二维码渲染
  5. 性能优化与模板设计技巧

1. 动态表格渲染

1.1 基础循环渲染

模板示例:

| 序号 | 姓名 | 年龄 |
|------|------|------|
| {{#employees}} | {{index}} | {{name}} | {{age}} | {{/employees}} |

Java示例:

List<Map<String, Object>> employees = new ArrayList<>();
employees.add(new HashMap<String, Object>() {{ put("index", 1); put("name", "张三"); put("age", 28); }});
employees.add(new HashMap<String, Object>() {{ put("index", 2); put("name", "李四"); put("age", 30); }});

Map<String, Object> data = new HashMap<>();
data.put("employees", employees);

XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
template.writeToFile("output.docx");
template.close();

1.2 使用 MiniTableRenderData 渲染更复杂表格

RowRenderData header = RowRenderData.build("序号", "姓名", "年龄");
List<RowRenderData> rows = Arrays.asList(
    RowRenderData.build("1", "张三", "28"),
    RowRenderData.build("2", "李四", "30")
);

MiniTableRenderData table = new MiniTableRenderData(header, rows);

Map<String, Object> data = new HashMap<>();
data.put("table", table);

XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
template.writeToFile("output_mini_table.docx");
template.close();

2. 单元格合并

// 水平合并第0行第0到1单元格
mergeCellsHorizontal(table, 0, 0, 1);

// 垂直合并第0列第1到2行单元格
mergeCellsVertically(table, 0, 1, 2);

public static void mergeCellsHorizontal(XWPFTable table, int row, int fromCell, int toCell) {
    for (int i = fromCell; i <= toCell; i++) {
        XWPFTableCell cell = table.getRow(row).getCell(i);
        if (i == fromCell) cell.getCTTc().addNewTcPr().addNewHMerge().setVal("restart");
        else cell.getCTTc().addNewTcPr().addNewHMerge().setVal("continue");
    }
}

public static void mergeCellsVertically(XWPFTable table, int col, int fromRow, int toRow) {
    for (int i = fromRow; i <= toRow; i++) {
        XWPFTableCell cell = table.getRow(i).getCell(col);
        if (i == fromRow) cell.getCTTc().addNewTcPr().addNewVMerge().setVal("restart");
        else cell.getCTTc().addNewTcPr().addNewVMerge().setVal("continue");
    }
}

3. 复杂多级表格嵌套渲染示例

  • 可以用 Map 嵌套 List 的方式,结合 {{#xxx}}...{{/xxx}} 多层循环

模板示例:

| 序号 | 姓名 | 项目列表 |
|------|------|----------|
| {{#employees}} | {{index}} | {{name}} | 
    {{#projects}}
    - {{projectName}} ({{duration}}月)
    {{/projects}}
  {{/employees}} |

Java示例:

List<Map<String, Object>> employees = new ArrayList<>();

Map<String, Object> emp1 = new HashMap<>();
emp1.put("index", 1);
emp1.put("name", "张三");
emp1.put("projects", Arrays.asList(
    Map.of("projectName", "项目A", "duration", 6),
    Map.of("projectName", "项目B", "duration", 3)
));

Map<String, Object> emp2 = new HashMap<>();
emp2.put("index", 2);
emp2.put("name", "李四");
emp2.put("projects", Arrays.asList(
    Map.of("projectName", "项目C", "duration", 12)
));

employees.add(emp1);
employees.add(emp2);

Map<String, Object> data = new HashMap<>();
data.put("employees", employees);

XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
template.writeToFile("output_nested.docx");
template.close();

4. 图片、条码、二维码渲染

4.1 图片

PictureRenderData picture = new PictureRenderData(60, 60, "path/to/image.jpg");
Map<String, Object> data = Map.of("picture", picture);

XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
template.writeToFile("output_picture.docx");
template.close();

模板中使用 {{picture}} 占位符。


4.2 条码与二维码

poi-tl支持条码,示例:

BarCodeRenderData barcode = new BarCodeRenderData(BarCodeRenderData.BarCodeType.CODE128, "1234567890");
QrCodeRenderData qrCode = new QrCodeRenderData("https://example.com", 100, 100);

Map<String, Object> data = new HashMap<>();
data.put("barcode", barcode);
data.put("qrcode", qrCode);

XWPFTemplate template = XWPFTemplate.compile("template.docx").render(data);
template.writeToFile("output_code.docx");
template.close();

5. 性能优化与模板设计技巧

  • 模板设计
    • 尽量减少大表格单元格嵌套,简化循环层次。
    • 合理使用 MiniTableRenderData 分离复杂表格。
  • 数据准备
    • 预处理数据成适合模板结构的格式,避免运行时复杂转换。
  • 缓存模板
    • 如果多次生成相同模板,可缓存XWPFTemplate对象。
  • 避免重复IO
    • 批量生成时,减少频繁读写文件。
  • 占位符规范
    • 遵守poi-tl占位符规则,不使用保留字符。