Skip to content

Commit

Permalink
ignore_error: add initial code
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcnamara committed Jan 31, 2025
1 parent f36d8f7 commit a9d82e3
Show file tree
Hide file tree
Showing 14 changed files with 409 additions and 3 deletions.
190 changes: 187 additions & 3 deletions src/worksheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1242,13 +1242,13 @@
mod tests;

use std::borrow::Cow;
use std::cmp;
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::io::Cursor;
use std::io::Write;
use std::mem;
use std::sync::{Arc, Mutex, RwLock};
use std::{cmp, fmt};

#[cfg(feature = "constant_memory")]
use tempfile::tempfile;
Expand Down Expand Up @@ -1516,6 +1516,7 @@ pub struct Worksheet {
nan: String,
infinity: String,
neg_infinity: String,
ignored_errors: HashMap<IgnoreError, String>,

#[cfg(feature = "constant_memory")]
pub(crate) file_writer: BufWriter<File>,
Expand Down Expand Up @@ -1730,6 +1731,7 @@ impl Worksheet {
nan: "NAN".to_string(),
infinity: "INF".to_string(),
neg_infinity: "-INF".to_string(),
ignored_errors: HashMap::new(),

// These collections need to be reset on resave.
comment_relationships: vec![],
Expand Down Expand Up @@ -13321,6 +13323,62 @@ impl Worksheet {
self
}

/// TODO
///
/// # Errors
///
/// - [`XlsxError::RowColumnLimitError`] - Row or column exceeds Excel's
/// worksheet limits.
///
pub fn ignore_error(
&mut self,
row: RowNum,
col: ColNum,
error_type: IgnoreError,
) -> Result<&mut Worksheet, XlsxError> {
self.ignore_error_range(row, col, row, col, error_type)
}

/// TODO
///
/// # Errors
///
/// - [`XlsxError::RowColumnLimitError`] - Row or column exceeds Excel's
/// worksheet limits.
/// - [`XlsxError::RowColumnOrderError`] - First row or column is larger
/// than the last row or column.
///
///
pub fn ignore_error_range(
&mut self,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
error_type: IgnoreError,
) -> Result<&mut Worksheet, XlsxError> {
// Check rows and cols are in the allowed range.
if !self.check_dimensions_only(first_row, first_col)
|| !self.check_dimensions_only(last_row, last_col)
{
return Err(XlsxError::RowColumnLimitError);
}

// Check order of first/last values.
if first_row > last_row || first_col > last_col {
return Err(XlsxError::RowColumnOrderError);
}

let range = utility::cell_range(first_row, first_col, last_row, last_col);

self.ignored_errors
.entry(error_type)
.and_modify(|sqref| *sqref = format!("{sqref} {range}"))
.or_insert(range);

Ok(self)
}

// -----------------------------------------------------------------------
// Crate level helper methods.
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -15824,6 +15882,11 @@ impl Worksheet {
self.write_col_breaks();
}

// Write the ignoredErrors element.
if !self.ignored_errors.is_empty() {
self.write_ignored_errors();
}

// Write the drawing element.
if !self.drawing.drawings.is_empty() {
self.write_drawing();
Expand Down Expand Up @@ -17441,9 +17504,21 @@ impl Worksheet {
String::new()
};

// Get the result type attribute.
let result_type = if result.parse::<f64>().is_err() {
r#" t="str""#
match result {
// Handle error results.
"#DIV/0!" | "#N/A" | "#NAME?" | "#NULL!" | "#NUM!" | "#REF!" | "#VALUE!"
| "#GETTING_DATA" => r#" t="e""#,

// Handle boolean results.
"TRUE" | "FALSE" => r#" t="b""#,

// Handle string results.
_ => r#" t="str""#,
}
} else {
// Handle/ignore for numeric results.
""
};

Expand Down Expand Up @@ -17477,9 +17552,21 @@ impl Worksheet {

let cm = if is_dynamic { r#" cm="1""# } else { "" };

// Get the result type attribute.
let result_type = if result.parse::<f64>().is_err() {
r#" t="str""#
match result {
// Handle error results.
"#DIV/0!" | "#N/A" | "#NAME?" | "#NULL!" | "#NUM!" | "#REF!" | "#VALUE!"
| "#GETTING_DATA" => r#" t="e""#,

// Handle boolean results.
"TRUE" | "FALSE" => r#" t="b""#,

// Handle string results.
_ => r#" t="str""#,
}
} else {
// Handle/ignore for numeric results.
""
};

Expand Down Expand Up @@ -18112,6 +18199,25 @@ impl Worksheet {
}
xml_end_tag(&mut self.writer, "x14:sparklines");
}

// Write the <ignoredErrors> element.
fn write_ignored_errors(&mut self) {
xml_start_tag_only(&mut self.writer, "ignoredErrors");

for error_type in IgnoreError::iterator() {
let error_name = error_type.to_string();

if let Some(error_range) = self.ignored_errors.get(&error_type) {
let attributes = [
("sqref", error_range.clone()),
(&error_name, "1".to_string()),
];
xml_empty_tag(&mut self.writer, "ignoredError", &attributes);
}
}

xml_end_tag(&mut self.writer, "ignoredErrors");
}
}

// -----------------------------------------------------------------------
Expand Down Expand Up @@ -18957,6 +19063,84 @@ impl DefinedName {
}
}

/// The `IgnoreError` enum defines the Excel cell error types that can be
/// ignored.
///
/// Used with the [`Worksheet::ignore_error()`](crate::Worksheet::ignore_error)
/// and
/// [`Worksheet::ignore_error_range()`](crate::Worksheet::ignore_error_range)
/// methods.
///
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum IgnoreError {
/// Ignore errors/warnings for numbers stored as text.
NumberStoredAsText,

/// Ignore errors/warnings for formula evaluation errors (such as divide by
/// zero).
EvalError,

/// Ignore errors/warnings for formulas that differ from surrounding
/// formulas.
FormulaDiffers,

/// Ignore errors/warnings for formulas that refer to empty cells.
EmptyCellReference,

/// Ignore errors/warnings for formulas that omit cells in a range.
FormulaRange,

/// Ignore errors/warnings for cells in a table that do not comply with
/// applicable data validation rules.
ListDataValidation,

/// Ignore errors/warnings for formulas that contain a two digit text
/// representation of a year.
TwoDigitTextYear,

/// Ignore errors/warnings for unlocked cells that contain formulas.
UnlockedFormula,

/// Ignore errors/warnings for cell formulas that differ from the column
/// formula.
CalculatedColumn,
}

impl IgnoreError {
/// Simple iterator for `IgnoreError`.
pub fn iterator() -> impl Iterator<Item = IgnoreError> {
[
Self::NumberStoredAsText,
Self::EvalError,
Self::FormulaDiffers,
Self::FormulaRange,
Self::UnlockedFormula,
Self::CalculatedColumn,
Self::EmptyCellReference,
Self::ListDataValidation,
Self::TwoDigitTextYear,
]
.iter()
.copied()
}
}

impl fmt::Display for IgnoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EvalError => write!(f, "evalError"),
Self::FormulaRange => write!(f, "formulaRange"),
Self::FormulaDiffers => write!(f, "formula"),
Self::UnlockedFormula => write!(f, "unlockedFormula"),
Self::CalculatedColumn => write!(f, "calculatedColumn"),
Self::TwoDigitTextYear => write!(f, "TwoDigitTextYear"),
Self::EmptyCellReference => write!(f, "emptyCellReference"),
Self::ListDataValidation => write!(f, "listDataValidation"),
Self::NumberStoredAsText => write!(f, "numberStoredAsText"),
}
}
}

#[derive(Clone, Debug)]
pub(crate) enum DefinedNameType {
Autofilter,
Expand Down
Binary file added tests/input/ignore_error01.xlsx
Binary file not shown.
Binary file added tests/input/ignore_error02.xlsx
Binary file not shown.
Binary file added tests/input/ignore_error03.xlsx
Binary file not shown.
Binary file added tests/input/ignore_error04.xlsx
Binary file not shown.
Binary file added tests/input/ignore_error05.xlsx
Binary file not shown.
Binary file added tests/input/ignore_error06.xlsx
Binary file not shown.
32 changes: 32 additions & 0 deletions tests/integration/ignore_error01.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Test case that compares a file generated by rust_xlsxwriter with a file
// created by Excel.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2025, John McNamara, [email protected]

use crate::common;
use rust_xlsxwriter::{Workbook, XlsxError};

// Create rust_xlsxwriter file to compare against Excel file.
fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.write_string(0, 0, "123")?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_ignore_error01() {
let test_runner = common::TestRunner::new()
.set_name("ignore_error01")
.set_function(create_new_xlsx_file)
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}
34 changes: 34 additions & 0 deletions tests/integration/ignore_error02.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Test case that compares a file generated by rust_xlsxwriter with a file
// created by Excel.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2025, John McNamara, [email protected]

use crate::common;
use rust_xlsxwriter::{IgnoreError, Workbook, XlsxError};

// Create rust_xlsxwriter file to compare against Excel file.
fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.write_string(0, 0, "123")?;

worksheet.ignore_error(0, 0, IgnoreError::NumberStoredAsText)?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_ignore_error02() {
let test_runner = common::TestRunner::new()
.set_name("ignore_error02")
.set_function(create_new_xlsx_file)
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}
36 changes: 36 additions & 0 deletions tests/integration/ignore_error03.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Test case that compares a file generated by rust_xlsxwriter with a file
// created by Excel.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2025, John McNamara, [email protected]

use crate::common;
use rust_xlsxwriter::{IgnoreError, Workbook, XlsxError};

// Create rust_xlsxwriter file to compare against Excel file.
fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

for row in 0..=9 {
worksheet.write_string(row, 0, "123")?;
}

worksheet.ignore_error_range(0, 0, 9, 0, IgnoreError::NumberStoredAsText)?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_ignore_error03() {
let test_runner = common::TestRunner::new()
.set_name("ignore_error03")
.set_function(create_new_xlsx_file)
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}
Loading

0 comments on commit a9d82e3

Please sign in to comment.