# 帳票設計書 7-カスタムフォーマットファイル出力

## 概要

本ドキュメントは、Decap CMSにおけるカスタムフォーマットによるファイル出力機能の設計を記述する。registerCustomFormat APIで登録されたカスタムフォーマットにより、任意の拡張子とフォーマッター（fromFile/toFile）を自由に定義し、独自形式のファイル出力を実現する仕組みを定義する。

### 本帳票の処理概要

本帳票は、Decap CMSの組み込みフォーマット（YAML、TOML、JSON、Frontmatter）以外の独自ファイル形式でコンテンツを出力する拡張機能を定義する。

**業務上の目的・背景**：Decap CMSは多様な静的サイトジェネレータやフレームワークと連携するため、組み込みフォーマットだけではカバーできないファイル形式のニーズが存在する。カスタムフォーマットAPIにより、利用者は独自のシリアライズ/デシリアライズロジックを登録でき、XMLやProtobuf、独自DSLなどの形式にも対応可能となる。

**帳票の利用シーン**：CMS初期化時にregisterCustomFormatを呼び出してカスタムフォーマットを登録し、コレクション設定でそのフォーマット名を指定する。エントリ保存時に登録されたカスタムフォーマッタのtoFileメソッドが呼び出される。

**主要な出力内容**：
1. カスタムフォーマッタのtoFileメソッドの戻り値（任意の文字列形式）
2. 登録時に指定された拡張子でのファイル生成
3. fromFileメソッドによるパース処理（読み込み時）

**帳票の出力タイミング**：エディタ画面で「Save」ボタンを押下した際、resolveFormatでカスタムフォーマッタが選択され、登録されたtoFileメソッドが呼び出される。

**帳票の利用者**：CMSプラグイン開発者、カスタムフォーマット利用者

## 帳票種別

データファイル出力（カスタム形式テキストファイル）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | エディタ画面 | /collections/{collection}/entries/{slug} | Saveボタン押下 |
| - | 新規エントリ作成画面 | /collections/{collection}/new | Saveボタン押下 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | カスタム（registerCustomFormat時に指定されたextension） |
| 用紙サイズ | N/A（テキストファイル） |
| 向き | N/A |
| ファイル名 | コレクション設定のslugテンプレート + カスタム拡張子 |
| 出力方法 | Gitリポジトリへのコミット（バックエンドAPI経由） |
| 文字コード | カスタムフォーマッタの実装に依存 |

### PDF固有設定

N/A

### Excel固有設定

N/A

## 帳票レイアウト

### レイアウト概要

カスタムフォーマッタのtoFileメソッドの実装に完全に依存する。フォーマッタはfromFile/toFileインターフェースを実装する必要がある。

```
┌─────────────────────────────────────┐
│ カスタムフォーマッタのtoFile()        │
│ の出力結果（任意の文字列形式）        │
│                                     │
│ 例: XML, CSV, 独自DSL等              │
└─────────────────────────────────────┘
```

### ヘッダー部

カスタムフォーマッタの実装に依存

### 明細部

カスタムフォーマッタの実装に依存

### フッター部

カスタムフォーマッタの実装に依存

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| エントリデータ | エディタで入力された全フィールドデータ | Yes |
| カスタムフォーマット登録 | registerCustomFormatによる事前登録 | Yes |

### ソート順

カスタムフォーマッタの実装に依存

### 改ページ条件

N/A

## データベース参照仕様

### 参照テーブル一覧

N/A（Gitリポジトリベース）

### テーブル別参照項目詳細

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| フォーマット登録 | `registry.formats[name] = { extension, formatter }` | N/A | registerCustomFormatで名前、拡張子、フォーマッタを登録 |
| フォーマッタ取得 | `registry.formats[name]?.formatter` | N/A | getFormatterで名前からフォーマッタを取得 |
| 拡張子マッピング | `Object.entries(registry.formats).reduce(...)` | N/A | getCustomFormatsExtensionsで全カスタムフォーマットの拡張子マップを生成 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[CMS初期化] --> B[registerCustomFormat呼び出し]
    B --> C[registry.formatsに登録]
    C --> D[エディタでSave操作]
    D --> E[resolveFormat でフォーマッタ決定]
    E --> F[formatByName でカスタムフォーマッタ取得]
    F --> G[getCustomFormatsFormatters呼び出し]
    G --> H[カスタムフォーマッタ.toFile呼び出し]
    H --> I[バックエンドAPIでGitコミット]
    I --> J[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| フォーマット未登録 | 指定されたフォーマット名がregistry.formatsに存在しない場合 | `No formatter available with name: {name}` | registerCustomFormatで事前に登録する |
| フォーマッタエラー | カスタムフォーマッタのtoFileが例外を投げた場合 | フォーマッタ実装依存 | フォーマッタの実装を確認 |
| バックエンドエラー | Gitコミットに失敗した場合 | バックエンド固有のエラーメッセージ | ネットワーク接続・認証情報を確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | カスタムフォーマッタの実装に依存 |
| 目標出力時間 | カスタムフォーマッタの実装に依存 |
| 同時出力数上限 | 1件（エディタ操作は逐次処理） |

## セキュリティ考慮事項

- カスタムフォーマッタはCMS初期化時に登録されるサードパーティコードであるため、信頼性の確認が必要
- registerCustomFormatは名前の重複チェックを行わず、後から登録したものが優先される（上書き可能）
- カスタムフォーマッタのfromFile/toFileメソッドは任意のコードを実行可能であるため、入力データの検証はフォーマッタ実装者の責任となる

## 備考

- registerCustomFormat(name, extension, formatter)で3つの引数を受け取る
- formatterオブジェクトはfromFile(content)とtoFile(data)メソッドを持つ必要がある
- getCustomFormatsExtensions()で全カスタムフォーマットの{name: extension}マップを取得可能
- getCustomFormatsFormatters()で全カスタムフォーマットの{name: formatter}マップを取得可能
- formatByName関数内でgetCustomFormatsFormatters()の結果が組み込みフォーマッタとマージされる（formats.ts 52行目）
- getFormatExtensions関数内でgetCustomFormatsExtensions()の結果が組み込み拡張子とマージされる（formats.ts 29行目）

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

カスタムフォーマットの登録データ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | registry.js | `packages/decap-cms-core/src/lib/registry.js` | registryオブジェクトのformatsプロパティ（34行目: `formats: {}`）を確認し、カスタムフォーマットの格納構造を理解 |

**読解のコツ**: registry.jsはDecap CMSの全グローバルレジストリを管理するファイルであり、カスタムフォーマット関連は末尾付近（289-312行目）に定義されている。formatsオブジェクトは`{ [name]: { extension, formatter } }`の構造を持つ。

#### Step 2: 登録APIを理解する

カスタムフォーマットの登録・取得APIを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | registry.js | `packages/decap-cms-core/src/lib/registry.js` | registerCustomFormat関数（289行目）でname, extension, formatterの3引数を受け取り、registry.formatsに登録する処理を確認 |
| 2-2 | registry.js | `packages/decap-cms-core/src/lib/registry.js` | getCustomFormats（293行目）、getCustomFormatsExtensions（297行目）、getCustomFormatsFormatters（304行目）の取得関数を確認 |

**主要処理フロー**:
1. **289行目**: `registerCustomFormat(name, extension, formatter)` - formatsレジストリに登録
2. **290行目**: `registry.formats[name] = { extension, formatter }` - 名前をキーにして格納
3. **297-301行目**: `getCustomFormatsExtensions()` - reduce()で{name: extension}マップを構築
4. **304-308行目**: `getCustomFormatsFormatters()` - reduce()で{name: formatter}マップを構築
5. **310-312行目**: `getFormatter(name)` - 特定フォーマットのformatterを取得

#### Step 3: フォーマット解決でのカスタムフォーマット統合を理解する

組み込みフォーマットとカスタムフォーマットがマージされる仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | formatByName関数（42-58行目）でgetCustomFormatsFormatters()の結果を組み込みフォーマッタにスプレッドでマージする処理（52行目）を確認 |
| 3-2 | formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | getFormatExtensions関数（28-30行目）でgetCustomFormatsExtensions()の結果を組み込み拡張子にマージする処理を確認 |

**主要処理フロー**:
- **42行目**: `formatByName(name, customDelimiter)` 関数
- **43-53行目**: formattersオブジェクトに組み込みフォーマッタを定義し、`...getCustomFormatsFormatters()` でカスタムフォーマッタをマージ
- **54行目**: `if (name in formatters)` で名前の存在を確認
- **55行目**: `return formatters[name]` でフォーマッタを返却
- **57行目**: 存在しない場合は`throw new Error`

### プログラム呼び出し階層図

```
CMS初期化時:
    registerCustomFormat (registry.js:289)
        +-- registry.formats[name] = { extension, formatter }

エントリ保存時:
    resolveFormat (formats.ts:66)
        |
        +-- formatByName (formats.ts:42)
                |
                +-- getCustomFormatsFormatters (registry.js:304)
                |       +-- Object.entries(registry.formats).reduce(...)
                |
                +-- formatters[name] (カスタムフォーマッタ)
                        |
                        +-- formatter.toFile (カスタム実装)
```

### データフロー図

```
[登録フェーズ]
CMS.registerCustomFormat(name, ext, formatter)
    |
    +-> registry.formats[name] = { extension: ext, formatter }

[出力フェーズ]
                                [処理]                          [出力]

entry.data (Object) -----> formatByName(name) -----> カスタム文字列
                                |                    (.{extension})
                           getCustomFormatsFormatters()
                                |
                           formatter.toFile(data)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| registry.js | `packages/decap-cms-core/src/lib/registry.js` | ソース | カスタムフォーマット登録・取得API（registerCustomFormat, getCustomFormats, getCustomFormatsExtensions, getCustomFormatsFormatters, getFormatter） |
| formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | ソース | フォーマット解決・カスタムフォーマッタとの統合（formatByName, getFormatExtensions） |
