Skip to main content

calamine/
auto.rs

1// SPDX-License-Identifier: MIT
2//
3// Copyright 2016-2025, Johann Tuffe.
4
5//! A module to convert file extension to reader
6
7use crate::errors::Error;
8use crate::vba::VbaProject;
9use crate::{
10    open_workbook, open_workbook_from_rs, Data, DataRef, HeaderRow, Metadata, Ods, Range, Reader,
11    ReaderRef, Xls, Xlsb, Xlsx,
12};
13use std::fs::File;
14use std::io::BufReader;
15use std::path::Path;
16
17/// A wrapper over all sheets when the file type is not known at static time
18pub enum Sheets<RS> {
19    /// Xls reader
20    Xls(Xls<RS>),
21    /// Xlsx reader
22    Xlsx(Xlsx<RS>),
23    /// Xlsb reader
24    Xlsb(Xlsb<RS>),
25    /// Ods reader
26    Ods(Ods<RS>),
27}
28
29/// Opens a workbook and define the file type at runtime.
30///
31/// Whenever possible use the statically known `open_workbook` function instead
32pub fn open_workbook_auto<P>(path: P) -> Result<Sheets<BufReader<File>>, Error>
33where
34    P: AsRef<Path>,
35{
36    let path = path.as_ref();
37    Ok(match path.extension().and_then(|e| e.to_str()) {
38        Some("xls" | "xla") => Sheets::Xls(open_workbook(path).map_err(Error::Xls)?),
39        Some("xlsx" | "xlsm" | "xlam") => Sheets::Xlsx(open_workbook(path).map_err(Error::Xlsx)?),
40        Some("xlsb") => Sheets::Xlsb(open_workbook(path).map_err(Error::Xlsb)?),
41        Some("ods") => Sheets::Ods(open_workbook(path).map_err(Error::Ods)?),
42        _ => {
43            if let Ok(ret) = open_workbook::<Xls<_>, _>(path) {
44                return Ok(Sheets::Xls(ret));
45            } else if let Ok(ret) = open_workbook::<Xlsx<_>, _>(path) {
46                return Ok(Sheets::Xlsx(ret));
47            } else if let Ok(ret) = open_workbook::<Xlsb<_>, _>(path) {
48                return Ok(Sheets::Xlsb(ret));
49            } else if let Ok(ret) = open_workbook::<Ods<_>, _>(path) {
50                return Ok(Sheets::Ods(ret));
51            } else {
52                return Err(Error::Msg("Cannot detect file format"));
53            };
54        }
55    })
56}
57
58/// Opens a workbook from the given bytes.
59///
60/// Whenever possible use the statically known `open_workbook_from_rs` function instead
61pub fn open_workbook_auto_from_rs<RS>(data: RS) -> Result<Sheets<RS>, Error>
62where
63    RS: std::io::Read + std::io::Seek + Clone,
64{
65    if let Ok(ret) = open_workbook_from_rs::<Xls<RS>, RS>(data.clone()) {
66        Ok(Sheets::Xls(ret))
67    } else if let Ok(ret) = open_workbook_from_rs::<Xlsx<RS>, RS>(data.clone()) {
68        Ok(Sheets::Xlsx(ret))
69    } else if let Ok(ret) = open_workbook_from_rs::<Xlsb<RS>, RS>(data.clone()) {
70        Ok(Sheets::Xlsb(ret))
71    } else if let Ok(ret) = open_workbook_from_rs::<Ods<RS>, RS>(data) {
72        Ok(Sheets::Ods(ret))
73    } else {
74        Err(Error::Msg("Cannot detect file format"))
75    }
76}
77
78impl<RS> Reader<RS> for Sheets<RS>
79where
80    RS: std::io::Read + std::io::Seek,
81{
82    type Error = Error;
83
84    /// Creates a new instance.
85    fn new(_reader: RS) -> Result<Self, Self::Error> {
86        Err(Error::Msg("Sheets must be created from a Path"))
87    }
88
89    fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self {
90        match self {
91            Sheets::Xls(e) => {
92                e.with_header_row(header_row);
93            }
94            Sheets::Xlsx(e) => {
95                e.with_header_row(header_row);
96            }
97            Sheets::Xlsb(e) => {
98                e.with_header_row(header_row);
99            }
100            Sheets::Ods(e) => {
101                e.with_header_row(header_row);
102            }
103        }
104        self
105    }
106
107    /// Gets `VbaProject`
108    fn vba_project(&mut self) -> Result<Option<VbaProject>, Self::Error> {
109        match self {
110            Sheets::Xls(e) => e.vba_project().map_err(Error::Xls),
111            Sheets::Xlsx(e) => e.vba_project().map_err(Error::Xlsx),
112            Sheets::Xlsb(e) => e.vba_project().map_err(Error::Xlsb),
113            Sheets::Ods(e) => e.vba_project().map_err(Error::Ods),
114        }
115    }
116
117    /// Initialize
118    fn metadata(&self) -> &Metadata {
119        match self {
120            Sheets::Xls(e) => e.metadata(),
121            Sheets::Xlsx(e) => e.metadata(),
122            Sheets::Xlsb(e) => e.metadata(),
123            Sheets::Ods(e) => e.metadata(),
124        }
125    }
126
127    /// Read worksheet data in corresponding worksheet path
128    fn worksheet_range(&mut self, name: &str) -> Result<Range<Data>, Self::Error> {
129        match self {
130            Sheets::Xls(e) => e.worksheet_range(name).map_err(Error::Xls),
131            Sheets::Xlsx(e) => e.worksheet_range(name).map_err(Error::Xlsx),
132            Sheets::Xlsb(e) => e.worksheet_range(name).map_err(Error::Xlsb),
133            Sheets::Ods(e) => e.worksheet_range(name).map_err(Error::Ods),
134        }
135    }
136
137    /// Read worksheet formula in corresponding worksheet path
138    fn worksheet_formula(&mut self, name: &str) -> Result<Range<String>, Self::Error> {
139        match self {
140            Sheets::Xls(e) => e.worksheet_formula(name).map_err(Error::Xls),
141            Sheets::Xlsx(e) => e.worksheet_formula(name).map_err(Error::Xlsx),
142            Sheets::Xlsb(e) => e.worksheet_formula(name).map_err(Error::Xlsb),
143            Sheets::Ods(e) => e.worksheet_formula(name).map_err(Error::Ods),
144        }
145    }
146
147    fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
148        match self {
149            Sheets::Xls(e) => e.worksheets(),
150            Sheets::Xlsx(e) => e.worksheets(),
151            Sheets::Xlsb(e) => e.worksheets(),
152            Sheets::Ods(e) => e.worksheets(),
153        }
154    }
155
156    #[cfg(feature = "picture")]
157    fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>> {
158        match self {
159            Sheets::Xls(e) => e.pictures(),
160            Sheets::Xlsx(e) => e.pictures(),
161            Sheets::Xlsb(e) => e.pictures(),
162            Sheets::Ods(e) => e.pictures(),
163        }
164    }
165}
166
167impl<RS> ReaderRef<RS> for Sheets<RS>
168where
169    RS: std::io::Read + std::io::Seek,
170{
171    fn worksheet_range_ref<'a>(
172        &'a mut self,
173        name: &str,
174    ) -> Result<Range<DataRef<'a>>, Self::Error> {
175        match self {
176            Sheets::Xlsx(e) => e.worksheet_range_ref(name).map_err(Error::Xlsx),
177            Sheets::Xlsb(e) => e.worksheet_range_ref(name).map_err(Error::Xlsb),
178            Sheets::Xls(_) => unimplemented!(),
179            Sheets::Ods(_) => unimplemented!(),
180        }
181    }
182}