Compare commits
6 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9f544e63d1 | 1 month ago |
|
|
a11ca56849 | 2 months ago |
|
|
6d446b4767 | 2 months ago |
|
|
9dd247b20c | 2 months ago |
|
|
3f1feea6cf | 3 months ago |
|
|
78d955afcb | 3 months ago |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
edition = "2024"
|
||||||
@ -1,18 +1,44 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Error {
|
pub enum Error {
|
||||||
pub msg: String
|
Msg(String),
|
||||||
|
XlsxError(String),
|
||||||
|
IoError(String),
|
||||||
|
PolarsError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl fmt::Display for Error {
|
||||||
pub fn new(msg: &str) -> Self {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
Error { msg: String::from(msg)}
|
match self {
|
||||||
|
Error::Msg(msg) => write!(f, "XlsxToCsvError: {msg}"),
|
||||||
|
Error::XlsxError(msg) => write!(f, "XlsxError: {msg}"),
|
||||||
|
Error::IoError(msg) => write!(f, "IoError: {msg}"),
|
||||||
|
Error::PolarsError(msg) => write!(f, "PolarsError: {msg}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl From<umya_spreadsheet::XlsxError> for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn from(value: umya_spreadsheet::XlsxError) -> Self {
|
||||||
write!(f, "Error: {}", self.msg)
|
Error::XlsxError(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Error {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Error::Msg(value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for Error {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Error::Msg(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
Error::IoError(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
pub mod arguments;
|
||||||
|
pub mod error;
|
||||||
|
pub mod xlsx;
|
||||||
|
pub mod xlsx_builder;
|
||||||
|
|
||||||
|
#[cfg(feature = "csv")]
|
||||||
|
pub mod xlsx_to_csv;
|
||||||
|
|
||||||
|
#[cfg(feature = "lazyframe")]
|
||||||
|
pub mod xlsx_to_lazyframe;
|
||||||
@ -1,14 +1,14 @@
|
|||||||
pub mod arguments;
|
pub use xlsxtocsv::{error::Error, xlsx::XlsxReader};
|
||||||
pub mod error;
|
|
||||||
pub mod xlsxtocsv;
|
fn main() -> Result<(), Error> {
|
||||||
|
let lf = XlsxReader::new("noms.xlsx")
|
||||||
use arguments::Arguments;
|
.with_active_worksheet()
|
||||||
use clap::Parser;
|
.with_fill_merged_cells(xlsxtocsv::arguments::FillMergedCells::Both)
|
||||||
use xlsxtocsv::xlsxtocsv;
|
.to_lazyframe()?;
|
||||||
|
|
||||||
fn main() {
|
let df = lf.collect()?;
|
||||||
let args = Arguments::parse();
|
|
||||||
if let Err(error) = xlsxtocsv(&args) {
|
println!("{df}");
|
||||||
eprintln!("{}", error);
|
|
||||||
}
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,397 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::io::{BufWriter, Write, stdout};
|
||||||
|
use std::path::Path;
|
||||||
|
use umya_spreadsheet::{Cell, Range, Spreadsheet, Worksheet, reader};
|
||||||
|
|
||||||
|
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)>>,
|
||||||
|
pub(crate) current_row: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XlsxReader {
|
||||||
|
/// Create an XlsxReader from a path or clap arguments
|
||||||
|
pub fn new(args: impl IntoArgs) -> Self {
|
||||||
|
let args = args.into_args();
|
||||||
|
|
||||||
|
XlsxReader {
|
||||||
|
args,
|
||||||
|
book: None,
|
||||||
|
sheet_index: None,
|
||||||
|
worksheet_dimensions: RefCell::new(None),
|
||||||
|
current_row: 0u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
if col < num_cols.try_into().unwrap() {
|
||||||
|
res[col as usize] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for XlsxReader {
|
||||||
|
type Item = Result<String, Error>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))?;
|
||||||
|
|
||||||
|
if args.list_worksheets {
|
||||||
|
println!("List of worksheets :");
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
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("cannot open sheet".into()),
|
||||||
|
};
|
||||||
|
match book.get_sheet(&(sheetnum as usize)) {
|
||||||
|
Some(sheet) => sheet,
|
||||||
|
None => return Err("cannot open sheet".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// get all the merged cells
|
||||||
|
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;
|
||||||
|
let mut num_rows = 0;
|
||||||
|
|
||||||
|
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 num_cols = num_cols;
|
||||||
|
let num_rows = num_rows;
|
||||||
|
|
||||||
|
/*
|
||||||
|
get hidden columns if needed
|
||||||
|
*/
|
||||||
|
let mut hidden_columns: Vec<u32> = Vec::new();
|
||||||
|
if !args.include_hidden_columns {
|
||||||
|
for i in 1..=num_cols {
|
||||||
|
if let Some(dim) = sheet.get_column_dimension_by_number(&i)
|
||||||
|
&& *dim.get_hidden()
|
||||||
|
{
|
||||||
|
hidden_columns.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the empty row string
|
||||||
|
let mut empty_row = String::from("");
|
||||||
|
for _ in 1..num_cols - (hidden_columns.len() as u32) {
|
||||||
|
empty_row.push(args.separator);
|
||||||
|
}
|
||||||
|
empty_row += args.end_of_line.as_str();
|
||||||
|
|
||||||
|
if args.skip_rows > num_rows {
|
||||||
|
return Err("Number of rows < number of rows to skip".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = stdout();
|
||||||
|
let mut writer = BufWriter::new(stdout.lock());
|
||||||
|
|
||||||
|
// for each row...
|
||||||
|
let mut seq_row_num = 0;
|
||||||
|
for i in (args.skip_rows + 1)..=num_rows {
|
||||||
|
let mut line = String::from("");
|
||||||
|
|
||||||
|
// take hidden rows if asked for
|
||||||
|
if !args.include_hidden_rows {
|
||||||
|
match sheet.get_row_dimension(&i) {
|
||||||
|
Some(dim) => {
|
||||||
|
if *dim.get_hidden() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
seq_row_num += 1;
|
||||||
|
line += number_row(&args.number_rows, args.separator, seq_row_num, i).as_str();
|
||||||
|
line += empty_row.as_str();
|
||||||
|
writer.write_all(line.as_bytes()).unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// number the row
|
||||||
|
seq_row_num += 1;
|
||||||
|
line += number_row(&args.number_rows, args.separator, seq_row_num, i).as_str();
|
||||||
|
|
||||||
|
// for each column in row...
|
||||||
|
let mut first = true;
|
||||||
|
for j in 1..=num_cols {
|
||||||
|
// if the column j has to be hidden, go to the next
|
||||||
|
if hidden_columns.contains(&j) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
line.push(args.separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell = match sheet.get_cell((j, i)) {
|
||||||
|
Some(cell) => cell,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// get value from cell depending on merged cells and fill merged policy
|
||||||
|
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) => get_value(merged_cell), //.get_formatted_value(),
|
||||||
|
None => String::from(""),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = get_value(cell); //.get_formatted_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply modifications to cells value (trim spaces, replace separator chars, line breaks etc.)
|
||||||
|
value = match args.trim {
|
||||||
|
TrimSpaces::End => String::from(value.trim_end()),
|
||||||
|
TrimSpaces::Start => String::from(value.trim_start()),
|
||||||
|
TrimSpaces::Both => String::from(value.trim()),
|
||||||
|
TrimSpaces::None => value,
|
||||||
|
};
|
||||||
|
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(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(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_all(line.as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn number_row(number_row: &NumberRows, separator: char, seqrownum: u32, i: u32) -> String {
|
||||||
|
match number_row {
|
||||||
|
NumberRows::AsIs => format!("{}{}", i, separator),
|
||||||
|
NumberRows::Sequential => format!("{}{}", seqrownum, separator),
|
||||||
|
NumberRows::None => String::from(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
umya_spreadsheet::CellRawValue::Lazy(lazy) => (*lazy.clone()).to_owned(),
|
||||||
|
umya_spreadsheet::CellRawValue::Numeric(num) => format!("{}", num),
|
||||||
|
umya_spreadsheet::CellRawValue::Bool(bo) => format!("{}", bo),
|
||||||
|
umya_spreadsheet::CellRawValue::Error(_) => String::from(""),
|
||||||
|
umya_spreadsheet::CellRawValue::Empty => String::from(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MergedCells {
|
||||||
|
merged_cells: Vec<Range>,
|
||||||
|
fill_horizontal: bool,
|
||||||
|
fill_vertical: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MergedCells {
|
||||||
|
pub fn new(sheet: &Worksheet, fill_horizontal: bool, fill_vertical: bool) -> Self {
|
||||||
|
let merged = sheet.get_merge_cells();
|
||||||
|
let mut merged_cells: Vec<Range> = vec![];
|
||||||
|
|
||||||
|
for cell in merged {
|
||||||
|
merged_cells.push(cell.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
MergedCells {
|
||||||
|
merged_cells,
|
||||||
|
fill_horizontal,
|
||||||
|
fill_vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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 || col == col_start)
|
||||||
|
&& (self.fill_vertical || row == row_start)
|
||||||
|
{
|
||||||
|
return Some((col_start, row_start));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,126 @@
|
|||||||
|
use crate::{
|
||||||
|
arguments::{FillMergedCells, IncludeHidden, NumberRows, TrimSpaces},
|
||||||
|
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) -> Self {
|
||||||
|
self.args.worksheet = String::from(worksheet_name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_active_worksheet(mut self) -> Self {
|
||||||
|
self.args.active_worksheet = true;
|
||||||
|
|
||||||
|
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_trim(mut self, trim: TrimSpaces) -> Self {
|
||||||
|
self.args.trim = trim;
|
||||||
|
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_avoid_empty_rows(mut self) -> Self {
|
||||||
|
self.args.avoid_empty_rows = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_header(mut self) -> Self {
|
||||||
|
self.args.header = true;
|
||||||
|
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,120 @@
|
|||||||
|
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<String, Error>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
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<XlsxToCsvLines, Error> {
|
||||||
|
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<dyn Write> = 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
use crate::{error::Error, xlsx::XlsxReader};
|
||||||
|
use polars::prelude::*;
|
||||||
|
|
||||||
|
impl From<PolarsError> for Error {
|
||||||
|
fn from(value: PolarsError) -> Self {
|
||||||
|
Error::PolarsError(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XlsxReader {
|
||||||
|
pub fn to_lazyframe(mut self) -> Result<LazyFrame, Error> {
|
||||||
|
self.finish()?;
|
||||||
|
let (num_cols, num_rows) = self.get_worksheet_dimensions();
|
||||||
|
|
||||||
|
let mut columns: Vec<Column> = (0..num_cols)
|
||||||
|
.map(|i| Column::new(format!("column_{i}").into(), Vec::<String>::new()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 1000;
|
||||||
|
let mut chunk: Vec<Vec<String>> = Vec::with_capacity(CHUNK_SIZE);
|
||||||
|
|
||||||
|
for current_row in 0..num_rows {
|
||||||
|
let row = self.get_row(current_row);
|
||||||
|
|
||||||
|
chunk.push(row);
|
||||||
|
|
||||||
|
if chunk.len() >= CHUNK_SIZE {
|
||||||
|
append_chunk(&mut columns, &chunk);
|
||||||
|
chunk.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !chunk.is_empty() {
|
||||||
|
append_chunk(&mut columns, &chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
let df =
|
||||||
|
DataFrame::new(num_rows as usize, columns).map_err(|e| Error::from(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(df.lazy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_chunk(columns: &mut [Column], chunk: &[Vec<String>]) {
|
||||||
|
for (col_idx, column) in columns.iter_mut().enumerate() {
|
||||||
|
let chunk_data: Vec<String> = chunk.iter().map(|row| row[col_idx].clone()).collect();
|
||||||
|
|
||||||
|
let chunk_column = Column::new("temp".into(), chunk_data);
|
||||||
|
column.append(&chunk_column).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,274 +0,0 @@
|
|||||||
use std::io::{BufWriter, Write, stdout};
|
|
||||||
use std::path::Path;
|
|
||||||
use umya_spreadsheet::{Cell, Range, Worksheet, reader};
|
|
||||||
|
|
||||||
use crate::arguments::{Arguments, IncludeHidden, NumberRows, TrimSpaces};
|
|
||||||
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());
|
|
||||||
|
|
||||||
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")),
|
|
||||||
};
|
|
||||||
let sheet = match book.get_sheet(&(sheetnum as usize)) {
|
|
||||||
Some(sheet) => sheet,
|
|
||||||
None => return Err(Error::new("cannot open sheet")),
|
|
||||||
};
|
|
||||||
sheet
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// get non-empty value size of the worksheet
|
|
||||||
let mut num_cols = 0;
|
|
||||||
let mut num_rows = 0;
|
|
||||||
|
|
||||||
for cell in sheet.get_cell_collection() {
|
|
||||||
let value = get_value(cell); //.get_formatted_value();
|
|
||||||
|
|
||||||
if value == "" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let coord = cell.get_coordinate();
|
|
||||||
let col_num = coord.get_col_num().clone();
|
|
||||||
let row_num = coord.get_row_num().clone();
|
|
||||||
if col_num > num_cols {
|
|
||||||
num_cols = col_num;
|
|
||||||
}
|
|
||||||
if row_num > num_rows {
|
|
||||||
num_rows = row_num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let num_cols = num_cols;
|
|
||||||
let num_rows = num_rows;
|
|
||||||
|
|
||||||
// get hidden columns if needed
|
|
||||||
let mut hidden_columns: Vec<u32> = Vec::new();
|
|
||||||
if !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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the empty row string
|
|
||||||
let mut empty_row = String::from("");
|
|
||||||
for _ in 1..num_cols - (hidden_columns.len() as u32) {
|
|
||||||
empty_row.push(args.separator);
|
|
||||||
}
|
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let stdout = stdout();
|
|
||||||
let mut writer = BufWriter::new(stdout.lock());
|
|
||||||
|
|
||||||
// for each row...
|
|
||||||
let mut seq_row_num = 0;
|
|
||||||
for i in (args.skip_rows + 1)..=num_rows {
|
|
||||||
let mut line = String::from("");
|
|
||||||
|
|
||||||
// take hidden rows if asked for
|
|
||||||
if !include_hidden_rows {
|
|
||||||
match sheet.get_row_dimension(&i) {
|
|
||||||
Some(dim) => {
|
|
||||||
if *dim.get_hidden() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
seq_row_num += 1;
|
|
||||||
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();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// number the row
|
|
||||||
seq_row_num += 1;
|
|
||||||
line += number_row(&args.number_rows, args.separator, seq_row_num, i).as_str();
|
|
||||||
|
|
||||||
// for each column in row...
|
|
||||||
let mut first = true;
|
|
||||||
for j in 1..=num_cols {
|
|
||||||
// if the column j has to be hidden, go to the next
|
|
||||||
if hidden_columns.contains(&j) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if first {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
line.push(args.separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cell = match sheet.get_cell((j, i)) {
|
|
||||||
Some(cell) => cell,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
// get value from cell depending on merged cells and fill merged policy
|
|
||||||
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) => get_value(merged_cell), //.get_formatted_value(),
|
|
||||||
None => String::from(""),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = get_value(cell); //.get_formatted_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply modifications to cells value (trim spaces, replace separator chars, line breaks etc.)
|
|
||||||
value = match args.trim {
|
|
||||||
TrimSpaces::End => String::from(value.trim_end()),
|
|
||||||
TrimSpaces::Start => String::from(value.trim_start()),
|
|
||||||
TrimSpaces::Both => String::from(value.trim()),
|
|
||||||
TrimSpaces::None => value,
|
|
||||||
};
|
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
line += value.as_str();
|
|
||||||
}
|
|
||||||
line += args.end_of_line.as_str();
|
|
||||||
writer.write(line.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn number_row(number_row: &NumberRows, separator: char, seqrownum: u32, i: u32) -> String {
|
|
||||||
match number_row {
|
|
||||||
NumberRows::AsIs => format!("{}{}", i, separator),
|
|
||||||
NumberRows::Sequential => format!("{}{}", seqrownum, separator),
|
|
||||||
NumberRows::None => String::from(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_value(cell: &Cell) -> String {
|
|
||||||
match cell.get_raw_value() {
|
|
||||||
umya_spreadsheet::CellRawValue::String(val) => String::from(val.clone()),
|
|
||||||
umya_spreadsheet::CellRawValue::RichText(text) => (*text.get_text()).to_owned(),
|
|
||||||
umya_spreadsheet::CellRawValue::Lazy(lazy) => (*lazy.clone()).to_owned(),
|
|
||||||
umya_spreadsheet::CellRawValue::Numeric(num) => format!("{}", num),
|
|
||||||
umya_spreadsheet::CellRawValue::Bool(bo) => format!("{}", bo),
|
|
||||||
umya_spreadsheet::CellRawValue::Error(_) => String::from(""),
|
|
||||||
umya_spreadsheet::CellRawValue::Empty => String::from(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MergedCells {
|
|
||||||
merged_cells: Vec<Range>,
|
|
||||||
fill_horizontal: bool,
|
|
||||||
fill_vertical: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MergedCells {
|
|
||||||
pub fn new(sheet: &Worksheet, fill_horizontal: bool, fill_vertical: bool) -> Self {
|
|
||||||
let merged = sheet.get_merge_cells();
|
|
||||||
let mut merged_cells: Vec<Range> = vec![];
|
|
||||||
|
|
||||||
for cell in merged {
|
|
||||||
merged_cells.push(cell.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
MergedCells {
|
|
||||||
merged_cells,
|
|
||||||
fill_horizontal,
|
|
||||||
fill_vertical,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
let col_start = range.get_coordinate_start_col().unwrap().get_num().clone();
|
|
||||||
let row_start = range.get_coordinate_start_row().unwrap().get_num().clone();
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
return Some((col_start, row_start));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in new issue