# 機能設計書 19-HTML設定

## 概要

本ドキュメントは、EtherpadにおけるHTML設定機能（setHTML）の設計仕様を定義する。この機能は、指定されたパッドのコンテンツをHTMLから設定（インポート）するためのAPIを提供する。

### 本機能の処理概要

HTML設定機能は、HTML形式のコンテンツをパースしてパッドのテキストと書式情報として設定する機能である。HTMLをEtherpadのAText形式に変換し、パッドの内容を置き換える。接続中のすべてのクライアントに変更が同期される。

**業務上の目的・背景**：
外部システムで作成されたHTML形式のドキュメントをEtherpadにインポートしたい場合に使用される。Webページのコンテンツ、リッチテキストエディタからの出力、Word文書のHTML変換など、様々なソースからのHTMLをEtherpadに取り込むことができる。

**機能の利用シーン**：
- Webページからのコンテンツインポート
- 他のエディタからのコンテンツ移行
- HTMLメールの内容をパッドに取り込み
- CMS連携でのコンテンツ設定
- テンプレートHTMLによるパッド初期化

**主要な処理内容**：
1. 対象パッドの存在確認
2. HTMLパラメータの検証
3. HTMLのパースとサニタイズ
4. HTMLからAText形式への変換
5. パッドへのコンテンツ設定
6. 接続中クライアントへの変更通知

**関連システム・外部連携**：
- REST API（POST /api/2/pads/html）を通じて外部システムから呼び出し可能
- Socket.IO経由で接続中クライアントにリアルタイム反映
- ImportHtmlモジュールを使用してHTML変換

**権限による制御**：
- APIキーまたはOAuth2トークンによる認証が必要
- パッドへの書き込み権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はAPIのみで提供され、直接関連する画面はない |

## 機能種別

データ更新（Update操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| padID | string | Yes | HTMLを設定するパッドのID | 正規表現に一致、存在するパッドであること |
| html | string | Yes | 設定するHTMLコンテンツ | 文字列型であること、有効なHTMLであること |
| authorId | string | No | 変更を行う著者のID（デフォルト: 空文字列） | 任意の文字列 |

### 入力データソース

REST API経由でのHTTPリクエスト（POST /api/2/pads/html）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| code | number | 結果コード（0: 成功、1: エラー、4: 認証エラー） |
| message | string | 結果メッセージ（"ok" または エラーメッセージ） |
| data | null | 成功時はnull |

### 出力先

- HTTPレスポンス（JSON形式）
- Socket.IO経由で接続中クライアントに変更通知

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ POST /api/2/pads/html
2. 認証チェック
   └─ APIキーまたはOAuth2トークンの検証
3. パラメータ抽出
   └─ padID, html, authorIdの取得
4. HTML検証
   └─ 文字列型であることを確認
5. パッド取得
   └─ getPadSafe(padID, true)
6. HTML設定
   └─ importHtml.setPadHTML(pad, html, authorId)
   └─ HTMLパース、変換、リビジョン追加
7. クライアント通知
   └─ padMessageHandler.updatePadClients(pad)
8. レスポンス返却
   └─ {code: 0, message: "ok", data: null}
```

### フローチャート

```mermaid
flowchart TD
    A[開始: setHTML API呼び出し] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D{htmlは文字列?}
    D -->|No| E[400 html is not a string]
    D -->|Yes| F[パッド存在確認]
    F -->|存在しない| G[400 padID does not exist]
    F -->|存在する| H[HTMLサニタイズ]
    H --> I[importHtml.setPadHTML]
    I -->|HTMLパース失敗| J[400 HTML is malformed]
    I -->|成功| K[パッドにテキスト設定]
    K --> L[appendRevision]
    L --> M[updatePadClients]
    M --> N[200 OK]
    N --> O[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-019-01 | 完全置換 | 既存コンテンツは完全に新しいHTMLで置き換えられる | 常時 |
| BR-019-02 | HTMLサニタイズ | 入力HTMLはサニタイズ・正規化される | 常時 |
| BR-019-03 | 書式保持 | HTML書式（太字、リスト等）はEtherpad形式に変換される | 対応タグの場合 |
| BR-019-04 | リビジョン記録 | 変更は新しいリビジョンとして記録される | 常時 |
| BR-019-05 | 不正HTML拒否 | パースできないHTMLはエラーを返す | HTMLが不正の場合 |

### 計算ロジック

HTML→AText変換のフロー:
1. rehypeでHTMLを正規化（空白圧縮）
2. jsdomでDOMツリー構築
3. contentcollectorでDOM→行・属性に変換
4. Changesetを構築してパッドに適用

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 参照 | pad:{padID} | SELECT | パッドメタデータ取得 |
| 更新 | pad:{padID} | UPDATE | atext, head, pool更新 |
| 挿入 | pad:{padID}:revs:{n} | INSERT | 新リビジョン追加 |
| 更新 | globalAuthor:{authorId} | UPDATE | 著者のパッド関連付け |

### テーブル別操作詳細

#### pad:{padID}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | atext | HTML変換後のテキストとその属性 | setHTML後の状態 |
| UPDATE | head | +1 または +2 | setText + appendRevision |
| UPDATE | pool | 属性情報追加 | HTMLから抽出された書式 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 1 | apierror | パッドが存在しない | 正しいパッドIDを指定 |
| 1 | apierror | htmlが文字列でない | 文字列としてhtmlを送信 |
| 1 | apierror | HTMLが不正（パース失敗） | 有効なHTMLを送信 |
| 4 | authError | 認証情報が無効 | 正しいAPIキーまたはトークンを使用 |

### リトライ仕様

データベース操作に関してはueberDB2のリトライ機構に依存。

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

pad更新とrevs挿入はPromise.allで並列実行される。setTextと同様の整合性特性を持つ。HTMLパース・変換処理は失敗時にDBへの書き込みは行われない。

## パフォーマンス要件

- HTMLサイズに比例した処理時間
- HTMLパース（jsdom）とDOM走査に時間がかかる
- 複雑なネスト構造やテーブルは処理負荷が高い

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

- APIキーまたはOAuth2認証が必須
- 入力HTMLはcleanText処理でサニタイズされる
- スクリプトタグ等の危険な要素は除去される
- パッドの内容を完全に上書きするため、破壊的操作として扱う

## 備考

- すべてのHTMLタグがサポートされるわけではない
- 対応タグ: h1, h2, strong, em, u, s, ol, ul, li 等
- styleやclass属性は無視される
- テーブルやイメージはサポートされない
- 空白はrehype-minify-whitespaceで圧縮される

---

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

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

### 推奨読解順序

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

HTML→AText変換における内部構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ImportHtml.ts | `src/node/utils/ImportHtml.ts` | setPadHTML関数（28-95行目） |
| 1-2 | contentcollector.ts | `src/static/js/contentcollector.ts` | makeContentCollector関数 |

**読解のコツ**: setPadHTMLがHTML変換の中核。rehypeで正規化→jsdomでパース→contentcollectorでDOM走査→Changeset構築の流れ。

#### Step 2: エントリーポイントを理解する

REST APIからの呼び出しフローを追跡する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RestAPI.ts | `src/node/handler/RestAPI.ts` | API定義（1363-1389行目でsetHTMLのマッピング） |
| 2-2 | APIHandler.ts | `src/node/handler/APIHandler.ts` | パラメータ定義（141行目でsetHTMLの引数） |

**主要処理フロー**:
1. **1363-1389行目**: POST /pads/html のルート定義
2. **141行目**: setHTMLの引数（padID, html, authorId）

#### Step 3: ビジネスロジックを理解する

実際のHTML設定ロジックを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | API.ts | `src/node/db/API.ts` | setHTML関数（288-306行目） |
| 3-2 | ImportHtml.ts | `src/node/utils/ImportHtml.ts` | setPadHTML関数（28-95行目） |
| 3-3 | contentcollector.ts | `src/static/js/contentcollector.ts` | collectContent関数 |

**主要処理フロー**:
- **API.ts 290-292行目**: HTML型チェック
- **API.ts 295行目**: パッド取得
- **API.ts 298-302行目**: importHtml.setPadHTML呼び出しとエラーハンドリング
- **API.ts 305行目**: updatePadClients呼び出し
- **ImportHtml.ts 30-33行目**: rehypeで空白圧縮
- **ImportHtml.ts 35-36行目**: jsdomでDOMパース
- **ImportHtml.ts 47-54行目**: contentcollectorでDOM→行変換
- **ImportHtml.ts 93-94行目**: pad.setText + appendRevision

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

```
POST /api/2/pads/html (RestAPI.ts)
    │
    └─ apiHandler.handle() (APIHandler.ts)
           │
           └─ api.setHTML() (API.ts:288-306)
                  │
                  ├─ HTML型チェック (290-292行目)
                  │
                  ├─ getPadSafe(padID, true) (295行目)
                  │
                  ├─ cleanText(html) (299行目)
                  │
                  └─ importHtml.setPadHTML(pad, html, authorId) (299行目)
                         │
                         ├─ rehype().use(minifyWhitespace) (30-33行目)
                         │
                         ├─ jsdom.JSDOM(html) (36行目)
                         │
                         ├─ document.body.appendChild(p) (40行目)
                         │
                         ├─ contentcollector.makeContentCollector() (47行目)
                         │      └─ cc.collectContent(body) (50行目)
                         │             └─ DOM走査して行・属性抽出
                         │
                         ├─ Builder() (73行目)
                         │      └─ Changeset構築
                         │
                         ├─ pad.setText('\n', authorId) (93行目)
                         │
                         └─ pad.appendRevision(changeset, authorId) (94行目)
                  │
                  └─ padMessageHandler.updatePadClients(pad) (305行目)
```

### データフロー図

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

padID         ───────▶ getPadSafe() ──────────────▶ Padオブジェクト
html                         │
authorId                     ▼
                    cleanText(html)
                         │
                         ▼
                    setPadHTML()
                         │
                         ├─▶ rehype正規化
                         │      └─▶ 空白圧縮
                         │
                         ├─▶ jsdomパース
                         │      └─▶ DOMツリー
                         │
                         ├─▶ contentcollector
                         │      ├─▶ 行抽出
                         │      └─▶ 属性抽出
                         │
                         ├─▶ Builder
                         │      └─▶ Changeset
                         │
                         └─▶ pad.setText + appendRevision
                                │
                                ├─▶ pad:{padID}:revs:{n} [INSERT]
                                │
                                └─▶ pad:{padID} [UPDATE]
                                │
                                ▼
                    updatePadClients()
                         │
                         └─▶ Socket.IO通知 ───────▶ 接続クライアント
                                │
                                ▼
                    {code: 0, message: "ok"} ─▶ JSON Response
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| API.ts | `src/node/db/API.ts` | ソース | setHTML関数の実装 |
| ImportHtml.ts | `src/node/utils/ImportHtml.ts` | ソース | setPadHTML関数 |
| contentcollector.ts | `src/static/js/contentcollector.ts` | ソース | DOM→行・属性変換 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | Padクラス、setText/appendRevision |
| Builder.ts | `src/static/js/Builder.ts` | ソース | Changeset構築 |
| RestAPI.ts | `src/node/handler/RestAPI.ts` | ソース | REST APIエンドポイント定義 |
| APIHandler.ts | `src/node/handler/APIHandler.ts` | ソース | API呼び出しハンドリング |
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | クライアント通知 |
