# 機能設計書: parsecsv - CSVパーサー

## 1. 機能概要

### 1.1 目的
parsecsvモジュールは、高性能なCSV（Comma Separated Value）パーサーを提供する。カンマ区切りのデータファイルを行単位で読み取り、フィールドに分解する機能を提供する。

### 1.2 機能範囲
- CSVファイル・ストリームの解析
- ヘッダー行の処理
- カスタム区切り文字・引用符・エスケープ文字のサポート
- 行単位でのデータ取得
- ヘッダー名によるフィールドアクセス

### 1.3 関連コンポーネント
- `streams` モジュール: 入力ストリーム
- `lexbase` モジュール: 基盤となるレクサー

## 2. データ構造

### 2.1 型定義

```nim
type
  CsvRow* = seq[string]  ## CSVの1行を表す文字列シーケンス

  CsvParser* = object of BaseLexer  ## CSVパーサーオブジェクト
    row*: CsvRow        ## 現在の行データ
    headers*: seq[string]  ## ヘッダー列名（readHeaderRow使用時）
    filename: string
    sep, quote, esc: char
    skipWhite: bool
    currRow: int

  CsvError* = object of IOError  ## CSVパースエラー
```

### 2.2 特徴
- **行単位処理**: `readRow()`で1行ずつ処理
- **ヘッダーサポート**: `readHeaderRow()`でヘッダーを読み取り、`rowEntry()`で名前アクセス
- **カスタム区切り文字**: デフォルトはカンマだが、タブ等に変更可能
- **引用符内改行**: 引用符で囲まれたフィールド内の改行をサポート

## 3. 主要機能

### 3.1 パーサー初期化

#### open（ストリーム版）
```nim
proc open*(self: var CsvParser, input: Stream, filename: string,
           separator = ',', quote = '"', escape = '\0',
           skipInitialSpace = false)
```
- **目的**: 入力ストリームでパーサーを初期化
- **パラメータ**:
  - `input`: 入力ストリーム
  - `filename`: エラーメッセージ用のファイル名
  - `separator`: フィールド区切り文字（デフォルト: `,`）
  - `quote`: 引用符文字（デフォルト: `"`）。`\0`で引用符解析を無効化
  - `escape`: エスケープ文字（デフォルト: `\0`）。`\0`の場合、引用符2つで1つの引用符を表現
  - `skipInitialSpace`: 区切り文字直後の空白をスキップ
- **ソース位置**: `lib/pure/parsecsv.nim:107-139`

#### open（ファイル版）
```nim
proc open*(self: var CsvParser, filename: string,
           separator = ',', quote = '"', escape = '\0',
           skipInitialSpace = false)
```
- **目的**: ファイルから直接パーサーを初期化
- **ソース位置**: `lib/pure/parsecsv.nim:141-157`

#### close
```nim
proc close*(self: var CsvParser) {.inline.}
```
- **目的**: パーサーと関連ストリームを閉じる
- **ソース位置**: `lib/pure/parsecsv.nim:292-294`

### 3.2 行の読み取り

#### readRow
```nim
proc readRow*(self: var CsvParser, columns = 0): bool
```
- **目的**: 次の行を読み取り、`self.row`に格納
- **パラメータ**:
  - `columns`: 期待する列数（0の場合はチェックなし）
- **戻り値**: 行が読み取れた場合は`true`、EOFの場合は`false`
- **注意**: 空行はスキップされる
- **ソース位置**: `lib/pure/parsecsv.nim:227-290`

#### readHeaderRow
```nim
proc readHeaderRow*(self: var CsvParser)
```
- **目的**: 最初の行をヘッダーとして読み取り、`self.headers`に格納
- **ソース位置**: `lib/pure/parsecsv.nim:296-312`

### 3.3 データアクセス

#### row（フィールド）
```nim
# self.row: CsvRow
```
- **目的**: 現在の行のフィールドを文字列シーケンスとして取得
- **アクセス**: `parser.row[0]`, `parser.row[1]`等でインデックスアクセス

#### rowEntry
```nim
proc rowEntry*(self: var CsvParser, entry: string): string
```
- **目的**: ヘッダー名で現在の行のフィールドを取得
- **前提**: `readHeaderRow()`が呼び出し済み
- **ソース位置**: `lib/pure/parsecsv.nim:314-320`

### 3.4 状態取得

#### processedRows
```nim
proc processedRows*(self: var CsvParser): int {.inline.}
```
- **目的**: 処理済み行数を返す
- **注意**: EOFに到達した場合も行数はインクリメントされる
- **ソース位置**: `lib/pure/parsecsv.nim:202-225`

## 4. 使用例

### 4.1 基本的な使用法
```nim
import std/parsecsv
import std/streams

var s = newFileStream("data.csv", fmRead)
if s == nil:
  quit("cannot open the file")

var parser: CsvParser
parser.open(s, "data.csv")
while parser.readRow():
  echo "new row: "
  for val in parser.row:
    echo "  ", val
parser.close()
```

### 4.2 ヘッダー付きCSV
```nim
import std/parsecsv

# CSVファイル例:
# Name,Age,City
# Alice,30,Tokyo
# Bob,25,Osaka

var parser: CsvParser
parser.open("data.csv")
parser.readHeaderRow()  # ヘッダーを読み込み

while parser.readRow():
  echo "Name: ", parser.rowEntry("Name")
  echo "Age: ", parser.rowEntry("Age")
  echo "City: ", parser.rowEntry("City")

parser.close()
```

### 4.3 カスタム区切り文字
```nim
import std/parsecsv
import std/streams

# タブ区切りファイル
var s = newStringStream("One\tTwo\tThree\n1\t2\t3")
var parser: CsvParser
parser.open(s, "data.tsv", separator = '\t')
while parser.readRow():
  for val in parser.row:
    echo val
parser.close()
```

### 4.4 引用符内の特殊文字
```nim
import std/parsecsv
import std/streams

# 引用符内のカンマと改行
var s = newStringStream("""Name,Description
"Alice","Hello, World"
"Bob","Line1
Line2"
""")
var parser: CsvParser
parser.open(s, "data.csv")
while parser.readRow():
  echo parser.row[0], ": ", parser.row[1]
parser.close()
```

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

### 5.1 推奨読解順序

1. **型定義** (75-92行目)
   - `CsvRow`, `CsvParser`, `CsvError`

2. **初期化と終了** (107-157行目, 292-294行目)
   - `open`, `close`プロシージャ

3. **フィールド解析** (159-200行目)
   - `parseField`: 1フィールドの解析ロジック

4. **行読み取り** (227-312行目)
   - `readRow`, `readHeaderRow`

5. **データアクセス** (202-225行目, 314-320行目)
   - `processedRows`, `rowEntry`

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

```
open(self, input, filename, ...) [107行目]
├── lexbase.open()
├── self.filename = filename
├── self.sep = separator
├── self.quote = quote
└── self.esc = escape

readRow(self, columns) [227行目]
├── 空行スキップ (258-262行目)
├── parseField(self, self.row[col]) [繰り返し]
│   ├── 引用符あり
│   │   ├── 引用符2つ → 1つの引用符
│   │   ├── エスケープ文字処理
│   │   └── 改行処理（handleCR/handleLF）
│   └── 引用符なし
│       └── 区切り文字まで読み取り
├── 区切り文字確認
├── 行終端処理 (273-283行目)
└── 列数チェック (287-289行目)

readHeaderRow(self) [296行目]
├── readRow()
└── self.headers = self.row

rowEntry(self, entry) [314行目]
└── find(self.headers, entry) → self.row[idx]
```

### 5.3 データフロー図

```
[CSVファイル/ストリーム]
        │
        ▼
┌─────────────────────────────────────┐
│          CsvParser                   │
│  ┌─────────────────────────────┐    │
│  │     BaseLexer (バッファ)     │    │
│  └───────────────┬─────────────┘    │
│                  │                   │
│  ┌───────────────▼─────────────┐    │
│  │    parseField() [繰り返し]   │    │
│  │    区切り/引用符/改行処理    │    │
│  └───────────────┬─────────────┘    │
│                  │                   │
│  ┌───────────────▼─────────────┐    │
│  │    self.row: CsvRow          │    │
│  │    ["val1", "val2", ...]     │    │
│  └───────────────┬─────────────┘    │
└──────────────────┼───────────────────┘
                   │
     ┌─────────────┼─────────────┐
     │             │             │
     ▼             ▼             ▼
  row[i]      rowEntry      headers
 インデックス   名前アクセス   ヘッダー
```

### 5.4 読解のコツ

- **引用符処理**: `parseField`内の`if self.buf[pos] == self.quote`ブロック（164-192行目）
- **エスケープ**: `self.esc == '\0'`の場合、引用符2つが1つの引用符を表す
- **空行スキップ**: `readRow`先頭で空行をスキップ（258-262行目）
- **reuse memory**: `setLen(a, 0)`でメモリ再利用（163行目）

## 6. 関連ファイル一覧

| ファイルパス | 種別 | 役割 |
|-------------|------|------|
| `lib/pure/parsecsv.nim` | ソース | メイン実装 |
| `lib/pure/lexbase.nim` | ソース | 基盤レクサー |
| `lib/pure/streams.nim` | ソース | ストリーム入力 |

## 7. 設計上の考慮事項

### 7.1 RFC 4180との互換性
- 引用符内のカンマ、改行をサポート
- 引用符2つで1つの引用符を表現（デフォルト）
- ヘッダー行のオプションサポート

### 7.2 カスタマイズ性
- 区切り文字: カンマ以外（タブ、セミコロン等）に変更可能
- 引用符: ダブルクォート以外に変更可能、または無効化
- エスケープ: バックスラッシュ等のエスケープ文字を指定可能

### 7.3 エラー処理
- `columns`パラメータで期待する列数をチェック
- 不正な行は`CsvError`例外を発生
- ファイルが開けない場合もエラーを発生

### 7.4 パフォーマンス
- メモリ再利用（`setLen(a, 0)`）で割り当てを削減
- `BaseLexer`のバッファリングで効率的な読み取り
- 空行のスキップで不要な処理を回避

### 7.5 制限事項
- `readRow`は空行をスキップする（空行を検出したい場合は低レベル処理が必要）
- ヘッダー名が重複する場合、最初のインデックスが使用される
- エンコーディング変換機能はない（UTF-8推奨）
