跳至主要内容

ADR-008: Word 文件產生模組(隔離式、handle-based)

ADR 編號: 008 狀態: 已接受 (Accepted) 決策日期: 2026-06-23 決策者: Framework Team + AI Assistant


摘要

appfuse-server 新增 io.leandev.appfuse.document 模組,提供以 .docx 範本為起點的 Word 文件產生能力(封裝 Apache POI XWPF,不洩漏 POI/OOXML 型別);採 handle/locator 模型(捨棄前一代的有狀態游標)、單一 Body 共用容器面,並支援把 appfuse-web RichTextEditorHTML 子集渲染進 Word。


背景 (Context)

問題陳述

多個既有系統(前一代框架的消費端 csp-serverasp-server)以「載入 .docx 範本 → 在其上填值與編排 → 輸出」的方式產生證書、檢驗報告、通知書等公文。前一代框架的 appfuse-core 有一套 io.leandev.appfuse.documentXWPF*Editor),但全面把 POI 與原始 OOXML schema 型別(XWPFParagraphXWPFTableCTSimpleField…)暴露在公開 API,違反現行框架「依賴隔離層」原則(見 csv / workbook 模組與 30-public-api.md)。

限制條件

  • 公開 API 不得洩漏 POI(org.apache.poi.*)或 OOXML schema(org.openxmlformats.*)型別。
  • 尺寸需與既有第一方型別 io.leandev.appfuse.measureLength / Paper)一致。
  • 富文本輸入格式為 appfuse-web RichTextEditor(Tiptap/ProseMirror)輸出的 HTML 字串,非前一代的 rtx AST。

假設前提

  • 前一代 document探索性設計,不視為最佳解;逐一重新評估其風格選擇。
  • 真實消費端(csp-server 9 個 service、asp-server 5 個 service)作為實務需求證據,不作為移植目標。

考量的方案 (Options Considered)

方案 A: 直接移植前一代 XWPF*Editor

說明: 把 appfuse-core 的 document 套件原樣搬入。

優點:

  • ✅ 功能即時齊備、零設計成本

缺點:

  • ❌ POI + OOXML schema(CTSimpleField 等)全洩漏進公開 API,違反隔離不變量
  • ❌ 沿用有狀態游標(seek/moveTo + 隱藏 cursor)與三套重複的 Editor

評分: 2/5

方案 B: 隔離式重設計、handle-based、單一 Body 共用面

說明: 以 csp/asp 的真實使用為能力清單,重新設計一組隔離 wrapper(Document/Body/Paragraph/Run/Table/Row/Cell),POI 藏在套件內部;定位改用 locator(findParagraphByText/BookmarkOptional),body/header/footer/cell 共用單一 Body 容器面。

優點:

  • ✅ 公開 API 零 POI/OOXML 洩漏,與 csv/workbook 一致
  • ✅ 無隱藏游標狀態,定位即取得 handle、可組合、易推理
  • ✅ 收斂前一代三套重複 Editor 為一個 Body

缺點:

  • ❌ 設計與實作成本較高(跨 run 取代、列/表複製、HTML 渲染需自行實作)

評分: 5/5

方案 C: 不做,續用前一代 jar

說明: 維持消費端各自依賴舊 appfuse-core

優點:

  • ✅ 零工作量

缺點:

  • ❌ 新專案無法用一致的隔離 API 產生 Word;技術債延續

評分: 1/5


決策 (Decision)

選擇方案: B

核心理由:

  1. 隔離不變量不可破——公開 API 洩漏 POI/OOXML 是現行框架明令禁止(30-public-api.md),方案 A 直接違反。
  2. 前一代是探索、非最佳解——游標模型與 fluent of() 多是「POI handle 薄封裝」的副產物;隔離後既無相容義務、又無 POI handle 可包,正好整治。
  3. 真實證據支撐——csp/asp 的 seek→就地編輯 idiom 每一處都能 1:1 對映成 find→編輯(locator),無功能缺口(見下節)。

權衡分析 (Trade-offs)

我們獲得什麼 (Gains)

  • ✅ 與 csv/workbook 同調的隔離 API;底層 POI 可升級/替換不影響應用層
  • ✅ handle 模型消除隱藏狀態;單一 Body 面消除三套重複
  • ✅ 順手把前一代的笨拙改掉(如 copyTo 兩步式 → appendCopyOf 一步回傳副本)

我們放棄什麼 (Losses)

  • ❌ 不與前一代 API 相容(但消費端不移植,無實際損失)

風險與緩解措施 (Risks & Mitigations)

風險嚴重性機率緩解措施
跨 run 文字取代、列/表深拷貝實作易出錯複用前一代驗證過的 POI 食譜(內部實作,不外露);單元測試覆蓋
HTML 子集渲染範圍蔓延v1 鎖定 marks + 區塊 + 清單 + 對齊 + 圖片;HTML 內嵌 <table> 列為 follow-up
jsoup 新依賴implementation 引入、不外露;僅 HTML 渲染路徑使用

影響 (Consequences)

正面影響

  • ➕ 新專案可用隔離 API 產生 Word 公文;與 Excel(workbook)能力對稱
  • ➕ 與 workbook 的 HTML 富文本需求(FU-14)共用 HTML 子集解析

負面影響

  • ➖ 模組較大(手寫範本套印的多種編排),分多個 commit 落地

中性影響

  • 🔸 富文本輸入正名為 HTML 子集(非 rtx AST);rtx 子系統不在本模組落點

實作指南 (Implementation Guidelines)

必須遵守的規則

  1. 公開簽章僅可出現自家型別、io.leandev.appfuse.measure.*java.awt.Color、JDK 標準型別;禁止 org.apache.poi.* / org.openxmlformats.*
  2. 定位一律 locator(回 Optional<handle>),不得引入有狀態游標。
  3. body/header/footer/cell 共用單一 Body 容器面(TableCell extends Body)。
  4. 尺寸用 Length/Paper、顏色用 java.awt.Color、字級用 double points
  5. 不內建 token 語法(範本作者自選 [x]/{{x}}),提供 findParagraphByText / replaceText 原語。

v1 範圍邊界

緩(follow-up)
範本載入/輸出、段落/run/表格/列編排、頁面章節、頁首頁尾內容、圖片、頁碼欄位、appendCopyOf、HTML 富文本(文字流 + 圖片)從零自由編排體驗、HTML 內嵌 <table> 渲染、list numbering 定義、文件合併、PDF 匯出、樣式定義建立

前一代 vs v1 對照(取捨依 csp/asp 證據)

議題前一代v1證據
定位有狀態游標 seek/moveTolocator findParagraphByText/Bookmarkcsp/asp 皆「seek→就地編輯」,1:1 對映 find→編輯
容器三套重複 Editor單一 Bodybody/header/footer/cell 操作相同(POI IBody
複製addTable()+copyTo(empty) 兩步appendCopyOf() 一步回傳副本asp 樣板表格複製 N 份的笨拙改善
顏色String hexjava.awt.Color與 workbook 一致
頁碼欄位CTSimpleField(OOXML 洩漏)addPageNumber() 隱藏csp 用但不碰回傳型別

相關文檔 (References)

內部文檔

  • 使用指南:../guides/core/document.md
  • 隔離策略對照模組:../guides/core/workbook.md../guides/core/csv.md

相關 ADR

  • 本模組與既有 csv / workbook 同採「依賴隔離層」哲學

變更歷史 (Change Log)

日期變更內容變更者
2026-06-23初版(v1 設計定稿)Framework Team + AI

文檔維護者: Development Team + AI Assistant 最後審閱: 2026-06-23