Skip to content
Merged
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
51 changes: 38 additions & 13 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,27 @@ pub fn read_f64(s: &[u8]) -> f64 {
f64::from_le_bytes(s[..8].try_into().unwrap())
}

/// Push literal column into a String buffer
pub fn push_column(mut col: u32, buf: &mut String) {
if col < 26 {
buf.push((b'A' + col as u8) as char);
} else {
let mut rev = String::new();
while col >= 26 {
let c = col % 26;
rev.push((b'A' + c as u8) as char);
col -= c;
col /= 26;
}
buf.extend(rev.chars().rev());
/// Convert a 0-based column index to an Excel column name (0 -> "A", 26 -> "AA").
pub fn push_column(col: u32, buf: &mut String) {
Comment thread
gaoflow marked this conversation as resolved.
let mut digits = [0u8; 6];
let len = column_name_digits(col, &mut digits);
for &d in &digits[..len] {
buf.push(d as char);
}
}

/// Write the Excel letters for a 0-based column index into `out` (most
/// significant first) and return how many were written.
pub(crate) fn column_name_digits(col: u32, out: &mut [u8]) -> usize {
let mut num = col + 1;
let mut n = 0;
while num > 0 {
out[n] = b'A' + ((num - 1) % 26) as u8;
n += 1;
num = (num - 1) / 26;
}
out[..n].reverse();
n
}

// Utility function to unescape standard XML entities or character references
Expand Down Expand Up @@ -1165,6 +1172,24 @@ pub const FTAB_ARGC: [u8; FTAB_LEN] = [
mod tests {
use super::*;

#[test]
fn test_push_column() {
let check = |col: u32, expected: &str| {
let mut got = String::new();
push_column(col, &mut got);
assert_eq!(got, expected, "push_column({col})");
};
check(0, "A");
check(25, "Z");
check(26, "AA");
check(27, "AB");
check(51, "AZ");
check(53, "BB");
check(701, "ZZ");
check(702, "AAA");
check(16383, "XFD");
}

#[test]
fn sound_to_u32() {
let data = b"ABCDEFGH";
Expand Down
26 changes: 21 additions & 5 deletions src/xls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1773,11 +1773,27 @@ fn parse_formula(
write!(&mut formula, "${row_last}").unwrap();
rgce = &rgce[6..];
} else {
formula.push('$');
push_column(read_u16(&rgce[4..6]) as u32, &mut formula);
write!(&mut formula, "${}:$", read_u16(&rgce[0..2]) as u32 + 1).unwrap();
push_column(read_u16(&rgce[6..8]) as u32, &mut formula);
write!(&mut formula, "${}", read_u16(&rgce[2..4]) as u32 + 1).unwrap();
// columnFirst/columnLast are ColRelU: 14-bit column + fColRel/fRwRel flags.
Comment thread
gaoflow marked this conversation as resolved.
let col_first = read_u16(&[rgce[4], rgce[5] & 0x3F]);
let col_last = read_u16(&[rgce[6], rgce[7] & 0x3F]);
let row_first = read_u16(&rgce[0..2]) as u32 + 1;
let row_last = read_u16(&rgce[2..4]) as u32 + 1;
if rgce[5] & 0x80 != 0x80 {
formula.push('$');
}
push_column(col_first as u32, &mut formula);
if rgce[5] & 0x40 != 0x40 {
formula.push('$');
}
write!(&mut formula, "{row_first}:").unwrap();
if rgce[7] & 0x80 != 0x80 {
formula.push('$');
}
push_column(col_last as u32, &mut formula);
if rgce[7] & 0x40 != 0x40 {
formula.push('$');
}
write!(&mut formula, "{row_last}").unwrap();
rgce = &rgce[8..];
}
}
Expand Down
11 changes: 3 additions & 8 deletions src/xlsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3340,14 +3340,9 @@ pub(crate) fn column_number_to_name(num: u32, buf: &mut Vec<u8>) -> Result<(), X
if num >= MAX_COLUMNS {
return Err(XlsxError::ColumnNumberOverflow);
}
let start = buf.len();
let mut num = num + 1;
while num > 0 {
let integer = ((num - 1) % 26 + 65) as u8;
buf.push(integer);
num = (num - 1) / 26;
}
buf[start..].reverse();
let mut digits = [0u8; 6];
let len = crate::utils::column_name_digits(num, &mut digits);
buf.extend_from_slice(&digits[..len]);
Ok(())
}

Expand Down
12 changes: 12 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3617,6 +3617,18 @@ fn xls_empty_string() {
assert_eq!(range.get_value((0, 0)), Some(&String("".to_string())));
}

#[test]
fn xls_formula_columns_beyond_z() {
// Formula column references at/after column AA (index 26) exercise the
// column-name rendering and the BIFF8 PtgArea column masking.
let mut wb: Xls<_> = wb("xls_formula_columns_beyond_z.xls");
let formula = wb.worksheet_formula("Sheet1").unwrap();
let mut rows = formula.rows();
assert_eq!(rows.next(), Some(&["SUM(AA1:AA3)".to_owned()][..]));
assert_eq!(rows.next(), Some(&["AA1+AB1".to_owned()][..]));
assert_eq!(rows.next(), None);
}

#[test]
fn xls_embedded_cross_sheet_chart_does_not_leak_cells() {
// Regression test: the chart on "Report" is a nested substream whose cached
Expand Down
Binary file added tests/xls_formula_columns_beyond_z.xls
Binary file not shown.
Loading