# 帳票設計書 1-HTMLエクスポート

## 概要

本ドキュメントは、Etherpadのパッド（共同編集ドキュメント）をHTML形式でエクスポートする機能に関する設計書である。ユーザーがパッドの内容を外部で閲覧・編集可能なHTML文書として取得するための仕様を定義する。

### 本帳票の処理概要

HTMLエクスポート機能は、Etherpadのパッドに含まれるリッチテキストコンテンツを標準的なHTML5形式に変換し、ダウンロード可能なファイルとして提供する処理を行う。

**業務上の目的・背景**：Etherpadは共同編集プラットフォームであり、作成したドキュメントを外部アプリケーション（ブラウザ、メールクライアント、他のWebサービス等）で利用したいニーズがある。HTMLエクスポートにより、書式を保持したままドキュメントを汎用的な形式で取り出すことが可能となり、コンテンツの再利用性と相互運用性を確保する。

**帳票の利用シーン**：ユーザーがパッドの編集を完了し、その内容をWebページとして公開したい場合や、メールに添付して共有したい場合、またはローカルに保存してオフラインで閲覧したい場合に利用される。会議議事録、ドキュメント下書き、共同執筆コンテンツなどの用途で頻繁に使用される。

**主要な出力内容**：
1. パッドのテキストコンテンツ（全文）
2. 書式情報（太字、斜体、下線、取り消し線）
3. 見出し構造（h1、h2）
4. リスト構造（番号付きリスト、箇条書きリスト、インデント）
5. ハイパーリンク（URLの自動検出とリンク化）
6. カスタムスタイル（プラグインによる追加スタイル）

**帳票の出力タイミング**：ユーザーがパッドページにアクセスし、エクスポートメニューからHTML形式を選択した時点で即座に生成・ダウンロードされる。特定のリビジョン（過去バージョン）を指定してエクスポートすることも可能。

**帳票の利用者**：パッドにアクセス権限を持つ全てのユーザー（編集者、閲覧者）が利用可能。読み取り専用アクセスの場合も、readOnlyIdを使用してエクスポートできる。

## 帳票種別

ドキュメントエクスポート（Web文書形式）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | パッド編集画面 | `/p/:pad/export/html` | エクスポートメニューから「HTML」を選択 |
| - | パッド編集画面（リビジョン指定） | `/p/:pad/:rev/export/html` | エクスポートメニューから「HTML」を選択（リビジョン指定） |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML |
| 用紙サイズ | 該当なし（Web文書） |
| 向き | 該当なし |
| ファイル名 | `{padId}.html` または `{readOnlyId}.html`（フックで上書き可能） |
| 出力方法 | ダウンロード（`Content-Disposition: attachment`） |
| 文字コード | UTF-8 |

### HTML固有設定

| 項目 | 内容 |
|-----|------|
| DOCTYPE | HTML5（`<!doctype html>`） |
| メタ情報 | generator: Etherpad, author: Etherpad, charset: utf-8 |
| マニフェスト | `/manifest.json` へのリンク |
| カスタムCSS | プラグインによる追加スタイル対応 |

## 帳票レイアウト

### レイアウト概要

HTML文書は標準的なHTML5構造に従い、ヘッダー部（head要素）と本文部（body要素）で構成される。

```
<!doctype html>
<html lang="en">
<head>
  ┌─────────────────────────────────────┐
  │  メタ情報・スタイルシート定義        │
  │  - title: パッドID                  │
  │  - meta: generator, author, charset  │
  │  - style: リスト・インデント用CSS    │
  │  - プラグイン追加CSS                 │
  └─────────────────────────────────────┘
</head>
<body>
  ┌─────────────────────────────────────┐
  │              本文部                  │
  │  - 段落テキスト                      │
  │  - 見出し（h1, h2）                  │
  │  - リスト（ol, ul）                  │
  │  - インライン書式                    │
  │  - ハイパーリンク                    │
  └─────────────────────────────────────┘
</body>
</html>
```

### ヘッダー部（head要素）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | title | ページタイトル | パッドID（readOnlyId優先） | エスケープ済みテキスト |
| 2 | manifest | PWAマニフェスト | 固定値 | `/manifest.json` |
| 3 | generator | 生成ツール情報 | 固定値 | `Etherpad` |
| 4 | author | 著者情報 | 固定値 | `Etherpad` |
| 5 | charset | 文字エンコーディング | 固定値 | `utf-8` |
| 6 | style | リスト用CSS | テンプレート組み込み | CSSテキスト |
| 7 | extraCSS | プラグイン追加CSS | `stylesForExport`フック | CSSテキスト |

### 本文部（body要素）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 段落 | 通常テキスト行 | atext.text | `テキスト<br>` |
| 2 | 見出し1 | h1見出し | heading1属性 | `<h1>テキスト</h1>` |
| 3 | 見出し2 | h2見出し | heading2属性 | `<h2>テキスト</h2>` |
| 4 | 太字 | 強調テキスト | bold属性 | `<strong>テキスト</strong>` |
| 5 | 斜体 | 斜体テキスト | italic属性 | `<em>テキスト</em>` |
| 6 | 下線 | 下線テキスト | underline属性 | `<u>テキスト</u>` |
| 7 | 取り消し線 | 取り消しテキスト | strikethrough属性 | `<s>テキスト</s>` |
| 8 | 番号リスト | 番号付きリスト | list属性(number) | `<ol><li>テキスト</li></ol>` |
| 9 | 箇条書き | 箇条書きリスト | list属性(bullet) | `<ul><li>テキスト</li></ul>` |
| 10 | リンク | ハイパーリンク | URL自動検出 | `<a href="URL" rel="noreferrer noopener">URL</a>` |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| パッドID | エクスポート対象のパッド識別子 | Yes |
| リビジョン番号 | 特定バージョンを指定（省略時は最新） | No |
| アクセス権限 | パッドへの読み取り権限 | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | 行番号 | 昇順（テキスト出現順） |

### 改ページ条件

該当なし（HTMLは単一ページドキュメントとして出力）

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| pad:{padId} | パッドメタ情報 | キー直接参照 |
| pad:{padId}:revs:{revNum} | リビジョン履歴 | リビジョン番号指定時 |

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

#### pad:{padId}

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| atext.text | 本文テキスト | 常時 | 最新テキスト |
| atext.attribs | 属性情報 | 常時 | 書式・リスト情報 |
| pool | 属性プール | 常時 | 属性ID→属性値マッピング |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| リスト番号 | CSSカウンタによる自動採番 | - | `counters(item, ".")` |
| ネストレベル | list属性の末尾数字を解析 | - | 正規表現 `([a-z]+)([0-9]+)` |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[エクスポートリクエスト<br>/p/:pad/export/html] --> B{ファイル形式チェック}
    B -->|html| C[アクセス権限確認]
    B -->|サポート外| Z[404エラー]
    C -->|権限なし| Y[アクセス拒否]
    C -->|権限あり| D{readOnlyId?}
    D -->|Yes| E[readOnlyIdからpadId取得]
    D -->|No| F[padIdを使用]
    E --> G[パッド存在確認]
    F --> G
    G -->|存在しない| X[404エラー]
    G -->|存在する| H{リビジョン指定?}
    H -->|Yes| I[指定リビジョンのatext取得]
    H -->|No| J[最新atextを使用]
    I --> K[getHTMLFromAtext実行]
    J --> K
    K --> L[各行を解析・HTML変換]
    L --> M[リスト構造を構築]
    M --> N[プラグインフック実行<br>exportHTMLAdditionalContent]
    N --> O[stylesForExportフック実行]
    O --> P[テンプレートでHTML文書生成]
    P --> Q[exportHTMLSendフック実行]
    Q --> R[Content-Disposition設定]
    R --> S[HTTPレスポンス送信]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| パッド不存在 | 指定されたpadIdが存在しない | 404 Not Found | ログ出力後、404レスポンス |
| アクセス拒否 | パッドへの読み取り権限がない | アクセス権に応じた処理 | hasPadAccess関数で判定 |
| 無効なリビジョン | 存在しないリビジョン番号 | エラー | checkValidRevで検証 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1パッド（行数上限なし） |
| 目標出力時間 | 1秒以内（一般的なパッドサイズ） |
| 同時出力数上限 | レートリミッター設定による（settings.importExportRateLimiting） |

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

- **XSS対策**: 出力時にSecurity.escapeHTML()でHTMLエスケープを実施
- **リンクセキュリティ**: 全リンクに`rel="noreferrer noopener"`を付与し、参照元情報の漏洩とtabnabbing攻撃を防止
- **アクセス制御**: hasPadAccess()によるパッド単位のアクセス権限チェック
- **レートリミッター**: DoS攻撃防止のためエクスポートリクエスト数を制限
- **属性エスケープ**: URLはSecurity.escapeHTMLAttribute()で属性値エスケープ

## 備考

- プラグインフック`exportHtmlAdditionalTags`により、カスタムタグの追加が可能
- プラグインフック`exportHtmlAdditionalTagsWithData`により、data属性付きspanタグの追加が可能
- プラグインフック`exportFileName`により、出力ファイル名のカスタマイズが可能
- 特殊文字（コードポイント12等）は自動的に除去される

---

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

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

### 推奨読解順序

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

パッドのテキストと属性情報の構造を理解することが最優先。Etherpadは独自のattributed string形式でリッチテキストを管理している。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PadType.ts | `src/node/types/PadType.ts` | PadType型定義、atext(text+attribs)構造、AText型 |
| 1-2 | AttributePool.ts | `src/static/js/AttributePool.ts` | 属性プールの仕組み、numToAttrib/attribToNumマッピング |

**読解のコツ**: atextはtext（生テキスト）とattribs（属性文字列）のペア。attribsは操作列（ops）として各文字範囲の属性を表現する。

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

HTTPリクエストからエクスポート処理への流れを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | importexport.ts | `src/node/hooks/express/importexport.ts` | Expressルーティング、`/p/:pad/:rev/export/:type`パターン |
| 2-2 | ExportHandler.ts | `src/node/handler/ExportHandler.ts` | doExport関数、形式別分岐ロジック |

**主要処理フロー**:
1. **29行目**: ルート定義 `/p/:pad{/:rev}/export/:type`
2. **31行目**: サポート形式リスト `['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad']`
3. **68行目**: exportHandler.doExport呼び出し

#### Step 3: HTML生成ロジックを理解する

ExportHtml.tsがHTMLエクスポートのコアロジック。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ExportHtml.ts | `src/node/utils/ExportHtml.ts` | getPadHTMLDocument、getHTMLFromAtext関数 |
| 3-2 | ExportHelper.ts | `src/node/utils/ExportHelper.ts` | _analyzeLine関数（行属性解析）、_encodeWhitespace |

**主要処理フロー**:
- **34-44行目**: getPadHTML - atextをHTMLに変換
- **46-477行目**: getHTMLFromAtext - メイン変換ロジック
- **51-52行目**: タグと属性のマッピング定義
- **128-294行目**: getLineHTML - 1行のHTML生成
- **479-500行目**: getPadHTMLDocument - 完全なHTML文書生成

#### Step 4: テンプレートを理解する

最終的なHTML文書はEJSテンプレートで生成される。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | export_html.html | `src/templates/export_html.html` | HTML5テンプレート構造、CSS定義、変数埋め込み |

**主要処理フロー**:
- **1-9行目**: HTML5 DOCTYPE、head要素開始
- **10-39行目**: リスト用CSS（カウンタ、インデント）
- **40行目**: extraCSS変数（プラグイン追加スタイル）
- **43-45行目**: body変数（本文HTML）

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

```
HTTPリクエスト /p/:pad/export/html
    │
    ├─ importexport.ts (expressCreateServer)
    │      └─ args.app.get('/p/:pad{/:rev}/export/:type')
    │             │
    │             ├─ hasPadAccess() - アクセス権確認
    │             ├─ readOnlyManager.getPadId() - ID解決
    │             ├─ padManager.doesPadExists() - 存在確認
    │             └─ exportHandler.doExport()
    │
    └─ ExportHandler.ts (doExport)
           │
           ├─ hooks.aCallFirst('exportFileName') - ファイル名フック
           ├─ res.attachment() - ダウンロード設定
           │
           └─ (type === 'html') の場合:
                  │
                  └─ ExportHtml.ts
                         │
                         ├─ getPadHTMLDocument()
                         │      ├─ padManager.getPad()
                         │      ├─ hooks.aCallAll('stylesForExport')
                         │      ├─ getPadHTML()
                         │      │      └─ getHTMLFromAtext()
                         │      │             ├─ hooks.aCallAll('exportHtmlAdditionalTags')
                         │      │             ├─ hooks.aCallAll('exportHtmlAdditionalTagsWithData')
                         │      │             ├─ _analyzeLine() [ExportHelper.ts]
                         │      │             ├─ getLineHTML() - 各行変換
                         │      │             └─ hooks.aCallAll('getLineHTMLForExport')
                         │      ├─ hooks.aCallAll('exportHTMLAdditionalContent')
                         │      └─ eejs.require('export_html.html')
                         │
                         └─ hooks.aCallFirst('exportHTMLSend') - 最終フック
```

### データフロー図

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

pad:{padId}          ┌─────────────────┐
  │                  │                 │
  ├─ atext.text ────▶│  getHTMLFrom    │
  │                  │    Atext()      │
  └─ atext.attribs ─▶│                 │
                     │  ┌───────────┐  │
AttributePool ──────▶│  │_analyzeLine│  │──▶ HTML本文
                     │  │  各行解析  │  │
                     │  └───────────┘  │
                     │  ┌───────────┐  │
                     │  │getLineHTML│  │
                     │  │ 書式変換  │  │
                     │  └───────────┘  │
                     └─────────────────┘
                              │
                              ▼
                     ┌─────────────────┐
stylesForExport ────▶│ export_html.html│──▶ 完全なHTML文書
フック出力           │  (EJSテンプレート)  │    (.html ファイル)
                     └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| importexport.ts | `src/node/hooks/express/importexport.ts` | ソース | Expressルーティング定義 |
| ExportHandler.ts | `src/node/handler/ExportHandler.ts` | ソース | エクスポート処理の振り分け |
| ExportHtml.ts | `src/node/utils/ExportHtml.ts` | ソース | HTML変換ロジック本体 |
| ExportHelper.ts | `src/node/utils/ExportHelper.ts` | ソース | 行解析ヘルパー関数 |
| export_html.html | `src/templates/export_html.html` | テンプレート | HTML出力テンプレート |
| PadType.ts | `src/node/types/PadType.ts` | 型定義 | パッドデータ型 |
| PadManager.ts | `src/node/db/PadManager.ts` | ソース | パッドデータ取得 |
| Changeset.ts | `src/static/js/Changeset.ts` | ソース | 属性文字列パーサー |
| AttributePool.ts | `src/static/js/AttributePool.ts` | ソース | 属性プール管理 |
| security.js | `src/static/js/security.js` | ソース | HTMLエスケープ処理 |
| Settings.ts | `src/node/utils/Settings.ts` | 設定 | レートリミッター設定等 |
