# **アーキテクチャ設計書 (Architecture Design)**
## **目的**
本設計書は、[requirements.md](requirements.md)で定義された要件を実現するための、xlsxzeroクレート全体の構造的な骨格を定義する。特に以下の点を明確にする:
* プログラム全体のモジュール構成と各モジュールの責務
* 主要なモジュール間の役割分担と依存関係
* 採用する設計原則やデザインパターン
* 技術選定とその理由
本クレートの中核的な目的は、**RAGシステムへの入力データ提供のため、Pure-Rust実装によりExcelファイルを構造化されたMarkdown形式に変換すること**である。
---
## **1. システム構成図(コンポーネント図)**
```
┌─────────────────────────────────────────────────────────────────────┐
│ xlsxzero crate │
│ (公開API / Facade Layer) │
└───────────────────────────┬─────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Builder │ │ Converter │ │ ErrorTypes │
│ Module │ │ Module │ │ Module │
│ (builder.rs) │ │(converter.rs)│ │ (error.rs) │
└───────┬───────┘ └──────┬───────┘ └─────────────────┘
│ │
│ ▼
│ ┌───────────────┐
└─────────>│ Parser │
│ Module │
│ (parser.rs) │
└───────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Formatter │ │ Grid │ │ Types │
│ Module │ │ Module │ │ Module │
│(formatter.rs)│ │ (grid.rs) │ │ (types.rs) │
└──────────────┘ └──────────────┘ └─────────────────┘
│
▼
┌──────────────────────────┐
│ Number Format Parser │
│ Submodule │
│ (format/mod.rs) │
└──────────────────────────┘
External Dependencies:
┌──────────────────────────────────────────────────────────────┐
│ calamine (Pure-Rust Excel parser) │
│ thiserror (Error handling) │
│ chrono (Date/Time handling, NaiveDateTime only) │
└──────────────────────────────────────────────────────────────┘
```
### **各モジュールの責務**
#### **1.1. Builder Module (`builder.rs`)**
**責務:**
* Fluent Builder APIの提供
* 変換設定の段階的な構築と検証
* ユーザーフレンドリーな設定インターフェースの提供
**主要な構造体:**
* `ConverterBuilder`: 設定構築用のビルダー構造体
* `ConversionConfig`: 内部設定データを保持する構造体
**提供する設定:**
* シート選択(インデックス、名前、すべて)
* セル範囲指定
* セル結合戦略(データ重複フィル / HTMLフォールバック)
* 日付形式(ISO 8601デフォルト / カスタム)
* 非表示要素の包含制御
* 数式出力モード
#### **1.2. Converter Module (`converter.rs`)**
**責務:**
* 公開APIのエントリーポイント
* Parser、Grid、Formatterの協調制御
* ストリーミング処理のオーケストレーション
**主要な構造体:**
* `Converter`: 変換処理のファサード
* 実装する主要メソッド:
* `convert<R: Read, W: Write>(&self, input: R, output: W) -> Result<(), XlsxToMdError>`
#### **1.3. Parser Module (`parser.rs`)**
**責務:**
* calamineを使用したExcelファイルの解析
* ストリーミング方式によるセルデータの抽出
* メタデータ(シート情報、セル結合範囲、書式情報)の収集
**主要な構造体:**
* `WorkbookParser`: calamineのラッパー、ワークブックレベルの操作
* `SheetParser`: シート単位のストリーミング解析
**出力:**
* `RawCellData`: 生のセルデータ(値、型、座標、書式ID)
* `MergedRegion`: セル結合範囲情報
* `SheetMetadata`: シートのメタデータ
#### **1.4. Grid Module (`grid.rs`)**
**責務:**
* スパースなセルデータから稠密なグリッド構造への再構築
* セル結合の論理的処理(データ重複フィル)
* Markdownテーブル生成のための前処理
**主要な構造体:**
* `LogicalGrid`: 論理的なテーブルグリッド表現
* `Cell`: 書式適用後の表示文字列を保持するセル構造
**処理フロー:**
1. パーサーから受け取ったスパースなセルデータを行列形式に展開
2. セル結合情報を解析し、結合戦略に基づいて処理
3. Formatterモジュールからのフォーマットされたセルデータでグリッドを埋める
#### **1.5. Formatter Module (`formatter.rs`)**
**責務:**
* 生のセル値(f64、String、Bool)から表示文字列への変換
* Number Format Stringの解析と適用
* 日付・時刻のシリアル値変換
**主要な構造体:**
* `CellFormatter`: セル値のフォーマット処理のファサード
* `DateFormatter`: 日付専用のフォーマッター
* `NumberFormatter`: 数値専用のフォーマッター
**依存関係:**
* Number Format Parser Submodule(独立したサブモジュール)
#### **1.6. Number Format Parser Submodule (`format/mod.rs`)**
**責務:**
* Excel Number Format Stringの構文解析
* セクション(正数、負数、ゼロ、テキスト)の分離
* フォーマットトークンの抽出と解釈
**主要な構造体:**
* `FormatParser`: パーサーのエントリーポイント
* `FormatSection`: セクション単位のフォーマット情報
* `FormatToken`: 個別のフォーマット要素(年、月、日、小数点など)
**備考:**
* このモジュールは将来的に独立したクレート(`formato`)として切り出し可能な設計とする
#### **1.7. Types Module (`types.rs`)**
**責務:**
* クレート全体で使用する共通のデータ型定義
* セル値の列挙型、座標型、メタデータ構造体など
**主要な型:**
* `CellValue`: セルの値を表す列挙型(Number(f64), String(String), Bool(bool), Error(String), Empty)
* `CellCoord`: セル座標 (row: u32, col: u32)
* `CellRange`: セル範囲 (start: CellCoord, end: CellCoord)
#### **1.8. Error Types Module (`error.rs`)**
**責務:**
* クレート全体のエラー型定義
* thiserrorを使用した構造化エラーの実装
**主要な型:**
* `XlsxToMdError`: 公開エラー型(列挙型)
* `Io(#[from] std::io::Error)`: I/Oエラー
* `Parse(#[from] calamine::Error)`: 解析エラー
* `Config(String)`: 設定エラー
* `UnsupportedFeature(String)`: 未サポート機能エラー
---
## **2. モジュール間の関係**
### **2.1. データフロー図**
```
┌─────────────┐
│ User │
│ Code │
└──────┬──────┘
│ 1. Create Builder
▼
┌─────────────────┐
│ ConverterBuilder│
└──────┬──────────┘
│ 2. Configure & Build
▼
┌─────────────────┐ ┌──────────────┐
│ Converter │────────>│ Config Data │
└──────┬──────────┘ └──────────────┘
│ 3. convert(input, output)
▼
┌─────────────────┐
│ WorkbookParser │<────── calamine (external)
└──────┬──────────┘
│ 4. Stream RawCellData + Metadata
▼
┌─────────────────┐ ┌──────────────┐
│ CellFormatter │────────>│ Format Parser│
└──────┬──────────┘ └──────────────┘
│ 5. Formatted Display String
▼
┌─────────────────┐
│ LogicalGrid │
└──────┬──────────┘
│ 6. Dense Grid Structure
▼
┌─────────────────┐
│ Markdown Writer │────────> output (std::io::Write)
└─────────────────┘
```
### **2.2. 主要ユースケースのシーケンス図**
#### **ユースケース: "Excelファイルを基本的なMarkdownテーブルに変換"**
```
User Builder Converter Parser Formatter Grid Output
│ │ │ │ │ │ │
│ new() │ │ │ │ │ │
├──────────────>│ │ │ │ │ │
│ │ │ │ │ │ │
│ with_sheet(0) │ │ │ │ │ │
├──────────────>│ │ │ │ │ │
│ │ │ │ │ │ │
│ build() │ │ │ │ │ │
├──────────────>│ │ │ │ │ │
│ │ Converter │ │ │ │ │
│<──────────────┤ │ │ │ │ │
│ │ │ │ │ │ │
│ convert(in, out) │ │ │ │ │
├─────────────────────────────>│ │ │ │ │
│ │ │ open_workbook(in) │ │ │
│ │ ├───────────>│ │ │ │
│ │ │ │ │ │ │
│ │ │ for each cell │ │ │
│ │ │<───────────┤ │ │ │
│ │ │ RawCellData│ │ │ │
│ │ │ │ │ │ │
│ │ │ format_cell(raw, fmt_str) │ │
│ │ ├───────────────────────>│ │ │
│ │ │ │ DisplayString │ │
│ │ │<───────────────────────┤ │ │
│ │ │ │ │ │ │
│ │ │ add_cell(coord, display_str) │ │
│ │ ├───────────────────────────────────>│ │
│ │ │ │ │ │ │
│ │ │ (repeat for all cells) │ │
│ │ │ │ │ │ │
│ │ │ render_markdown() │ │
│ │ ├───────────────────────────────────>│ │
│ │ │ │ │ write(out) │
│ │ │ │ │ ├──────────>│
│ │ │ │ │ │ │
│ │ │ Ok(()) │ │ │ │
│<─────────────────────────────┤ │ │ │ │
```
---
## **3. 設計原則・デザインパターン**
### **3.1. 採用する主要な設計原則**
#### **3.1.1. SOLID原則の適用**
* **Single Responsibility Principle (単一責任の原則)**:
* 各モジュールは明確に定義された単一の責務を持つ。
* 例: Parserモジュールは解析のみ、Formatterモジュールは書式適用のみを担当。
* **Open/Closed Principle (開放/閉鎖の原則)**:
* 拡張に対して開いており、修正に対して閉じている設計。
* 例: セル結合戦略を`MergeStrategy` traitとして抽象化し、新しい戦略を追加可能にする。
* **Dependency Inversion Principle (依存性逆転の原則)**:
* 上位モジュールは下位モジュールの具象に依存せず、抽象(trait)に依存する。
* 例: ConverterはParserの具象実装ではなく、`WorkbookReader` traitに依存。
#### **3.1.2. 関心の分離 (Separation of Concerns)**
* **解析ロジックと変換ロジックの分離**:
* Parser層(calamine依存)とFormatter層(書式処理)を完全に分離。
* Parserの変更がFormatterに影響を与えない構造。
* **データモデルとビジネスロジックの分離**:
* Types Moduleでデータ構造を定義し、各モジュールで操作ロジックを実装。
#### **3.1.3. ゼロコスト抽象化 (Zero-Cost Abstraction)**
* トレイトベースの抽象化を使用しつつ、ランタイムオーバーヘッドを最小化。
* 静的ディスパッチを優先し、動的ディスパッチは必要な場合のみ使用。
### **3.2. 適用するデザインパターン**
#### **3.2.1. Builder Pattern(ビルダーパターン)**
**適用箇所:** ConverterBuilder
**理由:**
* 複雑な設定(シート選択、範囲指定、結合戦略など)を段階的に構築。
* デフォルト値の提供と、ユーザーによる選択的なオーバーライドを可能にする。
* 設定の検証をbuild()時点で一括実行し、不正な設定を早期に検出。
**実装例:**
```rust
let converter = ConverterBuilder::new()
.with_sheet_index(0)
.with_merge_strategy(MergeStrategy::DataDuplication)
.with_date_format(DateFormat::Iso8601)
.build()?;
```
#### **3.2.2. Strategy Pattern(戦略パターン)**
**適用箇所:** セル結合処理
**理由:**
* 複数のセル結合戦略(データ重複フィル、HTMLフォールバック)を切り替え可能にする。
* ユーザーの要件に応じて、適切な戦略を選択可能。
**実装方針:**
* **Phase I**: 列挙型による実装(シンプルで実用的)
* **Phase II以降**: 必要に応じてtrait化(拡張性向上)
**実装例(Phase I):**
```rust
#[derive(Debug, Clone, Copy)]
pub enum MergeStrategy {
DataDuplication,
HtmlFallback,
}
impl MergeStrategy {
pub(crate) fn apply(&self, grid: &mut LogicalGrid, merge_regions: &[MergedRegion]) {
match self {
MergeStrategy::DataDuplication => apply_data_duplication(grid, merge_regions),
MergeStrategy::HtmlFallback => apply_html_fallback(grid, merge_regions),
}
}
}
```
**将来の拡張(Phase II以降、オプション):**
```rust
// trait化による拡張性向上
trait MergeStrategy {
fn apply(&self, grid: &mut LogicalGrid, merge_regions: &[MergedRegion]);
}
struct DataDuplicationStrategy;
struct HtmlFallbackStrategy;
impl MergeStrategy for DataDuplicationStrategy { /* ... */ }
impl MergeStrategy for HtmlFallbackStrategy { /* ... */ }
```
#### **3.2.3. Facade Pattern(ファサードパターン)**
**適用箇所:** Converter Module
**理由:**
* Parser、Formatter、Gridの複雑な協調処理を隠蔽し、シンプルな`convert()`メソッドのみを公開。
* ユーザーは内部実装の詳細を意識せずに変換処理を実行可能。
#### **3.2.4. Streaming Pattern(ストリーミングパターン)**
**適用箇所:** Parser Module
**理由:**
* 大規模ファイルをメモリに全展開せず、セル単位でストリーミング処理。
* メモリ使用量をファイルサイズの10%以下に抑制する要件を満たす。
**実装概要:**
* calamineの`Range::rows()`イテレータを使用し、行単位で処理。
* 各セルを処理後、即座にFormatterに渡し、フォーマット済みデータをGridに追加。
---
## **4. 技術選定**
### **4.1. 外部依存クレート**
| **calamine** | `^0.26` | Pure-Rust製のExcelパーサー。ストリーミング解析対応、高性能、メンテナンス活発。 | MIT/Apache-2.0 |
| **thiserror** | `^1.0` | 構造化エラーの定義を簡潔に記述可能。`#[from]`属性によるエラー変換の自動化。 | MIT/Apache-2.0 |
| **chrono** | `^0.4` | 日付・時刻処理の標準クレート。NaiveDateTime型を使用(タイムゾーン非依存)。 | MIT/Apache-2.0 |
### **4.2. 内部実装方針**
#### **4.2.1. ストリーミング処理の実現**
**選択:** calamineの`Range::rows()`イテレータ + BufWriter
**理由:**
* DOM方式(全データをメモリに展開)を避け、SAXライクなイベント駆動型処理を実現。
* 各行を処理後、即座に出力バッファに書き込むことで、メモリフットプリントを最小化。
**実装パターン:**
```rust
use std::io::{BufWriter, Write};
fn convert_sheet<W: Write>(range: Range<DataType>, output: W) -> Result<()> {
let mut writer = BufWriter::new(output);
for row in range.rows() {
for cell in row {
// セル処理
let formatted = format_cell(cell)?;
write!(writer, "{}\t", formatted)?;
}
writeln!(writer)?;
}
writer.flush()?;
Ok(())
}
```
#### **4.2.2. Number Format Stringの解析**
**選択:** 独自実装(将来的にformatoクレートへの切り出しを検討)
**理由:**
* 既存のformatoクレート(crates.io)は更新が停滞しており、本プロジェクトの要件を満たさない。
* Number Format Stringの仕様は複雑で、独自実装により正確な変換を保証。
* サブモジュール化により、テストとメンテナンスを容易化。
**実装範囲:**
* 日付・時刻フォーマット(`yyyy-mm-dd`, `hh:mm:ss`など)
* 数値フォーマット(`#,##0.00`, `0.00%`など)
* 条件付き書式の基本的なサポート(`[Red]`, `[>100]`など)
#### **4.2.3. セル結合の処理**
**デフォルト戦略: データ重複フィル**
**実装方針:**
1. calamineから`merged_regions()`でセル結合範囲を取得。
2. 結合範囲内の親セル(左上セル)の値を取得。
3. LogicalGrid上で、結合範囲内のすべての論理セルに親セルの値を複製。
4. 純粋なMarkdownテーブルとして出力。
**代替戦略: HTMLフォールバック**
**実装方針:**
1. 結合セルを検出した場合、テーブル全体をHTMLテーブルとして出力。
2. `rowspan`/`colspan`属性を使用して、構造的忠実性を維持。
---
## **5. パフォーマンス最適化戦略**
### **5.1. メモリ効率**
* **ストリーミング処理:** 全データをメモリに展開せず、行単位で処理。
* **BufWriterの使用:** 出力バッファリングにより、システムコール数を削減。
* **Copy-on-Writeの回避:** 可能な限り参照を使用し、不要なクローンを避ける。
### **5.2. 処理速度**
* **並列処理の検討:** 将来的にRayonクレートを使用し、複数シートの並列処理を実装可能。
* **アロケーション最小化:** `String::with_capacity()`で事前にメモリを確保し、再アロケーションを削減。
---
## **6. エラーハンドリング戦略**
### **6.1. エラー伝播の方針**
* すべての公開APIは`Result<T, XlsxToMdError>`を返す。
* 内部実装でのpanic!は禁止(デバッグビルドのみ許可)。
* `?`演算子による自動エラー変換を活用。
### **6.2. エラーコンテキストの提供**
* エラーメッセージには、ファイルパス、シート名、セル座標などのコンテキスト情報を含める。
**実装例:**
```rust
#[derive(Error, Debug)]
pub enum XlsxToMdError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse Excel file: {0}")]
Parse(#[from] calamine::Error),
#[error("Configuration error: {0}")]
Config(String),
#[error("Unsupported feature at sheet '{sheet}', cell {cell}: {message}")]
UnsupportedFeature {
sheet: String,
cell: String,
message: String,
},
}
```
---
## **7. テスト戦略**
### **7.1. 単体テスト**
* 各モジュールに対して、独立した単体テストを実装。
* Number Format Parserは、特に広範なテストケースを用意(Excelの標準書式をすべてカバー)。
### **7.2. 統合テスト**
* `tests/`ディレクトリに、実際のExcelファイルを使用した統合テストを配置。
* 以下のテストケースを網羅:
* 小規模ファイル(1MB未満)
* 中規模ファイル(10MB〜100MB)
* 大規模ファイル(1GB以上)
* セル結合を含むファイル
* 複雑な書式を含むファイル
### **7.3. ベンチマーク**
* `benches/`ディレクトリに、Criterionを使用したベンチマークを配置。
* メモリ使用量のプロファイリング(valgrind, heaptrack)。
---
## **8. 将来の拡張性**
### **8.1. プラグインアーキテクチャ**
* 将来的に、カスタムフォーマッターやカスタム結合戦略をプラグインとして追加可能な設計を検討。
### **8.2. WebAssembly対応**
* stdライブラリへの依存を最小限に抑え、wasm32-unknown-unknownターゲットへの対応を容易化。
### **8.3. フォーマット拡張**
* Markdown以外の出力フォーマット(JSON、CSV、HTML)をサポート可能な設計。
* 出力フォーマットをStrategy Patternで抽象化。
---
## **付録: モジュール構成(ファイル構造)**
```
xlsxzero/
├── Cargo.toml
├── src/
│ ├── lib.rs // クレートのエントリーポイント、公開APIの定義
│ ├── builder.rs // ConverterBuilderの実装
│ ├── converter.rs // Converterの実装(ファサード)
│ ├── parser.rs // WorkbookParser, SheetParserの実装
│ ├── formatter.rs // CellFormatter, DateFormatter, NumberFormatterの実装
│ ├── grid.rs // LogicalGrid, Cellの実装
│ ├── types.rs // 共通データ型の定義
│ ├── error.rs // XlsxToMdErrorの定義
│ └── format/ // Number Format Parser Submodule
│ ├── mod.rs // サブモジュールのエントリーポイント
│ ├── parser.rs // FormatParserの実装
│ ├── tokens.rs // FormatTokenの定義
│ └── sections.rs // FormatSectionの定義
├── tests/
│ ├── integration_test.rs // 統合テスト
│ └── fixtures/ // テスト用Excelファイル
├── benches/
│ └── benchmark.rs // パフォーマンステスト
└── examples/
└── basic_conversion.rs // 基本的な使用例
```
---
## **9. Phase II拡張: XML Metadata Parserモジュール**
### **9.1. 概要**
Phase Iでは、calamineのAPIのみを使用して基本機能を実装する。Phase IIでは、calamineで取得できない情報(Number Format String、非表示情報)を補完するため、**XML Metadata Parserモジュール**を追加実装する。
**背景(Issue #2調査結果):**
- ✅ calamine 0.32.0は数式・結合セル情報の取得に対応
- ❌ Number Format String(書式文字列)の取得は未対応
- ❌ 非表示行/列の情報取得は未対応(GitHub Issue #237)
**実装方針:**
- 依存クレートを増やさず、`quick-xml`(calamineが既に依存)と`zip`クレートを使用
- XML直接解析により、不足情報を補完
- calamineとの共存: calamineで基本データ取得 + XmlMetadataParserで補足情報取得
### **9.2. モジュール構成の拡張**
```
┌─────────────────────────────────────────────────────────────────────┐
│ xlsxzero crate │
│ (公開API / Facade Layer) │
└───────────────────────────┬─────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Builder │ │ Converter │ │ ErrorTypes │
│ Module │ │ Module │ │ Module │
└───────┬───────┘ └──────┬───────┘ └─────────────────┘
│ │
│ ▼
│ ┌───────────────┐
└─────────>│ Parser │◄────────────┐
│ Module │ │
└───────┬───────┘ │
│ │
┌──────────────────┼─────────────┐ │
│ │ │ │
▼ ▼ ▼ │
┌──────────────┐ ┌──────────────┐ ┌────────▼──────────┐
│ Formatter │ │ Grid │ │ XmlMetadataParser │ [Phase II]
│ Module │ │ Module │ │ Module │
└──────────────┘ └──────────────┘ └───────────────────┘
│
▼
┌──────────────────────────┐
│ Number Format Parser │
│ Submodule │
└──────────────────────────┘
```
### **9.3. XmlMetadataParser Module (`parser/metadata.rs`)**
#### **責務:**
* XLSX内部のXMLファイルから、calamineで取得不可能な情報を抽出
* Number Format String(カスタム書式)の取得
* 非表示行/列の情報取得
* 1904年エポック判定(オプション)
#### **主要な構造体:**
```rust
pub(crate) struct XlsxMetadataParser {
// xl/styles.xml から解析
num_formats: HashMap<u32, String>, // numFmtId -> formatCode
cell_xfs: Vec<CellXf>, // styleId -> (numFmtId, fontId, ...)
// xl/worksheets/sheet*.xml から解析
hidden_rows: HashMap<String, HashSet<u32>>, // sheet_name -> row_indices
hidden_cols: HashMap<String, HashSet<u32>>, // sheet_name -> col_indices
// xl/workbook.xml から解析(オプション)
is_1904: bool, // date1904属性
}
#[derive(Debug, Clone)]
pub(crate) struct CellXf {
pub num_fmt_id: u32,
pub font_id: Option<u32>,
pub fill_id: Option<u32>,
pub border_id: Option<u32>,
}
```
#### **主要なメソッド:**
```rust
impl XlsxMetadataParser {
/// XLSXファイル(ZIPアーカイブ)からメタデータを解析
pub fn new<R: Read + Seek>(xlsx_reader: R) -> Result<Self, XlsxToMdError> {
let mut archive = zip::ZipArchive::new(xlsx_reader)?;
// 1. xl/styles.xml を解析
let (num_formats, cell_xfs) = Self::parse_styles(&mut archive)?;
// 2. xl/worksheets/*.xml を解析
let (hidden_rows, hidden_cols) = Self::parse_worksheets(&mut archive)?;
// 3. xl/workbook.xml を解析(オプション)
let is_1904 = Self::parse_workbook(&mut archive)?;
Ok(Self { num_formats, cell_xfs, hidden_rows, hidden_cols, is_1904 })
}
/// styleIdからNumber Format Stringを取得
pub fn get_format_string(&self, style_id: u32) -> Option<&str> {
self.cell_xfs.get(style_id as usize)
.and_then(|xf| self.num_formats.get(&xf.num_fmt_id))
.map(|s| s.as_str())
}
/// 行が非表示かどうかを判定
pub fn is_row_hidden(&self, sheet_name: &str, row: u32) -> bool {
self.hidden_rows.get(sheet_name)
.map(|rows| rows.contains(&row))
.unwrap_or(false)
}
/// 列が非表示かどうかを判定
pub fn is_col_hidden(&self, sheet_name: &str, col: u32) -> bool {
self.hidden_cols.get(sheet_name)
.map(|cols| cols.contains(&col))
.unwrap_or(false)
}
/// xl/styles.xml の解析(プライベート)
fn parse_styles<R: Read + Seek>(
archive: &mut ZipArchive<R>
) -> Result<(HashMap<u32, String>, Vec<CellXf>), XlsxToMdError> {
// quick-xmlでストリーミング解析
// <numFmts><numFmt numFmtId="165" formatCode="0.000"/></numFmts>
// <cellXfs><xf numFmtId="165" fontId="0"/></cellXfs>
}
/// xl/worksheets/*.xml の解析(プライベート)
fn parse_worksheets<R: Read + Seek>(
archive: &mut ZipArchive<R>
) -> Result<(HashMap<String, HashSet<u32>>, HashMap<String, HashSet<u32>>), XlsxToMdError> {
// <row r="15" hidden="1">...</row>
// <cols><col min="3" max="3" hidden="1"/></cols>
}
/// xl/workbook.xml の解析(プライベート)
fn parse_workbook<R: Read + Seek>(
archive: &mut ZipArchive<R>
) -> Result<bool, XlsxToMdError> {
// <workbookPr date1904="true"/>
}
}
```
### **9.4. WorkbookParserとの統合**
Phase IIでは、`WorkbookParser`がcalamineとXmlMetadataParserの両方を保持する:
```rust
pub(crate) struct WorkbookParser<R: Read + Seek> {
workbook: Xlsx<R>, // Phase I
metadata: Option<XlsxMetadataParser>, // Phase II
}
impl<R: Read + Seek> WorkbookParser<R> {
/// Phase II対応のコンストラクタ
pub fn open_with_metadata(reader: R) -> Result<Self, XlsxToMdError> {
// 1回のファイル読み込みで両方を初期化
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)?;
let workbook = open_workbook_from_bytes(&buffer)?;
let metadata = Some(XlsxMetadataParser::new(Cursor::new(&buffer))?);
Ok(Self { workbook, metadata })
}
}
```
### **9.5. 解析対象XMLとデータマッピング**
#### **xl/styles.xml**
```xml
<styleSheet>
<numFmts count="1">
<numFmt numFmtId="165" formatCode="0.000"/>
</numFmts>
<cellXfs count="2">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
<xf numFmtId="165" fontId="0" fillId="0" borderId="0"/>
</cellXfs>
</styleSheet>
```
**マッピング:**
- styleId=0 → numFmtId=0 (ビルトイン書式 "General")
- styleId=1 → numFmtId=165 → formatCode="0.000"
#### **xl/worksheets/sheet1.xml**
```xml
<worksheet>
<cols>
<col min="3" max="3" hidden="1" width="0" customWidth="1"/>
</cols>
<sheetData>
<row r="15" hidden="1">
<c r="A15"><v>Hidden Cell</v></c>
</row>
</sheetData>
</worksheet>
```
**収集データ:**
- hidden_cols: {3} (C列が非表示)
- hidden_rows: {15} (15行目が非表示)
#### **xl/workbook.xml**
```xml
<workbook>
<workbookPr date1904="true"/>
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
</sheets>
</workbook>
```
**収集データ:**
- is_1904: true
### **9.6. ビルトイン書式IDマッピング**
Excelのビルトイン書式(numFmtId 0-163)は、ファイルに記載されないため、ハードコードマッピングが必要:
```rust
const BUILTIN_NUM_FORMATS: &[(u32, &str)] = &[
(0, "General"),
(1, "0"),
(2, "0.00"),
(3, "#,##0"),
(4, "#,##0.00"),
(9, "0%"),
(10, "0.00%"),
(14, "mm-dd-yy"),
(15, "d-mmm-yy"),
(16, "d-mmm"),
(17, "mmm-yy"),
(18, "h:mm AM/PM"),
(19, "h:mm:ss AM/PM"),
(20, "h:mm"),
(21, "h:mm:ss"),
(22, "m/d/yy h:mm"),
// ... (完全なリストは実装時に追加)
];
```
### **9.7. ディレクトリ構造の更新**
```
xlsxzero/
├── src/
│ ├── lib.rs
│ ├── builder.rs
│ ├── converter.rs
│ ├── parser.rs
│ ├── parser/
│ │ ├── mod.rs
│ │ └── metadata.rs // [Phase II追加] XmlMetadataParser
│ ├── formatter.rs
│ ├── grid.rs
│ ├── types.rs
│ ├── error.rs
│ └── format/
│ ├── mod.rs
│ ├── parser.rs
│ ├── tokens.rs
│ └── sections.rs
```
### **9.8. 実装の段階的アプローチ**
**Phase I (calamine APIのみ):**
- ✅ セル値の取得
- ✅ 数式情報の取得
- ✅ 結合セル情報の取得
- ⚠️ 書式情報: 型からの推測のみ
- ⚠️ 非表示情報: 無視(全セル処理)
**Phase II (XML直接解析追加):**
- 🔧 Number Format Stringの完全取得
- 🔧 非表示行/列の判定
- 🔧 1904年エポック判定
**Phase III (最適化、オプション):**
- 🔧 1回のZIP読み込みで両方を処理
- 🔧 メモリ効率の向上
---
**文書管理情報:**
* 作成日: 2025-11-20
* 最終更新日: 2025-11-21
* バージョン: 1.1 (Phase II拡張設計を追加)
* 関連文書: [requirements.md](requirements.md), [consistency_check.md](consistency_check.md), [issue_list.md](issue_list.md)