Apache POI通过模板导出Word文件
使用 Apache POI 通过模板导出 Word 文件的核心思路是 在 Word 模板中预定义占位符,然后通过 POI 读取模板并动态替换内容。以下是详细步骤和完整代码示例:
完整流程
1. 准备模板(定义占位符)
↓
2. 加载模板(XWPFDocument)
↓
3. 遍历段落、表格、图片占位符
↓
4. 替换文本、填充表格、插入图片
↓
5. 保存生成的新文档
一、模板设计
在 Word 文档(.docx)中定义占位符,例如 ${name}、${tableData}、${image}:
尊敬的 ${name}:
以下是您的订单信息:
${tableData}
订单附件图片:
${image}
二、依赖配置
在 Maven 项目中添加 POI 依赖(以 poi-ooxml 为例):
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
三、完整代码实现
1. 替换文本、表格和图片
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
public class WordTemplateExporter {
public static void main(String[] args) throws Exception {
// 1. 加载模板
FileInputStream fis = new FileInputStream("template.docx");
XWPFDocument doc = new XWPFDocument(fis);
// 2. 定义替换数据
Map<String, Object> data = new HashMap<>();
data.put("${name}", "张三");
data.put("${image}", "signature.png");
// 动态表格数据(示例)
List<List<String>> tableData = Arrays.asList(
Arrays.asList("商品", "数量", "价格"),
Arrays.asList("手机", "2", "yen5999"),
Arrays.asList("耳机", "1", "yen299")
);
data.put("${tableData}", tableData);
// 3. 执行替换
replacePlaceholders(doc, data);
// 4. 保存结果
FileOutputStream fos = new FileOutputStream("output.docx");
doc.write(fos);
fos.close();
fis.close();
}
// 核心替换方法
private static void replacePlaceholders(XWPFDocument doc, Map<String, Object> data) throws Exception {
for (XWPFParagraph paragraph : doc.getParagraphs()) {
replaceTextInParagraph(paragraph, data);
}
// 处理表格
for (XWPFTable table : doc.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
for (XWPFParagraph p : cell.getParagraphs()) {
replaceTextInParagraph(p, data);
}
}
}
}
// 处理图片
replaceImages(doc, data);
}
// 替换段落中的文本
private static void replaceTextInParagraph(XWPFParagraph paragraph, Map<String, Object> data) {
for (XWPFRun run : paragraph.getRuns()) {
String text = run.getText(0);
if (text != null) {
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (text.contains(entry.getKey())) {
String value = entry.getValue().toString();
text = text.replace(entry.getKey(), value);
run.setText(text, 0);
}
}
}
}
}
// 插入图片(替换占位符)
private static void replaceImages(XWPFDocument doc, Map<String, Object> data) throws Exception {
for (XWPFParagraph paragraph : doc.getParagraphs()) {
for (XWPFRun run : paragraph.getRuns()) {
String text = run.getText(0);
if (text != null && text.contains("${image}")) {
// 删除占位符文本
run.setText("", 0);
// 插入图片
String imagePath = (String) data.get("${image}");
FileInputStream imageStream = new FileInputStream(imagePath);
run.addPicture(
imageStream,
XWPFDocument.PICTURE_TYPE_PNG,
"image.png",
Units.toEMU(200), // 宽度(EMU单位)
Units.toEMU(100) // 高度
);
imageStream.close();
}
}
}
}
}
四、关键功能说明
1. 文本替换
- 原理:遍历所有段落和表格单元格,查找 ${key} 格式的占位符并替换。
- 注意:避免直接修改 CTP 底层 XML,防止破坏原有样式。
2. 表格动态填充
- 适用场景:模板中的表格需要动态填充多行数据。
- 扩展代码:若需循环生成表格行:
- XWPFTable table = doc.getTable(0); // 获取第一个表格 List<List<String>> data = ...; // 动态数据 for (List<String> rowData : data) { XWPFTableRow row = table.createRow(); for (int i = 0; i < rowData.size(); i++) { row.getCell(i).setText(rowData.get(i)); } }
3. 图片插入
- 占位符匹配:通过 ${image} 标记图片位置。
- 单位转换:使用 Units.toEMU() 将像素转换为 EMU 单位(1 EMU = 1/914400 英寸)。
五、常见问题解决
1. 中文乱码
- 原因:未指定中文字体。
- 解决:在运行时设置字体:
- run.setFontFamily("宋体"); run.setFontSize(12);
2. 图片不显示
- 检查项: 图片路径是否正确(使用绝对路径更可靠)。 图片格式是否受支持(PNG/JPG)。 占位符是否被正确替换。
3. 样式丢失
- 原因:直接修改 CTP 导致样式重置。
- 解决:通过 XWPFParagraph 和 XWPFRun 操作,避免直接操作底层 XML。
六、高级功能扩展
1. 保留原样式
- 方法:在替换文本时,保留 XWPFRun 的字体、颜色等属性:
- java
- java
- 复制
- XWPFRun newRun = paragraph.insertNewRun(index); newRun.setText("新文本"); newRun.setFontFamily(run.getFontFamily()); // 继承原字体 newRun.setFontSize(run.getFontSize()); // 继承原字号
2. 动态页眉/页脚
// 修改页眉
XWPFFooter footer = doc.getFooter(0);
footer.createParagraph().createRun().setText("机密文件 - 版权所有");
// 修改页脚(含页码)
XWPFFooter footer = doc.getFooter(0);
XWPFParagraph para = footer.createParagraph();
para.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = para.createRun();
run.setText("第 " + doc.getPageNumber() + " 页");
3. 列表缩进处理
// 设置列表层级
CTNumPr numPr = pPr.getOrCreateNumPr();
CTAbstractNumId abstractNumId = numPr.getOrCreateAbstractNumId();
abstractNumId.setVal(BigInteger.valueOf(1)); // 设置列表层级
通过以上方法,可以高效实现基于模板的 Word 文件导出。对于更复杂需求(如图表、公式),建议结合 Freemarker 生成 XML 内容后导入 POI。