diff --git a/.~lock.MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx# b/.~lock.MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx# new file mode 100644 index 0000000..e6b29f2 --- /dev/null +++ b/.~lock.MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx# @@ -0,0 +1 @@ +,sanchezn,pc-sanchezn,17.02.2026 15:16,/home/sanchezn/.local/share/onlyoffice; \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index dd82485..647199d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "xlsxtocsv" version = "0.1.0" edition = "2024" +[features] +csv = [] + [dependencies] clap = { version = "4.5.48", features = ["derive"] } rhai = "1.23.6" diff --git a/MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx b/MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx index a4559b0..9752ad7 100644 Binary files a/MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx and b/MAQUETTES ANALYSES FINANCIERES VUE SYNTHETIQUE VERSION LONGUE.xlsx differ diff --git a/src/arguments.rs b/src/arguments.rs index 8a7b609..c725c34 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -2,11 +2,12 @@ use clap::Parser; use clap::ValueEnum; use std::fmt; -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Debug, ValueEnum, Default)] pub enum FillMergedCells { None, Horizontal, Vertical, + #[default] Both, } @@ -21,11 +22,12 @@ impl fmt::Display for FillMergedCells { } } -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Debug, ValueEnum, Default)] pub enum IncludeHidden { None, Rows, Columns, + #[default] Both, } @@ -39,11 +41,12 @@ impl fmt::Display for IncludeHidden { } } } -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Debug, ValueEnum, Default)] pub enum TrimSpaces { End, Start, Both, + #[default] None, } @@ -62,10 +65,11 @@ impl fmt::Display for TrimSpaces { } } -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Debug, ValueEnum, Default)] pub enum NumberRows { AsIs, Sequential, + #[default] None, } @@ -106,6 +110,9 @@ pub struct RawArguments { /// Chosse worksheet #[arg(short, long, default_value_t = String::from("0"))] pub worksheet: String, + /// Choose active worksheet at save time in excel + #[arg(short, long, default_value_t = false)] + pub active_worksheet: bool, /// Trim white spaces at end of cells #[arg(short, long, default_value_t = TrimSpaces::None)] pub trim: TrimSpaces, @@ -137,15 +144,17 @@ pub struct Arguments { /// Replace separator char in cells by pub replace_separator_by: Option, /// include hidden lines to output - pub include_hidden: IncludeHidden, + // pub include_hidden: IncludeHidden, pub include_hidden_rows: bool, pub include_hidden_columns: bool, /// If merged cells, fill horizontally, vertically, both, or none - pub fill_merged_cells: FillMergedCells, + // pub fill_merged_cells: FillMergedCells, pub fill_merged_cells_horizontal: bool, pub fill_merged_cells_vertical: bool, /// Chosse worksheet pub worksheet: String, + /// Use the worksheet that was active when the file was last saved + pub active_worksheet: bool, /// Trim white spaces at end of cells pub trim: TrimSpaces, /// number the rows in first cell of each line @@ -167,6 +176,29 @@ impl Arguments { } } +impl Default for Arguments { + fn default() -> Self { + Self { + file: Default::default(), + list_worksheets: Default::default(), + separator: ';', + replace_separator_by: Default::default(), + include_hidden_rows: Default::default(), + include_hidden_columns: Default::default(), + fill_merged_cells_horizontal: Default::default(), + fill_merged_cells_vertical: Default::default(), + worksheet: String::from("0"), + active_worksheet: false, + trim: Default::default(), + number_rows: Default::default(), + skip_rows: Default::default(), + end_of_line: String::from("\n"), + replace_end_of_line_by: Default::default(), + filter: Default::default(), + } + } +} + impl From for Arguments { fn from(raw: RawArguments) -> Self { let (include_hidden_columns, include_hidden_rows) = match raw.include_hidden { @@ -189,13 +221,12 @@ impl From for Arguments { list_worksheets: raw.list_worksheets, separator: raw.separator, replace_separator_by: raw.replace_separator_by, - include_hidden: raw.include_hidden, include_hidden_columns, include_hidden_rows, - fill_merged_cells: raw.fill_merged_cells, fill_merged_cells_horizontal, fill_merged_cells_vertical, worksheet: raw.worksheet, + active_worksheet: raw.active_worksheet, trim: raw.trim, number_rows: raw.number_rows, skip_rows: raw.skip_rows, @@ -205,3 +236,31 @@ impl From for Arguments { } } } + +pub trait IntoArgs { + fn into_args(self) -> Arguments; +} + +impl IntoArgs for String { + fn into_args(self) -> Arguments { + Arguments { + file: self, + ..Default::default() + } + } +} + +impl IntoArgs for &str { + fn into_args(self) -> Arguments { + Arguments { + file: self.to_string(), + ..Default::default() + } + } +} + +impl IntoArgs for Arguments { + fn into_args(self) -> Arguments { + self + } +} diff --git a/src/lib.rs b/src/lib.rs index 8d16fa1..d6d4d5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ pub mod arguments; pub mod error; pub mod xlsx; +pub mod xlsx_builder; + +//#[cfg(feature = "csv")] +pub mod xlsx_to_csv; diff --git a/src/main.rs b/src/main.rs index 7426b6e..6af5368 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,14 @@ -use xlsxtocsv::{arguments::Arguments, xlsx::xlsxtocsv}; +use xlsxtocsv::{arguments::Arguments, xlsx::XlsxReader}; fn main() { let args = Arguments::parse(); - if let Err(error) = xlsxtocsv(&args) { - eprintln!("{}", error); + /* if let Err(error) = xlsxtocsv(&args) { + eprintln!("{}", error); + }*/ + + let xlsxreader = XlsxReader::new(args).unwrap(); + let reader = xlsxreader.to_csv(); + for line in reader { + println!("{line}"); } } diff --git a/src/xlsx.rs b/src/xlsx.rs index b6eea9b..2be26af 100644 --- a/src/xlsx.rs +++ b/src/xlsx.rs @@ -1,19 +1,140 @@ +use std::cell::RefCell; use std::io::{BufWriter, Write, stdout}; use std::path::Path; -use umya_spreadsheet::{Cell, Range, Worksheet, reader}; +use umya_spreadsheet::{Cell, Range, Spreadsheet, Worksheet, reader}; -use crate::arguments::{Arguments, NumberRows, TrimSpaces}; +use crate::arguments::{Arguments, IntoArgs, NumberRows, TrimSpaces}; use crate::error::Error; +pub struct XlsxReader { + pub(crate) args: Arguments, + pub(crate) book: Spreadsheet, + pub(crate) sheet_index: usize, + pub worksheet_dimensions: RefCell>, +} + +impl XlsxReader { + pub fn new(args: impl IntoArgs) -> Result { + let mut args = args.into_args(); + + let book = reader::xlsx::read(Path::new(args.file.as_str()))?; + + if args.active_worksheet && args.worksheet == "0" { + let sheetname = book.get_active_sheet().get_name(); + args.worksheet = String::from(sheetname); + } + let sheet_index = Self::get_worksheet_index(&book, args.worksheet.as_str())?; + + Ok(XlsxReader { + args, + book, + sheet_index, + worksheet_dimensions: RefCell::new(None), + }) + } + + pub fn list_worksheets(&self) -> Result, Error> { + let sheets = self.book.get_sheet_collection(); + let mut res = vec![]; + + for (i, sheet) in sheets.iter().enumerate() { + res.push((i, String::from(sheet.get_name()))); + } + Ok(res) + } + + pub fn to_lazyframe(&self) { + let sheet = self.get_sheet(); + println!("sheetname: {}", sheet.get_name()); + } + + pub(crate) fn get_worksheet_index( + book: &Spreadsheet, + worksheet_name: &str, + ) -> Result { + let sheet_index = if book.get_sheet_by_name(worksheet_name).is_some() { + // Le nom existe - trouve son index + book.get_sheet_collection() + .iter() + .position(|s| s.get_name() == worksheet_name) + .ok_or("cannot find sheet index")? + } else if let Ok(num) = worksheet_name.parse::() { + // Pas de nom correspondant, essaye comme numéro + if book.get_sheet(&num).is_none() { + return Err("cannot open sheet".into()); + } + num + } else { + return Err("cannot open sheet".into()); + }; + Ok(sheet_index) + } + + pub(crate) fn get_sheet(&self) -> &Worksheet { + self.book.get_sheet(&self.sheet_index).unwrap() + } + + pub(crate) fn get_worksheet_dimensions(&self) -> (u32, u32) { + if self.worksheet_dimensions.borrow().is_none() { + let mut num_cols = 0; + let mut num_rows = 0; + + for cell in self.get_sheet().get_cell_collection() { + let value = get_value(cell); //.get_formatted_value(); + + if value.is_empty() { + continue; + } + + let coord = cell.get_coordinate(); + let col_num = *coord.get_col_num(); + let row_num = *coord.get_row_num(); + if col_num > num_cols { + num_cols = col_num; + } + if row_num > num_rows { + num_rows = row_num; + } + } + let mut dim = self.worksheet_dimensions.borrow_mut(); + *dim = Some((num_cols, num_rows)); + } + let (c, r) = self.worksheet_dimensions.borrow().unwrap(); + (c, r) + } + + pub(crate) fn get_row(&self, row_num: u32) -> Vec { + let num_cols = self.get_worksheet_dimensions().0 as usize; + let sheet = self.get_sheet(); + let row = sheet.get_collection_by_row(&row_num); + + let mut res = vec![String::new(); num_cols]; + + for cell in row { + let txt = cell.get_formatted_value(); + let coord = cell.get_coordinate(); + let col = *coord.get_col_num() - 1; + res[col as usize] = txt; + } + + res + } +} + +pub fn list_worksheets(book: Spreadsheet) -> Result, Error> { + let sheets = book.get_sheet_collection(); + let mut res = vec![]; + for (i, sheet) in sheets.iter().enumerate() { + res.push((i, String::from(sheet.get_name()))); + } + Ok(res) +} + pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> { let book = reader::xlsx::read(Path::new(&args.file))?; if args.list_worksheets { println!("List of worksheets :"); - let sheets = book.get_sheet_collection(); - for (i, sheet) in sheets.iter().enumerate() { - println!(" {:3}: {}", i, sheet.get_name()); - } return Ok(()); } diff --git a/src/xlsx_builder.rs b/src/xlsx_builder.rs new file mode 100644 index 0000000..6bfcccb --- /dev/null +++ b/src/xlsx_builder.rs @@ -0,0 +1,113 @@ +use crate::{ + arguments::{FillMergedCells, IncludeHidden, NumberRows, TrimSpaces}, + error::Error, + xlsx::XlsxReader, +}; + +impl XlsxReader { + pub fn with_separator(mut self, separator: char) -> Self { + self.args.separator = separator; + self + } + + pub fn with_replace_separator_by(mut self, replacement: String) -> Self { + self.args.replace_separator_by = Some(replacement); + self + } + + pub fn with_include_hidden_columns(mut self, include: bool) -> Self { + self.args.include_hidden_columns = include; + self + } + + pub fn with_include_hidden_rows(mut self, include: bool) -> Self { + self.args.include_hidden_rows = include; + self + } + + pub fn with_include_hidden(mut self, include: IncludeHidden) -> Self { + let (col, row) = match include { + IncludeHidden::None => (false, false), + IncludeHidden::Rows => (false, true), + IncludeHidden::Columns => (true, false), + IncludeHidden::Both => (true, true), + }; + self.args.include_hidden_columns = col; + self.args.include_hidden_rows = row; + self + } + + pub fn with_fill_merged_cells_vertical(mut self, merge: bool) -> Self { + self.args.fill_merged_cells_vertical = merge; + self + } + + pub fn with_fill_merged_cells_horizontal(mut self, merge: bool) -> Self { + self.args.fill_merged_cells_horizontal = merge; + self + } + + pub fn with_fill_merged_cells(mut self, mode: FillMergedCells) -> Self { + let (horizontal, vertical) = match mode { + FillMergedCells::None => (false, false), + FillMergedCells::Horizontal => (true, false), + FillMergedCells::Vertical => (false, true), + FillMergedCells::Both => (true, true), + }; + self.args.fill_merged_cells_horizontal = horizontal; + self.args.fill_merged_cells_vertical = vertical; + self + } + + pub fn with_worksheet(mut self, worksheet_name: &str) -> Result { + self.args.worksheet = String::from(worksheet_name); + + self.sheet_index = Self::get_worksheet_index(&self.book, worksheet_name)?; + Ok(self) + } + + pub fn with_active_worksheet(mut self, active_worksheet: bool) -> Self { + self.args.active_worksheet = active_worksheet; + self + } + + pub fn with_trim_start(mut self) -> Self { + self.args.trim = match self.args.trim { + TrimSpaces::End => TrimSpaces::Both, + TrimSpaces::Start => TrimSpaces::Start, + TrimSpaces::Both => TrimSpaces::Both, + TrimSpaces::None => TrimSpaces::Start, + }; + self + } + + pub fn with_trim_end(mut self) -> Self { + self.args.trim = match self.args.trim { + TrimSpaces::End => TrimSpaces::End, + TrimSpaces::Start => TrimSpaces::Both, + TrimSpaces::Both => TrimSpaces::Both, + TrimSpaces::None => TrimSpaces::End, + }; + self + } + + pub fn with_number_rows(mut self, number_rows: NumberRows) -> Self { + self.args.number_rows = number_rows; + self + } + + pub fn with_skip_rows(mut self, skip: u32) -> Self { + self.args.skip_rows = skip; + self + } + + pub fn with_end_of_line(mut self, eol: String) -> Self { + self.args.end_of_line = eol; + self + } + + pub fn with_replace_end_of_line_by(mut self, replacement: String) -> Self { + self.args.replace_end_of_line_by = Some(replacement); + self + } +} diff --git a/src/xlsx_to_csv.rs b/src/xlsx_to_csv.rs new file mode 100644 index 0000000..df53cab --- /dev/null +++ b/src/xlsx_to_csv.rs @@ -0,0 +1,35 @@ +use crate::xlsx::XlsxReader; + +pub struct XlsxToCsvLines<'a> { + xlsx_reader: &'a XlsxReader, + current_row: u32, + num_rows: u32, +} + +impl<'a> Iterator for XlsxToCsvLines<'a> { + type Item = String; + + fn next(&mut self) -> Option { + if self.current_row > self.num_rows { + return None; + } + + let row = self.xlsx_reader.get_row(self.current_row); + self.current_row += 1; + + let row = row.join(";"); + + Some(row) + } +} + +impl XlsxReader { + pub fn to_csv(&self) -> XlsxToCsvLines<'_> { + let num_rows = self.get_worksheet_dimensions().1; + XlsxToCsvLines { + xlsx_reader: self, + current_row: 0, + num_rows, + } + } +}