跳至主要内容

Word 文件模組使用指南

Package: io.leandev.appfuse.document.* 狀態: 穩定(v1) 格式: Office Open XML(.docx


簡介

AppFuse Document 提供以 .docx 範本為起點的 Word 文件產生 API,封裝 Apache POI(XWPF),讓應用層不直接接觸 POI/OOXML 型別。設計決策與取捨見 ADR-008

核心特色

特色說明價值
範本錨定載入既有 .docx,在其上編排版面/樣式由設計師維護的範本決定
locator 定位findParagraphByText("[x]")Optional無隱藏游標狀態、可組合
共用容器面本文/頁首/頁尾/儲存格同一套 Body 操作一致、好學
HTML 富文本RichTextEditor 的 HTML 渲染進 Word表單富文本直接落地公文
依賴隔離層POI/OOXML 不洩漏到公開 API升級/替換底層不影響應用層

範圍與限制(v1)

支援規劃中(follow-up)
範本載入/輸出、段落/run/表格/列編排、頁面章節、頁首頁尾內容、圖片、頁碼欄位、appendCopyOf、HTML 富文本(文字流 + 圖片)HTML 內嵌 <table> 渲染、清單自動編號定義、文件合併、PDF 匯出、樣式定義建立、儲存格內新建巢狀表格

富文本輸入為 appfuse-web RichTextEditor(Tiptap)的 HTML 字串,非舊版 rtx AST。


快速開始

範本套印(最常見)

import io.leandev.appfuse.document.*;

try (InputStream template = getClass().getResourceAsStream("/templates/certificate.docx");
Document doc = Document.open(template);
OutputStream os = response.getOutputStream()) {

// 依占位文字定位並填值
doc.body().findParagraphByText("[customer]").ifPresent(p -> p.setText(customer.getName()));
doc.body().findParagraphByText("[date]").ifPresent(p -> p.setText(today));

// 或整份本文取代占位字串(跨段落、跨儲存格)
doc.body().replaceText("[certNo]", cert.getNo());

doc.write(os);
}

表格逐筆填(line items)

Table items = doc.body().tables().get(0);
for (OrderLine line : order.getLines()) {
TableRow row = items.appendRow();
row.cell(0).setText(line.name(), "answerStyle"); // 第二參數為樣板定義的樣式名
row.cell(1).setText(line.amount());
}

樣板表格複製多份(appendCopyOf)

// 以範本中既有的第一個表格為原型,每條產線複製一份再各自填值
Table prototype = doc.body().tables().get(0);
for (ProductionLine pl : productionLines) {
Table copy = doc.body().appendCopyOf(prototype);
copy.cell(0, 0).setText("產線:" + pl.getName());
}

從零建立

try (Document doc = Document.create()) {
doc.setPageSize(Paper.A4);
doc.body().appendParagraph("標題", "Heading1");
doc.body().appendParagraph("內文段落");
doc.write(os);
}

定位(locator)

不使用有狀態游標;定位即取得可編輯的 handle。

Body body = doc.body();

body.findParagraphByText("[name]"); // Optional<Paragraph>,部分文字比對
body.findParagraphByBookmark("sign_here"); // Optional<Paragraph>,依書籤
body.findCellByText("[total]"); // Optional<TableCell>
body.findCellByBookmark("amount"); // Optional<TableCell>

Body 是本文、頁首、頁尾、儲存格的共用容器面——以上方法在四者皆可用。


段落與文字格式

Paragraph p = doc.body().appendParagraph();
p.setText("重要");
p.setStyle("Heading2"); // 樣板定義的樣式名(style ID)
p.setAlignment(Alignment.CENTER);
p.setIndent(Length.ofCentimeter(1));
p.setSpacingAfter(Length.ofPoint(6));

Run run = p.addRun("加粗紅字");
run.setBold(true);
run.setColor(java.awt.Color.RED); // 顏色用 java.awt.Color
run.setFontSize(16); // 字級用 point
run.setUnderline(true);

p.addPageNumber(); // 頁碼欄位(常用於頁尾)

表格

Table table = doc.body().appendTable(3, 2); // 3 列 2 欄

table.cell(0, 0).setText("標題");
table.row(1).cell(0).setText("資料");
table.appendRow().cell(0).setText("新列");
table.duplicateRow(1); // 複製第 1 列(含內容格式)插入其後
table.removeRow(2);

table.mergeCellsVertically(0, 0, 2); // 第 0 欄、第 0–2 列垂直合併
table.mergeCellsHorizontally(0, 0, 1); // 第 0 列、第 0–1 欄水平合併
table.mergeRegion(0, 0, 1, 1); // 矩形區塊合併

table.setFullWidth(); // 滿版
table.setColumnWidth(0, Length.ofCentimeter(4));

TableCell cell = table.cell(0, 0);
cell.setVerticalAlignment(VerticalAlignment.CENTER);
cell.appendParagraph("儲存格內可再放多個段落"); // cell 本身即 Body 容器

頁首 / 頁尾

頁首頁尾的編輯面與本文相同(皆為 Body)。

Body footer = doc.footer(); // 預設頁尾(不存在則建立)
Paragraph p = footer.appendParagraph();
p.setAlignment(Alignment.CENTER);
p.addRun("第 ");
p.addPageNumber();
p.addRun(" 頁");

doc.firstFooter(); // 首頁頁尾
doc.header(); // 預設頁首

頁面與章節

doc.setPageSize(Paper.A4);
doc.setOrientation(Orientation.LANDSCAPE);
doc.setMargins(Length.ofCentimeter(2.5), Length.ofCentimeter(2),
Length.ofCentimeter(2.5), Length.ofCentimeter(2));
doc.setMarginFooter(Length.ofCentimeter(1));
doc.addPageBreak();
doc.addSectionBreak();
doc.restartPageNumber(1);

尺寸一律用框架的 io.leandev.appfuse.measure.Length / Paper


HTML 富文本

把 appfuse-web RichTextEditor(Tiptap/ProseMirror)輸出的 HTML 字串渲染進 Word。

// 場景 A:占位段落替換成富文本
doc.body().findParagraphByText("[content]")
.ifPresent(p -> p.replaceWithHtml(form.getRemarkHtml()));

// 場景 B:末端附加
doc.body().appendHtml(html);
HTML 子集→ Word
strong/em/u/s/code/mark/sup/subspan style=colorrun 格式
ph1–3blockquotepre段落/標題/樣式
ul/ol/li清單(前綴項目符號/編號)
text-align段落對齊
img(base64)內嵌圖片
table⏸ v1 不渲染(結構化表格請用原生 Table API)

圖片

Run run = doc.body().appendParagraph().addRun("");
run.addImage(inputStream, Length.ofCentimeter(3), Length.ofCentimeter(3)); // 串流
run.addImage(bufferedImage, Length.ofCentimeter(3), Length.ofCentimeter(2)); // BufferedImage(編碼為 PNG)

常見問題

Q: 為什麼不直接用 Apache POI?

A: 提供穩定的隔離 API、不洩漏 POI/OOXML 型別,與 csv/workbook 同策略;底層升級或替換不影響應用層。

Q: 占位符要用什麼語法?

A: 框架不規定。範本作者自選([x]{{x}} 皆可),以 findParagraphByText / replaceText 比對該字串即可。

Q: 樣式(setStyle("answerStyle"))的名字哪裡來?

A: 範本 .docx 內定義的樣式 ID。範本套印時引用範本既有樣式;v1 不支援以程式新建樣式定義。


API 參考

詳見 JavadocADR-008