Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/helper/coordinate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
31 changes: 19 additions & 12 deletions src/helper/number_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ pub fn to_formatted_string<S: AsRef<str>, P: AsRef<str>>(value: S, format: P) ->

let sections: Vec<&str> = split(&SECTION_REGEX, &format).collect();

let (_, split_format, split_value) = split_format(sections, &value.parse::<f64>().unwrap());
let Ok(parsed_val) = value.parse::<f64>() 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);

Expand All @@ -99,17 +103,20 @@ pub fn to_formatted_string<S: AsRef<str>, P: AsRef<str>>(value: S, format: P) ->

// Check for date/time characters (not inside quotes)

let reparsed = value.parse::<f64>().unwrap_or(0.0);

if DATE_TIME_REGEX.is_match(&format).unwrap_or(false) {
// datetime format
value = date_formater::format_as_date(&value.parse::<f64>().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::<f64>().unwrap();
value = Cow::Owned(conv_format.to_string());
if let Ok(conv_format) = format.trim_matches('"').parse::<f64>() {
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::<f64>().unwrap(), &format);
value = percentage_formater::format_as_percentage(&reparsed, &format);
} else {
value = number_formater::format_as_number(&value.parse::<f64>().unwrap(), &format);
value = number_formater::format_as_number(&reparsed, &format);
}
value.trim().to_string()
}
Expand Down Expand Up @@ -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::<f64>().unwrap();
if !split_format_compare(value, &condops[0], condval_one, ">=", &0f64) {
let condval_one = condvals[0].parse::<f64>().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::<f64>().unwrap();
let condval_two = &condvals[1].parse::<f64>().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::<f64>().unwrap_or(0.0);
let condval_two = condvals[1].parse::<f64>().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 {
Expand Down
31 changes: 19 additions & 12 deletions src/helper/number_format/number_formater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<usize>().is_err() {
//println!("format as fraction {} {}", value, format);
value = format_as_fraction(&value.parse::<f64>().unwrap(), &format);
value = format_as_fraction(&value.parse::<f64>().unwrap_or(0.0), &format);
}
} else {
// Handle the number itself

// scale number
value = (value.parse::<f64>().unwrap() / scale).to_string();
value = (value.parse::<f64>().unwrap_or(0.0) / scale).to_string();
// Strip #
format = format.replace('#', "0");
// Remove \
Expand Down Expand Up @@ -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::<f64>().unwrap().separate_with_commas();
value = value.parse::<f64>().unwrap_or(0.0).separate_with_commas();
}
let blocks: Vec<&str> = value.split('.').collect();
let left_value = blocks[0].to_string();
Expand All @@ -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::<i32>().unwrap() * pow);
let pow = 10i64.pow(right.len().min(18) as u32);
right_value = format!(
"{}",
right_value.parse::<i64>().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::<i32>().unwrap();
let ajst_int = ajst_str.parse::<i32>().unwrap_or(0);
if ajst_int > 4 {
right_value_conv = (right_value_conv.parse::<i32>().unwrap() + 1).to_string();
right_value_conv = (right_value_conv.parse::<i32>().unwrap_or(0) + 1).to_string();
}
right_value = right_value_conv;
}
Expand Down Expand Up @@ -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::<f64>().unwrap();
let divisor = format!("{}{}", 1, block).parse::<f64>().unwrap_or(1.0);
let size = block.len();
offset = *pos;

Expand Down Expand Up @@ -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::<f64>().unwrap(), &masks[0], &false);
let result1 = complex_number_format_mask(
&numbers[0].parse::<f64>().unwrap_or(0.0),
&masks[0],
&false,
);
let result2 = complex_number_format_mask(
&numbers[1]
.chars()
.rev()
.collect::<String>()
.parse::<f64>()
.unwrap(),
.unwrap_or(0.0),
&masks[1].chars().rev().collect::<String>(),
&false,
)
Expand Down
2 changes: 1 addition & 1 deletion src/helper/number_format/percentage_formater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) fn format_as_percentage<'input>(value: &f64, format: &'input str) ->
};
value = format!(
"{:0width$.len$}%",
(100f64 * &value.parse::<f64>().unwrap()).round(),
(100f64 * &value.parse::<f64>().unwrap_or(0.0)).round(),
width = 1,
len = len
);
Expand Down
79 changes: 78 additions & 1 deletion src/reader/driver.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use quick_xml::events::attributes::Attribute;
use std::io;
use std::path::{Component, Path, PathBuf};
use std::string::FromUtf8Error;

Expand Down Expand Up @@ -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<R>,
name: &str,
) -> Result<zip::read::ZipFile<'a>, 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<String> {
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,
})
}

Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/content_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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();
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/doc_props_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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(());
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/doc_props_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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(());
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/doc_props_custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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(());
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/jsa_project_bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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(());
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/shared_strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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(());
Expand Down
10 changes: 9 additions & 1 deletion src/reader/xlsx/styles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ pub fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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);

Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
target: &str,
) -> result::Result<Theme, XlsxError> {
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);

Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/vba_project_bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::ZipArchive<R>,
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(());
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/workbook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::structs::Worksheet;
pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::read::ZipArchive<R>,
) -> result::Result<Spreadsheet, XlsxError> {
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();
Expand Down
2 changes: 1 addition & 1 deletion src/reader/xlsx/workbook_rels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) fn read<R: io::Read + io::Seek>(
arv: &mut zip::read::ZipArchive<R>,
spreadsheet: &mut Spreadsheet,
) -> result::Result<Vec<(String, String, String)>, 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);

Expand Down
Loading