use crate::common::international::extract_numbers_international;
use calamine::{open_workbook_auto, DataType, Reader, Xls, Xlsx};
use std::path::Path;
pub fn parse_excel_file(file_path: &Path) -> crate::error::Result<Vec<f64>> {
let extension = file_path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
.to_lowercase();
match extension.as_str() {
"xlsx" => parse_xlsx_file(file_path),
"xls" => parse_xls_file(file_path),
_ => {
parse_excel_auto(file_path)
}
}
}
fn parse_xlsx_file(file_path: &Path) -> crate::error::Result<Vec<f64>> {
let mut workbook: Xlsx<_> = calamine::open_workbook(file_path).map_err(|e| {
crate::error::BenfError::FileError(format!("Failed to open XLSX file: {e}"))
})?;
extract_numbers_from_xlsx(&mut workbook)
}
fn parse_xls_file(file_path: &Path) -> crate::error::Result<Vec<f64>> {
let mut workbook: Xls<_> = calamine::open_workbook(file_path)
.map_err(|e| crate::error::BenfError::FileError(format!("Failed to open XLS file: {e}")))?;
extract_numbers_from_xls(&mut workbook)
}
fn parse_excel_auto(file_path: &Path) -> crate::error::Result<Vec<f64>> {
let workbook = open_workbook_auto(file_path).map_err(|e| {
crate::error::BenfError::FileError(format!("Failed to open Excel file: {e}"))
})?;
match workbook {
calamine::Sheets::Xlsx(mut xlsx) => extract_numbers_from_xlsx(&mut xlsx),
calamine::Sheets::Xls(mut xls) => extract_numbers_from_xls(&mut xls),
calamine::Sheets::Xlsb(mut xlsb) => extract_numbers_from_xlsb(&mut xlsb),
calamine::Sheets::Ods(mut ods) => extract_numbers_from_ods(&mut ods),
}
}
fn extract_numbers_from_xlsx(
workbook: &mut Xlsx<std::io::BufReader<std::fs::File>>,
) -> crate::error::Result<Vec<f64>> {
extract_numbers_from_workbook(workbook)
}
fn extract_numbers_from_xls(
workbook: &mut Xls<std::io::BufReader<std::fs::File>>,
) -> crate::error::Result<Vec<f64>> {
extract_numbers_from_workbook(workbook)
}
fn extract_numbers_from_xlsb(
workbook: &mut calamine::Xlsb<std::io::BufReader<std::fs::File>>,
) -> crate::error::Result<Vec<f64>> {
extract_numbers_from_workbook(workbook)
}
fn extract_numbers_from_ods(
workbook: &mut calamine::Ods<std::io::BufReader<std::fs::File>>,
) -> crate::error::Result<Vec<f64>> {
extract_numbers_from_workbook(workbook)
}
fn extract_numbers_from_workbook<R: Reader<std::io::BufReader<std::fs::File>>>(
workbook: &mut R,
) -> crate::error::Result<Vec<f64>> {
let mut all_numbers = Vec::new();
let sheet_names = workbook.sheet_names().to_vec();
for sheet_name in sheet_names {
if let Some(Ok(range)) = workbook.worksheet_range(&sheet_name) {
for row in range.rows() {
for cell in row {
match cell {
DataType::Float(f) => {
if *f != 0.0 && f.is_finite() {
all_numbers.push(*f);
}
}
DataType::Int(i) => {
if *i != 0 {
all_numbers.push(*i as f64);
}
}
DataType::String(s) => {
let extracted = extract_numbers_international(s);
all_numbers.extend(extracted);
}
_ => {}
}
}
}
}
}
if all_numbers.is_empty() {
return Err(crate::error::BenfError::NoNumbersFound);
}
Ok(all_numbers)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_excel_parsing_concept() {
let test_path = PathBuf::from("nonexistent.xlsx");
let result = parse_excel_file(&test_path);
assert!(result.is_err());
match result {
Err(crate::error::BenfError::FileError(_)) => {
}
_ => panic!("Expected file error for non-existent Excel file"),
}
}
#[test]
fn test_real_excel_file() {
let test_path = PathBuf::from("tests/fixtures/sample_data.xlsx");
if test_path.exists() {
let result = parse_excel_file(&test_path);
match result {
Ok(numbers) => {
assert!(!numbers.is_empty(), "Should extract at least some numbers");
println!(
"Extracted {count} numbers from Excel file",
count = numbers.len()
);
println!("All extracted numbers: {numbers:?}");
assert!(
numbers.contains(&1234567.89),
"Should contain amount 1234567.89"
);
assert!(
numbers.contains(&234567.12),
"Should contain amount 234567.12"
);
assert!(
numbers.contains(&567890.34),
"Should contain amount 567890.34"
);
assert!(
numbers.len() >= 10,
"Should extract at least 10 numbers, got {count}",
count = numbers.len()
);
println!(
"✅ Excel parsing test passed! Extracted all expected financial data."
);
}
Err(e) => {
println!("Excel parsing failed (expected if test file is missing): {e}");
}
}
} else {
println!("Test Excel file not found at {test_path:?}, skipping real file test");
}
}
}