# `parse/workbook.rs` 設計書
*[English](workbook.en.md)*
`src/parse/workbook.rs` に対応する設計書。[architecture.md](../architecture.md) が定義する `parse/` の責務のうち「`workbook.xml` のパース」を担う。`xl/workbook.xml` の `<sheets>` 直下に並ぶ `<sheet>` 要素を、ソース定義順を保った一覧へ変換する。[model/workbook.md 未決事項1](../model/workbook.md) が確定させた「`Workbook.sheets` は可視性を問わず全シートを含め、可視性は `SheetVisibility` として保持する」という方針をそのまま実装する。
## 責務・スコープ
- `xl/workbook.xml` をパースし、`<sheets><sheet name="..." sheetId="..." state="..." r:id="..."/></sheets>` の各 `<sheet>` をソース定義順の `Vec<WorkbookSheetEntry>` へ変換する
- `state` 属性(`"visible"` / `"hidden"` / `"veryHidden"`。省略時は可視)を [`model::sheet::SheetVisibility`](../model/sheet.md) へ変換する
- **含まない責務**: `r:id` を実体ファイルパスへ解決すること([`parse/relationships.rs`](relationships.md) が構築した `RelationshipMap` と突き合わせるのは `pipeline.rs` の責務)、シート実体(`worksheet.xml`)そのもののパース([`parse/worksheet.rs`](worksheet.md))、`model::Sheet` の構築そのもの(`pipeline.rs` が `WorkbookSheetEntry` から `name`/`visibility` を渡して構築する)
## 主要な型・関数(案)
```rust
use crate::error::Error;
use crate::model::sheet::SheetVisibility;
use crate::parse::{convert_xml_error, create_secure_reader, required_attr};
use std::io::BufRead;
/// `workbook.xml` の `<sheet>` 1件分。
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct WorkbookSheetEntry {
pub name: String,
/// `<sheets><sheet r:id="rId1" .../></sheets>` の r:id。
/// `pipeline.rs` が `parse::relationships::RelationshipMap` と突き合わせて
/// 実体ファイルパスを解決するためのキーとして使う。
pub r_id: String,
pub visibility: SheetVisibility,
}
/// `xl/workbook.xml` をパースし、`<sheets>` 直下の `<sheet>` 要素をソース
/// 定義順で返す。`<sheets>` 要素自体が存在しない場合は
/// `Error::MissingRequiredElement` を返す。`<sheets></sheets>` が空の場合は
/// 空の `Vec` を返す(0シートブックは構造上有効。[model/workbook.md テスト方針](../model/workbook.md) 参照)。
pub(crate) fn parse_workbook_xml(reader: impl BufRead, path: &str) -> Result<Vec<WorkbookSheetEntry>, Error> {
let mut xml_reader = create_secure_reader(reader);
let _ = (&mut xml_reader, path);
unimplemented!()
}
/// `state` 属性文字列を `SheetVisibility` へ変換する。属性が存在しない場合の
/// 既定値は `Visible`。認識できない値(拡張・破損ファイル由来)は `Visible`
/// へフォールバックする(エラーにしない。オープンクエスチョン3参照)。
fn parse_visibility(state: Option<&str>) -> SheetVisibility {
match state {
None | Some("visible") => SheetVisibility::Visible,
Some("hidden") => SheetVisibility::Hidden,
Some("veryHidden") => SheetVisibility::VeryHidden,
Some(_) => SheetVisibility::Visible,
}
}
```
## 依存関係
- 依存先: [`parse/mod.rs`](mod.md)(`create_secure_reader`, `convert_xml_error`, `required_attr`)、[`model/sheet.rs`](../model/sheet.md)(`SheetVisibility`)、[`error.rs`](../error.md)
- 依存元: `pipeline.rs`(フェーズ1。[`parse/relationships.rs`](relationships.md) が構築した `RelationshipMap` と `r_id` で突き合わせて各シートの実体ファイルパスを決定し、`WorkbookSheetEntry` の `name`/`visibility` から `model::Sheet` を構築したうえで [`parse/worksheet.rs`](worksheet.md) にフェーズ3のストリームパースを委譲する)
`parse/workbook.rs` は `model::sheet::SheetVisibility` を直接構築する。architecture.md 設計方針2が禁じるのは「`resolve/` がI/Oやモデル以外に依存すること」であり、`model/` 自体は元々 `parse/` に依存されることを前提とした純粋データ構造の置き場所([model/sheet.md 依存関係](../model/sheet.md) が `parse/worksheet.rs` を依存元として既に列挙している)であるため、`parse/` から `model/` への依存はこの方針と矛盾しない。
## エラー処理方針
- `<sheets>` 要素自体が存在しない場合、`workbook.xml` として構造的に不正であるため `Error::MissingRequiredElement` を返す
- `<sheet>` の `name` / `r:id` いずれかの属性が欠落している場合は `Error::MissingRequiredElement` を返す
- `state` 属性が既知の3値(`visible` / `hidden` / `veryHidden`)以外の場合は `Error::InvalidPackage` 等で拒否せず `Visible` へフォールバックする。可視性は表示上のヒントでありデータの完全性に関わらないため、[resolve/style.md エラー処理方針](../resolve/style.md) が採用する「個々の値解釈の緩やかな失敗はドキュメント全体を失敗させない」という方針と同じ考え方を適用する
- XMLとして構文的に不正な場合は [`convert_xml_error`](mod.md) を通じて `Error::XmlParse` または `Error::ZipBombDetected` に変換する
## テスト方針
- 複数の `<sheet>` を持つ `workbook.xml` から、ソース定義順を保った `Vec<WorkbookSheetEntry>` が得られることの確認
- `state` 属性省略時に `SheetVisibility::Visible` として解釈されることの確認
- `state="hidden"` / `state="veryHidden"` の解釈確認
- `state` 属性が未知の値(例: 将来の仕様拡張や破損ファイル由来の文字列)を持つ場合に、エラーとせず `SheetVisibility::Visible` へフォールバックすることの確認
- `name` 属性・`r:id` 属性のいずれかが欠落した `<sheet>` に対し `Error::MissingRequiredElement` を返すことの確認
- `<sheets>` 要素自体が存在しない `workbook.xml` に対し `Error::MissingRequiredElement` を返すことの確認
- `<sheets></sheets>` が空の場合に空の `Vec` を返すことの確認(0シートブックの結線。[model/workbook.md テスト方針](../model/workbook.md) と対応する)
## 未決事項 / オープンクエスチョン
1. **`sheetId` 属性の扱い**: `<sheet sheetId="1">` はパースするが、[`model::Sheet`](../model/sheet.md) に対応するフィールドがないため現状は破棄する想定。将来的にラウンドトリップ用途等で保持が必要になった場合、`WorkbookSheetEntry` に追加するか別途保持するかは未確定。
2. ~~`r:id` 属性の名前空間解決~~ → **解決**: [parse/mod.md オープンクエスチョン4](mod.md) で確定した「`quick_xml::NsReader` は採用せず文字列前方一致で簡略化する」方針に従い、`"r:id"` という属性名で直接照合する。
3. **`state` 属性の未知の値に対するフォールバック方針の妥当性**: 現状 `Visible` へフォールバックしているが、より安全側(誤って可視化しないよう `Hidden` 扱いにする)にすべきかは、実際の要件・利用ケース次第で未確定。