calamine とは
Rust の calamine
クレートは、Excel ファイルを読み込むためのライブラリです。
- calamine - Rust
- tafia/calamine: A pure Rust Excel/OpenDocument SpeadSheets file reader: rust on metal sheets
読み込み専用 (read-only) のライブラリですが、ピュアな Rust 実装で軽量です。 作者の tafia (Johann Tuffe) 氏によると、「書き込み (write) はめっちゃ複雑だから対応しないよ。セルのアップデートくらいなら対応するかもね」とのこと。
Rust プロジェクトで次のように依存関係を追加すれば準備完了です。
$ cargo add calamine
以下、Excel ファイルを読み込むサンプルコードです。
ワークシート名のリストを取得する (Xlsx#sheet_names)
open_workbook
で Excel ファイルを開いて、Xlsx
インスタンスを取得するところがすべての始まりです。
Xlsx#sheet_names
メソッドで、ワークシート名の一覧を取得できます。
ワークシートを読み込む (Xlsx#worksheet_range, worksheet_range_at)
Xlsx#worksheet_range
メソッドで、ワークシート内のデータ(セル)を読み込むことができます。
このようにデータを読み込む場合は、open_workbook
で Excel ファイルを開くときに、mutable 変数で受け取る必要があることに注意してください(ワークシートの情報を内部に展開するため)。
ワークシート名ではなく、インデックスでワークシートを指定したいときは、worksheet_range
の代わりに worksheet_range_at
メソッドを使います。
if let Some(Ok(range)) = workbook.worksheet_range_at(0) {
println!("{:?}", range);
}
値を持つセルの範囲を取得する (Range#get_size)
Range#get_size
メソッドで、ワークシート内のどの範囲(行/列)にセルが存在するかを調べることができます。
上記方法で、Range
オブジェクトを取得済みと想定しています。
// range は Range<DataType> インスタンス
println!("rows = {}", range.get_size().0);
println!("cols = {}", range.get_size().1);
1 行ずつループ処理する (Range#rows)
ここで使用する Excel ファイル (sample.xlsx
) の内容は、次のようになっていると想定します。
(空)と書いてあるのは空白セルです。
つまり、3 行 x 3 列で、歯抜けのセルが含まれたワークシートです。
A1セル | B1セル | (空) |
A2セル | B2セル | C2セル |
(空) | B3セル | (空) |
Range
オブジェクトの rows
メソッドを使って、次のように 1 行ずつループ処理することができます。
各行のカラム数は len()
メソッドで取得できますが、この値は最も多くのカラムを含む行に合わせた値になるようです(今回の sample.xlsx
では、すべての行のカラム数が 3 になる)。
let mut workbook: Xlsx<_> = calamine::open_workbook("sample.xlsx")?;
if let Some(Ok(range)) = workbook.worksheet_range_at(0) {
for row in range.rows() {
println!("len={}, row={:?}, row[0]={:?}", row.len(), row, row[0]);
}
}
空のセルは、DataType
列挙型の Empty
バリアントとして表現されます。
ループ時に行のインデックスが欲しければ、enumerate
と組み合わせて次のようにします。
for (row_index, row) in range.rows().enumerate() {
println!("{}: {:?}", row_index, row);
}
すべてのセルを取り出す (Range#cells)
Range
内のセルを、行単位ではなくまとめて取得したいときは、Range#cells
メソッドを使用します。
let cells = range.cells(); // Cells<DataType>
println!("セルの数 = {}", cells.len());
for cell in cells {
let row = cell.0;
let col = cell.1;
let data = cell.2;
println!("[{}, {}] = {}", row, col, data);
}
各セルの値 (cell.2
) は、DataType
という列挙型で表現されており、各バリアントで異なるタイプのデータを保持するようになっています。
match
式を使えば、含まれているデータのタイプに応じて分岐処理することができます。
for cell in range.cells() {
print!("[{}, {}] = ", cell.0, cell.1); // セルの位置を表示
match cell.2 {
DataType::Empty => println!("空っぽのセルです"),
DataType::String(s) => println!("{} という文字列セルです", s),
DataType::Int(n) => println!("{} という整数値セルです", n),
DataType::Float(f) => println!("{} という浮動小数点数セルです", f),
DataType::Bool(b) => println!("{} という真偽値セルです", b),
DataType::DateTime(d) => println!("{} という日時セルです", d),
DataType::Error(e) => println!("ERROR: {}", e),
}
}
空セルや文字列セルかどうかを調べる専用のメソッドも用意されています。
let data = cell.2;
println!("is_empty = {}", data.is_empty());
println!("is_string = {}", data.is_string());
文字列セルの場合だけ、その文字列を取り出しつつ処理したい場合は、DataType#get_string
メソッドで Option<&str>
を取得します。
let data = cell.2;
if let Some(s) = data.get_string() {
println!("文字列セルの値: {}", s);
}
単一のセルの値を取得する (Range#get_value, get)
Range#get_value
メソッドで、指定した行・列のセルの値を取得することができます。
戻り値は Option<&DataType>
型になっており、空セルの場合は Option::None
が返されます。
let cell = range.get_value((0, 1)); // Option<&DataType>
println!("{:?}", cell); //=> Some(String("Hello"))