| 方案 | 核心原理 | 模板維護 | 複雜排版 | 圖片/表格 | Android 支援 | 授權 |
|---|---|---|---|---|---|---|
| FreeMarker + XML/FTL | 模板引擎替換佔位符,生成 Word XML (OOXML) | ⭐⭐⭐ Word 編輯另存為 XML | ⭐⭐⭐⭐ 支援條件、循環、巢狀 | ⭐⭐ Base64 嵌入,較繁瑣 | ❌ 依賴 AWT/字體包 | Apache 2.0 |
| Apache POI (XWPF) | 直接操作 OOXML DOM 模型 | ⭐⭐ Word 模板含 ${} 佔位符 | ⭐⭐⭐ 程式化控制,靈活但代碼量大 | ⭐⭐⭐⭐ 原生支援圖片、表格、樣式 | ✅ 純 Java,可裁剪依賴 | Apache 2.0 |
| iText 7 + pdfHTML | HTML/CSS 轉 PDF,再轉 Word(或直接生成 PDF) | ⭐⭐⭐ HTML 模板最易維護 | ⭐⭐⭐ CSS 排版強大 | ⭐⭐⭐ HTML 標籤原生支援 | ✅ 支援 Android | AGPL / 商業授權 |
| Aspose.Words | 商業級文檔處理引擎 | ⭐⭐⭐ 支援 Word 原生模板 | ⭐⭐⭐⭐⭐ 完美還原 Word 渲染 | ⭐⭐⭐⭐⭐ 企業級功能全 | ✅ 支援 | 商業收費(貴) |
| JodConverter (LibreOffice) | 調用 LibreOffice 服務轉換 | ⭐⭐⭐ 任意格式互轉 | ⭐⭐⭐⭐ 依賴 LibreOffice 渲染 | ⭐⭐⭐⭐ 原生支援 | ❌ 需安裝 LibreOffice | LGPL |
XML (Word 2003 XML) 或 .ftl。${field}、<#list items as item>${item.name} 等指令。${base64Img} 。public class DocxGenerator {
private final Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
public DocxGenerator() {
cfg.setClassForTemplateLoading(this.getClass(), "/templates");
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
}
public void generate(Map data, String templateName, File output)
throws IOException, TemplateException {
Template template = cfg.getTemplate(templateName + ".ftl");
try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output), "UTF-8"))) {
template.process(data, out);
}
}
// 圖片轉 Base64 輔助
public String imageToBase64(File imgFile) throws IOException {
byte[] bytes = Files.readAllBytes(imgFile.toPath());
return Base64.getEncoder().encodeToString(bytes);
}
}
<!-- 列表項自動換行 -->
<#list deductItems as item>
<w:t>${item}</w:t><w:br/>
#list>
<!-- 條件顯示 -->
<#if score >= 60>
<w:t>通過</w:t>
<#else>
<w:t style="color:red">不及格</w:t>
#if>
XWPFDocument:文檔模型根節點。XWPFParagraph/XWPFRun:段落與文本運行(樣式最小單位)。XWPFTable/XWPFTableRow/XWPFTableCell:表格結構。public class PoiDocxUtil {
public static XWPFDocument renderTemplate(String templatePath, Map data)
throws IOException, InvalidFormatException {
try (FileInputStream fis = new FileInputStream(templatePath)) {
XWPFDocument doc = new XWPFDocument(OPCPackage.open(fis));
// 1. 替換段落文本(含表格內段落)
replaceInParagraphs(doc.getParagraphs(), data);
for (XWPFTable table : doc.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
replaceInParagraphs(cell.getParagraphs(), data);
}
}
}
// 2. 處理圖片佔位符:${img:logo} -> 圖片流
processImages(doc, data);
return doc;
}
}
private static void replaceInParagraphs(List paragraphs, Map data) {
for (XWPFParagraph p : paragraphs) {
for (XWPFRun run : p.getRuns()) {
String text = run.getText(0);
if (text == null) continue;
for (Map.Entry e : data.entrySet()) {
String placeholder = "${" + e.getKey() + "}";
if (text.contains(placeholder)) {
text = text.replace(placeholder, e.getValue().toString());
run.setText(text, 0);
}
}
}
}
}
private static void processImages(XWPFDocument doc, Map data) throws IOException, InvalidFormatException {
// 遍歷 runs 尋找 ${img:key} 格式佔位符
// 刪除佔位符 run,在同一段落插入圖片 run
// 代碼略...
}
}
使用 iText 7 pdfHTML 或 Flying Saucer 將 HTML+CSS 轉為 PDF,再用 LibreOffice/Pandoc 轉 Word;或直接生成 PDF 交付(現代業務多接受 PDF)。
ooxml-schemas 依賴。yum install fontconfig dejavu-sans-fonts google-noto-sans-cjk-ttc-fonts,或在代碼註冊字體目錄。XWPFDocument 全量載入 DOM,大文件改用 SAX 事件流模式 (XWPFDocument.write() 仍需完整 DOM),極大文檔考慮分章生成再合併。ooxml-schemas 替換為 poi-ooxml-full 避免傳遞依賴地獄。Configuration 線程安全可單例;POI XWPFDocument 非線程安全,每請求新建實例。