use std::{ fs::File, io::{BufWriter, Write, stderr, stdout}, }; use crate::{error::Error, xlsx::XlsxReader}; #[derive(Clone, Debug)] pub struct XlsxToCsvLines { xlsx_reader: XlsxReader, current_row: u32, num_rows: u32, separator: String, end_of_line: String, } impl Iterator for XlsxToCsvLines { type Item = Result; fn next(&mut self) -> Option { if self.current_row > self.num_rows { return None; } let mut row = self.xlsx_reader.get_row(self.current_row); match &self.xlsx_reader.args.replace_separator_by { Some(replacement) => { row = row .iter() .map(|v| v.replace(self.xlsx_reader.args.separator, replacement.as_str())) .collect() } None => { if row .iter() .any(|v| v.contains(self.xlsx_reader.args.separator)) { return Some(Err( "Some cells contains the separator char. Use a replacement for separator char inside cells.".into(), )); } } } self.current_row += 1; let mut row = row.join(self.separator.as_str()); match &self.xlsx_reader.args.replace_end_of_line_by { Some(replacement) => row = row.replace(self.end_of_line.as_str(), replacement), None => { if row.contains(self.end_of_line.as_str()) { return Some(Err("Some cells contains the end of line char. Use a replacement for end of line char inside cells.".into())); } } } let row = row + self.end_of_line.as_str(); Some(Ok(row)) } } pub enum Output { File(String), Stdout, Stderr, } pub trait IntoOutput { fn into_output(self) -> Output; } impl IntoOutput for Output { fn into_output(self) -> Output { self } } impl IntoOutput for String { fn into_output(self) -> Output { Output::File(self) } } impl IntoOutput for &str { fn into_output(self) -> Output { Output::File(String::from(self)) } } impl XlsxReader { pub fn to_csv_lines(mut self) -> Result { self.finish()?; let num_rows = self.get_worksheet_dimensions().1; let end_of_line = self.args.end_of_line.clone(); let separator = String::from(self.args.separator); Ok(XlsxToCsvLines { xlsx_reader: self, current_row: 0, num_rows, separator, end_of_line, }) } pub fn to_csv(self, output: impl IntoOutput) -> Result<(), Error> { let output = output.into_output(); let mut writer: Box = match output { Output::File(filename) => Box::new(BufWriter::new(File::open(filename)?)), Output::Stdout => Box::new(BufWriter::new(stdout().lock())), Output::Stderr => Box::new(BufWriter::new(stderr().lock())), }; for line in self.to_csv_lines()? { writer.write_all(line?.as_bytes())?; } Ok(()) } }