# 機能設計書 55-ドキュメント変換エクスポート

## 概要

本ドキュメントは、Etherpadにおけるドキュメント変換エクスポート機能の設計仕様を記載する。パッドのコンテンツをdoc、pdf、odt形式に変換してエクスポートする機能について詳細に解説する。

### 本機能の処理概要

ドキュメント変換エクスポート機能は、パッドの編集内容をMicrosoft Word（doc）、PDF、OpenDocument（odt）などの形式に変換してエクスポートする機能を提供する。この変換にはLibreOfficeまたはAbiWordの外部コンバータが使用される。

**業務上の目的・背景**：
共同編集で作成したドキュメントを、一般的なオフィス文書形式で配布したり、印刷用のPDFとして出力したりする必要がある。これらの形式は広く普及しており、Microsoft Office、LibreOffice、Adobe Reader等の多くのアプリケーションで開くことができる。

**機能の利用シーン**：
- 会議資料をPDFで配布する場合
- 報告書をWord形式で提出する場合
- ドキュメントをOpenDocument形式で共有する場合
- 印刷用の整形されたドキュメントを作成する場合

**主要な処理内容**：
1. エクスポートリクエストを受信（GET /p/:pad/export/{doc|pdf|odt}）
2. パッドからHTMLを生成
3. 一時ファイルとしてHTMLを保存
4. LibreOffice/AbiWordで目的形式に変換
5. 変換されたファイルをHTTPレスポンスとして返却
6. 一時ファイルをクリーンアップ

**関連システム・外部連携**：
- LibreOffice（soffice）: メインの変換ツール
- AbiWord: LibreOfficeが利用不可の場合の代替
- プラグインフック: exportConvertで変換処理をカスタマイズ可能

**権限による制御**：
パッドへの読み取りアクセス権があれば実行可能。ただし、変換ツールがサーバーにインストールされている必要がある。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | パッド編集画面 | 補助機能 | エクスポートポップアップからdoc/pdf/odt形式でダウンロード |
| 3 | タイムスライダー画面 | 補助機能 | エクスポートポップアップからdoc/pdf/odt形式でダウンロード |

## 機能種別

帳票出力 / データエクスポート / ドキュメント変換

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| pad | string | Yes | URLパスパラメータとして指定されるパッドID | 存在するパッドID |
| type | string | Yes | エクスポート形式（doc, pdf, odt） | サポート形式であること |
| rev | string | No | エクスポート対象のリビジョン番号 | 数値であること |

### 入力データソース

- HTTPリクエスト（GET /p/:pad/export/{doc|pdf|odt}）
- パッドデータベース

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| content | binary | 変換されたドキュメントファイル |

### 出力先

- HTTPレスポンス（バイナリファイルダウンロード）
- Content-Disposition: attachment; filename="{padId}.{type}"

## 処理フロー

### 処理シーケンス

```
1. HTTPリクエスト受信（GET /p/:pad/export/{type}）
   └─ express-rate-limitによるレート制限チェック
2. コンバータ利用可能性チェック
   └─ settings.soffice または settings.abiword の設定確認
3. パッドアクセス権限検証
   └─ hasPadAccessでセッション・トークン検証
4. パッドID解決
   └─ 読み取り専用IDの場合は実パッドIDを取得
5. パッド存在確認
   └─ padManager.doesPadExistsで確認
6. HTML生成
   └─ exporthtml.getPadHTMLDocumentでHTML生成
7. 一時ファイル保存
   └─ 一時ディレクトリにHTMLを書き込み
8. プラグインフック確認
   └─ exportConvertフックでカスタム変換がある場合はそちらを使用
9. コンバータ実行
   └─ LibreOffice/AbiWordで目的形式に変換
10. ファイル送信
    └─ res.sendFileで変換されたファイルを送信
11. クリーンアップ
    └─ 一時ファイル削除
```

### フローチャート

```mermaid
flowchart TD
    A[エクスポートリクエスト受信] --> B{レート制限チェック}
    B -->|制限内| C{コンバータ利用可能?}
    B -->|制限超過| Z1[429エラー]
    C -->|No| Z2[コンバータ未設定エラー]
    C -->|Yes| D{アクセス権限チェック}
    D -->|許可| E{パッド存在確認}
    D -->|拒否| Z3[403エラー]
    E -->|存在| F[HTML生成]
    E -->|不存在| Z4[404エラー]
    F --> G[一時ファイルに保存]
    G --> H{exportConvertフック?}
    H -->|あり| I[プラグイン変換]
    H -->|なし| J{soffice設定?}
    J -->|Yes| K[LibreOffice変換]
    J -->|No| L[AbiWord変換]
    I --> M[res.sendFile]
    K --> M
    L --> M
    M --> N[一時ファイル削除]
    N --> O[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-55-01 | コンバータ必須 | sofficeまたはabiwordが設定されていない場合はエラー | 常時 |
| BR-55-02 | soffice優先 | sofficeとabiword両方が設定されている場合はsoffice使用 | 両方設定時 |
| BR-55-03 | doc変換2段階 | doc形式はHTMLから直接変換できないため、一度odtに変換後docに変換 | doc形式エクスポート時 |
| BR-55-04 | PDF用Draw変換 | PDFインポート後のエクスポートはLO Drawを使用 | PDFソース時 |
| BR-55-05 | 変換タイムアウト | LibreOffice変換は120秒でタイムアウト | 常時 |
| BR-55-06 | キュー処理 | 変換は1件ずつキュー処理（同時実行なし） | 常時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| パッドデータ取得 | pad:{padId} | SELECT | HTML生成のためパッドATextを取得 |
| リビジョンデータ取得 | pad:{padId}:revs:{n} | SELECT | 特定リビジョンのAText取得（rev指定時） |

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

データベースへの書き込み操作はなし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| N/A | 設定エラー | コンバータ未設定 | settings.jsonでsoffice/abiwordを設定 |
| 404 | Not Found | パッドが存在しない | 正しいパッドIDを指定 |
| 403 | Forbidden | アクセス権限がない | 適切な認証を行う |
| 429 | Too Many Requests | レート制限超過 | 時間をおいて再試行 |
| 500 | 変換エラー | LibreOffice/AbiWord変換失敗 | ファイル内容確認、サーバーログ確認 |

### リトライ仕様

自動リトライは実装されていない。LibreOfficeがハングした場合は120秒後に強制終了される。

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

読み取り専用操作のためトランザクション管理は不要。

## パフォーマンス要件

- LibreOffice/AbiWordの起動と変換に数秒〜数十秒かかる
- 同時変換は1件に制限（キュー処理）
- 変換タイムアウトは120秒
- レート制限により過負荷を防止

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

- レート制限によるDoS攻撃対策
- hasPadAccessによる認証・認可チェック
- 一時ファイルは処理完了後に削除
- LibreOfficeは--headless、--nolockcheckオプションで実行
- CORSヘッダー設定（Access-Control-Allow-Origin: *）

## 備考

- Windows環境ではファイルロック解放の遅延に対応するため100msの待機処理あり
- LibreOfficeがハングした場合の強制終了処理を実装
- AbiWordはsofficeより古い実装だが、一部環境では依然として有用

---

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

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

### 推奨読解順序

#### Step 1: 設定とコンバータ選択を理解する

変換ツールの設定と選択ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Settings.ts | `src/node/utils/Settings.ts` | soffice、abiword設定とexportAvailable関数 |

**読解のコツ**: exportAvailable()はsofficeまたはabiwordが設定されているかを返す。両方設定されている場合はsofficeが優先。

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

HTTPリクエストのルーティングとコンバータ利用可能性チェックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | importexport.ts | `src/node/hooks/express/importexport.ts` | GETルーティング設定 |

**主要処理フロー**:
1. **31行目**: 対応形式チェック（pdf, doc, odt含む）
2. **38-48行目**: コンバータ未設定時のエラーメッセージ
3. **68行目**: exportHandler.doExportの呼び出し

#### Step 3: エクスポートハンドラを理解する

HTML生成から変換処理への流れを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ExportHandler.ts | `src/node/handler/ExportHandler.ts` | メインエクスポート処理 |

**主要処理フロー**:
- **77行目**: exporthtml.getPadHTMLDocumentでHTML生成
- **91-93行目**: 一時ファイルパス生成（ランダム番号付き）
- **93行目**: HTMLを一時ファイルに書き込み
- **99行目**: 出力ファイルパス設定
- **101-102行目**: exportConvertフックでプラグイン変換の確認
- **106-110行目**: コンバータ選択（soffice優先）
- **110行目**: converter.convertFileで変換実行
- **114行目**: res.sendFileで送信
- **117-124行目**: 一時ファイル削除（Windows対応含む）

#### Step 4: LibreOffice変換を理解する

実際の変換処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | LibreOffice.ts | `src/node/utils/LibreOffice.ts` | LibreOffice変換処理 |

**主要処理フロー**:
- **30-79行目**: doConvertTask - 実際の変換処理
- **38-49行目**: sofficeコマンドライン引数の構築
- **61-64行目**: 120秒タイムアウト処理
- **74-78行目**: 出力ファイルのリネーム
- **82行目**: async.queueでキュー作成（同時実行数1）
- **92-117行目**: convertFile - エクスポート用エントリーポイント
- **110-113行目**: doc形式の2段階変換（HTML→odt→doc）

**読解のコツ**: LibreOfficeは--headless、--invisibleオプションでGUIなしで実行。出力ファイル名はsofficeが自動決定するため、後でリネームが必要。

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

```
expressCreateServer (importexport.ts)
    │
    └─ GET /p/:pad/export/{doc|pdf|odt}
           │
           ├─ exportAvailable() チェック
           │
           ├─ hasPadAccess
           │
           ├─ padManager.doesPadExists
           │
           └─ exportHandler.doExport (ExportHandler.ts)
                  │
                  ├─ exporthtml.getPadHTMLDocument
                  │
                  ├─ fsp_writeFile (HTML一時保存)
                  │
                  ├─ hooks.aCallAll('exportConvert')
                  │      └─ [プラグイン変換]
                  │
                  └─ converter.convertFile
                         │
                         ├─ [soffice設定時] LibreOffice.convertFile
                         │      │
                         │      ├─ [doc形式] 2段階変換
                         │      │      ├─ queue.pushAsync (odt変換)
                         │      │      └─ queue.pushAsync (doc変換)
                         │      │
                         │      └─ [その他] queue.pushAsync
                         │             └─ doConvertTask
                         │                    └─ runCmd (soffice実行)
                         │
                         └─ [abiword設定時] Abiword.convertFile
```

### データフロー図

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

HTTPリクエスト       ┌─────────────────┐
GET /p/:pad/        │  ルーティング    │
export/{type} ─────▶│  importexport   │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  権限チェック    │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
パッドDB ─────────▶ │  HTML生成       │
pad:{padId}         │  ExportHtml     │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  一時ファイル    │
                    │  HTML保存       │ ───────▶ /tmp/etherpad_export_xxx.html
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  コンバータ実行  │
                    │  LibreOffice/   │
                    │  AbiWord        │
                    └────────┬────────┘
                             │
一時ファイル ◀───────────────┘
/tmp/etherpad_export_xxx.{type}
                             │
                             ▼
                    ┌─────────────────┐
                    │  res.sendFile   │ ───────▶ .doc/.pdf/.odt
                    │  ファイル送信   │          ダウンロード
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  一時ファイル    │
                    │  削除           │
                    └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| importexport.ts | `src/node/hooks/express/importexport.ts` | ソース | Expressルーティング定義 |
| ExportHandler.ts | `src/node/handler/ExportHandler.ts` | ソース | エクスポートハンドラ |
| ExportHtml.ts | `src/node/utils/ExportHtml.ts` | ソース | HTML生成 |
| LibreOffice.ts | `src/node/utils/LibreOffice.ts` | ソース | LibreOffice変換制御 |
| Abiword.ts | `src/node/utils/Abiword.ts` | ソース | AbiWord変換制御 |
| run_cmd.ts | `src/node/utils/run_cmd.ts` | ソース | 外部コマンド実行 |
| Settings.ts | `src/node/utils/Settings.ts` | ソース | soffice/abiword設定 |
| PadManager.ts | `src/node/db/PadManager.ts` | ソース | パッド管理 |
