From a05d0a52b426e36e01bfb306d5f51468e993ee00 Mon Sep 17 00:00:00 2001 From: Nicolas Sanchez Date: Sat, 4 Oct 2025 18:39:43 +0200 Subject: [PATCH] =?UTF-8?q?ajout=20de=20la=20duplication=20du=20contenu=20?= =?UTF-8?q?=C3=A9tendu=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .~lock.test_masque.xlsx# | 2 +- src/arguments.rs | 2 +- src/main.rs | 71 ++++---------------------- src/xlsxtocsv.rs | 104 +++++++++++++++++++++++++++++++++++++++ test_masque.csv | 2 - test_masque.xlsx | Bin 5735 -> 5826 bytes 6 files changed, 116 insertions(+), 65 deletions(-) create mode 100644 src/xlsxtocsv.rs diff --git a/.~lock.test_masque.xlsx# b/.~lock.test_masque.xlsx# index fa82ba9..76cea7e 100644 --- a/.~lock.test_masque.xlsx# +++ b/.~lock.test_masque.xlsx# @@ -1 +1 @@ -,sanchezn,pc-sanchezn,04.10.2025 13:10,file:///home/sanchezn/.config/libreoffice/4; \ No newline at end of file +,sanchezn,pc-sanchezn,04.10.2025 18:38,file:///home/sanchezn/.config/libreoffice/4; \ No newline at end of file diff --git a/src/arguments.rs b/src/arguments.rs index 776100d..8ae9188 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -11,7 +11,7 @@ pub struct Arguments { /// Replacement char to replace separator in cells text #[arg(short, long)] pub replacement: Option, - /// Default hidden lines are not included to output + /// include hidden lines to output #[arg(short, long, default_value_t=false)] pub include_hidden_lines: bool, } diff --git a/src/main.rs b/src/main.rs index 34f74e9..c3f3559 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,67 +1,16 @@ -use clap::Parser; -use std::path::Path; -use umya_spreadsheet::reader; - -pub mod arguments; -use arguments::Arguments; - pub mod error; -use crate::error::Error; - -fn main() -> Result<(), Error> { - let args = Arguments::parse(); - let book = reader::xlsx::read(Path::new(&args.file)) - .expect(format!("Can't open {}", args.file).as_str()); +pub mod arguments; +pub mod xlsxtocsv; - let sheet = match book.get_sheet(&0) { - Some(sheet) => sheet, - None => return Err(Error::new("cannot open sheet")), - }; +use clap::Parser; +use arguments::Arguments; +use xlsxtocsv::xlsxtocsv; - let (num_cols, num_rows) = sheet.get_highest_column_and_row(); - for i in 1..=num_rows { - if ! args.include_hidden_lines { - match sheet.get_row_dimension(&i) { - Some(dim) => { - if *dim.get_hidden() { - continue; - } - } - None => continue, - } - } - let row = sheet.get_collection_by_row(&i); - let row_len = row.len(); - let mut first = true; - for cell in row { - if first { - first = false; - } else { - print!("{}", args.separator); - } - let mut value = cell.get_formatted_value(); - if let Some(ref replacement) = args.replacement { - value = value.replace(args.separator, replacement); - } else { - if value.contains(args.separator) { - return Err(Error::new( - format!( - "Cell {} contains separator char", - cell.get_coordinate().get_coordinate() - ) - .as_str(), - )); - } - } - print!("{}", value); - } - for _ in row_len..num_cols as usize { - print!("{}", args.separator); - } - println!(""); +fn main() { + let args = Arguments::parse(); + if let Err(error) = xlsxtocsv(&args) { + eprintln!("{}", error); } - - Ok(()) -} +} \ No newline at end of file diff --git a/src/xlsxtocsv.rs b/src/xlsxtocsv.rs new file mode 100644 index 0000000..88fb8f6 --- /dev/null +++ b/src/xlsxtocsv.rs @@ -0,0 +1,104 @@ +use std::path::Path; +use umya_spreadsheet::{Range, Worksheet, reader}; + +use crate::arguments::Arguments; +use crate::error::Error; + +pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> { + let book = reader::xlsx::read(Path::new(&args.file)) + .expect(format!("Can't open {}", args.file).as_str()); + + let sheet = match book.get_sheet(&0) { + Some(sheet) => sheet, + None => return Err(Error::new("cannot open sheet")), + }; + + let merged_cells = MergedCells::new(sheet); + + let (num_cols, num_rows) = sheet.get_highest_column_and_row(); + + for i in 1..=num_rows { + if !args.include_hidden_lines { + match sheet.get_row_dimension(&i) { + Some(dim) => { + if *dim.get_hidden() { + continue; + } + } + None => continue, + } + } + let row = sheet.get_collection_by_row(&i); + let row_len = row.len(); + let mut first = true; + for cell in row { + if first { + first = false; + } else { + print!("{}", args.separator); + } + + let cell_coordinate = cell.get_coordinate(); + let mut value; + if let Some((col, row)) = merged_cells.in_merged_cell(*cell_coordinate.get_col_num(), *cell_coordinate.get_row_num()) { + value = match sheet.get_cell((col,row)) { + Some(merged_cell) => merged_cell.get_formatted_value(), + None => String::from(""), + } + } else { + value = cell.get_formatted_value(); + } + + if let Some(ref replacement) = args.replacement { + value = value.replace(args.separator, replacement); + } else { + if value.contains(args.separator) { + return Err(Error::new( + format!( + "Cell {} contains separator char, use -r to choose a replacement char", + cell.get_coordinate().get_coordinate() + ) + .as_str(), + )); + } + } + print!("{}", value); + } + for _ in row_len..num_cols as usize { + print!("{}", args.separator); + } + println!(""); + } + + Ok(()) +} + +struct MergedCells { + merged_cells: Vec, +} + +impl MergedCells { + + pub fn new(sheet: &Worksheet) -> Self { + let merged = sheet.get_merge_cells(); + let mut merged_cells: Vec = vec![]; + + for cell in merged { + merged_cells.push(cell.clone()); + } + + MergedCells { merged_cells } + } + + pub fn in_merged_cell(&self, col: u32, row: u32) -> Option<(u32, u32)> { + for range in &self.merged_cells { + if col >= *range.get_coordinate_start_col().unwrap().get_num() && col <= *range.get_coordinate_end_col().unwrap().get_num() && + row >= *range.get_coordinate_start_row().unwrap().get_num() && row <= *range.get_coordinate_end_row().unwrap().get_num() { + return Some((range.get_coordinate_start_col().unwrap().get_num().clone(), (range.get_coordinate_start_row().unwrap().get_num().clone()))); + } + + } + None + } + +} diff --git a/test_masque.csv b/test_masque.csv index bcd028a..71f7a5d 100644 --- a/test_masque.csv +++ b/test_masque.csv @@ -1,4 +1,2 @@ A1;12;10 A2;33; -A4,A4;45; -A5;90; diff --git a/test_masque.xlsx b/test_masque.xlsx index 5f6bfa706a8ca8f35ad25b3e108755ebc4072fd6..cd7043ef554fea8094d288eda061a6a3b2031a10 100644 GIT binary patch delta 3032 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 P6F^c!i7-h0{iFW^7eZZ6 delta 2926 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 Z5`7q0lwOzzm&U`7s|0g1jqsiv{0~+sMo9nw