1use sheetkit_xml::worksheet::{CellTypeTag, Row, WorksheetXml};
8
9use crate::cell::CellValue;
10use crate::error::{Error, Result};
11use crate::sst::SharedStringTable;
12use crate::utils::cell_ref::{cell_name_to_coordinates, coordinates_to_cell_name};
13use crate::utils::constants::{MAX_ROWS, MAX_ROW_HEIGHT};
14
15#[allow(clippy::type_complexity)]
21pub fn get_rows(
22 ws: &WorksheetXml,
23 sst: &SharedStringTable,
24) -> Result<Vec<(u32, Vec<(u32, CellValue)>)>> {
25 let mut result = Vec::new();
26
27 for row in &ws.sheet_data.rows {
28 if row.cells.is_empty() {
29 continue;
30 }
31
32 let mut cells = Vec::new();
33 for cell in &row.cells {
34 let col_num = if cell.col > 0 {
35 cell.col
36 } else {
37 cell_name_to_coordinates(cell.r.as_str())?.0
38 };
39 let value = resolve_cell_value(cell, sst);
40 cells.push((col_num, value));
41 }
42
43 result.push((row.r, cells));
44 }
45
46 Ok(result)
47}
48
49pub fn resolve_sst_value(sst: &SharedStringTable, index: &str) -> CellValue {
52 let idx: usize = match index.parse() {
53 Ok(i) => i,
54 Err(_) => return CellValue::Empty,
55 };
56 let s = sst.get(idx).unwrap_or("").to_string();
57 CellValue::String(s)
58}
59
60pub fn parse_cell_type_value(
66 cell_type: CellTypeTag,
67 value: Option<&str>,
68 formula: Option<&sheetkit_xml::worksheet::CellFormula>,
69 inline_str: Option<&sheetkit_xml::worksheet::InlineString>,
70 sst: &SharedStringTable,
71) -> CellValue {
72 if let Some(f) = formula {
73 let expr = f.value.clone().unwrap_or_default();
74 let cached = match (cell_type, value) {
75 (CellTypeTag::Boolean, Some(v)) => Some(Box::new(CellValue::Bool(v == "1"))),
76 (CellTypeTag::Error, Some(v)) => Some(Box::new(CellValue::Error(v.to_string()))),
77 (_, Some(v)) => v
78 .parse::<f64>()
79 .ok()
80 .map(|n| Box::new(CellValue::Number(n))),
81 _ => None,
82 };
83 return CellValue::Formula {
84 expr,
85 result: cached,
86 };
87 }
88
89 match (cell_type, value) {
90 (CellTypeTag::SharedString, Some(v)) => resolve_sst_value(sst, v),
91 (CellTypeTag::Boolean, Some(v)) => CellValue::Bool(v == "1"),
92 (CellTypeTag::Error, Some(v)) => CellValue::Error(v.to_string()),
93 (CellTypeTag::InlineString, _) => {
94 let s = inline_str.and_then(|is| is.t.clone()).unwrap_or_default();
95 CellValue::String(s)
96 }
97 (CellTypeTag::FormulaString, Some(v)) => CellValue::String(v.to_string()),
98 (CellTypeTag::None | CellTypeTag::Number, Some(v)) => match v.parse::<f64>() {
99 Ok(n) => CellValue::Number(n),
100 Err(_) => CellValue::Empty,
101 },
102 _ => CellValue::Empty,
103 }
104}
105
106pub fn resolve_cell_value(
109 cell: &sheetkit_xml::worksheet::Cell,
110 sst: &SharedStringTable,
111) -> CellValue {
112 parse_cell_type_value(
113 cell.t,
114 cell.v.as_deref(),
115 cell.f.as_deref(),
116 cell.is.as_deref(),
117 sst,
118 )
119}
120
121pub fn insert_rows(ws: &mut WorksheetXml, start_row: u32, count: u32) -> Result<()> {
127 if start_row == 0 {
128 return Err(Error::InvalidRowNumber(0));
129 }
130 if count == 0 {
131 return Ok(());
132 }
133 let max_existing = ws.sheet_data.rows.iter().map(|r| r.r).max().unwrap_or(0);
135 let furthest = max_existing.max(start_row);
136 if furthest.checked_add(count).is_none_or(|v| v > MAX_ROWS) {
137 return Err(Error::InvalidRowNumber(furthest + count));
138 }
139
140 for row in ws.sheet_data.rows.iter_mut().rev() {
143 if row.r >= start_row {
144 let new_row_num = row.r + count;
145 shift_row_cells(row, new_row_num)?;
146 row.r = new_row_num;
147 }
148 }
149
150 Ok(())
151}
152
153pub fn remove_row(ws: &mut WorksheetXml, row: u32) -> Result<()> {
155 if row == 0 {
156 return Err(Error::InvalidRowNumber(0));
157 }
158
159 if let Ok(idx) = ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
161 ws.sheet_data.rows.remove(idx);
162 }
163
164 for r in ws.sheet_data.rows.iter_mut() {
166 if r.r > row {
167 let new_row_num = r.r - 1;
168 shift_row_cells(r, new_row_num)?;
169 r.r = new_row_num;
170 }
171 }
172
173 Ok(())
174}
175
176pub fn duplicate_row(ws: &mut WorksheetXml, row: u32) -> Result<()> {
178 duplicate_row_to(ws, row, row + 1)
179}
180
181pub fn duplicate_row_to(ws: &mut WorksheetXml, row: u32, target: u32) -> Result<()> {
184 if row == 0 {
185 return Err(Error::InvalidRowNumber(0));
186 }
187 if target == 0 {
188 return Err(Error::InvalidRowNumber(0));
189 }
190 if target > MAX_ROWS {
191 return Err(Error::InvalidRowNumber(target));
192 }
193
194 let source = ws
196 .sheet_data
197 .rows
198 .binary_search_by_key(&row, |r| r.r)
199 .ok()
200 .map(|idx| ws.sheet_data.rows[idx].clone())
201 .ok_or(Error::InvalidRowNumber(row))?;
202
203 insert_rows(ws, target, 1)?;
205
206 let mut new_row = source;
208 shift_row_cells(&mut new_row, target)?;
209 new_row.r = target;
210
211 match ws.sheet_data.rows.binary_search_by_key(&target, |r| r.r) {
213 Ok(idx) => ws.sheet_data.rows[idx] = new_row,
214 Err(pos) => ws.sheet_data.rows.insert(pos, new_row),
215 }
216
217 Ok(())
218}
219
220pub fn set_row_height(ws: &mut WorksheetXml, row: u32, height: f64) -> Result<()> {
224 if row == 0 || row > MAX_ROWS {
225 return Err(Error::InvalidRowNumber(row));
226 }
227 if !(0.0..=MAX_ROW_HEIGHT).contains(&height) {
228 return Err(Error::RowHeightExceeded {
229 height,
230 max: MAX_ROW_HEIGHT,
231 });
232 }
233
234 let r = find_or_create_row(ws, row);
235 r.ht = Some(height);
236 r.custom_height = Some(true);
237 Ok(())
238}
239
240pub fn get_row_height(ws: &WorksheetXml, row: u32) -> Option<f64> {
243 ws.sheet_data
244 .rows
245 .binary_search_by_key(&row, |r| r.r)
246 .ok()
247 .and_then(|idx| ws.sheet_data.rows[idx].ht)
248}
249
250pub fn set_row_visible(ws: &mut WorksheetXml, row: u32, visible: bool) -> Result<()> {
252 if row == 0 || row > MAX_ROWS {
253 return Err(Error::InvalidRowNumber(row));
254 }
255
256 let r = find_or_create_row(ws, row);
257 r.hidden = if visible { None } else { Some(true) };
258 Ok(())
259}
260
261pub fn get_row_visible(ws: &WorksheetXml, row: u32) -> bool {
266 ws.sheet_data
267 .rows
268 .binary_search_by_key(&row, |r| r.r)
269 .ok()
270 .and_then(|idx| ws.sheet_data.rows[idx].hidden)
271 .map(|h| !h)
272 .unwrap_or(true)
273}
274
275pub fn get_row_outline_level(ws: &WorksheetXml, row: u32) -> u8 {
277 ws.sheet_data
278 .rows
279 .binary_search_by_key(&row, |r| r.r)
280 .ok()
281 .and_then(|idx| ws.sheet_data.rows[idx].outline_level)
282 .unwrap_or(0)
283}
284
285pub fn set_row_outline_level(ws: &mut WorksheetXml, row: u32, level: u8) -> Result<()> {
289 if row == 0 || row > MAX_ROWS {
290 return Err(Error::InvalidRowNumber(row));
291 }
292 if level > 7 {
293 return Err(Error::OutlineLevelExceeded { level, max: 7 });
294 }
295
296 let r = find_or_create_row(ws, row);
297 r.outline_level = if level == 0 { None } else { Some(level) };
298 Ok(())
299}
300
301pub fn set_row_style(ws: &mut WorksheetXml, row: u32, style_id: u32) -> Result<()> {
306 if row == 0 || row > MAX_ROWS {
307 return Err(Error::InvalidRowNumber(row));
308 }
309
310 let r = find_or_create_row(ws, row);
311 r.s = Some(style_id);
312 r.custom_format = if style_id == 0 { None } else { Some(true) };
313
314 for cell in r.cells.iter_mut() {
316 cell.s = Some(style_id);
317 }
318 Ok(())
319}
320
321pub fn get_row_style(ws: &WorksheetXml, row: u32) -> u32 {
324 ws.sheet_data
325 .rows
326 .binary_search_by_key(&row, |r| r.r)
327 .ok()
328 .and_then(|idx| ws.sheet_data.rows[idx].s)
329 .unwrap_or(0)
330}
331
332fn shift_row_cells(row: &mut Row, new_row_num: u32) -> Result<()> {
334 for cell in row.cells.iter_mut() {
335 let (col, _) = cell_name_to_coordinates(cell.r.as_str())?;
336 cell.r = coordinates_to_cell_name(col, new_row_num)?.into();
337 cell.col = col;
338 }
339 Ok(())
340}
341
342fn find_or_create_row(ws: &mut WorksheetXml, row: u32) -> &mut Row {
345 match ws.sheet_data.rows.binary_search_by_key(&row, |r| r.r) {
346 Ok(idx) => &mut ws.sheet_data.rows[idx],
347 Err(pos) => {
348 ws.sheet_data.rows.insert(
349 pos,
350 Row {
351 r: row,
352 spans: None,
353 s: None,
354 custom_format: None,
355 ht: None,
356 hidden: None,
357 custom_height: None,
358 outline_level: None,
359 cells: vec![],
360 },
361 );
362 &mut ws.sheet_data.rows[pos]
363 }
364 }
365}
366
367#[cfg(test)]
368#[allow(clippy::field_reassign_with_default)]
369mod tests {
370 use super::*;
371 use sheetkit_xml::worksheet::{Cell, CellTypeTag, SheetData};
372
373 fn sample_ws() -> WorksheetXml {
375 let mut ws = WorksheetXml::default();
376 ws.sheet_data = SheetData {
377 rows: vec![
378 Row {
379 r: 1,
380 spans: None,
381 s: None,
382 custom_format: None,
383 ht: None,
384 hidden: None,
385 custom_height: None,
386 outline_level: None,
387 cells: vec![
388 Cell {
389 r: "A1".into(),
390 col: 1,
391 s: None,
392 t: CellTypeTag::None,
393 v: Some("10".to_string()),
394 f: None,
395 is: None,
396 },
397 Cell {
398 r: "B1".into(),
399 col: 2,
400 s: None,
401 t: CellTypeTag::None,
402 v: Some("20".to_string()),
403 f: None,
404 is: None,
405 },
406 ],
407 },
408 Row {
409 r: 2,
410 spans: None,
411 s: None,
412 custom_format: None,
413 ht: None,
414 hidden: None,
415 custom_height: None,
416 outline_level: None,
417 cells: vec![Cell {
418 r: "A2".into(),
419 col: 1,
420 s: None,
421 t: CellTypeTag::None,
422 v: Some("30".to_string()),
423 f: None,
424 is: None,
425 }],
426 },
427 Row {
428 r: 5,
429 spans: None,
430 s: None,
431 custom_format: None,
432 ht: None,
433 hidden: None,
434 custom_height: None,
435 outline_level: None,
436 cells: vec![Cell {
437 r: "C5".into(),
438 col: 3,
439 s: None,
440 t: CellTypeTag::None,
441 v: Some("50".to_string()),
442 f: None,
443 is: None,
444 }],
445 },
446 ],
447 };
448 ws
449 }
450
451 #[test]
452 fn test_insert_rows_shifts_cells_down() {
453 let mut ws = sample_ws();
454 insert_rows(&mut ws, 2, 3).unwrap();
455
456 assert_eq!(ws.sheet_data.rows[0].r, 1);
458 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
459
460 assert_eq!(ws.sheet_data.rows[1].r, 5);
462 assert_eq!(ws.sheet_data.rows[1].cells[0].r, "A5");
463
464 assert_eq!(ws.sheet_data.rows[2].r, 8);
466 assert_eq!(ws.sheet_data.rows[2].cells[0].r, "C8");
467 }
468
469 #[test]
470 fn test_insert_rows_at_row_1() {
471 let mut ws = sample_ws();
472 insert_rows(&mut ws, 1, 2).unwrap();
473
474 assert_eq!(ws.sheet_data.rows[0].r, 3);
476 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A3");
477 assert_eq!(ws.sheet_data.rows[1].r, 4);
478 assert_eq!(ws.sheet_data.rows[2].r, 7);
479 }
480
481 #[test]
482 fn test_insert_rows_count_zero_is_noop() {
483 let mut ws = sample_ws();
484 insert_rows(&mut ws, 1, 0).unwrap();
485 assert_eq!(ws.sheet_data.rows[0].r, 1);
486 assert_eq!(ws.sheet_data.rows[1].r, 2);
487 assert_eq!(ws.sheet_data.rows[2].r, 5);
488 }
489
490 #[test]
491 fn test_insert_rows_row_zero_returns_error() {
492 let mut ws = sample_ws();
493 let result = insert_rows(&mut ws, 0, 1);
494 assert!(result.is_err());
495 }
496
497 #[test]
498 fn test_insert_rows_beyond_max_returns_error() {
499 let mut ws = WorksheetXml::default();
500 ws.sheet_data.rows.push(Row {
501 r: MAX_ROWS,
502 spans: None,
503 s: None,
504 custom_format: None,
505 ht: None,
506 hidden: None,
507 custom_height: None,
508 outline_level: None,
509 cells: vec![],
510 });
511 let result = insert_rows(&mut ws, 1, 1);
512 assert!(result.is_err());
513 }
514
515 #[test]
516 fn test_insert_rows_on_empty_sheet() {
517 let mut ws = WorksheetXml::default();
518 insert_rows(&mut ws, 1, 5).unwrap();
519 assert!(ws.sheet_data.rows.is_empty());
520 }
521
522 #[test]
523 fn test_remove_row_shifts_up() {
524 let mut ws = sample_ws();
525 remove_row(&mut ws, 2).unwrap();
526
527 assert_eq!(ws.sheet_data.rows[0].r, 1);
529 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
530
531 assert_eq!(ws.sheet_data.rows.len(), 2);
533 assert_eq!(ws.sheet_data.rows[1].r, 4);
534 assert_eq!(ws.sheet_data.rows[1].cells[0].r, "C4");
535 }
536
537 #[test]
538 fn test_remove_first_row() {
539 let mut ws = sample_ws();
540 remove_row(&mut ws, 1).unwrap();
541
542 assert_eq!(ws.sheet_data.rows[0].r, 1);
544 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
545 assert_eq!(ws.sheet_data.rows[1].r, 4);
546 }
547
548 #[test]
549 fn test_remove_nonexistent_row_still_shifts() {
550 let mut ws = sample_ws();
551 remove_row(&mut ws, 3).unwrap();
553 assert_eq!(ws.sheet_data.rows.len(), 3); assert_eq!(ws.sheet_data.rows[2].r, 4); }
556
557 #[test]
558 fn test_remove_row_zero_returns_error() {
559 let mut ws = sample_ws();
560 let result = remove_row(&mut ws, 0);
561 assert!(result.is_err());
562 }
563
564 #[test]
565 fn test_duplicate_row_inserts_copy_below() {
566 let mut ws = sample_ws();
567 duplicate_row(&mut ws, 1).unwrap();
568
569 assert_eq!(ws.sheet_data.rows[0].r, 1);
571 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
572 assert_eq!(ws.sheet_data.rows[0].cells[0].v, Some("10".to_string()));
573
574 assert_eq!(ws.sheet_data.rows[1].r, 2);
576 assert_eq!(ws.sheet_data.rows[1].cells[0].r, "A2");
577 assert_eq!(ws.sheet_data.rows[1].cells[0].v, Some("10".to_string()));
578 assert_eq!(ws.sheet_data.rows[1].cells.len(), 2);
579
580 assert_eq!(ws.sheet_data.rows[2].r, 3);
582 assert_eq!(ws.sheet_data.rows[2].cells[0].r, "A3");
583 }
584
585 #[test]
586 fn test_duplicate_row_to_specific_target() {
587 let mut ws = sample_ws();
588 duplicate_row_to(&mut ws, 1, 5).unwrap();
589
590 assert_eq!(ws.sheet_data.rows[0].r, 1);
592
593 let row5 = ws.sheet_data.rows.iter().find(|r| r.r == 5).unwrap();
595 assert_eq!(row5.cells[0].r, "A5");
596 assert_eq!(row5.cells[0].v, Some("10".to_string()));
597
598 let row6 = ws.sheet_data.rows.iter().find(|r| r.r == 6).unwrap();
600 assert_eq!(row6.cells[0].r, "C6");
601 }
602
603 #[test]
604 fn test_duplicate_nonexistent_row_returns_error() {
605 let mut ws = sample_ws();
606 let result = duplicate_row(&mut ws, 99);
607 assert!(result.is_err());
608 }
609
610 #[test]
611 fn test_set_and_get_row_height() {
612 let mut ws = sample_ws();
613 set_row_height(&mut ws, 1, 25.5).unwrap();
614
615 assert_eq!(get_row_height(&ws, 1), Some(25.5));
616 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
617 assert_eq!(row.custom_height, Some(true));
618 }
619
620 #[test]
621 fn test_set_row_height_creates_row_if_missing() {
622 let mut ws = WorksheetXml::default();
623 set_row_height(&mut ws, 10, 30.0).unwrap();
624
625 assert_eq!(get_row_height(&ws, 10), Some(30.0));
626 assert_eq!(ws.sheet_data.rows.len(), 1);
627 assert_eq!(ws.sheet_data.rows[0].r, 10);
628 }
629
630 #[test]
631 fn test_set_row_height_zero_is_valid() {
632 let mut ws = WorksheetXml::default();
633 set_row_height(&mut ws, 1, 0.0).unwrap();
634 assert_eq!(get_row_height(&ws, 1), Some(0.0));
635 }
636
637 #[test]
638 fn test_set_row_height_max_is_valid() {
639 let mut ws = WorksheetXml::default();
640 set_row_height(&mut ws, 1, 409.0).unwrap();
641 assert_eq!(get_row_height(&ws, 1), Some(409.0));
642 }
643
644 #[test]
645 fn test_set_row_height_exceeds_max_returns_error() {
646 let mut ws = WorksheetXml::default();
647 let result = set_row_height(&mut ws, 1, 410.0);
648 assert!(result.is_err());
649 assert!(matches!(
650 result.unwrap_err(),
651 Error::RowHeightExceeded { .. }
652 ));
653 }
654
655 #[test]
656 fn test_set_row_height_negative_returns_error() {
657 let mut ws = WorksheetXml::default();
658 let result = set_row_height(&mut ws, 1, -1.0);
659 assert!(result.is_err());
660 }
661
662 #[test]
663 fn test_set_row_height_row_zero_returns_error() {
664 let mut ws = WorksheetXml::default();
665 let result = set_row_height(&mut ws, 0, 15.0);
666 assert!(result.is_err());
667 }
668
669 #[test]
670 fn test_get_row_height_nonexistent_returns_none() {
671 let ws = WorksheetXml::default();
672 assert_eq!(get_row_height(&ws, 99), None);
673 }
674
675 #[test]
676 fn test_set_row_hidden() {
677 let mut ws = sample_ws();
678 set_row_visible(&mut ws, 1, false).unwrap();
679
680 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
681 assert_eq!(row.hidden, Some(true));
682 }
683
684 #[test]
685 fn test_set_row_visible_clears_hidden() {
686 let mut ws = sample_ws();
687 set_row_visible(&mut ws, 1, false).unwrap();
688 set_row_visible(&mut ws, 1, true).unwrap();
689
690 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
691 assert_eq!(row.hidden, None);
692 }
693
694 #[test]
695 fn test_set_row_visible_creates_row_if_missing() {
696 let mut ws = WorksheetXml::default();
697 set_row_visible(&mut ws, 3, false).unwrap();
698 assert_eq!(ws.sheet_data.rows.len(), 1);
699 assert_eq!(ws.sheet_data.rows[0].r, 3);
700 assert_eq!(ws.sheet_data.rows[0].hidden, Some(true));
701 }
702
703 #[test]
704 fn test_set_row_visible_row_zero_returns_error() {
705 let mut ws = WorksheetXml::default();
706 let result = set_row_visible(&mut ws, 0, true);
707 assert!(result.is_err());
708 }
709
710 #[test]
711 fn test_set_row_outline_level() {
712 let mut ws = sample_ws();
713 set_row_outline_level(&mut ws, 1, 3).unwrap();
714
715 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
716 assert_eq!(row.outline_level, Some(3));
717 }
718
719 #[test]
720 fn test_set_row_outline_level_zero_clears() {
721 let mut ws = sample_ws();
722 set_row_outline_level(&mut ws, 1, 3).unwrap();
723 set_row_outline_level(&mut ws, 1, 0).unwrap();
724
725 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
726 assert_eq!(row.outline_level, None);
727 }
728
729 #[test]
730 fn test_set_row_outline_level_exceeds_max_returns_error() {
731 let mut ws = sample_ws();
732 let result = set_row_outline_level(&mut ws, 1, 8);
733 assert!(result.is_err());
734 }
735
736 #[test]
737 fn test_set_row_outline_level_row_zero_returns_error() {
738 let mut ws = WorksheetXml::default();
739 let result = set_row_outline_level(&mut ws, 0, 1);
740 assert!(result.is_err());
741 }
742
743 #[test]
744 fn test_get_row_visible_default_is_true() {
745 let ws = sample_ws();
746 assert!(get_row_visible(&ws, 1));
747 }
748
749 #[test]
750 fn test_get_row_visible_nonexistent_row_is_true() {
751 let ws = WorksheetXml::default();
752 assert!(get_row_visible(&ws, 99));
753 }
754
755 #[test]
756 fn test_get_row_visible_after_hide() {
757 let mut ws = sample_ws();
758 set_row_visible(&mut ws, 1, false).unwrap();
759 assert!(!get_row_visible(&ws, 1));
760 }
761
762 #[test]
763 fn test_get_row_visible_after_hide_then_show() {
764 let mut ws = sample_ws();
765 set_row_visible(&mut ws, 1, false).unwrap();
766 set_row_visible(&mut ws, 1, true).unwrap();
767 assert!(get_row_visible(&ws, 1));
768 }
769
770 #[test]
771 fn test_get_row_outline_level_default_is_zero() {
772 let ws = sample_ws();
773 assert_eq!(get_row_outline_level(&ws, 1), 0);
774 }
775
776 #[test]
777 fn test_get_row_outline_level_nonexistent_row() {
778 let ws = WorksheetXml::default();
779 assert_eq!(get_row_outline_level(&ws, 99), 0);
780 }
781
782 #[test]
783 fn test_get_row_outline_level_after_set() {
784 let mut ws = sample_ws();
785 set_row_outline_level(&mut ws, 1, 5).unwrap();
786 assert_eq!(get_row_outline_level(&ws, 1), 5);
787 }
788
789 #[test]
790 fn test_get_row_outline_level_after_clear() {
791 let mut ws = sample_ws();
792 set_row_outline_level(&mut ws, 1, 3).unwrap();
793 set_row_outline_level(&mut ws, 1, 0).unwrap();
794 assert_eq!(get_row_outline_level(&ws, 1), 0);
795 }
796
797 #[test]
800 fn test_get_rows_empty_sheet() {
801 let ws = WorksheetXml::default();
802 let sst = SharedStringTable::new();
803 let rows = get_rows(&ws, &sst).unwrap();
804 assert!(rows.is_empty());
805 }
806
807 #[test]
808 fn test_get_rows_returns_numeric_values() {
809 let ws = sample_ws();
810 let sst = SharedStringTable::new();
811 let rows = get_rows(&ws, &sst).unwrap();
812
813 assert_eq!(rows.len(), 3);
814
815 assert_eq!(rows[0].0, 1);
817 assert_eq!(rows[0].1.len(), 2);
818 assert_eq!(rows[0].1[0].0, 1);
819 assert_eq!(rows[0].1[0].1, CellValue::Number(10.0));
820 assert_eq!(rows[0].1[1].0, 2);
821 assert_eq!(rows[0].1[1].1, CellValue::Number(20.0));
822
823 assert_eq!(rows[1].0, 2);
825 assert_eq!(rows[1].1.len(), 1);
826 assert_eq!(rows[1].1[0].0, 1);
827 assert_eq!(rows[1].1[0].1, CellValue::Number(30.0));
828
829 assert_eq!(rows[2].0, 5);
831 assert_eq!(rows[2].1.len(), 1);
832 assert_eq!(rows[2].1[0].0, 3);
833 assert_eq!(rows[2].1[0].1, CellValue::Number(50.0));
834 }
835
836 #[test]
837 fn test_get_rows_shared_strings() {
838 let mut sst = SharedStringTable::new();
839 sst.add("hello");
840 sst.add("world");
841
842 let mut ws = WorksheetXml::default();
843 ws.sheet_data = SheetData {
844 rows: vec![Row {
845 r: 1,
846 spans: None,
847 s: None,
848 custom_format: None,
849 ht: None,
850 hidden: None,
851 custom_height: None,
852 outline_level: None,
853 cells: vec![
854 Cell {
855 r: "A1".into(),
856 col: 1,
857 s: None,
858 t: CellTypeTag::SharedString,
859 v: Some("0".to_string()),
860 f: None,
861 is: None,
862 },
863 Cell {
864 r: "B1".into(),
865 col: 2,
866 s: None,
867 t: CellTypeTag::SharedString,
868 v: Some("1".to_string()),
869 f: None,
870 is: None,
871 },
872 ],
873 }],
874 };
875
876 let rows = get_rows(&ws, &sst).unwrap();
877 assert_eq!(rows.len(), 1);
878 assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
879 assert_eq!(rows[0].1[1].1, CellValue::String("world".to_string()));
880 }
881
882 #[test]
883 fn test_get_rows_mixed_types() {
884 let mut sst = SharedStringTable::new();
885 sst.add("text");
886
887 let mut ws = WorksheetXml::default();
888 ws.sheet_data = SheetData {
889 rows: vec![Row {
890 r: 1,
891 spans: None,
892 s: None,
893 custom_format: None,
894 ht: None,
895 hidden: None,
896 custom_height: None,
897 outline_level: None,
898 cells: vec![
899 Cell {
900 r: "A1".into(),
901 col: 1,
902 s: None,
903 t: CellTypeTag::SharedString,
904 v: Some("0".to_string()),
905 f: None,
906 is: None,
907 },
908 Cell {
909 r: "B1".into(),
910 col: 2,
911 s: None,
912 t: CellTypeTag::None,
913 v: Some("42.5".to_string()),
914 f: None,
915 is: None,
916 },
917 Cell {
918 r: "C1".into(),
919 col: 3,
920 s: None,
921 t: CellTypeTag::Boolean,
922 v: Some("1".to_string()),
923 f: None,
924 is: None,
925 },
926 Cell {
927 r: "D1".into(),
928 col: 4,
929 s: None,
930 t: CellTypeTag::Error,
931 v: Some("#DIV/0!".to_string()),
932 f: None,
933 is: None,
934 },
935 ],
936 }],
937 };
938
939 let rows = get_rows(&ws, &sst).unwrap();
940 assert_eq!(rows.len(), 1);
941 assert_eq!(rows[0].1[0].1, CellValue::String("text".to_string()));
942 assert_eq!(rows[0].1[1].1, CellValue::Number(42.5));
943 assert_eq!(rows[0].1[2].1, CellValue::Bool(true));
944 assert_eq!(rows[0].1[3].1, CellValue::Error("#DIV/0!".to_string()));
945 }
946
947 #[test]
948 fn test_get_rows_skips_rows_with_no_cells() {
949 let mut ws = WorksheetXml::default();
950 ws.sheet_data = SheetData {
951 rows: vec![
952 Row {
953 r: 1,
954 spans: None,
955 s: None,
956 custom_format: None,
957 ht: None,
958 hidden: None,
959 custom_height: None,
960 outline_level: None,
961 cells: vec![Cell {
962 r: "A1".into(),
963 col: 1,
964 s: None,
965 t: CellTypeTag::None,
966 v: Some("1".to_string()),
967 f: None,
968 is: None,
969 }],
970 },
971 Row {
973 r: 2,
974 spans: None,
975 s: None,
976 custom_format: None,
977 ht: Some(30.0),
978 hidden: None,
979 custom_height: Some(true),
980 outline_level: None,
981 cells: vec![],
982 },
983 Row {
984 r: 3,
985 spans: None,
986 s: None,
987 custom_format: None,
988 ht: None,
989 hidden: None,
990 custom_height: None,
991 outline_level: None,
992 cells: vec![Cell {
993 r: "A3".into(),
994 col: 1,
995 s: None,
996 t: CellTypeTag::None,
997 v: Some("3".to_string()),
998 f: None,
999 is: None,
1000 }],
1001 },
1002 ],
1003 };
1004
1005 let sst = SharedStringTable::new();
1006 let rows = get_rows(&ws, &sst).unwrap();
1007 assert_eq!(rows.len(), 2);
1009 assert_eq!(rows[0].0, 1);
1010 assert_eq!(rows[1].0, 3);
1011 }
1012
1013 #[test]
1014 fn test_get_rows_with_formula() {
1015 let mut ws = WorksheetXml::default();
1016 ws.sheet_data = SheetData {
1017 rows: vec![Row {
1018 r: 1,
1019 spans: None,
1020 s: None,
1021 custom_format: None,
1022 ht: None,
1023 hidden: None,
1024 custom_height: None,
1025 outline_level: None,
1026 cells: vec![Cell {
1027 r: "A1".into(),
1028 col: 1,
1029 s: None,
1030 t: CellTypeTag::None,
1031 v: Some("42".to_string()),
1032 f: Some(Box::new(sheetkit_xml::worksheet::CellFormula {
1033 t: None,
1034 reference: None,
1035 si: None,
1036 value: Some("B1+C1".to_string()),
1037 })),
1038 is: None,
1039 }],
1040 }],
1041 };
1042
1043 let sst = SharedStringTable::new();
1044 let rows = get_rows(&ws, &sst).unwrap();
1045 assert_eq!(rows.len(), 1);
1046 match &rows[0].1[0].1 {
1047 CellValue::Formula { expr, result } => {
1048 assert_eq!(expr, "B1+C1");
1049 assert_eq!(*result, Some(Box::new(CellValue::Number(42.0))));
1050 }
1051 _ => panic!("expected Formula"),
1052 }
1053 }
1054
1055 #[test]
1056 fn test_get_rows_with_inline_string() {
1057 let mut ws = WorksheetXml::default();
1058 ws.sheet_data = SheetData {
1059 rows: vec![Row {
1060 r: 1,
1061 spans: None,
1062 s: None,
1063 custom_format: None,
1064 ht: None,
1065 hidden: None,
1066 custom_height: None,
1067 outline_level: None,
1068 cells: vec![Cell {
1069 r: "A1".into(),
1070 col: 1,
1071 s: None,
1072 t: CellTypeTag::InlineString,
1073 v: None,
1074 f: None,
1075 is: Some(Box::new(sheetkit_xml::worksheet::InlineString {
1076 t: Some("inline text".to_string()),
1077 })),
1078 }],
1079 }],
1080 };
1081
1082 let sst = SharedStringTable::new();
1083 let rows = get_rows(&ws, &sst).unwrap();
1084 assert_eq!(rows.len(), 1);
1085 assert_eq!(rows[0].1[0].1, CellValue::String("inline text".to_string()));
1086 }
1087
1088 #[test]
1091 fn test_get_row_style_default_is_zero() {
1092 let ws = WorksheetXml::default();
1093 assert_eq!(get_row_style(&ws, 1), 0);
1094 }
1095
1096 #[test]
1097 fn test_get_row_style_nonexistent_row_is_zero() {
1098 let ws = sample_ws();
1099 assert_eq!(get_row_style(&ws, 99), 0);
1100 }
1101
1102 #[test]
1103 fn test_set_row_style_applies_style() {
1104 let mut ws = sample_ws();
1105 set_row_style(&mut ws, 1, 5).unwrap();
1106
1107 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
1108 assert_eq!(row.s, Some(5));
1109 assert_eq!(row.custom_format, Some(true));
1110 }
1111
1112 #[test]
1113 fn test_set_row_style_applies_to_existing_cells() {
1114 let mut ws = sample_ws();
1115 set_row_style(&mut ws, 1, 3).unwrap();
1116
1117 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
1118 for cell in &row.cells {
1119 assert_eq!(cell.s, Some(3));
1120 }
1121 }
1122
1123 #[test]
1124 fn test_get_row_style_after_set() {
1125 let mut ws = sample_ws();
1126 set_row_style(&mut ws, 2, 7).unwrap();
1127 assert_eq!(get_row_style(&ws, 2), 7);
1128 }
1129
1130 #[test]
1131 fn test_set_row_style_creates_row_if_missing() {
1132 let mut ws = WorksheetXml::default();
1133 set_row_style(&mut ws, 5, 2).unwrap();
1134
1135 assert_eq!(ws.sheet_data.rows.len(), 1);
1136 assert_eq!(ws.sheet_data.rows[0].r, 5);
1137 assert_eq!(ws.sheet_data.rows[0].s, Some(2));
1138 }
1139
1140 #[test]
1141 fn test_set_row_style_zero_clears_custom_format() {
1142 let mut ws = sample_ws();
1143 set_row_style(&mut ws, 1, 5).unwrap();
1144 set_row_style(&mut ws, 1, 0).unwrap();
1145
1146 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
1147 assert_eq!(row.s, Some(0));
1148 assert_eq!(row.custom_format, None);
1149 }
1150
1151 #[test]
1152 fn test_set_row_style_row_zero_returns_error() {
1153 let mut ws = WorksheetXml::default();
1154 let result = set_row_style(&mut ws, 0, 1);
1155 assert!(result.is_err());
1156 }
1157}