Skip to content

Commit

Permalink
Merge pull request #1271 from jqnatividad/excel-moremetadata-examples
Browse files Browse the repository at this point in the history
`excel`: moar metadata! moar examples!
  • Loading branch information
jqnatividad authored Sep 2, 2023
2 parents 9c8f514 + 4c2d9ff commit 8f21b65
Show file tree
Hide file tree
Showing 8 changed files with 473 additions and 11 deletions.
9 changes: 4 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ serial_test = { version = "2.0", features = ["file_locks"] }
[patch.crates-io]
geosuggest-core = { git = "https://github.com/estin/geosuggest", rev = "5c6b08b" }
geosuggest-utils = { git = "https://github.com/estin/geosuggest", rev = "5c6b08b" }
calamine = { git = "https://github.com/tafia/calamine", rev = "a54bd9c" }

[features]
default = ["mimalloc"]
Expand Down
Binary file added resources/test/any_sheets.ods
Binary file not shown.
Binary file added resources/test/any_sheets.xls
Binary file not shown.
Binary file added resources/test/any_sheets.xlsb
Binary file not shown.
Binary file added resources/test/any_sheets.xlsx
Binary file not shown.
73 changes: 69 additions & 4 deletions src/cmd/excel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,50 @@ static USAGE: &str = r#"
Exports a specified Excel/ODS sheet to a CSV file.
The first row of a sheet is assumed to be the header row.
For examples, see https://github.com/jqnatividad/qsv/blob/master/tests/test_excel.rs.
Examples:
Export the first sheet of an Excel file to a CSV file:
qsv excel input.xlsx > output.csv
qsv excel input.xlsx --output output.csv
Export the first sheet of an ODS file to a CSV file:
qsv excel input.ods > output.csv
qsv excel input.ods -o output.csv
Export the first sheet of an Excel file to a CSV file with different delimiters:
# semicolon
qsv excel input.xlsx -d ";" > output.csv
# tab
qsv excel input.xlsx -d "\t" > output.tsv
Export a sheet by name (case-insensitive):
qsv excel --sheet "Sheet 3" input.xlsx
Export a sheet by index:
# this exports the 3nd sheet (0-based index)
qsv excel -s 2 input.xlsx
Export the last sheet (negative index)):
qsv excel -s -1 input.xlsx
Export the second to last sheet:
qsv excel -s -2 input.xls
Export a range of cells in the first sheet:
qsv excel --range C3:T25 input.xlsx
Export a range of cells in the second sheet:
qsv excel --range C3:T25 -s 1 input.xlsx
Export metadata for all sheets in CSV format:
qsv excel --metadata c input.xlsx
Export metadata for all sheets in JSON format:
qsv excel --metadata j input.xlsx
# pretty-printed JSON
qsv excel --metadata J input.xlsx
For more examples, see https://github.com/jqnatividad/qsv/blob/master/tests/test_excel.rs.
Usage:
qsv excel [options] [<input>]
Expand Down Expand Up @@ -53,7 +96,7 @@ Common options:

use std::{cmp, fmt::Write, path::PathBuf};

use calamine::{open_workbook_auto, DataType, Range, Reader};
use calamine::{open_workbook_auto, DataType, Range, Reader, SheetType};
use indicatif::HumanCount;
#[cfg(any(feature = "feature_capable", feature = "lite"))]
use indicatif::{ProgressBar, ProgressDrawTarget};
Expand Down Expand Up @@ -92,6 +135,8 @@ enum MetadataMode {
struct SheetMetadata {
index: usize,
name: String,
typ: String,
visible: String,
headers: Vec<String>,
num_columns: usize,
num_rows: usize,
Expand Down Expand Up @@ -213,6 +258,7 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
return fail!("No sheets found.");
}
let num_sheets = sheet_names.len();
#[allow(clippy::redundant_clone)]
let sheet_vec = sheet_names.to_owned();

let mut wtr = Config::new(&args.flag_output)
Expand Down Expand Up @@ -251,7 +297,13 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
match result {
Ok(result) => result,
Err(e) => {
return fail_clierror!("Cannot retrieve range from {sheet_name}: {e}.");
let sheet_type = workbook.sheets_metadata()[i].typ;
if sheet_type == SheetType::ChartSheet {
// return an empty range for ChartSheet
Range::empty()
} else {
return fail_clierror!("Cannot retrieve range from {sheet_name}: {e}.");
}
},
}
} else {
Expand Down Expand Up @@ -308,6 +360,8 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
let sheetmetadata_struct = SheetMetadata {
index: i,
name: sheet_name.to_string(),
typ: format!("{:?}", workbook.sheets_metadata()[i].typ),
visible: format!("{:?}", workbook.sheets_metadata()[i].visible),
headers: header_vec,
num_columns,
num_rows,
Expand All @@ -326,6 +380,8 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
metadata_fields.extend_from_slice(&[
"index",
"sheet_name",
"type",
"visible",
"headers",
"num_columns",
"num_rows",
Expand All @@ -344,6 +400,8 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
sheetmetadata.index.to_string(),
sheetmetadata.name,
format!("{:?}", sheetmetadata.headers),
sheetmetadata.typ,
sheetmetadata.visible,
sheetmetadata.num_columns.to_string(),
sheetmetadata.num_rows.to_string(),
format!("{:?}", sheetmetadata.safe_headers),
Expand Down Expand Up @@ -433,6 +491,13 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
return fail_clierror!("Cannot get sheet index for {sheet}");
};

let sheet_type = workbook.sheets_metadata()[sheet_index].typ;
if sheet_type != SheetType::WorkSheet {
return fail_incorrectusage_clierror!(
"Can only export Worksheets. {sheet} is a {sheet_type:?}."
);
}

let mut range = if let Some(result) = workbook.worksheet_range_at(sheet_index) {
match result {
Ok(result) => result,
Expand Down Expand Up @@ -648,7 +713,7 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
wtr.write_record(&record)?;
} // end of main processing loop
} else {
return fail_clierror!("\"{sheet}\" sheet is empty");
return fail_clierror!("\"{sheet}\" sheet is empty.");
}

wtr.flush()?;
Expand Down
Loading

0 comments on commit 8f21b65

Please sign in to comment.