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