diff --git a/Cargo.toml b/Cargo.toml index 57d7689..2936f3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,6 @@ serde_derive = "1.0" sha256 = "1.1" [features] -default = [] +default = ["dates"] dates = ["chrono", "once_cell"] picture = [] diff --git a/src/utils.rs b/src/utils.rs index 0177542..96c2892 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -34,6 +34,11 @@ pub fn read_u16(s: &[u8]) -> u16 { u16::from_le_bytes(s[..2].try_into().unwrap()) } +#[inline] +pub fn read_i16(s: &[u8]) -> i16 { + i16::from_le_bytes(s[..2].try_into().unwrap()) +} + #[inline] pub fn read_u64(s: &[u8]) -> u64 { u64::from_le_bytes(s[..8].try_into().unwrap()) diff --git a/src/xls.rs b/src/xls.rs index 174445e..4bfd09a 100644 --- a/src/xls.rs +++ b/src/xls.rs @@ -15,7 +15,7 @@ use crate::formats::{ }; #[cfg(feature = "picture")] use crate::utils::read_usize; -use crate::utils::{push_column, read_f64, read_i32, read_u16, read_u32}; +use crate::utils::{push_column, read_f64, read_i16, read_i32, read_u16, read_u32}; use crate::vba::VbaProject; use crate::{Cell, CellErrorType, DataType, Metadata, Range, Reader}; @@ -239,6 +239,13 @@ impl Reader for Xls { } } +#[derive(Debug, Clone, Copy)] +struct Xti { + _isup_book: u16, + itab_first: i16, + _itab_last: i16, +} + impl Xls { fn parse_workbook(&mut self, mut reader: RS, mut cfb: Cfb) -> Result<(), XlsError> { // gets workbook and worksheets stream, or early exit @@ -313,12 +320,11 @@ impl Xls { 0x0017 => { // ExternSheet let cxti = read_u16(r.data) as usize; - xtis.extend( - r.data[2..] - .chunks(6) - .take(cxti) - .map(|xti| read_u16(&xti[2..]) as usize), - ); + xtis.extend(r.data[2..].chunks(6).take(cxti).map(|xti| Xti { + _isup_book: read_u16(&xti[..2]), + itab_first: read_i16(&xti[2..4]), + _itab_last: read_i16(&xti[4..]), + })); } 0x00FC => strings = parse_sst(&mut r, &mut encoding)?, // SST #[cfg(feature = "picture")] @@ -347,16 +353,15 @@ impl Xls { let defined_names = defined_names .into_iter() - .map(|(name, (i, f))| { + .map(|(name, (i, mut f))| { if let Some(i) = i { - if i >= xtis.len() || xtis[i] >= sheet_names.len() { - (name, format!("#REF!{}", f)) - } else { - (name, format!("{}!{}", sheet_names[xtis[i]].1, f)) - } - } else { - (name, f) + let sh = xtis + .get(i) + .and_then(|xti| sheet_names.get(xti.itab_first as usize)) + .map_or("#REF", |sh| &sh.1); + f = format!("{sh}!{f}"); } + (name, f) }) .collect::>(); @@ -397,6 +402,7 @@ impl Xls { &r.data[20..], &fmla_sheet_names, &defined_names, + &xtis, &mut encoding, ) .unwrap_or_else(|e| { @@ -940,6 +946,7 @@ fn parse_formula( mut rgce: &[u8], sheets: &[String], names: &[(String, String)], + xtis: &[Xti], encoding: &mut XlsEncoding, ) -> Result { let mut stack = Vec::new(); @@ -953,13 +960,24 @@ fn parse_formula( 0x3a | 0x5a | 0x7a => { // PtgRef3d let ixti = read_u16(&rgce[0..2]); + let rowu = read_u16(&rgce[2..]); + let colu = read_u16(&rgce[4..]); + let sh = xtis + .get(ixti as usize) + .and_then(|xti| sheets.get(xti.itab_first as usize)) + .map_or("#REF", |sh| &sh); stack.push(formula.len()); - formula.push_str(sheets.get(ixti as usize).map_or("#REF", |s| &**s)); + formula.push_str(sh); formula.push('!'); - // TODO: check with relative columns - formula.push('$'); - push_column(read_u16(&rgce[4..6]) as u32, &mut formula); - write!(&mut formula, "${}", read_u16(&rgce[2..4]) as u32 + 1).unwrap(); + let col = colu << 2; // first 14 bits only + if colu & 2 != 0 { + formula.push('$'); + } + push_column(col as u32, &mut formula); + if colu & 1 != 0 { + formula.push('$'); + } + write!(&mut formula, "{}", rowu + 1).unwrap(); rgce = &rgce[6..]; } 0x3b | 0x5b | 0x7b => { diff --git a/tests/test.rs b/tests/test.rs index fd59759..1816882 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1229,3 +1229,16 @@ fn ods_number_rows_repeated() { ] ); } + +#[test] +fn xls_formula() { + setup(); + let path = format!("{}/tests/xls_formula.xls", env!("CARGO_MANIFEST_DIR")); + let mut wb: Xls<_> = open_workbook(&path).unwrap(); + let formula = wb.worksheet_formula("Sheet1").unwrap().unwrap(); + let mut rows = formula.rows(); + assert_eq!(rows.next(), Some(&["A1*2".to_owned()][..])); + assert_eq!(rows.next(), Some(&["2*Sheet2!A1".to_owned()][..])); + assert_eq!(rows.next(), Some(&["A1+Sheet2!A1".to_owned()][..])); + assert_eq!(rows.next(), None); +} diff --git a/tests/xls_formula.xls b/tests/xls_formula.xls new file mode 100644 index 0000000..6068910 Binary files /dev/null and b/tests/xls_formula.xls differ