use sheetkit_xml::worksheet::{CellTypeTag, Row, WorksheetXml};
use crate::cell::CellValue;
use crate::error::{Error, Result};
use crate::sst::SharedStringTable;
use crate::utils::cell_ref::{cell_name_to_coordinates, coordinates_to_cell_name};
use crate::utils::constants::{MAX_ROWS, MAX_ROW_HEIGHT};
#[allow(clippy::type_complexity)]
pub fn get_rows(
ws: &WorksheetXml,
sst: &SharedStringTable,
style_is_date: &[bool],
) -> Result<Vec<(u32, Vec<(u32, CellValue)>)>> {
let mut result = Vec::new();
for row in &ws.sheet_data.rows {
if row.cells.is_empty() {
continue;
}
let mut cells = Vec::new();
for cell in &row.cells {
let col_num = if cell.col > 0 {
cell.col
} else {
cell_name_to_coordinates(cell.r.as_str())?.0
};
let value = resolve_cell_value(cell, sst, style_is_date);
cells.push((col_num, value));
}
result.push((row.r, cells));
}
Ok(result)
}
pub fn resolve_sst_value(sst: &SharedStringTable, index: &str) -> CellValue {
let idx: usize = match index.parse() {
Ok(i) => i,
Err(_) => return CellValue::Empty,
};
let s = sst.get(idx).unwrap_or("").to_string();
CellValue::String(s)
}
pub fn parse_cell_type_value(
cell_type: CellTypeTag,
value: Option<&str>,
formula: Option<&sheetkit_xml::worksheet::CellFormula>,
inline_str: Option<&sheetkit_xml::worksheet::InlineString>,
sst: &SharedStringTable,
promote_number_to_date: bool,
) -> CellValue {
if let Some(f) = formula {
let expr = f.value.clone().unwrap_or_default();
let cached = match (cell_type, value) {
(CellTypeTag::Boolean, Some(v)) => Some(Box::new(CellValue::Bool(v == "1"))),
(CellTypeTag::Error, Some(v)) => Some(Box::new(CellValue::Error(v.to_string()))),
(_, Some(v)) => v
.parse::<f64>()
.ok()
.map(|n| Box::new(CellValue::Number(n))),
_ => None,
};
return CellValue::Formula {
expr,
result: cached,
};
}
match (cell_type, value) {
(CellTypeTag::SharedString, Some(v)) => resolve_sst_value(sst, v),
(CellTypeTag::Boolean, Some(v)) => CellValue::Bool(v == "1"),
(CellTypeTag::Error, Some(v)) => CellValue::Error(v.to_string()),
(CellTypeTag::InlineString, _) => {
let s = inline_str.and_then(|is| is.t.clone()).unwrap_or_default();
CellValue::String(s)
}
(CellTypeTag::FormulaString, Some(v)) => CellValue::String(v.to_string()),
(CellTypeTag::None | CellTypeTag::Number, Some(v)) => match v.parse::<f64>() {
Ok(n) => {
if promote_number_to_date {
CellValue::Date(n)
} else {
CellValue::Number(n)
}
}
Err(_) => CellValue::Empty,
},
_ => CellValue::Empty,
}
}
pub fn resolve_cell_value(
cell: &sheetkit_xml::worksheet::Cell,
sst: &SharedStringTable,
style_is_date: &[bool],
) -> CellValue {
let promote_number_to_date = cell
.s
.and_then(|idx| style_is_date.get(idx as usize).copied())
.unwrap_or(false);
parse_cell_type_value(
cell.t,
cell.v.as_deref(),
cell.f.as_deref(),
cell.is.as_deref(),
sst,
promote_number_to_date,
)
}
pub fn insert_rows(ws: &mut WorksheetXml, start_row: u32, count: u32) -> Result<()> {
if start_row == 0 {
return Err(Error::InvalidRowNumber(0));
}
if count == 0 {
return Ok(());
}
let max_existing = ws.sheet_data.rows.iter().map(|r| r.r).max().unwrap_or(0);
let furthest = max_existing.max(start_row);
if furthest.checked_add(count).is_none_or(|v| v > MAX_ROWS) {
return Err(Error::InvalidRowNumber(furthest + count));
}
for row in ws.sheet_data.rows.iter_mut().rev() {
if row.r >= start_row {
let new_row_num = row.r + count;
shift_row_cells(row, new_row_num)?;
row.r = new_row_num;
}
}
Ok(())
}
pub fn remove_row(ws: &mut WorksheetXml, row: u32) -> Result<()> {
if row == 0 {
return Err(Error::InvalidRowNumber(0));
}
if let Ok(idx) = ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
ws.sheet_data.rows.remove(idx);
}
for r in ws.sheet_data.rows.iter_mut() {
if r.r > row {
let new_row_num = r.r - 1;
shift_row_cells(r, new_row_num)?;
r.r = new_row_num;
}
}
Ok(())
}
pub fn duplicate_row(ws: &mut WorksheetXml, row: u32) -> Result<()> {
duplicate_row_to(ws, row, row + 1)
}
pub fn duplicate_row_to(ws: &mut WorksheetXml, row: u32, target: u32) -> Result<()> {
if row == 0 {
return Err(Error::InvalidRowNumber(0));
}
if target == 0 {
return Err(Error::InvalidRowNumber(0));
}
if target > MAX_ROWS {
return Err(Error::InvalidRowNumber(target));
}
let source = ws
.sheet_data
.rows
.binary_search_by_key(&row, |r| r.r)
.ok()
.map(|idx| ws.sheet_data.rows[idx].clone())
.ok_or(Error::InvalidRowNumber(row))?;
insert_rows(ws, target, 1)?;
let mut new_row = source;
shift_row_cells(&mut new_row, target)?;
new_row.r = target;
match ws.sheet_data.rows.binary_search_by_key(&target, |r| r.r) {
Ok(idx) => ws.sheet_data.rows[idx] = new_row,
Err(pos) => ws.sheet_data.rows.insert(pos, new_row),
}
Ok(())
}
pub fn set_row_height(ws: &mut WorksheetXml, row: u32, height: f64) -> Result<()> {
if row == 0 || row > MAX_ROWS {
return Err(Error::InvalidRowNumber(row));
}
if !(0.0..=MAX_ROW_HEIGHT).contains(&height) {
return Err(Error::RowHeightExceeded {
height,
max: MAX_ROW_HEIGHT,
});
}
let r = find_or_create_row(ws, row);
r.ht = Some(height);
r.custom_height = Some(true);
Ok(())
}
pub fn get_row_height(ws: &WorksheetXml, row: u32) -> Option<f64> {
ws.sheet_data
.rows
.binary_search_by_key(&row, |r| r.r)
.ok()
.and_then(|idx| ws.sheet_data.rows[idx].ht)
}
pub fn set_row_visible(ws: &mut WorksheetXml, row: u32, visible: bool) -> Result<()> {
if row == 0 || row > MAX_ROWS {
return Err(Error::InvalidRowNumber(row));
}
let r = find_or_create_row(ws, row);
r.hidden = if visible { None } else { Some(true) };
Ok(())
}
pub fn get_row_visible(ws: &WorksheetXml, row: u32) -> bool {
ws.sheet_data
.rows
.binary_search_by_key(&row, |r| r.r)
.ok()
.and_then(|idx| ws.sheet_data.rows[idx].hidden)
.map(|h| !h)
.unwrap_or(true)
}
pub fn get_row_outline_level(ws: &WorksheetXml, row: u32) -> u8 {
ws.sheet_data
.rows
.binary_search_by_key(&row, |r| r.r)
.ok()
.and_then(|idx| ws.sheet_data.rows[idx].outline_level)
.unwrap_or(0)
}
pub fn set_row_outline_level(ws: &mut WorksheetXml, row: u32, level: u8) -> Result<()> {
if row == 0 || row > MAX_ROWS {
return Err(Error::InvalidRowNumber(row));
}
if level > 7 {
return Err(Error::OutlineLevelExceeded { level, max: 7 });
}
let r = find_or_create_row(ws, row);
r.outline_level = if level == 0 { None } else { Some(level) };
Ok(())
}
pub fn set_row_style(ws: &mut WorksheetXml, row: u32, style_id: u32) -> Result<()> {
if row == 0 || row > MAX_ROWS {
return Err(Error::InvalidRowNumber(row));
}
let r = find_or_create_row(ws, row);
r.s = Some(style_id);
r.custom_format = if style_id == 0 { None } else { Some(true) };
for cell in r.cells.iter_mut() {
cell.s = Some(style_id);
}
Ok(())
}
pub fn get_row_style(ws: &WorksheetXml, row: u32) -> u32 {
ws.sheet_data
.rows
.binary_search_by_key(&row, |r| r.r)
.ok()
.and_then(|idx| ws.sheet_data.rows[idx].s)
.unwrap_or(0)
}
fn shift_row_cells(row: &mut Row, new_row_num: u32) -> Result<()> {
for cell in row.cells.iter_mut() {
let (col, _) = cell_name_to_coordinates(cell.r.as_str())?;
cell.r = coordinates_to_cell_name(col, new_row_num)?.into();
cell.col = col;
}
Ok(())
}
fn find_or_create_row(ws: &mut WorksheetXml, row: u32) -> &mut Row {
match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
Ok(idx) => &mut ws.sheet_data.rows[idx],
Err(pos) => {
ws.sheet_data.rows.insert(
pos,
Row {
r: row,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![],
},
);
&mut ws.sheet_data.rows[pos]
}
}
}
#[cfg(test)]
#[allow(clippy::field_reassign_with_default)]
mod tests {
use super::*;
use sheetkit_xml::worksheet::{Cell, CellTypeTag, SheetData};
fn sample_ws() -> WorksheetXml {
let mut ws = WorksheetXml::default();
ws.sheet_data = SheetData {
rows: vec![
Row {
r: 1,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![
Cell {
r: "A1".into(),
col: 1,
s: None,
t: CellTypeTag::None,
v: Some("10".to_string()),
f: None,
is: None,
},
Cell {
r: "B1".into(),
col: 2,
s: None,
t: CellTypeTag::None,
v: Some("20".to_string()),
f: None,
is: None,
},
],
},
Row {
r: 2,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![Cell {
r: "A2".into(),
col: 1,
s: None,
t: CellTypeTag::None,
v: Some("30".to_string()),
f: None,
is: None,
}],
},
Row {
r: 5,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![Cell {
r: "C5".into(),
col: 3,
s: None,
t: CellTypeTag::None,
v: Some("50".to_string()),
f: None,
is: None,
}],
},
],
};
ws
}
#[test]
fn test_insert_rows_shifts_cells_down() {
let mut ws = sample_ws();
insert_rows(&mut ws, 2, 3).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 1);
assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
assert_eq!(ws.sheet_data.rows[1].r, 5);
assert_eq!(ws.sheet_data.rows[1].cells[0].r, "A5");
assert_eq!(ws.sheet_data.rows[2].r, 8);
assert_eq!(ws.sheet_data.rows[2].cells[0].r, "C8");
}
#[test]
fn test_insert_rows_at_row_1() {
let mut ws = sample_ws();
insert_rows(&mut ws, 1, 2).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 3);
assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A3");
assert_eq!(ws.sheet_data.rows[1].r, 4);
assert_eq!(ws.sheet_data.rows[2].r, 7);
}
#[test]
fn test_insert_rows_count_zero_is_noop() {
let mut ws = sample_ws();
insert_rows(&mut ws, 1, 0).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 1);
assert_eq!(ws.sheet_data.rows[1].r, 2);
assert_eq!(ws.sheet_data.rows[2].r, 5);
}
#[test]
fn test_insert_rows_row_zero_returns_error() {
let mut ws = sample_ws();
let result = insert_rows(&mut ws, 0, 1);
assert!(result.is_err());
}
#[test]
fn test_insert_rows_beyond_max_returns_error() {
let mut ws = WorksheetXml::default();
ws.sheet_data.rows.push(Row {
r: MAX_ROWS,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![],
});
let result = insert_rows(&mut ws, 1, 1);
assert!(result.is_err());
}
#[test]
fn test_insert_rows_on_empty_sheet() {
let mut ws = WorksheetXml::default();
insert_rows(&mut ws, 1, 5).unwrap();
assert!(ws.sheet_data.rows.is_empty());
}
#[test]
fn test_remove_row_shifts_up() {
let mut ws = sample_ws();
remove_row(&mut ws, 2).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 1);
assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
assert_eq!(ws.sheet_data.rows.len(), 2);
assert_eq!(ws.sheet_data.rows[1].r, 4);
assert_eq!(ws.sheet_data.rows[1].cells[0].r, "C4");
}
#[test]
fn test_remove_first_row() {
let mut ws = sample_ws();
remove_row(&mut ws, 1).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 1);
assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
assert_eq!(ws.sheet_data.rows[1].r, 4);
}
#[test]
fn test_remove_nonexistent_row_still_shifts() {
let mut ws = sample_ws();
remove_row(&mut ws, 3).unwrap();
assert_eq!(ws.sheet_data.rows.len(), 3); assert_eq!(ws.sheet_data.rows[2].r, 4); }
#[test]
fn test_remove_row_zero_returns_error() {
let mut ws = sample_ws();
let result = remove_row(&mut ws, 0);
assert!(result.is_err());
}
#[test]
fn test_duplicate_row_inserts_copy_below() {
let mut ws = sample_ws();
duplicate_row(&mut ws, 1).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 1);
assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
assert_eq!(ws.sheet_data.rows[0].cells[0].v, Some("10".to_string()));
assert_eq!(ws.sheet_data.rows[1].r, 2);
assert_eq!(ws.sheet_data.rows[1].cells[0].r, "A2");
assert_eq!(ws.sheet_data.rows[1].cells[0].v, Some("10".to_string()));
assert_eq!(ws.sheet_data.rows[1].cells.len(), 2);
assert_eq!(ws.sheet_data.rows[2].r, 3);
assert_eq!(ws.sheet_data.rows[2].cells[0].r, "A3");
}
#[test]
fn test_duplicate_row_to_specific_target() {
let mut ws = sample_ws();
duplicate_row_to(&mut ws, 1, 5).unwrap();
assert_eq!(ws.sheet_data.rows[0].r, 1);
let row5 = ws.sheet_data.rows.iter().find(|r| r.r == 5).unwrap();
assert_eq!(row5.cells[0].r, "A5");
assert_eq!(row5.cells[0].v, Some("10".to_string()));
let row6 = ws.sheet_data.rows.iter().find(|r| r.r == 6).unwrap();
assert_eq!(row6.cells[0].r, "C6");
}
#[test]
fn test_duplicate_nonexistent_row_returns_error() {
let mut ws = sample_ws();
let result = duplicate_row(&mut ws, 99);
assert!(result.is_err());
}
#[test]
fn test_set_and_get_row_height() {
let mut ws = sample_ws();
set_row_height(&mut ws, 1, 25.5).unwrap();
assert_eq!(get_row_height(&ws, 1), Some(25.5));
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.custom_height, Some(true));
}
#[test]
fn test_set_row_height_creates_row_if_missing() {
let mut ws = WorksheetXml::default();
set_row_height(&mut ws, 10, 30.0).unwrap();
assert_eq!(get_row_height(&ws, 10), Some(30.0));
assert_eq!(ws.sheet_data.rows.len(), 1);
assert_eq!(ws.sheet_data.rows[0].r, 10);
}
#[test]
fn test_set_row_height_zero_is_valid() {
let mut ws = WorksheetXml::default();
set_row_height(&mut ws, 1, 0.0).unwrap();
assert_eq!(get_row_height(&ws, 1), Some(0.0));
}
#[test]
fn test_set_row_height_max_is_valid() {
let mut ws = WorksheetXml::default();
set_row_height(&mut ws, 1, 409.0).unwrap();
assert_eq!(get_row_height(&ws, 1), Some(409.0));
}
#[test]
fn test_set_row_height_exceeds_max_returns_error() {
let mut ws = WorksheetXml::default();
let result = set_row_height(&mut ws, 1, 410.0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
Error::RowHeightExceeded { .. }
));
}
#[test]
fn test_set_row_height_negative_returns_error() {
let mut ws = WorksheetXml::default();
let result = set_row_height(&mut ws, 1, -1.0);
assert!(result.is_err());
}
#[test]
fn test_set_row_height_row_zero_returns_error() {
let mut ws = WorksheetXml::default();
let result = set_row_height(&mut ws, 0, 15.0);
assert!(result.is_err());
}
#[test]
fn test_get_row_height_nonexistent_returns_none() {
let ws = WorksheetXml::default();
assert_eq!(get_row_height(&ws, 99), None);
}
#[test]
fn test_set_row_hidden() {
let mut ws = sample_ws();
set_row_visible(&mut ws, 1, false).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.hidden, Some(true));
}
#[test]
fn test_set_row_visible_clears_hidden() {
let mut ws = sample_ws();
set_row_visible(&mut ws, 1, false).unwrap();
set_row_visible(&mut ws, 1, true).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.hidden, None);
}
#[test]
fn test_set_row_visible_creates_row_if_missing() {
let mut ws = WorksheetXml::default();
set_row_visible(&mut ws, 3, false).unwrap();
assert_eq!(ws.sheet_data.rows.len(), 1);
assert_eq!(ws.sheet_data.rows[0].r, 3);
assert_eq!(ws.sheet_data.rows[0].hidden, Some(true));
}
#[test]
fn test_set_row_visible_row_zero_returns_error() {
let mut ws = WorksheetXml::default();
let result = set_row_visible(&mut ws, 0, true);
assert!(result.is_err());
}
#[test]
fn test_set_row_outline_level() {
let mut ws = sample_ws();
set_row_outline_level(&mut ws, 1, 3).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.outline_level, Some(3));
}
#[test]
fn test_set_row_outline_level_zero_clears() {
let mut ws = sample_ws();
set_row_outline_level(&mut ws, 1, 3).unwrap();
set_row_outline_level(&mut ws, 1, 0).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.outline_level, None);
}
#[test]
fn test_set_row_outline_level_exceeds_max_returns_error() {
let mut ws = sample_ws();
let result = set_row_outline_level(&mut ws, 1, 8);
assert!(result.is_err());
}
#[test]
fn test_set_row_outline_level_row_zero_returns_error() {
let mut ws = WorksheetXml::default();
let result = set_row_outline_level(&mut ws, 0, 1);
assert!(result.is_err());
}
#[test]
fn test_get_row_visible_default_is_true() {
let ws = sample_ws();
assert!(get_row_visible(&ws, 1));
}
#[test]
fn test_get_row_visible_nonexistent_row_is_true() {
let ws = WorksheetXml::default();
assert!(get_row_visible(&ws, 99));
}
#[test]
fn test_get_row_visible_after_hide() {
let mut ws = sample_ws();
set_row_visible(&mut ws, 1, false).unwrap();
assert!(!get_row_visible(&ws, 1));
}
#[test]
fn test_get_row_visible_after_hide_then_show() {
let mut ws = sample_ws();
set_row_visible(&mut ws, 1, false).unwrap();
set_row_visible(&mut ws, 1, true).unwrap();
assert!(get_row_visible(&ws, 1));
}
#[test]
fn test_get_row_outline_level_default_is_zero() {
let ws = sample_ws();
assert_eq!(get_row_outline_level(&ws, 1), 0);
}
#[test]
fn test_get_row_outline_level_nonexistent_row() {
let ws = WorksheetXml::default();
assert_eq!(get_row_outline_level(&ws, 99), 0);
}
#[test]
fn test_get_row_outline_level_after_set() {
let mut ws = sample_ws();
set_row_outline_level(&mut ws, 1, 5).unwrap();
assert_eq!(get_row_outline_level(&ws, 1), 5);
}
#[test]
fn test_get_row_outline_level_after_clear() {
let mut ws = sample_ws();
set_row_outline_level(&mut ws, 1, 3).unwrap();
set_row_outline_level(&mut ws, 1, 0).unwrap();
assert_eq!(get_row_outline_level(&ws, 1), 0);
}
#[test]
fn test_get_rows_empty_sheet() {
let ws = WorksheetXml::default();
let sst = SharedStringTable::new();
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert!(rows.is_empty());
}
#[test]
fn test_get_rows_returns_numeric_values() {
let ws = sample_ws();
let sst = SharedStringTable::new();
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].0, 1);
assert_eq!(rows[0].1.len(), 2);
assert_eq!(rows[0].1[0].0, 1);
assert_eq!(rows[0].1[0].1, CellValue::Number(10.0));
assert_eq!(rows[0].1[1].0, 2);
assert_eq!(rows[0].1[1].1, CellValue::Number(20.0));
assert_eq!(rows[1].0, 2);
assert_eq!(rows[1].1.len(), 1);
assert_eq!(rows[1].1[0].0, 1);
assert_eq!(rows[1].1[0].1, CellValue::Number(30.0));
assert_eq!(rows[2].0, 5);
assert_eq!(rows[2].1.len(), 1);
assert_eq!(rows[2].1[0].0, 3);
assert_eq!(rows[2].1[0].1, CellValue::Number(50.0));
}
#[test]
fn test_get_rows_shared_strings() {
let mut sst = SharedStringTable::new();
sst.add("hello");
sst.add("world");
let mut ws = WorksheetXml::default();
ws.sheet_data = SheetData {
rows: vec![Row {
r: 1,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![
Cell {
r: "A1".into(),
col: 1,
s: None,
t: CellTypeTag::SharedString,
v: Some("0".to_string()),
f: None,
is: None,
},
Cell {
r: "B1".into(),
col: 2,
s: None,
t: CellTypeTag::SharedString,
v: Some("1".to_string()),
f: None,
is: None,
},
],
}],
};
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
assert_eq!(rows[0].1[1].1, CellValue::String("world".to_string()));
}
#[test]
fn test_get_rows_mixed_types() {
let mut sst = SharedStringTable::new();
sst.add("text");
let mut ws = WorksheetXml::default();
ws.sheet_data = SheetData {
rows: vec![Row {
r: 1,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![
Cell {
r: "A1".into(),
col: 1,
s: None,
t: CellTypeTag::SharedString,
v: Some("0".to_string()),
f: None,
is: None,
},
Cell {
r: "B1".into(),
col: 2,
s: None,
t: CellTypeTag::None,
v: Some("42.5".to_string()),
f: None,
is: None,
},
Cell {
r: "C1".into(),
col: 3,
s: None,
t: CellTypeTag::Boolean,
v: Some("1".to_string()),
f: None,
is: None,
},
Cell {
r: "D1".into(),
col: 4,
s: None,
t: CellTypeTag::Error,
v: Some("#DIV/0!".to_string()),
f: None,
is: None,
},
],
}],
};
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].1[0].1, CellValue::String("text".to_string()));
assert_eq!(rows[0].1[1].1, CellValue::Number(42.5));
assert_eq!(rows[0].1[2].1, CellValue::Bool(true));
assert_eq!(rows[0].1[3].1, CellValue::Error("#DIV/0!".to_string()));
}
#[test]
fn test_get_rows_skips_rows_with_no_cells() {
let mut ws = WorksheetXml::default();
ws.sheet_data = SheetData {
rows: vec![
Row {
r: 1,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![Cell {
r: "A1".into(),
col: 1,
s: None,
t: CellTypeTag::None,
v: Some("1".to_string()),
f: None,
is: None,
}],
},
Row {
r: 2,
spans: None,
s: None,
custom_format: None,
ht: Some(30.0),
hidden: None,
custom_height: Some(true),
outline_level: None,
cells: vec![],
},
Row {
r: 3,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![Cell {
r: "A3".into(),
col: 1,
s: None,
t: CellTypeTag::None,
v: Some("3".to_string()),
f: None,
is: None,
}],
},
],
};
let sst = SharedStringTable::new();
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].0, 1);
assert_eq!(rows[1].0, 3);
}
#[test]
fn test_get_rows_with_formula() {
let mut ws = WorksheetXml::default();
ws.sheet_data = SheetData {
rows: vec![Row {
r: 1,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![Cell {
r: "A1".into(),
col: 1,
s: None,
t: CellTypeTag::None,
v: Some("42".to_string()),
f: Some(Box::new(sheetkit_xml::worksheet::CellFormula {
t: None,
reference: None,
si: None,
value: Some("B1+C1".to_string()),
})),
is: None,
}],
}],
};
let sst = SharedStringTable::new();
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert_eq!(rows.len(), 1);
match &rows[0].1[0].1 {
CellValue::Formula { expr, result } => {
assert_eq!(expr, "B1+C1");
assert_eq!(*result, Some(Box::new(CellValue::Number(42.0))));
}
_ => panic!("expected Formula"),
}
}
#[test]
fn test_get_rows_with_inline_string() {
let mut ws = WorksheetXml::default();
ws.sheet_data = SheetData {
rows: vec![Row {
r: 1,
spans: None,
s: None,
custom_format: None,
ht: None,
hidden: None,
custom_height: None,
outline_level: None,
cells: vec![Cell {
r: "A1".into(),
col: 1,
s: None,
t: CellTypeTag::InlineString,
v: None,
f: None,
is: Some(Box::new(sheetkit_xml::worksheet::InlineString {
t: Some("inline text".to_string()),
})),
}],
}],
};
let sst = SharedStringTable::new();
let rows = get_rows(&ws, &sst, &[]).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].1[0].1, CellValue::String("inline text".to_string()));
}
#[test]
fn test_get_row_style_default_is_zero() {
let ws = WorksheetXml::default();
assert_eq!(get_row_style(&ws, 1), 0);
}
#[test]
fn test_get_row_style_nonexistent_row_is_zero() {
let ws = sample_ws();
assert_eq!(get_row_style(&ws, 99), 0);
}
#[test]
fn test_set_row_style_applies_style() {
let mut ws = sample_ws();
set_row_style(&mut ws, 1, 5).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.s, Some(5));
assert_eq!(row.custom_format, Some(true));
}
#[test]
fn test_set_row_style_applies_to_existing_cells() {
let mut ws = sample_ws();
set_row_style(&mut ws, 1, 3).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
for cell in &row.cells {
assert_eq!(cell.s, Some(3));
}
}
#[test]
fn test_get_row_style_after_set() {
let mut ws = sample_ws();
set_row_style(&mut ws, 2, 7).unwrap();
assert_eq!(get_row_style(&ws, 2), 7);
}
#[test]
fn test_set_row_style_creates_row_if_missing() {
let mut ws = WorksheetXml::default();
set_row_style(&mut ws, 5, 2).unwrap();
assert_eq!(ws.sheet_data.rows.len(), 1);
assert_eq!(ws.sheet_data.rows[0].r, 5);
assert_eq!(ws.sheet_data.rows[0].s, Some(2));
}
#[test]
fn test_set_row_style_zero_clears_custom_format() {
let mut ws = sample_ws();
set_row_style(&mut ws, 1, 5).unwrap();
set_row_style(&mut ws, 1, 0).unwrap();
let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
assert_eq!(row.s, Some(0));
assert_eq!(row.custom_format, None);
}
#[test]
fn test_set_row_style_row_zero_returns_error() {
let mut ws = WorksheetXml::default();
let result = set_row_style(&mut ws, 0, 1);
assert!(result.is_err());
}
}