|
|
|
|
@ -1,59 +1,177 @@
|
|
|
|
|
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, IncludeHidden, NumberRows, TrimSpaces};
|
|
|
|
|
use crate::arguments::{Arguments, IntoArgs, NumberRows, TrimSpaces};
|
|
|
|
|
use crate::error::Error;
|
|
|
|
|
|
|
|
|
|
/// A reader for .xlsx files with extended options.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct XlsxReader {
|
|
|
|
|
pub(crate) args: Arguments,
|
|
|
|
|
pub(crate) book: Option<Spreadsheet>,
|
|
|
|
|
pub(crate) sheet_index: Option<usize>,
|
|
|
|
|
pub(crate) worksheet_dimensions: RefCell<Option<(u32, u32)>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl XlsxReader {
|
|
|
|
|
pub fn new(args: impl IntoArgs) -> Self {
|
|
|
|
|
let args = args.into_args();
|
|
|
|
|
|
|
|
|
|
XlsxReader {
|
|
|
|
|
args,
|
|
|
|
|
book: None,
|
|
|
|
|
sheet_index: None,
|
|
|
|
|
worksheet_dimensions: RefCell::new(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn list_worksheets(&self) -> Result<Vec<(usize, String)>, Error> {
|
|
|
|
|
let book = match &self.book {
|
|
|
|
|
Some(book) => book,
|
|
|
|
|
None => return Err("Call finish before list_worksheets.".into()),
|
|
|
|
|
};
|
|
|
|
|
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(crate) fn finish(&mut self) -> Result<(), Error> {
|
|
|
|
|
let book = reader::xlsx::read(Path::new(self.args.file.as_str()))?;
|
|
|
|
|
|
|
|
|
|
if self.args.active_worksheet && self.args.worksheet == "0" {
|
|
|
|
|
let sheetname = book.get_active_sheet().get_name();
|
|
|
|
|
self.args.worksheet = String::from(sheetname);
|
|
|
|
|
}
|
|
|
|
|
let sheet_index = Self::get_worksheet_index(&book, self.args.worksheet.as_str())?;
|
|
|
|
|
|
|
|
|
|
self.book = Some(book);
|
|
|
|
|
self.sheet_index = Some(sheet_index);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) -> Result<&Worksheet, Error> {
|
|
|
|
|
let book = match &self.book {
|
|
|
|
|
Some(book) => book,
|
|
|
|
|
None => return Err("Call finish before get_sheet.".into()),
|
|
|
|
|
};
|
|
|
|
|
Ok(book.get_sheet(&self.sheet_index.unwrap()).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;
|
|
|
|
|
let sheet = self.get_sheet().unwrap();
|
|
|
|
|
|
|
|
|
|
for cell in 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().unwrap();
|
|
|
|
|
let row = sheet.get_collection_by_row(&row_num);
|
|
|
|
|
|
|
|
|
|
let mut res = vec![String::new(); num_cols];
|
|
|
|
|
|
|
|
|
|
for cell in row {
|
|
|
|
|
let value = cell.get_formatted_value();
|
|
|
|
|
let coord = cell.get_coordinate();
|
|
|
|
|
let col = *coord.get_col_num() - 1;
|
|
|
|
|
res[col as usize] = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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> {
|
|
|
|
|
let book = reader::xlsx::read(Path::new(&args.file))
|
|
|
|
|
.expect(format!("Can't open {}", args.file).as_str());
|
|
|
|
|
let book = reader::xlsx::read(Path::new(&args.file))?;
|
|
|
|
|
|
|
|
|
|
if args.list_worksheets {
|
|
|
|
|
println!("List of worksheets :");
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
let sheets = book.get_sheet_collection();
|
|
|
|
|
for sheet in sheets {
|
|
|
|
|
println!(" {:3}: {}", i, sheet.get_name());
|
|
|
|
|
i += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (include_hidden_columns, include_hidden_rows) = match args.include_hidden {
|
|
|
|
|
IncludeHidden::None => (false, false),
|
|
|
|
|
IncludeHidden::Rows => (false, true),
|
|
|
|
|
IncludeHidden::Columns => (true, false),
|
|
|
|
|
IncludeHidden::Both => (true, true),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// get the sheet from name or number if specified, else the first of the spreadsheet
|
|
|
|
|
let sheet = match book.get_sheet_by_name(&args.worksheet) {
|
|
|
|
|
Some(sheet) => sheet,
|
|
|
|
|
None => {
|
|
|
|
|
let sheetnum: u32 = match args.worksheet.parse() {
|
|
|
|
|
Ok(sheetnum) => sheetnum,
|
|
|
|
|
Err(_) => return Err(Error::new("cannot open sheet")),
|
|
|
|
|
Err(_) => return Err("cannot open sheet".into()),
|
|
|
|
|
};
|
|
|
|
|
let sheet = match book.get_sheet(&(sheetnum as usize)) {
|
|
|
|
|
match book.get_sheet(&(sheetnum as usize)) {
|
|
|
|
|
Some(sheet) => sheet,
|
|
|
|
|
None => return Err(Error::new("cannot open sheet")),
|
|
|
|
|
};
|
|
|
|
|
sheet
|
|
|
|
|
None => return Err("cannot open sheet".into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// set the merged cells policy
|
|
|
|
|
let (horiz, vert) = match args.fill_merged_cells {
|
|
|
|
|
crate::arguments::FillMergedCells::None => (false, false),
|
|
|
|
|
crate::arguments::FillMergedCells::Horizontal => (true, false),
|
|
|
|
|
crate::arguments::FillMergedCells::Vertical => (false, true),
|
|
|
|
|
crate::arguments::FillMergedCells::Both => (true, true),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// get all the merged cells
|
|
|
|
|
let merged_cells = MergedCells::new(sheet, horiz, vert);
|
|
|
|
|
let merged_cells = MergedCells::new(
|
|
|
|
|
sheet,
|
|
|
|
|
args.fill_merged_cells_horizontal,
|
|
|
|
|
args.fill_merged_cells_vertical,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// get non-empty value size of the worksheet
|
|
|
|
|
let mut num_cols = 0;
|
|
|
|
|
@ -62,13 +180,13 @@ pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
|
|
|
|
|
for cell in sheet.get_cell_collection() {
|
|
|
|
|
let value = get_value(cell); //.get_formatted_value();
|
|
|
|
|
|
|
|
|
|
if value == "" {
|
|
|
|
|
if value.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let coord = cell.get_coordinate();
|
|
|
|
|
let col_num = coord.get_col_num().clone();
|
|
|
|
|
let row_num = coord.get_row_num().clone();
|
|
|
|
|
let col_num = *coord.get_col_num();
|
|
|
|
|
let row_num = *coord.get_row_num();
|
|
|
|
|
if col_num > num_cols {
|
|
|
|
|
num_cols = col_num;
|
|
|
|
|
}
|
|
|
|
|
@ -79,14 +197,16 @@ pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
|
|
|
|
|
let num_cols = num_cols;
|
|
|
|
|
let num_rows = num_rows;
|
|
|
|
|
|
|
|
|
|
// get hidden columns if needed
|
|
|
|
|
/*
|
|
|
|
|
get hidden columns if needed
|
|
|
|
|
*/
|
|
|
|
|
let mut hidden_columns: Vec<u32> = Vec::new();
|
|
|
|
|
if !include_hidden_columns {
|
|
|
|
|
if !args.include_hidden_columns {
|
|
|
|
|
for i in 1..=num_cols {
|
|
|
|
|
if let Some(dim) = sheet.get_column_dimension_by_number(&i) {
|
|
|
|
|
if *dim.get_hidden() {
|
|
|
|
|
hidden_columns.push(i);
|
|
|
|
|
}
|
|
|
|
|
if let Some(dim) = sheet.get_column_dimension_by_number(&i)
|
|
|
|
|
&& *dim.get_hidden()
|
|
|
|
|
{
|
|
|
|
|
hidden_columns.push(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -99,7 +219,7 @@ pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
|
|
|
|
|
empty_row += args.end_of_line.as_str();
|
|
|
|
|
|
|
|
|
|
if args.skip_rows > num_rows {
|
|
|
|
|
return Err(Error::new("Number of rows < number of rows to skip"));
|
|
|
|
|
return Err("Number of rows < number of rows to skip".into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let stdout = stdout();
|
|
|
|
|
@ -111,7 +231,7 @@ pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
|
|
|
|
|
let mut line = String::from("");
|
|
|
|
|
|
|
|
|
|
// take hidden rows if asked for
|
|
|
|
|
if !include_hidden_rows {
|
|
|
|
|
if !args.include_hidden_rows {
|
|
|
|
|
match sheet.get_row_dimension(&i) {
|
|
|
|
|
Some(dim) => {
|
|
|
|
|
if *dim.get_hidden() {
|
|
|
|
|
@ -120,9 +240,9 @@ pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
seq_row_num += 1;
|
|
|
|
|
line += number_row(&args.number_rows, args.separator, seq_row_num, i).as_str();
|
|
|
|
|
line += number_row(&args.number_rows, args.separator, seq_row_num, i).as_str();
|
|
|
|
|
line += empty_row.as_str();
|
|
|
|
|
writer.write(line.as_bytes()).unwrap();
|
|
|
|
|
writer.write_all(line.as_bytes()).unwrap();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -175,34 +295,26 @@ pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
|
|
|
|
|
value = value.replace('\r', "").replace('\n', " ");
|
|
|
|
|
if let Some(ref replacement) = args.replace_separator_by {
|
|
|
|
|
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(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
} else if value.contains(args.separator) {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Cell {} contains separator char, use -r to choose a replacement char",
|
|
|
|
|
cell.get_coordinate().get_coordinate()
|
|
|
|
|
)
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref replacement) = args.replace_end_of_line_by {
|
|
|
|
|
value = value.replace(&args.end_of_line, replacement);
|
|
|
|
|
} else {
|
|
|
|
|
if value.contains(&args.end_of_line) {
|
|
|
|
|
return Err(Error::new(
|
|
|
|
|
format!(
|
|
|
|
|
"Cell {} contains end of line string, use -R to choose a replacement string",
|
|
|
|
|
cell.get_coordinate().get_coordinate()
|
|
|
|
|
)
|
|
|
|
|
.as_str(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
} else if value.contains(&args.end_of_line) {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Cell {} contains end of line string, use -R to choose a replacement string",
|
|
|
|
|
cell.get_coordinate().get_coordinate()
|
|
|
|
|
)
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
line += value.as_str();
|
|
|
|
|
}
|
|
|
|
|
line += args.end_of_line.as_str();
|
|
|
|
|
writer.write(line.as_bytes()).unwrap();
|
|
|
|
|
writer.write_all(line.as_bytes()).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
@ -217,6 +329,7 @@ fn number_row(number_row: &NumberRows, separator: char, seqrownum: u32, i: u32)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_value(cell: &Cell) -> String {
|
|
|
|
|
//cell.get_formatted_value()
|
|
|
|
|
match cell.get_raw_value() {
|
|
|
|
|
umya_spreadsheet::CellRawValue::String(val) => String::from(val.clone()),
|
|
|
|
|
umya_spreadsheet::CellRawValue::RichText(text) => (*text.get_text()).to_owned(),
|
|
|
|
|
@ -257,13 +370,11 @@ impl MergedCells {
|
|
|
|
|
&& row >= *range.get_coordinate_start_row().unwrap().get_num()
|
|
|
|
|
&& row <= *range.get_coordinate_end_row().unwrap().get_num()
|
|
|
|
|
{
|
|
|
|
|
let col_start = range.get_coordinate_start_col().unwrap().get_num().clone();
|
|
|
|
|
let row_start = range.get_coordinate_start_row().unwrap().get_num().clone();
|
|
|
|
|
let col_start = *range.get_coordinate_start_col().unwrap().get_num();
|
|
|
|
|
let row_start = *range.get_coordinate_start_row().unwrap().get_num();
|
|
|
|
|
|
|
|
|
|
if self.fill_horizontal && self.fill_vertical
|
|
|
|
|
|| self.fill_horizontal && row == row_start
|
|
|
|
|
|| self.fill_vertical && col == col_start
|
|
|
|
|
|| col == col_start && row == row_start
|
|
|
|
|
if (self.fill_horizontal || col == col_start)
|
|
|
|
|
&& (self.fill_vertical || row == row_start)
|
|
|
|
|
{
|
|
|
|
|
return Some((col_start, row_start));
|
|
|
|
|
}
|