# Office ファイル系ツール（ReadPdf / ReadExcel / ReadDocx / ReadPPTX / ReadMsg / PdfToImages / SplitExcelSheets / SplitDocxSections）

ローカル workspace の Office 文書・PDF を読み込むツール群。

## 読み取り系

### ReadPdf

```js
// 全文抽出 (page 区切りで markdown)
ReadPdf({ path: "input/manual.pdf" })

// ページ範囲を絞る
ReadPdf({ path: "input/spec.pdf", page_range: "10-25" })

// grep -n 風検索 (query mode)
ReadPdf({ path: "input/manual.pdf", query: "保証期間" })
ReadPdf({ path: "input/manual.pdf", query: "保証期間", context_lines: 5 })
ReadPdf({ path: "input/spec.pdf", query: "第\\d+条", query_mode: "regex" })
```

スキャン PDF（テキストレイヤなし）の場合は **自動で PdfToImages + Vision OCR にフォールバック**。手動で `PdfToImages → ReadImage` を呼ぶ必要は通常ない。query を併用すると OCR 結果にも同じフィルタが適用される。

#### 引数

| 引数 | 型 | デフォルト | 説明 |
|---|---|---|---|
| `path` | string | (required) | workspace 相対の PDF パス |
| `page_range` | `"3"` / `"1-5"` | (全ページ) | 抽出ページ範囲 |
| `max_pages` | number | (無制限) | 抽出ページ数の上限 |
| `max_chars` | number | 50,000 | 返却文字数の上限 |
| `query` | string | (なし) | 一致行のみ grep -n 風で返す。trim 後 empty なら全文 mode |
| `query_mode` | `substring` / `regex` / `iregex` | `substring` | 検索モード。substring は大小無視 + metachar auto-escape |
| `context_lines` | number (0..20) | 2 | query マッチ前後の context 行数 |

#### query mode の出力例

```
# foo.pdf, query: "保証期間"

### Summary
- Total pages: 50
- Pages with match: 3
- Total matches: 5

### Matches

## Page 7 — 2 matches
   6:   商品は購入日より
>  7: 保証期間内に故障した場合、無償修理対象…
   8: ただし、消耗品は対象外です。
  …
> 22: 延長保証期間は最大 3 年まで…
```

`>` が一致行、空白マーカーが context 行。隣接マッチは context window が overlap したら 1 cluster にまとめられ context 重複を回避。

#### gotcha

- **MAX_MATCHES_PER_PAGE = 50**: 1 ページで 50 件超のマッチは打ち切り、page header に `(capped)` を付与。`"the"` のような broad pattern を絞るシグナル
- **`query_mode: "regex"` の invalid pattern** → `isError: true` で friendly メッセージ。substring mode は metachar をエスケープするので絶対に regex error にならない
- **OCR fallback path** でも query 適用。スキャン PDF + キーワード検索 OK
- 上記 PdfToImages の手動呼び出しは **DPI / 出力ファイル個別管理** がしたい時のみ。普通の "PDF を vision で読みたい" は ReadPdf 単発で済む

### ReadExcel

```js
ReadExcel({ path: "input/data.xlsx" })
// → 全シートのセル内容をテキスト形式で返す
```

巨大な Excel は token を食うので、シートを絞る場合は SplitExcelSheets を使う。

#### include_styles — セル装飾の取得（オプション）

```js
ReadExcel({ path: "input/report.xlsx", include_styles: true })
// → 値テーブルの後に ### Styles セクションが追記される
```

デフォルトは `false`（後方互換）。`include_styles: true` を指定すると、値テーブルの直後に各シートの `### Styles` セクションが出力される。

**出力形式 — dedup legend + range map:**

```
### Styles — Sheet1
s1 = fill:#FFF2CC;font:bold
s2 = border:top(thin),bottom(double,#808080);numFmt:"0.00%"

s1: A1:D1,A5:D5
s2: B2
merges: B2:D4
```

- **legend**: スタイル ID (`s1`, `s2`, ...) とそのシグネチャを列挙。シグネチャは `fill` / `font` / `border` / `numFmt` / `align` のうち非デフォルト部分を `;` 区切りで結合した文字列。
- **range map**: 同一スタイルを持つセルを矩形にまとめて `A1:D3` 形式で列挙。単一セルは `A1`。
- **merges**: 結合セル範囲の一覧（`ws.model.merges` から取得）。
- **conditionalFormatting**: 条件付き書式が存在する場合 `present (effective styles not evaluated)` と表示（実際の表示色は評価しない）。
- **装飾のみ・値なしセルも捕捉**: スタイルスキャンは `includeEmpty:true` で実行するため、値がなく書式だけ設定されたセルも対象に含まれる。
- **`sheet`/`range` フィルタを尊重**: `include_styles` は値テーブルと同じ `sheet`/`range` フィルタでスキャン範囲を限定する。指定した範囲外のセルのスタイルと、範囲に交差しない結合セルは結果に含まれない。
- **デフォルト色の省略**: デフォルト色 (theme index 1 = 通常黒) に明示設定したフォント色はデフォルト扱いで省略される。同様に border の theme index 0（自動色）も省略される。

**テーマカラーの解決（ベストエフォート）:**

テーマカラーはファイルの `theme1.xml` から読み取り、OOXML の lt/dk スワップを適用して `#RRGGBB` に解決する。解決できた場合は `theme(N,resolved=#RRGGBB)` の形式で表示し、`resolved=` は近似値であることに注意。テーマ XML が解析できない場合は `theme(N,tint=T)` のように生の参照のまま出力する。

**max_style_ranges — range 数の上限:**

`max_style_ranges`（デフォルト 250）で全シートを通じた range 出力数を制限する。上限に達した場合は `[styles truncated: max_style_ranges=N reached]` を表示し、以降のスタイルは省略される。値テーブルが先に出力されるため、スタイルセクションがトークン上限で切り捨てられても値は失われない。

**編集には python + openpyxl を推奨:**

ReadExcel はスタイルを読むだけで、書き込みは行わない。既存スタイルを保持したまま値を編集する場合は `Bash` ツールで python + openpyxl を使うこと（openpyxl は触れていないセルのスタイルを保持する）。

### ReadDocx

```js
ReadDocx({ file_path: "input/spec.docx" })
// → 本文 + 表を抽出
```

### ReadPPTX

```js
ReadPPTX({ file_path: "input/slides.pptx" })
// → 各スライドのテキスト・表・スピーカーノートを返す
```

### ReadMsg

Outlook の `.msg`（OLE2 / CFBF 複合バイナリ）メールを読む。

```js
ReadMsg({ file_path: "input/inquiry.msg" })
// → 件名・差出人・宛先・CC・日時・本文（テキスト）と添付一覧を返す
```

返却テキストの構成:

```
Subject: 見積もりのご依頼
From: 山田太郎 <taro@example.com>
To: sales@example.com
Date: ...

（本文テキスト）

Attachments (2):
- 見積書.pdf (20480 bytes) -> input/見積書.pdf
- data.xlsx (8192 bytes) -> input/data.xlsx
```

ポイント:

- 本文は plain text を優先。HTML メールはタグを除去して整形。どちらも取れない場合は `(no text body)`
- **添付は `input/` に自動保存される**。保存後は種別ごとのツールで開く（PDF → ReadPdf、Excel → ReadExcel、画像 → ReadImage など）
- ファイル名は basename に正規化し、パス区切りや制御文字を除去（ディレクトリトラバーサル防止）。同名衝突時は連番を付与
- 添付に埋め込まれた `.msg`（メール in メール）は保存せず一覧に注記のみ。必要なら個別に扱う
- 不正な `.msg`（CFBF シグネチャ不一致）は中身を読まずエラーを返す。バイナリがそのまま出力に混ざることはない

## 変換・分割系

### PdfToImages

```js
PdfToImages({ file_path: "input/scan.pdf", dpi: 200 })
// → output/ReadPdf/page-001.png, page-002.png, ... に保存
```

スキャン PDF を ReadImage で扱うときの前段。

### SplitExcelSheets

```js
SplitExcelSheets({ file_path: "input/big.xlsx" })
// → output/excel/{sheetname}.md と manifest.json を生成
```

シート単位で別ファイルにすることで、Read で必要なものだけ取り出せる。

### SplitDocxSections

```js
SplitDocxSections({ file_path: "input/long-spec.docx" })
// → 見出しベースで分割した .md と manifest.json を生成
```

長い Word 文書を構造化して Read で取り回しやすくする。

## ファイル選択の指針

| ファイル | 第一選択 | フォールバック |
|---|---|---|
| PDF（テキストあり） | ReadPdf | - |
| PDF（スキャン画像） | ReadPdf（自動 OCR フォールバック） | 手動 PdfToImages → ReadImage |
| PDF 内をキーワード検索 | ReadPdf + `query` | Read → Grep（ReadPdf で出力保存後） |
| Excel（小〜中） | ReadExcel | - |
| Excel（巨大） | SplitExcelSheets → Read | - |
| Word（短〜中） | ReadDocx | - |
| Word（長文・章構成） | SplitDocxSections → Read | - |
| PowerPoint | ReadPPTX | - |

## 注意

- すべて workspace 内のローカルファイル（`input/` または `output/`）が対象
- URL 指定不可 → DownloadFile で先にローカル保存
- 全ツール read-only（書き込まない）

## ファイルサイズ上限

Read 系ツールは悪意あるファイル / リソース枯渇対策として入力サイズを検証する。デフォルトは以下の通りで、`config.yaml` の `tools` セクション、または Settings UI の「Tools → Office ファイルサイズ上限」から変更可能。

| ツール | デフォルト | config キー |
|---|---|---|
| ReadExcel | 10 MB | `tools.office_excel_max_size_mb` |
| ReadDocx | 10 MB | `tools.office_docx_max_size_mb` |
| ReadPdf | 10 MB | `tools.office_pdf_max_size_mb` |
| ReadPPTX | 50 MB | `tools.office_pptx_max_size_mb` |
| ReadPPTX 展開後 | 200 MB | `tools.office_pptx_max_uncompressed_mb` |

PPTX の「展開後」は ZIP bomb 検知用で、ZIP 内の全エントリの非圧縮合計サイズに対する閾値。超過時は `ZIP bomb detected: ...` エラーとなる。

マクロ付きファイル（`.xlsm` / `.docm` / `.pptm` / `.xlsb`）は警告付きで読み込まれる（実行はされない）。
