ajout des with et export csv

refactor
Nicolas Sanchez 4 weeks ago
parent 3f1feea6cf
commit 9dd247b20c

@ -0,0 +1 @@
,sanchezn,pc-sanchezn,17.02.2026 15:16,/home/sanchezn/.local/share/onlyoffice;

@ -3,6 +3,9 @@ name = "xlsxtocsv"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[features]
csv = []
[dependencies] [dependencies]
clap = { version = "4.5.48", features = ["derive"] } clap = { version = "4.5.48", features = ["derive"] }
rhai = "1.23.6" rhai = "1.23.6"

@ -2,11 +2,12 @@ use clap::Parser;
use clap::ValueEnum; use clap::ValueEnum;
use std::fmt; use std::fmt;
#[derive(Clone, Debug, ValueEnum)] #[derive(Clone, Debug, ValueEnum, Default)]
pub enum FillMergedCells { pub enum FillMergedCells {
None, None,
Horizontal, Horizontal,
Vertical, Vertical,
#[default]
Both, Both,
} }
@ -21,11 +22,12 @@ impl fmt::Display for FillMergedCells {
} }
} }
#[derive(Clone, Debug, ValueEnum)] #[derive(Clone, Debug, ValueEnum, Default)]
pub enum IncludeHidden { pub enum IncludeHidden {
None, None,
Rows, Rows,
Columns, Columns,
#[default]
Both, Both,
} }
@ -39,11 +41,12 @@ impl fmt::Display for IncludeHidden {
} }
} }
} }
#[derive(Clone, Debug, ValueEnum)] #[derive(Clone, Debug, ValueEnum, Default)]
pub enum TrimSpaces { pub enum TrimSpaces {
End, End,
Start, Start,
Both, Both,
#[default]
None, None,
} }
@ -62,10 +65,11 @@ impl fmt::Display for TrimSpaces {
} }
} }
#[derive(Clone, Debug, ValueEnum)] #[derive(Clone, Debug, ValueEnum, Default)]
pub enum NumberRows { pub enum NumberRows {
AsIs, AsIs,
Sequential, Sequential,
#[default]
None, None,
} }
@ -106,6 +110,9 @@ pub struct RawArguments {
/// Chosse worksheet /// Chosse worksheet
#[arg(short, long, default_value_t = String::from("0"))] #[arg(short, long, default_value_t = String::from("0"))]
pub worksheet: String, 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 /// Trim white spaces at end of cells
#[arg(short, long, default_value_t = TrimSpaces::None)] #[arg(short, long, default_value_t = TrimSpaces::None)]
pub trim: TrimSpaces, pub trim: TrimSpaces,
@ -137,15 +144,17 @@ pub struct Arguments {
/// Replace separator char in cells by /// Replace separator char in cells by
pub replace_separator_by: Option<String>, pub replace_separator_by: Option<String>,
/// include hidden lines to output /// include hidden lines to output
pub include_hidden: IncludeHidden, // pub include_hidden: IncludeHidden,
pub include_hidden_rows: bool, pub include_hidden_rows: bool,
pub include_hidden_columns: bool, pub include_hidden_columns: bool,
/// If merged cells, fill horizontally, vertically, both, or none /// 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_horizontal: bool,
pub fill_merged_cells_vertical: bool, pub fill_merged_cells_vertical: bool,
/// Chosse worksheet /// Chosse worksheet
pub worksheet: String, 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 /// Trim white spaces at end of cells
pub trim: TrimSpaces, pub trim: TrimSpaces,
/// number the rows in first cell of each line /// 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<RawArguments> for Arguments { impl From<RawArguments> for Arguments {
fn from(raw: RawArguments) -> Self { fn from(raw: RawArguments) -> Self {
let (include_hidden_columns, include_hidden_rows) = match raw.include_hidden { let (include_hidden_columns, include_hidden_rows) = match raw.include_hidden {
@ -189,13 +221,12 @@ impl From<RawArguments> for Arguments {
list_worksheets: raw.list_worksheets, list_worksheets: raw.list_worksheets,
separator: raw.separator, separator: raw.separator,
replace_separator_by: raw.replace_separator_by, replace_separator_by: raw.replace_separator_by,
include_hidden: raw.include_hidden,
include_hidden_columns, include_hidden_columns,
include_hidden_rows, include_hidden_rows,
fill_merged_cells: raw.fill_merged_cells,
fill_merged_cells_horizontal, fill_merged_cells_horizontal,
fill_merged_cells_vertical, fill_merged_cells_vertical,
worksheet: raw.worksheet, worksheet: raw.worksheet,
active_worksheet: raw.active_worksheet,
trim: raw.trim, trim: raw.trim,
number_rows: raw.number_rows, number_rows: raw.number_rows,
skip_rows: raw.skip_rows, skip_rows: raw.skip_rows,
@ -205,3 +236,31 @@ impl From<RawArguments> 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
}
}

@ -1,3 +1,7 @@
pub mod arguments; pub mod arguments;
pub mod error; pub mod error;
pub mod xlsx; pub mod xlsx;
pub mod xlsx_builder;
//#[cfg(feature = "csv")]
pub mod xlsx_to_csv;

@ -1,8 +1,14 @@
use xlsxtocsv::{arguments::Arguments, xlsx::xlsxtocsv}; use xlsxtocsv::{arguments::Arguments, xlsx::XlsxReader};
fn main() { fn main() {
let args = Arguments::parse(); let args = Arguments::parse();
if let Err(error) = xlsxtocsv(&args) { /* if let Err(error) = xlsxtocsv(&args) {
eprintln!("{}", error); eprintln!("{}", error);
}*/
let xlsxreader = XlsxReader::new(args).unwrap();
let reader = xlsxreader.to_csv();
for line in reader {
println!("{line}");
} }
} }

@ -1,19 +1,140 @@
use std::cell::RefCell;
use std::io::{BufWriter, Write, stdout}; use std::io::{BufWriter, Write, stdout};
use std::path::Path; 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; use crate::error::Error;
pub struct XlsxReader {
pub(crate) args: Arguments,
pub(crate) book: Spreadsheet,
pub(crate) sheet_index: usize,
pub worksheet_dimensions: RefCell<Option<(u32, u32)>>,
}
impl XlsxReader {
pub fn new(args: impl IntoArgs) -> Result<Self, Error> {
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<Vec<(usize, String)>, 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<usize, Error> {
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::<usize>() {
// 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<String> {
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<Vec<(usize, String)>, 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> { pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
let book = reader::xlsx::read(Path::new(&args.file))?; let book = reader::xlsx::read(Path::new(&args.file))?;
if args.list_worksheets { if args.list_worksheets {
println!("List of 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(()); return Ok(());
} }

@ -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, Error> {
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
}
}

@ -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<Self::Item> {
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,
}
}
}
Loading…
Cancel
Save