# 機能設計書 66-リンク処理

## 概要

本ドキュメントは、Wikipedia iOSアプリにおける「リンク処理」機能の設計仕様を記載する。アプリ内外のリンクの処理とナビゲーションを提供するものである。

### 本機能の処理概要

本機能は、アプリ内で発生するリンク（URL）を解析し、適切な遷移先（記事画面、外部ブラウザ、インアプリWebView等）へ振り分ける。Wikipedia/Wikimediaドメインのリンクはアプリ内で処理し、外部リンクはSafariやアプリ内WebViewで開く。

**業務上の目的・背景**：Wikipedia記事には多数の内部リンクと外部リンクが含まれる。ユーザーがこれらのリンクをタップした際に、シームレスなナビゲーション体験を提供する。記事間の移動はアプリ内で完結させ、関連記事への回遊を促進する。

**機能の利用シーン**：
- 記事内の他記事へのリンクをタップした際
- 検索結果から記事を開く際
- 外部サイトへのリンクをタップした際
- ディープリンク（Universal Link）でアプリを起動した際
- 曖昧さ回避ページの記事選択時

**主要な処理内容**：
1. URLの解析とWikimediaプロジェクトの判定
2. 名前空間（Main/Talk/User/Special等）の識別
3. 遷移先タイプの決定（article/talk/search/diff/history等）
4. 適切なCoordinator/ViewControllerへの振り分け
5. 外部リンクのブラウザ処理

**関連システム・外部連携**：
- Safari/SFSafariViewController: 外部リンクの表示
- Universal Links: iOS標準のディープリンク処理
- WMF Configuration: ホスト許可リストの管理

**権限による制御**：認証不要。ただし一部のSpecialページ（MyTalk等）はログインユーザーのみ有効。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 71 | DisambiguationPages画面 | 主画面 | 曖昧さ回避ページ一覧表示 |
| 5 | Article画面 | 遷移先画面 | 記事リンクからの記事表示 |
| 34 | TalkPage画面 | 遷移先画面 | トークページリンクからの表示 |
| 4 | Search画面 | 遷移先画面 | 検索リンクからの検索実行 |

## 機能種別

ルーティング / ナビゲーション / URL解析

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | URL | Yes | 処理対象のURL | 有効なURL形式 |
| navigationController | UINavigationController | Yes | ナビゲーションコントローラー | nil不可 |
| dataStore | MWKDataStore? | No | データストア | nil時はshared使用 |
| theme | Theme | Yes | 現在のテーマ設定 | nil不可 |
| articleSource | ArticleSource | Yes | 記事のソース（リンク元情報） | - |
| permanentUsername | String? | No | ログインユーザー名 | Specialページ処理に使用 |
| tabConfig | ArticleTabConfig? | No | タブ設定 | デフォルト: appendArticleAndAssignCurrentTab |

### 入力データソース

- WebView: 記事内リンクのタップイベント
- Universal Links: iOS標準のディープリンク
- アプリ内URL: 検索結果、関連記事等

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| destination | Router.Destination | 遷移先タイプ |
| 遷移結果 | Bool | 遷移成功/失敗 |

### 出力先

- 画面遷移: 対応するViewController
- 外部ブラウザ: Safari/SFSafariViewController

## 処理フロー

### 処理シーケンス

```
1. LinkCoordinator/Router初期化
   └─ URL、navigationController等を受け取る

2. URL解析
   └─ wmf_siteでWikimediaドメイン判定
   └─ WikimediaProject判定

3. パス解析
   └─ wikiResourcePath取得
   └─ 名前空間とタイトル抽出

4. 遷移先決定（Destination enum）
   └─ .article: 記事ページ
   └─ .talk: トークページ
   └─ .userTalk: ユーザートークページ
   └─ .search: 検索ページ
   └─ .articleHistory: 編集履歴
   └─ .articleDiff: 差分比較
   └─ .inAppLink: インアプリWebView
   └─ .externalLink: 外部ブラウザ
   └─ .audio: 音声ファイル
   └─ .onThisDay: 今日の出来事
   └─ .readingListsImport: リーディングリストインポート
   └─ .login: ログイン
   └─ .watchlist: ウォッチリスト

5. 遷移実行
   └─ 対応するCoordinatorを生成
   └─ start()で遷移実行
```

### フローチャート

```mermaid
flowchart TD
    A[URLタップ] --> B{Wikimediaドメイン?}
    B -->|No| C[externalLink]
    B -->|Yes| D[WikimediaProject判定]

    D --> E{wikiResourcePath?}
    E -->|No| F{wResourcePath?}
    E -->|Yes| G[名前空間判定]

    F -->|No| H[webViewDestination]
    F -->|Yes| I[indexPath解析]

    G -->|Main| J{メインページ?}
    J -->|Yes| H
    J -->|No| K[.article]

    G -->|Talk| L[.talk]
    G -->|UserTalk| M[.userTalk]
    G -->|Special| N[Special処理]
    G -->|Wikipedia| O[Wikipedia処理]
    G -->|その他| H

    N --> P{mobilediff?}
    P -->|Yes| Q[.articleDiff]
    P -->|No| R{history?}
    R -->|Yes| S[.articleHistory]
    R -->|No| T{その他Special}

    I --> U{search?}
    U -->|Yes| V[.search]
    U -->|No| W{diff/oldid?}
    W -->|Yes| Q
    W -->|No| S

    C --> X[Safari/SFSafariVC]
    H --> Y[inAppWebView]
    K --> Z[ArticleCoordinator]
    L --> AA[TalkPageVC]
    Q --> AB[DiffContainerVC]
    S --> AC[PageHistoryVC]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-66-01 | メインページ除外 | メインページはネイティブ記事ビューに遷移しない | Main名前空間でメインページタイトル |
| BR-66-02 | thankyou除外 | 寄付完了ページはWebView表示 | host == "thankyou.wikipedia.org" |
| BR-66-03 | Special:MyTalk変換 | MyTalkはUser_talk:{username}に変換 | EN/TEST言語、ログイン時 |
| BR-66-04 | 音声ファイル処理 | Wikimediaホスト音声ファイルは特別処理 | .ogg/.mp3等の拡張子 |
| BR-66-05 | プロジェクト別処理 | プロジェクトによりネイティブ処理対象が異なる | WikimediaProject.supportsNative* |

### 計算ロジック

URL正規化:
```swift
let canonicalURL = url.canonical
// HTTPをHTTPSに変換
// モバイルドメインをデスクトップに変換
// 冗長なパラメータを削除
```

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

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

本機能はルーティングのみで、直接的なデータベース操作は行わない。遷移先の機能でデータベース操作が発生する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| E-66-01 | URL解析失敗 | 不正なURL形式 | .unknownを返す |
| E-66-02 | プロジェクト不明 | WikimediaProject解決失敗 | 外部リンクとして処理 |
| E-66-03 | パス解析失敗 | wikiResourcePath取得失敗 | WebView表示にフォールバック |

### リトライ仕様

リンク処理にリトライは不要。失敗時は外部リンクとして処理。

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

本機能はトランザクション処理を行わない。

## パフォーマンス要件

- URL解析: 10ms以内
- 遷移先決定: 即時（正規表現マッチング）
- 画面遷移開始: 100ms以内

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

- 外部リンクはサニタイズ後に処理
- JavaScript URLは拒否
- hostCanRouteToInAppWebViewで許可ホストを制限
- Specialページのユーザー名は認証済みユーザーのみ使用

## 備考

- Routerクラス（WMF Framework）とLinkCoordinator（App）の2層構造
- LinkCoordinatorは記事遷移に特化した簡易版
- Routerはより汎用的なルーティング処理
- 正規表現でmobilediff/historyパターンをマッチング

---

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

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

### 推奨読解順序

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

遷移先タイプを定義するDestination enumを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Router.swift | `WMF Framework/Router.swift` | Destination enumの全ケースを理解する |

**読解のコツ**:
- Destination enumが遷移先タイプを網羅的に定義
- 各ケースに関連するURLパラメータが付随

**主要処理フロー**:
- **3-17行目**: Destination enum定義
  - .inAppLink: インアプリWebView
  - .externalLink: 外部ブラウザ
  - .article: 記事
  - .articleHistory: 編集履歴
  - .articleDiff: 差分
  - .talk: トークページ
  - .userTalk: ユーザートーク
  - .search: 検索
  - .audio: 音声
  - .onThisDay: 今日の出来事
  - .readingListsImport: リスト読み込み
  - .login: ログイン
  - .watchlist: ウォッチリスト

#### Step 2: メインルーティングロジックを理解する

Routerクラスのdestinationメソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Router.swift | `WMF Framework/Router.swift` | destination(for:permanentUsername:)の処理フローを追う |

**主要処理フロー**:
- **28-41行目**: destination(for:permanentUsername:)メインエントリーポイント
- **30-38行目**: WikimediaProject判定、音声ファイル判定
- **40行目**: destinationForHostURL呼び出し

#### Step 3: wikiResourcePath解析を理解する

名前空間別の処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Router.swift | `WMF Framework/Router.swift` | destinationForWikiResourceURL()を理解する |

**主要処理フロー**:
- **54-161行目**: destinationForWikiResourceURL()
- **64-72行目**: Talk/UserTalk名前空間処理
- **73-128行目**: Special名前空間処理（mobilediff、history、MyTalk等）
- **129-140行目**: Main名前空間処理（メインページ判定含む）
- **141-157行目**: Wikipedia名前空間処理（OnThisDay）

#### Step 4: wResourcePath解析を理解する

index.phpパラメータ解析を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Router.swift | `WMF Framework/Router.swift` | destinationForWResourceURL()を理解する |

**主要処理フロー**:
- **163-249行目**: destinationForWResourceURL()
- **187-189行目**: search パラメータ処理
- **210-246行目**: diff/oldid/action=history パラメータ処理

#### Step 5: LinkCoordinatorを理解する

記事遷移特化のCoordinatorを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | LinkCoordinator.swift | `Wikipedia/Code/LinkCoordinator.swift` | 簡易版ルーティングを理解する |

**主要処理フロー**:
- **6-9行目**: Destination enum（article/unknownのみ）
- **29-50行目**: start()メソッドで遷移実行
- **52-95行目**: destination(for:)でURLを解析

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

```
[URL入力]
    │
    ├─ LinkCoordinator（記事遷移特化）
    │      │
    │      ├─ destination(for:)
    │      │      └─ .article / .unknown
    │      │
    │      └─ start()
    │             └─ ArticleCoordinator.start()
    │
    └─ Router（汎用ルーティング）
           │
           ├─ destination(for:permanentUsername:)
           │      │
           │      ├─ WikimediaProject判定
           │      │
           │      └─ destinationForHostURL()
           │             │
           │             ├─ destinationForWikiResourceURL()
           │             │      ├─ .talk
           │             │      ├─ .userTalk
           │             │      ├─ .article
           │             │      ├─ .articleDiff
           │             │      ├─ .articleHistory
           │             │      ├─ .onThisDay
           │             │      ├─ .login
           │             │      ├─ .watchlist
           │             │      └─ .readingListsImport
           │             │
           │             ├─ destinationForWResourceURL()
           │             │      ├─ .search
           │             │      ├─ .articleDiff
           │             │      └─ .articleHistory
           │             │
           │             └─ webViewDestinationForHostURL()
           │                    ├─ .inAppLink
           │                    └─ .externalLink
           │
           └─ doesOpenInBrowser(for:permanentUsername:)
```

### データフロー図

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

URL ───▶ wmf_site ───▶ WikimediaProject判定
              │
              ▼
        wikiResourcePath ───▶ 名前空間判定
              │
              ▼
         ┌───────────────────────────────────────┐
         │           Destination判定             │
         │  ┌─────────────────────────────────┐  │
         │  │ Main → .article                │  │
         │  │ Talk → .talk                   │  │
         │  │ Special → .diff/.history/...   │  │
         │  │ Other → .inAppLink/.external   │  │
         │  └─────────────────────────────────┘  │
         └───────────────────────────────────────┘
              │
              ▼
         Coordinator/VC ───▶ 画面遷移
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Router.swift | `WMF Framework/Router.swift` | ソース | 汎用URLルーティング |
| LinkCoordinator.swift | `Wikipedia/Code/LinkCoordinator.swift` | ソース | 記事遷移特化Coordinator |
| ViewControllerRouter.swift | `Wikipedia/Code/ViewControllerRouter.swift` | ソース | VC間ルーティング |
| ArticleCoordinator.swift | `Wikipedia/Code/ArticleCoordinator.swift` | ソース | 記事画面遷移 |
| TalkPageViewController.swift | `Wikipedia/Code/TalkPageViewController.swift` | ソース | トークページ表示 |
| DiffContainerViewController.swift | `Wikipedia/Code/DiffContainerViewController.swift` | ソース | 差分表示 |
| PageHistoryViewController.swift | `Wikipedia/Code/PageHistoryViewController.swift` | ソース | 編集履歴表示 |
| Configuration.swift | `WMF Framework/Configuration.swift` | ソース | ホスト許可リスト管理 |
| WikimediaProject.swift | `WMF Framework/WikimediaProject.swift` | ソース | Wikimediaプロジェクト判定 |
| URL+Extensions.swift | `WMF Framework/URL+Extensions.swift` | ソース | URL拡張（wikiResourcePath等） |
