diff --git a/src/helper/coordinate.rs b/src/helper/coordinate.rs index 46152243..73010333 100644 --- a/src/helper/coordinate.rs +++ b/src/helper/coordinate.rs @@ -117,7 +117,7 @@ pub fn coordinate_from_index_with_lock( #[inline] pub(crate) fn adjustment_insert_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> u32 { if (num >= root_num && offset_num != &0) { - num + offset_num + num.saturating_add(*offset_num) } else { *num } @@ -126,7 +126,7 @@ pub(crate) fn adjustment_insert_coordinate(num: &u32, root_num: &u32, offset_num #[inline] pub(crate) fn adjustment_remove_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> u32 { if (num >= root_num && offset_num != &0) { - num - offset_num + num.saturating_sub(*offset_num) } else { *num } diff --git a/src/helper/number_format.rs b/src/helper/number_format.rs index f435eb78..ecc253fd 100644 --- a/src/helper/number_format.rs +++ b/src/helper/number_format.rs @@ -86,7 +86,11 @@ pub fn to_formatted_string, P: AsRef>(value: S, format: P) -> let sections: Vec<&str> = split(&SECTION_REGEX, &format).collect(); - let (_, split_format, split_value) = split_format(sections, &value.parse::().unwrap()); + let Ok(parsed_val) = value.parse::() else { + return value.to_string(); + }; + + let (_, split_format, split_value) = split_format(sections, &parsed_val); format = Cow::Owned(split_format); value = Cow::Owned(split_value); @@ -99,17 +103,20 @@ pub fn to_formatted_string, P: AsRef>(value: S, format: P) -> // Check for date/time characters (not inside quotes) + let reparsed = value.parse::().unwrap_or(0.0); + if DATE_TIME_REGEX.is_match(&format).unwrap_or(false) { // datetime format - value = date_formater::format_as_date(&value.parse::().unwrap(), &format); + value = date_formater::format_as_date(&reparsed, &format); } else if format.starts_with('"') && format.ends_with('"') { - let conv_format = format.trim_matches('"').parse::().unwrap(); - value = Cow::Owned(conv_format.to_string()); + if let Ok(conv_format) = format.trim_matches('"').parse::() { + value = Cow::Owned(conv_format.to_string()); + } } else if PERCENT_DOLLAR_REGEX.is_match(&format).unwrap_or(false) { // % number format - value = percentage_formater::format_as_percentage(&value.parse::().unwrap(), &format); + value = percentage_formater::format_as_percentage(&reparsed, &format); } else { - value = number_formater::format_as_number(&value.parse::().unwrap(), &format); + value = number_formater::format_as_number(&reparsed, &format); } value.trim().to_string() } @@ -175,18 +182,18 @@ fn split_format(sections: Vec<&str>, value: &f64) -> (String, String, String) { match cnt { 2 => { absval = absval.abs(); - let condval_one = &condvals[0].parse::().unwrap(); - if !split_format_compare(value, &condops[0], condval_one, ">=", &0f64) { + let condval_one = condvals[0].parse::().unwrap_or(0.0); + if !split_format_compare(value, &condops[0], &condval_one, ">=", &0f64) { color = &colors[1]; format = &converted_sections[1]; } } 3 | 4 => { absval = absval.abs(); - let condval_one = &condvals[0].parse::().unwrap(); - let condval_two = &condvals[1].parse::().unwrap(); - if !split_format_compare(value, &condops[0], condval_one, ">", &0f64) { - if split_format_compare(value, &condops[1], condval_two, "<", &0f64) { + let condval_one = condvals[0].parse::().unwrap_or(0.0); + let condval_two = condvals[1].parse::().unwrap_or(0.0); + if !split_format_compare(value, &condops[0], &condval_one, ">", &0f64) { + if split_format_compare(value, &condops[1], &condval_two, "<", &0f64) { color = &colors[1]; format = &converted_sections[1]; } else { diff --git a/src/helper/number_format/number_formater.rs b/src/helper/number_format/number_formater.rs index 77ca82b8..0c3d3c8d 100644 --- a/src/helper/number_format/number_formater.rs +++ b/src/helper/number_format/number_formater.rs @@ -53,13 +53,13 @@ pub(crate) fn format_as_number<'input>(value: &f64, format: &'input str) -> Cow< if FRACTION_REGEX.is_match(&format).unwrap_or(false) { if value.parse::().is_err() { //println!("format as fraction {} {}", value, format); - value = format_as_fraction(&value.parse::().unwrap(), &format); + value = format_as_fraction(&value.parse::().unwrap_or(0.0), &format); } } else { // Handle the number itself // scale number - value = (value.parse::().unwrap() / scale).to_string(); + value = (value.parse::().unwrap_or(0.0) / scale).to_string(); // Strip # format = format.replace('#', "0"); // Remove \ @@ -114,11 +114,12 @@ fn format_straight_numeric_value( ) -> String { let mut value = value.to_string(); - let right = matches.get(3).unwrap(); + let empty = String::new(); + let right = matches.get(3).unwrap_or(&empty); // minimun width of formatted number (including dot) if *use_thousands { - value = value.parse::().unwrap().separate_with_commas(); + value = value.parse::().unwrap_or(0.0).separate_with_commas(); } let blocks: Vec<&str> = value.split('.').collect(); let left_value = blocks[0].to_string(); @@ -133,14 +134,17 @@ fn format_straight_numeric_value( if right_value == "0" { right_value = right.to_string(); } else if right.len() > right_value.len() { - let pow = 10i32.pow(right.len() as u32); - right_value = format!("{}", right_value.parse::().unwrap() * pow); + let pow = 10i64.pow(right.len().min(18) as u32); + right_value = format!( + "{}", + right_value.parse::().unwrap_or(0).saturating_mul(pow) + ); } else { let mut right_value_conv: String = right_value.chars().take(right.len()).collect(); let ajst_str: String = right_value.chars().skip(right.len()).take(1).collect(); - let ajst_int = ajst_str.parse::().unwrap(); + let ajst_int = ajst_str.parse::().unwrap_or(0); if ajst_int > 4 { - right_value_conv = (right_value_conv.parse::().unwrap() + 1).to_string(); + right_value_conv = (right_value_conv.parse::().unwrap_or(0) + 1).to_string(); } right_value = right_value_conv; } @@ -210,7 +214,7 @@ fn process_complex_number_format_mask(number: &f64, mask: &str) -> String { let mut number = *number; let mut offset: usize = 0; for (block, pos) in masking_blocks.iter().rev() { - let divisor = format!("{}{}", 1, block).parse::().unwrap(); + let divisor = format!("{}{}", 1, block).parse::().unwrap_or(1.0); let size = block.len(); offset = *pos; @@ -248,15 +252,18 @@ fn complex_number_format_mask(number: &f64, mask: &str, split_on_point: &bool) - if masks.len() > 2 { masks = merge_complex_number_format_masks(&numbers, &masks); } - let result1 = - complex_number_format_mask(&numbers[0].parse::().unwrap(), &masks[0], &false); + let result1 = complex_number_format_mask( + &numbers[0].parse::().unwrap_or(0.0), + &masks[0], + &false, + ); let result2 = complex_number_format_mask( &numbers[1] .chars() .rev() .collect::() .parse::() - .unwrap(), + .unwrap_or(0.0), &masks[1].chars().rev().collect::(), &false, ) diff --git a/src/helper/number_format/percentage_formater.rs b/src/helper/number_format/percentage_formater.rs index 333e788c..da01b740 100644 --- a/src/helper/number_format/percentage_formater.rs +++ b/src/helper/number_format/percentage_formater.rs @@ -11,7 +11,7 @@ pub(crate) fn format_as_percentage<'input>(value: &f64, format: &'input str) -> }; value = format!( "{:0width$.len$}%", - (100f64 * &value.parse::().unwrap()).round(), + (100f64 * &value.parse::().unwrap_or(0.0)).round(), width = 1, len = len ); diff --git a/src/reader/driver.rs b/src/reader/driver.rs index 6f41b3f1..1f60baca 100644 --- a/src/reader/driver.rs +++ b/src/reader/driver.rs @@ -1,4 +1,5 @@ use quick_xml::events::attributes::Attribute; +use std::io; use std::path::{Component, Path, PathBuf}; use std::string::FromUtf8Error; @@ -77,15 +78,91 @@ pub(crate) fn normalize_path_to_str(path: &str) -> String { ret.to_str().unwrap_or("").replace('\\', "/") } +/// Look up a zip entry by name, tolerating backslash path separators and +/// case differences that appear in XLSX files generated by some Windows tools. +/// +/// Try order: +/// 1. Exact match (fast path — zero overhead for well-formed files). +/// 2. Backslash-separated variant of the requested name. +/// 3. Case-insensitive scan with separator normalization (last resort). +pub(crate) fn zip_by_name<'a, R: io::Read + io::Seek>( + arv: &'a mut zip::ZipArchive, + name: &str, +) -> Result, zip::result::ZipError> { + // 1. Exact match (common case) + if arv.by_name(name).is_ok() { + // Re-borrow: `by_name` returns a ZipFile that borrows `arv`, so we + // must call it again after the probe to satisfy the borrow checker. + return arv.by_name(name); + } + + // 2. Backslash variant: "xl/workbook.xml" → "xl\\workbook.xml" + let backslash_name = name.replace('/', "\\"); + if backslash_name != name { + if arv.by_name(&backslash_name).is_ok() { + return arv.by_name(&backslash_name); + } + } + + // 3. Case-insensitive scan with separator normalization + let normalized = name.replace('\\', "/").to_ascii_lowercase(); + for i in 0..arv.len() { + let entry_name = match arv.by_index(i) { + Ok(f) => f.name().to_owned(), + Err(_) => continue, + }; + let entry_normalized = entry_name.replace('\\', "/").to_ascii_lowercase(); + if entry_normalized == normalized { + return arv.by_name(&entry_name); + } + } + + Err(zip::result::ZipError::FileNotFound) +} + +/// Look up an XML attribute by key, falling back to a match on just the +/// local name (after the colon) when the exact prefixed key is not found. +/// +/// This handles XLSX files that use non-standard namespace prefixes +/// (e.g. `d3p1:id` instead of `r:id`). #[inline] pub(crate) fn get_attribute(e: &quick_xml::events::BytesStart<'_>, key: &[u8]) -> Option { - e.attributes() + // 1. Exact match (fast path) + let result = e + .attributes() .with_checks(false) .find_map(|attr| match attr { Ok(ref attr) if attr.key.into_inner() == key => { Some(get_attribute_value(attr).unwrap()) } _ => None, + }); + if result.is_some() { + return result; + } + + // 2. For namespaced keys like "r:id", fall back to matching the local name + // ("id") against any attribute whose local name matches. + let local_name = match key.iter().position(|&b| b == b':') { + Some(pos) => &key[pos + 1..], + None => return None, // Only fall back for namespaced keys + }; + e.attributes() + .with_checks(false) + .find_map(|attr| match attr { + Ok(ref attr) => { + let attr_key = attr.key.into_inner(); + let attr_local = match attr_key.iter().position(|&b| b == b':') { + Some(pos) => &attr_key[pos + 1..], + None => attr_key, + }; + if attr_local == local_name { + Some(get_attribute_value(attr).unwrap()) + } else { + None + } + } + _ => None, }) } diff --git a/src/reader/xlsx/content_types.rs b/src/reader/xlsx/content_types.rs index 197261ac..2413b389 100644 --- a/src/reader/xlsx/content_types.rs +++ b/src/reader/xlsx/content_types.rs @@ -10,7 +10,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let r = io::BufReader::new(arv.by_name(CONTENT_TYPES)?); + let r = io::BufReader::new(zip_by_name(arv, CONTENT_TYPES)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); let mut list: Vec<(String, String)> = Vec::new(); diff --git a/src/reader/xlsx/doc_props_app.rs b/src/reader/xlsx/doc_props_app.rs index ad8a9263..cd1aee4a 100644 --- a/src/reader/xlsx/doc_props_app.rs +++ b/src/reader/xlsx/doc_props_app.rs @@ -10,7 +10,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let r = io::BufReader::new(match arv.by_name(ARC_APP) { + let r = io::BufReader::new(match super::driver::zip_by_name(arv, ARC_APP) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { return Ok(()); diff --git a/src/reader/xlsx/doc_props_core.rs b/src/reader/xlsx/doc_props_core.rs index 37bdd699..5b002d4d 100644 --- a/src/reader/xlsx/doc_props_core.rs +++ b/src/reader/xlsx/doc_props_core.rs @@ -10,7 +10,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let r = io::BufReader::new(match arv.by_name(ARC_CORE) { + let r = io::BufReader::new(match super::driver::zip_by_name(arv, ARC_CORE) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { return Ok(()); diff --git a/src/reader/xlsx/doc_props_custom.rs b/src/reader/xlsx/doc_props_custom.rs index 28d3e184..56d0bf8d 100644 --- a/src/reader/xlsx/doc_props_custom.rs +++ b/src/reader/xlsx/doc_props_custom.rs @@ -10,7 +10,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let r = io::BufReader::new(match arv.by_name(ARC_CUSTOM) { + let r = io::BufReader::new(match super::driver::zip_by_name(arv, ARC_CUSTOM) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { return Ok(()); diff --git a/src/reader/xlsx/jsa_project_bin.rs b/src/reader/xlsx/jsa_project_bin.rs index 55ea72c6..911c44cf 100644 --- a/src/reader/xlsx/jsa_project_bin.rs +++ b/src/reader/xlsx/jsa_project_bin.rs @@ -9,7 +9,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let mut r = io::BufReader::new(match arv.by_name(PKG_JSA_PROJECT) { + let mut r = io::BufReader::new(match super::driver::zip_by_name(arv, PKG_JSA_PROJECT) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { return Ok(()); diff --git a/src/reader/xlsx/shared_strings.rs b/src/reader/xlsx/shared_strings.rs index 03ea1e8e..2568f90c 100644 --- a/src/reader/xlsx/shared_strings.rs +++ b/src/reader/xlsx/shared_strings.rs @@ -12,7 +12,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let r = io::BufReader::new(match arv.by_name(PKG_SHARED_STRINGS) { + let r = io::BufReader::new(match super::driver::zip_by_name(arv, PKG_SHARED_STRINGS) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { return Ok(()); diff --git a/src/reader/xlsx/styles.rs b/src/reader/xlsx/styles.rs index 04d75109..16f6f2c1 100644 --- a/src/reader/xlsx/styles.rs +++ b/src/reader/xlsx/styles.rs @@ -12,7 +12,15 @@ pub fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let r = io::BufReader::new(arv.by_name(PKG_STYLES)?); + let r = io::BufReader::new(match super::driver::zip_by_name(arv, PKG_STYLES) { + Ok(v) => v, + Err(zip::result::ZipError::FileNotFound) => { + return Ok(()); + } + Err(e) => { + return Err(e.into()); + } + }); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); diff --git a/src/reader/xlsx/theme.rs b/src/reader/xlsx/theme.rs index 0fd302ff..ed9591ab 100644 --- a/src/reader/xlsx/theme.rs +++ b/src/reader/xlsx/theme.rs @@ -9,7 +9,7 @@ pub fn read( arv: &mut zip::ZipArchive, target: &str, ) -> result::Result { - let r = io::BufReader::new(arv.by_name(&format!("xl/{}", target))?); + let r = io::BufReader::new(super::driver::zip_by_name(arv, &format!("xl/{}", target))?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); diff --git a/src/reader/xlsx/vba_project_bin.rs b/src/reader/xlsx/vba_project_bin.rs index b0b3fd5d..17598225 100644 --- a/src/reader/xlsx/vba_project_bin.rs +++ b/src/reader/xlsx/vba_project_bin.rs @@ -9,7 +9,7 @@ pub(crate) fn read( arv: &mut zip::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result<(), XlsxError> { - let mut r = io::BufReader::new(match arv.by_name(PKG_VBA_PROJECT) { + let mut r = io::BufReader::new(match super::driver::zip_by_name(arv, PKG_VBA_PROJECT) { Ok(v) => v, Err(zip::result::ZipError::FileNotFound) => { return Ok(()); diff --git a/src/reader/xlsx/workbook.rs b/src/reader/xlsx/workbook.rs index ffd51b03..f96f9991 100644 --- a/src/reader/xlsx/workbook.rs +++ b/src/reader/xlsx/workbook.rs @@ -17,7 +17,7 @@ use crate::structs::Worksheet; pub(crate) fn read( arv: &mut zip::read::ZipArchive, ) -> result::Result { - let r = io::BufReader::new(arv.by_name(PKG_WORKBOOK)?); + let r = io::BufReader::new(zip_by_name(arv, PKG_WORKBOOK)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); let mut spreadsheet = Spreadsheet::default(); diff --git a/src/reader/xlsx/workbook_rels.rs b/src/reader/xlsx/workbook_rels.rs index d1210582..c013315e 100644 --- a/src/reader/xlsx/workbook_rels.rs +++ b/src/reader/xlsx/workbook_rels.rs @@ -10,7 +10,7 @@ pub(crate) fn read( arv: &mut zip::read::ZipArchive, spreadsheet: &mut Spreadsheet, ) -> result::Result, XlsxError> { - let r = io::BufReader::new(arv.by_name(PKG_WORKBOOK_RELS)?); + let r = io::BufReader::new(zip_by_name(arv, PKG_WORKBOOK_RELS)?); let mut reader = Reader::from_reader(r); reader.config_mut().trim_text(true); diff --git a/src/structs/cell.rs b/src/structs/cell.rs index 74e4bc5d..05d3f130 100644 --- a/src/structs/cell.rs +++ b/src/structs/cell.rs @@ -360,8 +360,10 @@ impl Cell { } if let Some(v) = get_attribute(e, b"s") { - let style = stylesheet.get_style(v.parse::().unwrap()); - self.set_style(style); + if let Ok(id) = v.parse::() { + let style = stylesheet.get_style(id); + self.set_style(style); + } } if let Some(v) = get_attribute(e, b"t") { @@ -409,12 +411,13 @@ impl Cell { self.set_value_string_crate(&string_value); } "s" => { - let index = string_value.parse::().unwrap(); - let shared_string_item = shared_string_table - .get_shared_string_item() - .get(index) - .unwrap(); - self.set_shared_string_item(shared_string_item.clone()); + if let Ok(index) = string_value.parse::() { + if let Some(shared_string_item) = + shared_string_table.get_shared_string_item().get(index) + { + self.set_shared_string_item(shared_string_item.clone()); + } + } } "b" => { let prm = string_value == "1"; diff --git a/src/structs/cell_formula.rs b/src/structs/cell_formula.rs index 46ab781b..7e316db8 100644 --- a/src/structs/cell_formula.rs +++ b/src/structs/cell_formula.rs @@ -200,15 +200,17 @@ impl CellFormula { Some((parent_cell_reference_str, token)) => { let parent_cell = index_from_coordinate(parent_cell_reference_str); let self_cell = index_from_coordinate(cell_reference_str); - let parent_col_num = parent_cell.0.unwrap(); - let parent_row_num = parent_cell.1.unwrap(); - let self_col_num = self_cell.0.unwrap(); - let self_row_num = self_cell.1.unwrap(); + let (Some(parent_col_num), Some(parent_row_num), ..) = parent_cell else { + return; + }; + let (Some(self_col_num), Some(self_row_num), ..) = self_cell else { + return; + }; let root_col_num = parent_col_num; let root_row_num = parent_row_num; - let offset_col_num = self_col_num - root_col_num; - let offset_row_num = self_row_num - parent_row_num; + let offset_col_num = self_col_num.wrapping_sub(root_col_num); + let offset_row_num = self_row_num.wrapping_sub(root_row_num); let mut token_new = token.clone(); let value = adjustment_insert_formula_coordinate( diff --git a/src/structs/conditional_formatting_rule.rs b/src/structs/conditional_formatting_rule.rs index 73a253d3..48a16d04 100644 --- a/src/structs/conditional_formatting_rule.rs +++ b/src/structs/conditional_formatting_rule.rs @@ -270,9 +270,10 @@ impl ConditionalFormattingRule { set_string_from_xml!(self, e, operator, "operator"); if let Some(v) = get_attribute(e, b"dxfId") { - let dxf_id = v.parse::().unwrap(); - let style = differential_formats.get_style(dxf_id); - self.set_style(style); + if let Ok(dxf_id) = v.parse::() { + let style = differential_formats.get_style(dxf_id); + self.set_style(style); + } } set_string_from_xml!(self, e, priority, "priority"); @@ -321,7 +322,7 @@ impl ConditionalFormattingRule { return } }, - Event::Eof => panic!("Error: Could not find {} end element", "cfRule") + Event::Eof => return ); } diff --git a/src/structs/data_bar.rs b/src/structs/data_bar.rs index d05257fd..1c86a85c 100644 --- a/src/structs/data_bar.rs +++ b/src/structs/data_bar.rs @@ -80,7 +80,7 @@ impl DataBar { return } }, - Event::Eof => panic!("Error: Could not find {} end element", "dataBar") + Event::Eof => return ); } diff --git a/src/structs/icon_set.rs b/src/structs/icon_set.rs index a636943e..168c828e 100644 --- a/src/structs/icon_set.rs +++ b/src/structs/icon_set.rs @@ -76,11 +76,11 @@ impl IconSet { } }, Event::End(ref e) => { - if e.name().into_inner() == b"dataBar" { + if e.name().into_inner() == b"iconSet" { return } }, - Event::Eof => panic!("Error: Could not find {} end element", "dataBar") + Event::Eof => return ); } diff --git a/src/structs/int32_value.rs b/src/structs/int32_value.rs index cb8af4d7..b5f0c0b1 100644 --- a/src/structs/int32_value.rs +++ b/src/structs/int32_value.rs @@ -24,7 +24,13 @@ impl Int32Value { #[inline] pub(crate) fn set_value_string>(&mut self, value: S) -> &mut Self { - self.set_value(value.into().parse::().unwrap()) + let s = value.into(); + self.set_value(s.parse::().unwrap_or_else(|_| { + s.parse::() + .ok() + .and_then(|v| i32::try_from(v).ok()) + .unwrap_or(0) + })) } #[inline] diff --git a/src/structs/raw/raw_file.rs b/src/structs/raw/raw_file.rs index e721734f..5a8d062a 100644 --- a/src/structs/raw/raw_file.rs +++ b/src/structs/raw/raw_file.rs @@ -73,9 +73,17 @@ impl RawFile { target: &str, ) { let path_str = join_paths(base_path, target); - let mut r = io::BufReader::new(arv.by_name(&path_str).unwrap()); + let Ok(file) = zip_by_name(arv, &path_str) else { + // File not found in archive — skip gracefully. + self.set_file_target(path_str); + return; + }; + let mut r = io::BufReader::new(file); let mut buf = Vec::new(); - r.read_to_end(&mut buf).unwrap(); + if r.read_to_end(&mut buf).is_err() { + self.set_file_target(path_str); + return; + } self.set_file_target(path_str); self.set_file_data(&buf); diff --git a/src/structs/raw/raw_relationship.rs b/src/structs/raw/raw_relationship.rs index 84ec2fb3..02760160 100644 --- a/src/structs/raw/raw_relationship.rs +++ b/src/structs/raw/raw_relationship.rs @@ -87,9 +87,18 @@ impl RawRelationship { arv: &mut zip::read::ZipArchive, base_path: &str, ) { - self.set_id(get_attribute(e, b"Id").unwrap()); - self.set_type(get_attribute(e, b"Type").unwrap()); - self.set_target(get_attribute(e, b"Target").unwrap()); + let Some(id) = get_attribute(e, b"Id") else { + return; + }; + let Some(type_val) = get_attribute(e, b"Type") else { + return; + }; + let Some(target) = get_attribute(e, b"Target") else { + return; + }; + self.set_id(id); + self.set_type(type_val); + self.set_target(target); if let Some(v) = get_attribute(e, b"TargetMode") { self.set_target_mode(v); } diff --git a/src/structs/raw/raw_relationships.rs b/src/structs/raw/raw_relationships.rs index 16116ec0..5d2c0e1a 100644 --- a/src/structs/raw/raw_relationships.rs +++ b/src/structs/raw/raw_relationships.rs @@ -68,7 +68,7 @@ impl RawRelationships { ) -> bool { let data = { let path_str = join_paths(base_path, target); - let file_path = match arv.by_name(&path_str) { + let file_path = match zip_by_name(arv, &path_str) { Ok(v) => v, Err(_) => { return false; diff --git a/src/structs/stylesheet.rs b/src/structs/stylesheet.rs index a64be1c3..3c5e563b 100644 --- a/src/structs/stylesheet.rs +++ b/src/structs/stylesheet.rs @@ -181,7 +181,7 @@ impl Stylesheet { #[inline] pub(crate) fn get_style(&self, id: usize) -> Style { - self.maked_style_list.get(id).unwrap().clone() + self.maked_style_list.get(id).cloned().unwrap_or_default() } pub(crate) fn make_style(&mut self) -> &mut Self { @@ -232,8 +232,9 @@ impl Stylesheet { } if apply { let id = *cell_format.get_font_id() as usize; - let obj = self.fonts.get_font().get(id).unwrap(); - style.set_font(obj.clone()); + if let Some(obj) = self.fonts.get_font().get(id) { + style.set_font(obj.clone()); + } } // fill @@ -246,8 +247,9 @@ impl Stylesheet { } if apply { let id = *cell_format.get_fill_id() as usize; - let obj = self.fills.get_fill().get(id).unwrap(); - style.set_fill(obj.clone()); + if let Some(obj) = self.fills.get_fill().get(id) { + style.set_fill(obj.clone()); + } } // borders @@ -260,8 +262,9 @@ impl Stylesheet { } if apply { let id = *cell_format.get_border_id() as usize; - let obj = self.borders.get_borders().get(id).unwrap(); - style.set_borders(obj.clone()); + if let Some(obj) = self.borders.get_borders().get(id) { + style.set_borders(obj.clone()); + } } // format_id diff --git a/src/structs/u_int32_value.rs b/src/structs/u_int32_value.rs index 8cda971f..030d92ae 100644 --- a/src/structs/u_int32_value.rs +++ b/src/structs/u_int32_value.rs @@ -21,7 +21,13 @@ impl UInt32Value { #[inline] pub(crate) fn set_value_string>(&mut self, value: S) -> &mut Self { - self.set_value(value.into().parse::().unwrap()) + let s = value.into(); + self.set_value(s.parse::().unwrap_or_else(|_| { + s.parse::() + .ok() + .and_then(|v| u32::try_from(v).ok()) + .unwrap_or(u32::MAX) + })) } #[inline] diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 4e9711c3..160618f0 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -2106,3 +2106,63 @@ fn issue_293() { let path = std::path::Path::new("./tests/result_files/r_issue_293.xlsx"); let _ = umya_spreadsheet::writer::xlsx::write(&book, path); } + +// --- Panic safety: backslash zip paths (Windows-generated XLSX) --------------- + +#[test] +fn backslash_paths_tdf131575() { + let path = std::path::Path::new("./tests/test_files/tdf131575.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +#[test] +fn backslash_paths_tdf76115() { + let path = std::path::Path::new("./tests/test_files/tdf76115.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +#[test] +fn backslash_paths_49609() { + let path = std::path::Path::new("./tests/test_files/49609.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +// --- Panic safety: missing optional styles.xml -------------------------------- + +#[test] +fn missing_styles_56278() { + let path = std::path::Path::new("./tests/test_files/56278.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +#[test] +fn missing_styles_tdf121887() { + let path = std::path::Path::new("./tests/test_files/tdf121887.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +#[test] +fn missing_styles_59021() { + let path = std::path::Path::new("./tests/test_files/59021.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +// --- Panic safety: arithmetic overflow / unwrap on None ----------------------- + +#[test] +fn overflow_functions_excel_2010() { + let path = std::path::Path::new("./tests/test_files/functions-excel-2010.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +#[test] +fn overflow_formula_eval_test_data() { + let path = std::path::Path::new("./tests/test_files/FormulaEvalTestData_Copy.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} + +#[test] +fn missing_attr_64450() { + let path = std::path::Path::new("./tests/test_files/64450.xlsx"); + let _book = umya_spreadsheet::reader::xlsx::read(path).unwrap(); +} diff --git a/tests/test_files/49609.xlsx b/tests/test_files/49609.xlsx new file mode 100644 index 00000000..03d9d12c Binary files /dev/null and b/tests/test_files/49609.xlsx differ diff --git a/tests/test_files/56278.xlsx b/tests/test_files/56278.xlsx new file mode 100644 index 00000000..f7108512 Binary files /dev/null and b/tests/test_files/56278.xlsx differ diff --git a/tests/test_files/59021.xlsx b/tests/test_files/59021.xlsx new file mode 100644 index 00000000..0f485d27 Binary files /dev/null and b/tests/test_files/59021.xlsx differ diff --git a/tests/test_files/64450.xlsx b/tests/test_files/64450.xlsx new file mode 100644 index 00000000..f96b1794 Binary files /dev/null and b/tests/test_files/64450.xlsx differ diff --git a/tests/test_files/FormulaEvalTestData_Copy.xlsx b/tests/test_files/FormulaEvalTestData_Copy.xlsx new file mode 100644 index 00000000..d9dfac62 Binary files /dev/null and b/tests/test_files/FormulaEvalTestData_Copy.xlsx differ diff --git a/tests/test_files/functions-excel-2010.xlsx b/tests/test_files/functions-excel-2010.xlsx new file mode 100644 index 00000000..152fbbc4 Binary files /dev/null and b/tests/test_files/functions-excel-2010.xlsx differ diff --git a/tests/test_files/tdf121887.xlsx b/tests/test_files/tdf121887.xlsx new file mode 100644 index 00000000..57d67d5a Binary files /dev/null and b/tests/test_files/tdf121887.xlsx differ diff --git a/tests/test_files/tdf131575.xlsx b/tests/test_files/tdf131575.xlsx new file mode 100644 index 00000000..307d7dea Binary files /dev/null and b/tests/test_files/tdf131575.xlsx differ diff --git a/tests/test_files/tdf76115.xlsx b/tests/test_files/tdf76115.xlsx new file mode 100644 index 00000000..ebc6126d Binary files /dev/null and b/tests/test_files/tdf76115.xlsx differ