Skip to content

feat(architecture): radical translation OOXML library — Word↔Swift edit-isomorphism (fully faithful functor) as core contract #99

@kiki830621

Description

@kiki830621

Problem

設計討論的脈絡:從「論文 docx rescue」一路往上追到「OOXML library 的核心契約應該是什麼」。結論是 edit-isomorphism (fully faithful functor) 應該成為 macdoc OOXML 工具鏈的核心架構契約,而非只是某個 library 的內部實作細節。

這個 issue 把該討論固定下來,作為後續 ADR、library 切割、conformance suite 設計的錨點。下游已存在的 #92 (dxedit)、#88 (R→WordBuilderSwift)、#90 (che-pptx-mcp) 是這個架構的特例 / front-end,不是替代品。

Type

feature (architectural foundation)

Priority

P2 — 策略性方向設定,不擋當前論文 rescue,但決定後續所有 OOXML 工具的設計約束。

討論軌跡(由淺入深)

階段 1:論文是 use case,不是目標

最初討論從「thesis/main.tex 自包含化」「pandoc 為何 docx 生得乾淨」延伸。觀察:

  • pandoc 乾淨的根本原因:從 AST 生成 + reference.docx 模板 + write-only(不必處理 round-trip)
  • rescue 路徑難的根本原因:在已腐爛 docx 上動刀,要保留所有未知 XML(sectPr、comments、watermark、custom styles、vendor extensions)同時精準改某幾段
  • 關鍵體悟:論文 rescue 是 OOXML library 的真實 use case,library 才是真正的目標,thesis 是驗收測試

階段 2:三條路徑的選擇

路徑 模型 困難度
A. Swift 是 source-of-truth(.tex 模式) 單向 Swift → docx 中 — 沒 round-trip 問題,但既有 docx 要先轉
B. 完全雙向 (radical translation) 任意 docx ↔ Swift 高 — 但是真正的目標
C. 混合(自動 decompile 後 hand-craft) 一次性 docx → Swift,之後 Swift 為主 低,但放棄雙向

決定:走路徑 B(radical translation)

階段 3:Quine indeterminacy + canonical-identity

Radical translation(Quine 1960)的核心觀察:翻譯本質上 underdetermined。同一份 OOXML 可以有多種 Swift 表達都符合行為,設計時必須主動選擇保留哪種等價:

級別 契約 評估
Byte-identity serialize(parse(x)) == x byte-by-byte ❌ 不可能(XML 多種合法寫法)
Canonical-identity parse → mutate → serialize 後,未動到的部分經 c14n 後完全相同 library 的核心契約
Semantic-equivalence Word 渲染相同即可 ⚠️ 太弱,會丟資訊

例:<w:b/> / <w:b w:val="true"/> / <w:b w:val="1"/> 三種同語意,canonical-identity 表示「使用者輸入哪一種,我們就保留哪一種,直到 mutation 明確改變它」。

階段 4:Tree + Lens(不是 Struct serialization)

模型 結果
Struct serialization(Document.sections: [Section],Codable) ❌ 未知 XML 在 parse → struct 時消失,無法 round-trip
Tree + Lens ✅ Layer 0 保真 tree,Layer 1 typed lens 不擁有資料,只讀寫 backing tree

這是 swift-syntax / Roslyn / lxml / JetBrains PSI 共通的 reference 架構。

// Layer 0: lossless tree (保留 attribute 順序、whitespace、unknown namespace)
struct OOXMLNode {
    let qname: QualifiedName
    let attributes: OrderedDict<QualifiedName, String>
    let children: [Child]
    enum Child { case element(OOXMLNode); case text(String); case comment(String); case pi(String) }
    let trivia: Trivia
}

// Layer 1: typed lens (NodeRef 是指向 Layer 0 的 cursor,不擁有資料)
struct Run {
    let backing: NodeRef
    var bold: Bool {
        get { backing.findChild(.w("rPr"))?.findChild(.w("b")) != nil }
        nonmutating set { ... mutate Layer 0 ... }
    }
}

階段 5:核心升級 — 從 state-isomorphism 升到 edit-isomorphism

使用者觀察:

「每一個在 word 的編輯都要在 swift 上有一個對等的 isomorphism」

這是強得多的契約:不只 state 雙射,edit operation 也必須雙射。Categorical 重述:存在 fully faithful functor F 從 (category of docx states + Word edits) 到 (category of Swift representations + Swift edits)。

        word_edit_e
docx ──────────────► docx'
  │                     │
τ │                     │ τ           (τ 是 docx → Swift 的翻譯)
  ▼                     ▼
swift ───────────────► swift'
        swift_edit_e'

含義:library 的核心 type 不是 Document,是 Edit

之前 之後
Document first-class Edit first-class
Lens setter 偷偷改 tree Lens setter 回傳 Edit value,由 transaction 應用
Save = serialize Document Save = replay edit history → serialize 結果
一份 .swift 描述「文件長什麼樣」 一份 .swift 描述「文件如何被建構」
// 兩層 edit algebra
enum WordEdit: Edit {  // 高層 — 對應 Word UI 動作
    case insertText(at: Position, text: String)
    case applyRunFormat(Range, RunFormat)
    case applyParagraphStyle(at: ParagraphRef, style: StyleId)
    case insertTable(at: Position, rows: Int, cols: Int)
    case insertEquation(at: Position, omml: String)
    case acceptRevision(RevisionId)
    // ... ~50–200 cases, mirrors Word UI commands
}

enum OOXMLEdit: Edit {  // 低層 — XML mutation primitives
    case insertElement(at: Path, element: Node)
    case deleteElement(at: Path)
    case setAttribute(at: Path, qname: QName, value: String)
    case removeAttribute(at: Path, qname: QName)
    case renameElement(at: Path, qname: QName)
    case mergeAdjacentElements(at: Path)
    case splitElement(at: Path, where: Position)
    // ... ~30 primitives, full XML tree mutation algebra
}

// WordEdit 編譯成 [OOXMLEdit]
extension WordEdit {
    func lower() -> [OOXMLEdit] { ... }
}

階段 6:免費獲得的能力

把 Edit 做 first-class 後,自動獲得(without 額外設計):

能力 機制
Undo / redo 每個 Edit 有 inverse()
Edit replay Document = empty `
可序列化編輯歷史 Edit 是 Codable
Diff 兩份 docx 找出最短 edit sequence transform A → B
多人協作 OT/CRDT 在 Edit 層 merge
Audit log edit history = 完整變更紀錄
Time-travel debugging 退到任何中間狀態
AI editing AI 改 typed Edit,不改 raw OOXML

Architectural Layers

┌──────────────────────────────────────────────────────┐
│ Layer 4: User authors / AI edits                     │   thesis.swift
├──────────────────────────────────────────────────────┤
│ Layer 3: DSL (result builder, optional frontend)     │   Document { Chapter { ... } }
├──────────────────────────────────────────────────────┤
│ Layer 2: Semantic API (Word-UI-mirroring)            │   doc.applyBold(.lastWord)
│           = WordEdit + high-level lenses             │
├──────────────────────────────────────────────────────┤
│ Layer 1: Typed lens (Run, Paragraph, Section)        │   run.bold = true
│           backed by Layer 0, returns OOXMLEdit       │
├──────────────────────────────────────────────────────┤
│ Layer 0: Lossless OOXML tree                         │   OOXMLNode + trivia
│           parse(bytes) → Tree → serialize → bytes    │
│           c14n round-trip identity                   │
└──────────────────────────────────────────────────────┘

CD Discipline (Methodology)

correction comment 4376317532 提取——fully faithful functor 是抽象 math claim,commutative diagram 是工程實踐,兩者必須分開。CD methodology 是這個 library 設計的 review gate:每個 WordEdit / OOXMLEdit case 的 PR 必附 CD diagram + commute 證明,reviewer 檢查不過就退回。

CD methodology 給的 6 樣具體東西(functor abstract alone 沒給的)

用途 具體 mechanism functor abstract 的差距
Verification spec generator 對每個 edit case e,CD 自動定義驗證義務:τ(e_word(s)) == e_swift(τ(s)) 對所有 s 成立 functor 只說「存在這樣的 e_swift」,沒說怎麼測
Documentation standard 每個新 case 的 PR 必附 CD ASCII diagram(像 IETF RFC 的 ladder) functor 文件可能只有 type signature
Test generator(property-based) CD 兩條 path 直接生成 QuickCheck-style property:∀s. f₁(s) == f₂(s) 沒 CD 就要手寫 property,易漏 corner case
Incompatibility-surface detector 畫不出 commute 的 CD = edit 設計有 hidden state / cross-layer leak / context-dependent — 強迫設計暴露 functor abstraction 隱藏這些 leak
Compositional reasoning CD 可組合:e₁ ∘ e₂ 的 CD = paste e₁ CD 和 e₂ CD 用文字推 composition 容易漏
Cross-layer cube 兩層 edit algebra(WordEdit / OOXMLEdit)對應 3D cube CD,6 個面都要 commute 只有 functor type 看不到 lower() 的 naturality

Canonical example — WordEdit.applyBold 的 CD obligation

WordEdit.applyBold(range)
            │
docx_state ─┼──────────► docx_state'    (Word UI: select range + Cmd-B)
            │                  │
         τ │                  │ τ
            │                  │
            ▼                  ▼
swift_state ────────────► swift_state'  (Swift: state.runs[range].bold = true)
            │
        WordEdit.applyBold(range)
                ↓ lower()
          [OOXMLEdit.insertElement(at: rPr_path, element: <w:b/>)]

CD obligation 1: Word UI commute(上面那個方塊)
CD obligation 2: lower() 對任意 state 與此 OOXMLEdit list 結果等價
                 (內層 OOXMLEdit composition 也是個 CD)

如果 range 跨 paragraph 邊界,CD 變成「splittedRange + per-paragraph apply」的 multi-step CD——若無法畫成 commute,代表 applyBold 設計有 boundary ambiguity,必須先解決才能進 master(blocking review gate)。

Reference works

  • Pierce, Benjamin C. Basic Category Theory for Computer Scientists(MIT Press, 1991)— CD as proof obligation 的經典處理
  • Bird, Richard & de Moor, Oege. Algebra of Programming(Prentice Hall, 1997)— categorical CD 在 program derivation 的應用,與本 issue 的 edit-algebra 同源

Prior Art (macdoc/docs/ existing design corpus)

note comment 4376298416 確認:這個 issue 的大部分技術主張已經在既有 docs 形式化,#99 真正新增的只有 categorical 抽象層級 + CD discipline + Edit-as-first-class type + two-layer algebra + Word UI 對 isomorphism 驗證 framework 五件事(見上面 Discussion §6 與 CD Discipline 章節)。下表把每個 #99 主張對應到既有 docs 段落,後續 ADRs 起草時應直接引用:

#99 核心主張 既有 docs 對應 狀態
Round-trip canonical-identity 為 library 核心契約 lossless-conversion.md §0「公理: convert⁻¹(convert(w)) ≡ w byte-identical」、§0.3「Round-Trip Test 為最終驗收標準」 已是 macdoc 第零原則
Quine indeterminacy → canonical forms lossless-conversion.md §0.4「Galois Connection 與 Retraction」、§0.5「Canonical Forms (MD*)」、§0.5.3「雙層架構」 已形式化,含 retraction (f∘g)² = f∘g
Tree + Lens > Struct serialization structural-editing-paradigm.md §3「Dirty-Tracked Overlay Save」、§4「macdoc 完成了任務,其他工具完成不了」、§7「lossless-conversion 與 editing-paradigm 互補」 已實作 ooxml-swift v0.13.0 onwards
為什麼通用 AST 會失真 functional-correspondence.md §2「資訊瓶頸的形式化: 若 π non-injective,組合 τ ∘ π 必然丟失資訊」、§2.5「瓶頸定理」 已有形式化證明
保留所有未知 XML(vendor extensions、theme、people、Zotero customXml...) structural-editing-paradigm.md §1「真實世界 .docx 是什麼樣子」(34 xmlns、theme1.xml、people.xml、commentsExtended、嵌入字型 odttf、Zotero customXml)、§4 對照表 已實作 + 已驗證
三個驗證層級(Level 1/2/3) lossless-conversion.md §0.4.3「Retraction / Canonical Bijection / Perfect Bijection」 已形式化,含實務 diagnostic 流程
兩條 invariants(unmodified bit-exact + modified content-equality) structural-editing-paradigm.md §10「寫給未來的自己」(Invariant 1 v0.13.0、Invariant 2 v0.20.1)、§3.1「Modified parts content-equality」 已收斂為 architectural primitive,9 sub-cycles 驗證(#56 R5-CONT-4 / #58 A / #59 B / #60 C / #65 D / #66 E)
Conformance suite(corpus + fuzz + c14n diff) structural-editing-paradigm.md §3.1「cross-cutting matrix-pin LOAD-BEARING across 5 preservation classes」、§9「可 falsify 的 claim 設計」 已有 NTPU thesis fixture + 5-class ratio floors

尚未細讀的 docs(後續 follow-up)

  • formal-information-theory.md
  • philosophy.md
  • modular-architecture.md
  • no-overwrite-principle.md

可能還有更多 prior art 待擷取。

時程估計校準後果

Body「時程估計」章節原本說「Layer 0 lossless tree 3-6 個月」過度悲觀。v0.13.0 onwards 已 ship overlay save、v0.20.3 ship 5 preservation classes。實際剩下的工作是「從 architectural primitive 升級到 explicit Edit type」+「擴大 edit operation surface area」+「CD discipline 上手成本(2-4 週)」,範圍小很多。後續可能需要重寫該章節。

跟既有 issues 的關係

Issue 在這個架構的位置 為何不重複
#92 dxedit (declarative docx-edit CLI) Layer 3 frontend(YAML manifest → WordEdit script) dxedit 是某種 DSL 投影,需要 Layer 1/2 支撐
#88 R → WordBuilderSwift script generator Layer 4 caller R 端產出的 .swift 必須在 Layer 2 / 3 寫,否則 round-trip 不保
#90 che-pptx-mcp + Swift workflow 同型架構應用到 PPTX(OOXML 親戚) OOXMLEdit 可重用,Word/Pptx 特化在 Layer 1+
word-builder-swift 0.9.0(#71 已 ship) Layer 2 write-only 的雛形 目前還是 struct-serialization 模型,要往 lens 模型升級才能合進來

Reference Architecture(設計時要細讀)

專案 為什麼看
swift-syntax Apple 自己的 lossless Swift parser;每個 token 帶 leading/trailing trivia;mutation 是 SyntaxRewriter — 直接抄這個架構到 OOXML
Roslyn (C#) 同樣理念,更老更成熟,Microsoft 寫了大量「為什麼這樣設計」的部落格
tree-sitter C library,多語言 incremental parsing,CST 保留 trivia
lxml (Python) XML round-trip 的金標準,雖然不是 typed
JetBrains PSI trivia + typed view 同層架構
Microsoft OpenXML SDK 完整 schema,但 struct serialization,失真案例可參考為反面教材
python-docx 普及但 lossy,避免重蹈覆轍
pandoc Writers.Docx reference template + write-only,證明可行的 minimum 等級

ADRs to Draft(此 issue 的 follow-ups)

每個下面項目應該成為一份獨立 ADR:

  • ADR-001:Round-trip 契約定為 canonical-identity(對齊 swift-syntax)
  • ADR-002:核心 type 是 Edit 而非 Document(fully faithful functor 為設計約束)+ 規範 PR 必附 CD diagram + commute 證明(見 CD Discipline 章節)
  • ADR-003:兩層 edit algebra(WordEdit / OOXMLEdit),lower() 為兩層橋接
  • ADR-004:Module 切割 — OOXMLSyntax(L0/L1)vs OOXMLSemantic(L2)vs OOXMLDSL(L3)
  • ADR-005:Vendor extension 政策 — 一切 preserve,選定 namespace(w14, m14, v) 給 typed lens
  • ADR-006:Mutation API 風格 — immutable 還是 nonmutating set on backing tree
  • ADR-007:Conformance suite 規格 — corpus 來源、fuzz 策略、c14n diff tooling
  • ADR-008:Word-UI 自動化 isomorphism 驗證 — macOS AppleScript / Office.js 對照測試 framework
  • ADR-009:Commutative-diagram authoring guidelines — diagram 規範、ASCII art 範本、PR template、tooling 建議(見 CD Discipline 章節)

驗證(Forward + Backward)

宣稱 isomorphism 必須能驗證。對每個 WordEdit case:

  • Forward:對任意 docx state s,serialize(case.apply(to: parse(s))) ≡ Word 開啟 s 後執行對應 UI 動作的結果(透過 Office Automation 自動化測試)
  • Backward:對任意 docx pair (s, s') 是 Word 一次 UI 動作的結果,存在唯一的 WordEdit case 使得 case.apply(to: parse(s)) 產出 s'

這需要建立一個對 Word.app 的 differential testing framework — 是大工程,但是宣稱 isomorphism 的代價。屬 ADR-008。

設計挑戰(已知,不要假裝沒有)

  1. Edit 粒度:太細變百萬條 edit 難用,太粗失 isomorphism。Word UndoRecord 是 reference,但不可全抄
  2. Word 隱性副作用:smart quotes、auto-capitalize、AutoCorrect — 要不要納入 isomorphism?
  3. State-dependent edits:Bold 按鈕在已 bold 選取上是 toggle off — edit 帶 context 還是 immediate-mode?
  4. Position 表達:path-based vs ID-based。多人協作只能 ID-based
  5. VBA / Macro 執行:不納入(Turing-complete subsystem,不在 OOXML 規格內)
  6. 效能:lossless tree + lens setter return Edit 的開銷 — 大檔案要測
  7. Vendor extension:Word 寫 mc:AlternateContent 處理 forward compatibility,要 model 這個機制

時程估計(校正後)

Milestone 時間
Layer 0 lossless tree(任意 docx parse → serialize c14n-identical) 3–6 個月
OOXMLEdit primitives(~30 case,full coverage) 6–12 個月
WordEdit primitives(~50 case,常用動作) 跟 OOXMLEdit 同期
Conformance + isomorphism test framework 6–12 個月
WordEdit 涵蓋 ~80% Word UI 動作 18–24 個月
AI editing 流暢(AI 直接在 Edit 層操作) 同上
完整覆蓋 Word UI(含 track changes、content control、SmartArt) 3–5 年

對 ooxml-swift / che-word-mcp 的具體影響

現況 升級方向
ooxml-swift 多半 ad-hoc XML 操作 + 高層 API 砍掉重練成 Layer 0 + 1 + 2 三層
Round-trip 「希望不壞」 Round-trip 「契約保證」+ 自動 conformance test
一個 monorepo / single module 拆 OOXMLSyntax / OOXMLSemantic / OOXMLDSL
881 個現有 tests 加 fuzzing layer:隨機真實 docx parse → serialize → c14n diff,diff 應為空
che-word-mcp 工具直接呼叫 ooxml-swift mutation 改為產出 Edit value,由 transaction 層 apply

Differentiation(為什麼這個 library 值得做)

目前沒有任何 OOXML library 提供:

  • Roslyn-level 保真 (canonical round-trip identity)
  • Edit-as-first-class 設計
  • Compile-time invariant via Swift type system(phantom type、enum、property wrapper)

對應的市場空缺:

  • python-docx — lossy
  • MS OpenXML SDK — struct serialization,vendor extension 不完整
  • docx4j — JAXB 同樣問題
  • Aspose.Words — 閉源 + 內部 model
  • pandoc — write-only
  • Apple ecosystem — 沒有任何 native Swift OOXML library

Apple 生態 + radical translation + edit-isomorphism = 沒人佔住的位置

可發 paper 的 contribution:「The first OOXML library with formally-verified Word-edit isomorphism」。

Source

This issue was filed via /idd-issue after a Claude Code design discussion on 2026-05-05. The discussion progressed through:

  1. Thesis docx self-contained main_full.tex generation
  2. Why pandoc-generated docx is clean(write-only + reference template)
  3. Strategic shift: thesis is use case, library is goal
  4. Three architectural paths(A: source-of-truth DSL / B: bidirectional radical translation / C: hybrid)
  5. User's choice: Path B(radical translation)
  6. Design implications: canonical-identity round-trip + Tree+Lens architecture
  7. User's key insight:edit-isomorphism > state-isomorphism (fully faithful functor)
  8. Implications:Edit as first-class type、two-layer edit algebra、verification framework

Linked-Context Siblings Filed (v2.48.0+ #529)

(none — no orphan sibling mentions in linked context;all design points captured in this single comprehensive architectural issue)


Current Status

Phase: created
Last updated: 2026-05-05 by idd-update(triggered after idd-comment correction)

Key Decisions

  • Prior art exists in macdoc/docs/feat(architecture): radical translation OOXML library — Word↔Swift edit-isomorphism (fully faithful functor) as core contract #99 是既有設計文件的 categorical 抽象化,不是新設計。lossless-conversion.md §0(perfect reversibility 公理)、§0.4(Galois Connection / retraction)、§0.5(Canonical Forms MD*)已形式化 round-trip 契約;structural-editing-paradigm.md §3+§10(dirty-tracked overlay save + Invariants 1+2)已實作 Tree+Lens 模型;functional-correspondence.md §2(資訊瓶頸定理)已論證 struct serialization 為何失真。詳見 note comment
  • feat(architecture): radical translation OOXML library — Word↔Swift edit-isomorphism (fully faithful functor) as core contract #99 真正 novel 的 contribution = 5 件事(經 correction comment 修正前為 4 件):
    1. Categorical formalization(fully faithful functor 統一既有 invariants)— 數學 claim
    2. Commutative diagram (CD) as engineering methodology — functor 的工程實踐配對:每個新 edit case 的 PR 必附 CD + commute 證明,作為 review gate(這是上一條 note 漏算的)
    3. Edit-as-first-class type(enum WordEdit / enum OOXMLEdit value type with inverse() / compose() / Codable / replay)
    4. Two-layer edit algebra(WordEdit 高層 / OOXMLEdit 低層 + lower() 為兩層橋接,3D cube CD 6 面 commute)
    5. Word UI 對 isomorphism 驗證 framework(對 Word.app 的 differential testing,forward + backward 雙向)
  • 時程估計需要重新校準 — body 的「Layer 0 lossless tree 3-6 個月」過度悲觀,因為 v0.13.0 onwards 已 ship overlay save、v0.20.3 ship 了 5 preservation classes。實際剩下的工作是「從 architectural primitive 升級到 explicit Edit type」+「擴大 edit operation surface area」+「CD discipline 上手成本(2-4 週)」,範圍小很多

Scope Changes

  • Body 8 個 ADR checklist 需要修正:
    • ADR-001(round-trip 契約)→ 直接引用 lossless-conversion.md §0
    • ADR-002 升級為「定義 Edit type + 規範 PR 必附 CD diagram + commute 證明」
    • ADR-007(conformance suite)→ 擴展現有 NTPU thesis fixture matrix-pin,新增 CD obligation 自動產生 property-based test
    • 新增 ADR-009: Commutative-diagram authoring guidelines(diagram 規範、ASCII art 範本、tooling 建議)
  • 未在 body 列出的相關 docs 需要 follow-up 細讀:formal-information-theory.mdphilosophy.mdmodular-architecture.mdno-overwrite-principle.md

Blocking

  • (none) — 此 issue 為策略性方向設定,不擋當前工作

Commits

  • (none yet) — issue 建立後尚未產生任何實作 commit

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions