Skip to content

Commit

Permalink
Merge pull request #1721 from jqnatividad/1689-excel_error-format
Browse files Browse the repository at this point in the history
`excel`: add `--error-format` option
  • Loading branch information
jqnatividad authored Apr 5, 2024
2 parents 933960e + fbe1ff8 commit 83b3960
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 4 deletions.
Binary file modified resources/test/excel-xlsx.xlsx
Binary file not shown.
60 changes: 56 additions & 4 deletions src/cmd/excel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ Excel options:
All other Excel options are ignored.
[default: none]
--error-format <format> The format to use when formatting error cells.
There are 3 formats:
- "code": return the error code.
(e.g. #DIV/0!)
- "formula": return the formula.
(e.g. #=A1/B1 where B1 is 0; #=100/0)
- "both": return both error code and the formula.
(e.g. #DIV/0!: =A1/B1)
For now, extracting the formula text only works reliably on macOS.
[default: code]
--flexible Continue even if the number of columns is different from row to row.
--trim Trim all fields so that leading & trailing whitespaces are removed.
Also removes embedded linebreaks.
Expand Down Expand Up @@ -119,6 +129,7 @@ struct Args {
arg_input: String,
flag_sheet: String,
flag_metadata: String,
flag_error_format: String,
flag_flexible: bool,
flag_trim: bool,
flag_output: Option<String>,
Expand All @@ -139,6 +150,13 @@ enum MetadataMode {
None,
}

#[derive(PartialEq)]
enum ErrorFormat {
Code,
Formula,
Both,
}

#[derive(Serialize, Deserialize)]
struct SheetMetadata {
index: usize,
Expand Down Expand Up @@ -551,12 +569,24 @@ pub fn run(argv: &[&str]) -> CliResult<()> {

let (row_count, col_count) = range.get_size();

let error_format = match args.flag_error_format.to_lowercase().as_str() {
"formula" => ErrorFormat::Formula,
"both" => ErrorFormat::Both,
_ => ErrorFormat::Code,
};

if row_count == 0 {
if !requested_range.is_empty() {
return fail_clierror!("\"{requested_range}\" range in sheet \"{sheet}\" is empty.");
}
return fail_clierror!("\"{sheet}\" sheet is empty.");
}
// there are rows to export
let mut rows_iter = range.rows();

let range_start = range.start().unwrap_or((0, 0));
let sheet_formulas = workbook.worksheet_formula(&sheet)?;

// amortize allocations
let mut record = csv::StringRecord::with_capacity(500, col_count);
let mut trimmed_record = csv::StringRecord::with_capacity(500, col_count);
Expand Down Expand Up @@ -611,11 +641,14 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
String::new()
};

let mut rows = Vec::with_capacity(row_count);
let mut rows: Vec<(u32, &[Data])> = Vec::with_capacity(row_count);

// we add 1 as we already processed the header row
let mut row_idx = range_start.0 + 1;
// queue rest of the rows for processing as data rows
for row in rows_iter {
rows.push(row);
rows.push((row_idx, row));
row_idx += 1;
}

// set RAYON_NUM_THREADS
Expand All @@ -638,8 +671,12 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
let mut formatted_date = String::new();

let mut processed_chunk: Vec<csv::StringRecord> = Vec::with_capacity(chunk_size);
let mut col_idx = 0_u32;

let formula_get_value_error = "cannot get formula".to_string();
let mut cell_value: &String;

for row in chunk {
for (row_idx, row) in chunk {
for cell in *row {
match *cell {
Data::Empty => record.push_field(""),
Expand Down Expand Up @@ -707,13 +744,28 @@ pub fn run(argv: &[&str]) -> CliResult<()> {

record.push_field(&work_date);
},
Data::Error(ref e) => record.push_field(&format!("{e}")),
Data::Bool(ref b) => {
record.push_field(if *b { "true" } else { "false" });
},
Data::DateTimeIso(ref dt) => record.push_field(dt),
Data::DurationIso(ref d) => record.push_field(d),
Data::Error(ref e) => {
match error_format {
ErrorFormat::Code => record.push_field(&format!("{e}")),
ErrorFormat::Formula | ErrorFormat::Both => {
cell_value = sheet_formulas
.get_value((*row_idx, col_idx))
.unwrap_or(&formula_get_value_error);
if error_format == ErrorFormat::Formula {
record.push_field(&format!("#={cell_value}"));
} else {
record.push_field(&format!("{e}: ={cell_value}"));
}
},
};
},
};
col_idx += 1;
}

if trim {
Expand Down
72 changes: 72 additions & 0 deletions tests/test_excel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,78 @@ fn excel_cellerrors() {
svec!["3", "50", "20"],
svec!["4", "33.333333333333336", "3"],
svec!["5", "25", "4"],
svec!["#VALUE!", "#VALUE!", "#VALUE!"],
svec!["7", "20", "#NAME?"],
svec!["8", "Hello", "hello"],
svec!["9", "abcd", "wxyz"],
];
assert_eq!(got, expected);
}

// for now, only run this test on macos
// as it cannot get the formula text on linux or windows
#[cfg(target_os = "macos")]
#[test]
fn excel_cellerrors_formula() {
let wrk = Workdir::new("excel_cellerrors_formula");

let xls_file = wrk.load_test_file("excel-xlsx.xlsx");

let mut cmd = wrk.command("excel");
cmd.args(["--sheet", "cellerrors"])
.args(["--error-format", "formula"])
.arg(xls_file);

let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
let expected = vec![
svec!["col1", "col 2", "column-3"],
svec!["1", "-50", "15"],
svec!["2", "#=100/0", "#=4*te"],
svec!["3", "50", "20"],
svec!["4", "33.333333333333336", "3"],
svec!["5", "25", "4"],
svec![
"#=B7+12",
"#=C7+20",
"#=_xlfn._xlws.SORT(_xlfn.CHOOSECOLS(A3:B20, 3))"
],
svec!["7", "20", "#=SUM(C2:C7)"],
svec!["8", "Hello", "hello"],
svec!["9", "abcd", "wxyz"],
];
assert_eq!(got, expected);
}

// same as above, only run this test on macos
// as it cannot get the formula text on linux or windows
#[cfg(target_os = "macos")]
#[test]
fn excel_cellerrors_both() {
let wrk = Workdir::new("excel_cellerrors_both");

let xls_file = wrk.load_test_file("excel-xlsx.xlsx");

let mut cmd = wrk.command("excel");
cmd.args(["--sheet", "cellerrors"])
.args(["--error-format", "both"])
.arg(xls_file);

let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
let expected = vec![
svec!["col1", "col 2", "column-3"],
svec!["1", "-50", "15"],
svec!["2", "#DIV/0!: =100/0", "#NAME?: =4*te"],
svec!["3", "50", "20"],
svec!["4", "33.333333333333336", "3"],
svec!["5", "25", "4"],
svec![
"#VALUE!: =B7+12",
"#VALUE!: =C7+20",
"#VALUE!: =_xlfn._xlws.SORT(_xlfn.CHOOSECOLS(A3:B20, 3))"
],
svec!["7", "20", "#NAME?: =SUM(C2:C7)"],
svec!["8", "Hello", "hello"],
svec!["9", "abcd", "wxyz"],
];
assert_eq!(got, expected);
}
Expand Down

0 comments on commit 83b3960

Please sign in to comment.