# 機能設計書 67-コンテンツフォーマット対応

## 概要

本ドキュメントは、Decap CMSのコンテンツフォーマット対応機能の設計を定義する。本機能は、YAML、TOML、JSON、Frontmatter（YAML/TOML/JSON）形式のコンテンツファイルのパース（読込）とシリアライズ（書込）をサポートする。

### 本機能の処理概要

コンテンツフォーマット対応機能は、CMSが扱うコンテンツファイルのデータ形式を統一的に管理するフォーマッタ群である。コレクション設定やファイル拡張子に基づいて適切なフォーマッタを選択し、ファイル内容のパースとシリアライズを行う。

**業務上の目的・背景**：静的サイトジェネレータ（Hugo、Jekyll、Gatsby等）はYAML、TOML、JSON、Frontmatter等の様々な形式でコンテンツを管理する。Decap CMSが多様な静的サイトジェネレータと連携するためには、これらの形式を統一的に扱えるフォーマット処理基盤が不可欠である。

**機能の利用シーン**：エントリの読み込み時にファイル内容をパースしてデータオブジェクトに変換、保存時にデータオブジェクトをファイル内容にシリアライズする際に使用される。バックエンドとの間のデータ変換の核となる機能。

**主要な処理内容**：
1. YAML形式のパース・シリアライズ（yamlライブラリ使用）
2. TOML形式のパース・シリアライズ（@iarna/toml + tomlify使用）
3. JSON形式のパース・シリアライズ（JSON.parse/stringify使用）
4. Frontmatter形式のパース・シリアライズ（gray-matterライブラリ使用）
5. フォーマットの自動推論（ファイル拡張子、先頭行による判定）
6. カスタムデリミタ対応
7. キーのソート順指定
8. YAMLコメント保持

**関連システム・外部連携**：バックエンドシステム（GitHub/GitLab等）から取得したファイル内容の変換、カスタムフォーマットレジストリとの連携。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 参照画面 | エントリデータの読込・保存時にフォーマット変換 |
| 7 | エントリ新規作成画面 | 参照画面 | 新規エントリ保存時にフォーマット変換 |

## 機能種別

データ変換 / シリアライズ・デシリアライズ

## 入力仕様

### 入力パラメータ

#### resolveFormat関数

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collection | Collection | Yes | コレクション定義（format, extension, frontmatter_delimiter等） | - |
| entry | EntryObject / EntryValue | Yes | エントリオブジェクト（pathを含む） | - |

#### fromFile関数（各フォーマッタ共通）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| content | string | Yes | ファイル内容文字列 | - |

#### toFile関数（各フォーマッタ共通）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| data | object | Yes | シリアライズ対象のデータオブジェクト | - |
| sortedKeys | string[] | No | キーのソート順 | - |
| comments | Record<string, string> | No | YAMLコメント（YAML形式のみ） | - |

### 入力データソース

- バックエンド: Gitリポジトリから読み込んだファイル内容
- コレクション設定: format、extension、frontmatter_delimiterの各設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| fromFile結果 | object | パースされたデータオブジェクト |
| toFile結果 | string | シリアライズされたファイル内容文字列 |

### 出力先

- Redux Store: パースされたエントリデータ
- バックエンド: シリアライズされたファイル内容

## 処理フロー

### 処理シーケンス

```
1. フォーマット解決（resolveFormat）
   ├─ コレクションのformat設定があればそれを使用
   ├─ ファイルパスの拡張子からフォーマットを推論
   ├─ コレクションのextension設定から推論
   └─ いずれもなければデフォルト（frontmatter）を使用
2. パース（fromFile）
   ├─ YAML: yaml.parse(content)
   ├─ TOML: parseToml(content)
   ├─ JSON: JSON.parse(content)
   └─ Frontmatter: gray-matter(content) → {data, body}
3. シリアライズ（toFile）
   ├─ YAML: yaml.createNode + sortKeys + addComments → doc.toString()
   ├─ TOML: tomlify.toToml(data, {replace, sort})
   ├─ JSON: JSON.stringify(data, null, 2)
   └─ Frontmatter: gray-matter.stringify(body, meta, {engines, format})
```

### フローチャート

```mermaid
flowchart TD
    A[resolveFormat] --> B{collection.format設定?}
    B -->|Yes| C[formatByName]
    B -->|No| D{entry.path拡張子?}
    D -->|Yes| E[extensionFormatters]
    D -->|No| F{collection.extension?}
    F -->|Yes| G[extensionFormatters]
    F -->|No| H[デフォルト: frontmatter]
    C --> I[Formatter]
    E --> I
    G --> I
    H --> I
    I --> J{操作}
    J -->|読込| K[formatter.fromFile]
    J -->|書込| L[formatter.toFile]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-67-01 | フォーマット優先順位 | 1)collection.format、2)ファイル拡張子、3)collection.extension、4)デフォルト(frontmatter)の順で解決 | resolveFormat呼び出し時 |
| BR-67-02 | Frontmatter自動推論 | 先頭行が'---'ならYAML、'+++'ならTOML、'{'ならJSONとして推論 | FrontmatterInfer使用時 |
| BR-67-03 | カスタムデリミタ | frontmatter_delimiterで文字列または[開始, 終了]の配列でカスタムデリミタを指定可能 | frontmatter_delimiter設定時 |
| BR-67-04 | YAMLタイムスタンプ | YAML形式ではカスタムタグ'!timestamp'でDateオブジェクトをISO 8601形式でシリアライズ | YAML形式使用時 |
| BR-67-05 | YAML末尾処理 | YAML fromFileで末尾の'---'をトリム | YAML形式使用時 |
| BR-67-06 | Frontmatter body | Frontmatter形式ではbodyフィールドとメタデータを分離して管理。bodyが空の場合は結果に含めない | Frontmatter形式使用時 |
| BR-67-07 | カスタムフォーマット統合 | getCustomFormatsFormatters()でレジストリ登録のカスタムフォーマッタも含めて解決 | カスタムフォーマット登録時 |
| BR-67-08 | 拡張子マッピング | yml/yaml→yml, toml→toml, json→json, frontmatter/md系→md | 常時 |

### 計算ロジック

- sortKeys: `sortedKeys.indexOf(selector(a))` と `sortedKeys.indexOf(selector(b))` の比較でソート順を決定

## データベース操作仕様

### 操作別データベース影響一覧

該当なし（純粋なデータ変換処理）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | フォーマット未定義エラー | formatByNameで未知のフォーマット名が指定された場合 | `No formatter available with name: ${name}`をthrow |
| - | パースエラー | ファイル内容が対応する形式に合致しない場合 | 各パーサーのエラーをそのまま伝搬 |
| - | Frontmatter推論失敗 | 先頭行が認識できない形式の場合 | console.warnで警告 |

### リトライ仕様

リトライ処理は行わない。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- パース・シリアライズ処理は同期的に実行（大規模ファイルでもブロッキングなし前提）

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

- YAML パースではカスタムタグ（timestampTag）のみを許可し、任意のコード実行を防止
- TOMLパースでは`@iarna/toml`のセキュアなパーサーを使用

## 備考

- Frontmatter形式はgray-matterライブラリを使用しており、独自のparse/stringifyエンジンを注入している
- TOML形式ではAssetProxyのpath出力やdayjsの日付フォーマットに対応する独自outputReplacerを使用

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | helpers.ts | `packages/decap-cms-core/src/formats/helpers.ts` | sortKeys関数の汎用ソートロジック |

**読解のコツ**: フォーマッタは`fromFile(content: string) => object`と`toFile(data: object, ...) => string`の2つのメソッドを持つインターフェース。Frontmatter形式はFrontmatterFormatterクラスで実装される。

#### Step 2: フォーマット解決を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | resolveFormat関数のフォーマット解決優先順位がエントリーポイント |

**主要処理フロー**:
- **15-16行目**: frontmatterFormats定義
- **17-26行目**: formatExtensions - フォーマット名と拡張子のマッピング
- **28-30行目**: getFormatExtensions() - カスタムフォーマット含むマッピング取得
- **32-40行目**: extensionFormatters - 拡張子からフォーマッタへのマッピング
- **42-58行目**: formatByName() - フォーマット名からフォーマッタを取得（カスタム含む）
- **66-97行目**: resolveFormat() - 4段階のフォーマット解決ロジック

#### Step 3: 各フォーマッタを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | yaml.ts | `packages/decap-cms-core/src/formats/yaml.ts` | yamlライブラリ使用。addComments、timestampTag、sortKeysが特徴 |
| 3-2 | toml.ts | `packages/decap-cms-core/src/formats/toml.ts` | parseToml/tomlify使用。outputReplacerでAssetProxy/dayjs対応 |
| 3-3 | json.ts | `packages/decap-cms-core/src/formats/json.ts` | JSON.parse/stringifyの薄いラッパー |
| 3-4 | frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | gray-matter使用。YAML/TOML/JSON Frontmatterに対応 |

**主要処理フロー（yaml.ts）**:
- **7-21行目**: addComments - YAMLノードにコメントを付与
- **23-37行目**: timestampTag - DateオブジェクトのISO 8601変換
- **40-45行目**: fromFile - yaml.parse（末尾'---'トリム付き）
- **47-57行目**: toFile - createNode → addComments → sortKeys → doc.toString()

**主要処理フロー（frontmatter.ts）**:
- **56-73行目**: inferFrontmatterFormat - 先頭行から形式を推論
- **94-136行目**: FrontmatterFormatterクラス - fromFile/toFileの実装
- **101-109行目**: fromFile - gray-matter + bodyフィールド分離
- **112-135行目**: toFile - gray-matter.stringify + 末尾改行トリム

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

```
resolveFormat(collection, entry)
    │
    ├─ formatByName(name, customDelimiter)
    │   ├─ yamlFormatter (yaml.ts)
    │   ├─ tomlFormatter (toml.ts)
    │   ├─ jsonFormatter (json.ts)
    │   ├─ FrontmatterInfer / frontmatterYAML/TOML/JSON (frontmatter.ts)
    │   └─ getCustomFormatsFormatters() (registry.js)
    │
    └─ extensionFormatters[extension]
        └─ 拡張子からフォーマッタ解決

formatter.fromFile(content)
    ├─ yaml: yaml.parse(content, { customTags: [timestampTag] })
    ├─ toml: parseToml(content)
    ├─ json: JSON.parse(content)
    └─ frontmatter: matter(content, { engines: parsers, ...format })

formatter.toFile(data, sortedKeys, comments)
    ├─ yaml: createNode → addComments → sortKeys → doc.toString()
    ├─ toml: tomlify.toToml(data, { replace, sort })
    ├─ json: JSON.stringify(data, null, 2)
    └─ frontmatter: matter.stringify(body, meta, { engines, ...format })
```

### データフロー図

```
[入力]                      [処理]                          [出力]

ファイル内容 ─────▶ resolveFormat() → formatter
(string)               │
                      ▼
               formatter.fromFile(content) ──▶ データオブジェクト
                                                    │
                                                    ▼
               formatter.toFile(data) ◀── データオブジェクト
                       │
                      ▼
               ファイル内容 (string) ──▶ バックエンド保存
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| formats.ts | `packages/decap-cms-core/src/formats/formats.ts` | ソース | フォーマット解決のエントリーポイント |
| yaml.ts | `packages/decap-cms-core/src/formats/yaml.ts` | ソース | YAMLフォーマッタ |
| toml.ts | `packages/decap-cms-core/src/formats/toml.ts` | ソース | TOMLフォーマッタ |
| json.ts | `packages/decap-cms-core/src/formats/json.ts` | ソース | JSONフォーマッタ |
| frontmatter.ts | `packages/decap-cms-core/src/formats/frontmatter.ts` | ソース | Frontmatterフォーマッタ |
| helpers.ts | `packages/decap-cms-core/src/formats/helpers.ts` | ソース | ソートヘルパー関数 |
| registry.js | `packages/decap-cms-core/src/lib/registry.js` | ソース | カスタムフォーマット登録・取得 |
