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_ref(),
116 cell.is.as_ref(),
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)]
368mod tests {
369 use super::*;
370 use sheetkit_xml::worksheet::{Cell, CellTypeTag, SheetData};
371
372 fn sample_ws() -> WorksheetXml {
374 let mut ws = WorksheetXml::default();
375 ws.sheet_data = SheetData {
376 rows: vec![
377 Row {
378 r: 1,
379 spans: None,
380 s: None,
381 custom_format: None,
382 ht: None,
383 hidden: None,
384 custom_height: None,
385 outline_level: None,
386 cells: vec![
387 Cell {
388 r: "A1".into(),
389 col: 1,
390 s: None,
391 t: CellTypeTag::None,
392 v: Some("10".to_string()),
393 f: None,
394 is: None,
395 },
396 Cell {
397 r: "B1".into(),
398 col: 2,
399 s: None,
400 t: CellTypeTag::None,
401 v: Some("20".to_string()),
402 f: None,
403 is: None,
404 },
405 ],
406 },
407 Row {
408 r: 2,
409 spans: None,
410 s: None,
411 custom_format: None,
412 ht: None,
413 hidden: None,
414 custom_height: None,
415 outline_level: None,
416 cells: vec![Cell {
417 r: "A2".into(),
418 col: 1,
419 s: None,
420 t: CellTypeTag::None,
421 v: Some("30".to_string()),
422 f: None,
423 is: None,
424 }],
425 },
426 Row {
427 r: 5,
428 spans: None,
429 s: None,
430 custom_format: None,
431 ht: None,
432 hidden: None,
433 custom_height: None,
434 outline_level: None,
435 cells: vec![Cell {
436 r: "C5".into(),
437 col: 3,
438 s: None,
439 t: CellTypeTag::None,
440 v: Some("50".to_string()),
441 f: None,
442 is: None,
443 }],
444 },
445 ],
446 };
447 ws
448 }
449
450 #[test]
451 fn test_insert_rows_shifts_cells_down() {
452 let mut ws = sample_ws();
453 insert_rows(&mut ws, 2, 3).unwrap();
454
455 assert_eq!(ws.sheet_data.rows[0].r, 1);
457 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
458
459 assert_eq!(ws.sheet_data.rows[1].r, 5);
461 assert_eq!(ws.sheet_data.rows[1].cells[0].r, "A5");
462
463 assert_eq!(ws.sheet_data.rows[2].r, 8);
465 assert_eq!(ws.sheet_data.rows[2].cells[0].r, "C8");
466 }
467
468 #[test]
469 fn test_insert_rows_at_row_1() {
470 let mut ws = sample_ws();
471 insert_rows(&mut ws, 1, 2).unwrap();
472
473 assert_eq!(ws.sheet_data.rows[0].r, 3);
475 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A3");
476 assert_eq!(ws.sheet_data.rows[1].r, 4);
477 assert_eq!(ws.sheet_data.rows[2].r, 7);
478 }
479
480 #[test]
481 fn test_insert_rows_count_zero_is_noop() {
482 let mut ws = sample_ws();
483 insert_rows(&mut ws, 1, 0).unwrap();
484 assert_eq!(ws.sheet_data.rows[0].r, 1);
485 assert_eq!(ws.sheet_data.rows[1].r, 2);
486 assert_eq!(ws.sheet_data.rows[2].r, 5);
487 }
488
489 #[test]
490 fn test_insert_rows_row_zero_returns_error() {
491 let mut ws = sample_ws();
492 let result = insert_rows(&mut ws, 0, 1);
493 assert!(result.is_err());
494 }
495
496 #[test]
497 fn test_insert_rows_beyond_max_returns_error() {
498 let mut ws = WorksheetXml::default();
499 ws.sheet_data.rows.push(Row {
500 r: MAX_ROWS,
501 spans: None,
502 s: None,
503 custom_format: None,
504 ht: None,
505 hidden: None,
506 custom_height: None,
507 outline_level: None,
508 cells: vec![],
509 });
510 let result = insert_rows(&mut ws, 1, 1);
511 assert!(result.is_err());
512 }
513
514 #[test]
515 fn test_insert_rows_on_empty_sheet() {
516 let mut ws = WorksheetXml::default();
517 insert_rows(&mut ws, 1, 5).unwrap();
518 assert!(ws.sheet_data.rows.is_empty());
519 }
520
521 #[test]
522 fn test_remove_row_shifts_up() {
523 let mut ws = sample_ws();
524 remove_row(&mut ws, 2).unwrap();
525
526 assert_eq!(ws.sheet_data.rows[0].r, 1);
528 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
529
530 assert_eq!(ws.sheet_data.rows.len(), 2);
532 assert_eq!(ws.sheet_data.rows[1].r, 4);
533 assert_eq!(ws.sheet_data.rows[1].cells[0].r, "C4");
534 }
535
536 #[test]
537 fn test_remove_first_row() {
538 let mut ws = sample_ws();
539 remove_row(&mut ws, 1).unwrap();
540
541 assert_eq!(ws.sheet_data.rows[0].r, 1);
543 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
544 assert_eq!(ws.sheet_data.rows[1].r, 4);
545 }
546
547 #[test]
548 fn test_remove_nonexistent_row_still_shifts() {
549 let mut ws = sample_ws();
550 remove_row(&mut ws, 3).unwrap();
552 assert_eq!(ws.sheet_data.rows.len(), 3); assert_eq!(ws.sheet_data.rows[2].r, 4); }
555
556 #[test]
557 fn test_remove_row_zero_returns_error() {
558 let mut ws = sample_ws();
559 let result = remove_row(&mut ws, 0);
560 assert!(result.is_err());
561 }
562
563 #[test]
564 fn test_duplicate_row_inserts_copy_below() {
565 let mut ws = sample_ws();
566 duplicate_row(&mut ws, 1).unwrap();
567
568 assert_eq!(ws.sheet_data.rows[0].r, 1);
570 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
571 assert_eq!(ws.sheet_data.rows[0].cells[0].v, Some("10".to_string()));
572
573 assert_eq!(ws.sheet_data.rows[1].r, 2);
575 assert_eq!(ws.sheet_data.rows[1].cells[0].r, "A2");
576 assert_eq!(ws.sheet_data.rows[1].cells[0].v, Some("10".to_string()));
577 assert_eq!(ws.sheet_data.rows[1].cells.len(), 2);
578
579 assert_eq!(ws.sheet_data.rows[2].r, 3);
581 assert_eq!(ws.sheet_data.rows[2].cells[0].r, "A3");
582 }
583
584 #[test]
585 fn test_duplicate_row_to_specific_target() {
586 let mut ws = sample_ws();
587 duplicate_row_to(&mut ws, 1, 5).unwrap();
588
589 assert_eq!(ws.sheet_data.rows[0].r, 1);
591
592 let row5 = ws.sheet_data.rows.iter().find(|r| r.r == 5).unwrap();
594 assert_eq!(row5.cells[0].r, "A5");
595 assert_eq!(row5.cells[0].v, Some("10".to_string()));
596
597 let row6 = ws.sheet_data.rows.iter().find(|r| r.r == 6).unwrap();
599 assert_eq!(row6.cells[0].r, "C6");
600 }
601
602 #[test]
603 fn test_duplicate_nonexistent_row_returns_error() {
604 let mut ws = sample_ws();
605 let result = duplicate_row(&mut ws, 99);
606 assert!(result.is_err());
607 }
608
609 #[test]
610 fn test_set_and_get_row_height() {
611 let mut ws = sample_ws();
612 set_row_height(&mut ws, 1, 25.5).unwrap();
613
614 assert_eq!(get_row_height(&ws, 1), Some(25.5));
615 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
616 assert_eq!(row.custom_height, Some(true));
617 }
618
619 #[test]
620 fn test_set_row_height_creates_row_if_missing() {
621 let mut ws = WorksheetXml::default();
622 set_row_height(&mut ws, 10, 30.0).unwrap();
623
624 assert_eq!(get_row_height(&ws, 10), Some(30.0));
625 assert_eq!(ws.sheet_data.rows.len(), 1);
626 assert_eq!(ws.sheet_data.rows[0].r, 10);
627 }
628
629 #[test]
630 fn test_set_row_height_zero_is_valid() {
631 let mut ws = WorksheetXml::default();
632 set_row_height(&mut ws, 1, 0.0).unwrap();
633 assert_eq!(get_row_height(&ws, 1), Some(0.0));
634 }
635
636 #[test]
637 fn test_set_row_height_max_is_valid() {
638 let mut ws = WorksheetXml::default();
639 set_row_height(&mut ws, 1, 409.0).unwrap();
640 assert_eq!(get_row_height(&ws, 1), Some(409.0));
641 }
642
643 #[test]
644 fn test_set_row_height_exceeds_max_returns_error() {
645 let mut ws = WorksheetXml::default();
646 let result = set_row_height(&mut ws, 1, 410.0);
647 assert!(result.is_err());
648 assert!(matches!(
649 result.unwrap_err(),
650 Error::RowHeightExceeded { .. }
651 ));
652 }
653
654 #[test]
655 fn test_set_row_height_negative_returns_error() {
656 let mut ws = WorksheetXml::default();
657 let result = set_row_height(&mut ws, 1, -1.0);
658 assert!(result.is_err());
659 }
660
661 #[test]
662 fn test_set_row_height_row_zero_returns_error() {
663 let mut ws = WorksheetXml::default();
664 let result = set_row_height(&mut ws, 0, 15.0);
665 assert!(result.is_err());
666 }
667
668 #[test]
669 fn test_get_row_height_nonexistent_returns_none() {
670 let ws = WorksheetXml::default();
671 assert_eq!(get_row_height(&ws, 99), None);
672 }
673
674 #[test]
675 fn test_set_row_hidden() {
676 let mut ws = sample_ws();
677 set_row_visible(&mut ws, 1, false).unwrap();
678
679 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
680 assert_eq!(row.hidden, Some(true));
681 }
682
683 #[test]
684 fn test_set_row_visible_clears_hidden() {
685 let mut ws = sample_ws();
686 set_row_visible(&mut ws, 1, false).unwrap();
687 set_row_visible(&mut ws, 1, true).unwrap();
688
689 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
690 assert_eq!(row.hidden, None);
691 }
692
693 #[test]
694 fn test_set_row_visible_creates_row_if_missing() {
695 let mut ws = WorksheetXml::default();
696 set_row_visible(&mut ws, 3, false).unwrap();
697 assert_eq!(ws.sheet_data.rows.len(), 1);
698 assert_eq!(ws.sheet_data.rows[0].r, 3);
699 assert_eq!(ws.sheet_data.rows[0].hidden, Some(true));
700 }
701
702 #[test]
703 fn test_set_row_visible_row_zero_returns_error() {
704 let mut ws = WorksheetXml::default();
705 let result = set_row_visible(&mut ws, 0, true);
706 assert!(result.is_err());
707 }
708
709 #[test]
710 fn test_set_row_outline_level() {
711 let mut ws = sample_ws();
712 set_row_outline_level(&mut ws, 1, 3).unwrap();
713
714 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
715 assert_eq!(row.outline_level, Some(3));
716 }
717
718 #[test]
719 fn test_set_row_outline_level_zero_clears() {
720 let mut ws = sample_ws();
721 set_row_outline_level(&mut ws, 1, 3).unwrap();
722 set_row_outline_level(&mut ws, 1, 0).unwrap();
723
724 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
725 assert_eq!(row.outline_level, None);
726 }
727
728 #[test]
729 fn test_set_row_outline_level_exceeds_max_returns_error() {
730 let mut ws = sample_ws();
731 let result = set_row_outline_level(&mut ws, 1, 8);
732 assert!(result.is_err());
733 }
734
735 #[test]
736 fn test_set_row_outline_level_row_zero_returns_error() {
737 let mut ws = WorksheetXml::default();
738 let result = set_row_outline_level(&mut ws, 0, 1);
739 assert!(result.is_err());
740 }
741
742 #[test]
743 fn test_get_row_visible_default_is_true() {
744 let ws = sample_ws();
745 assert!(get_row_visible(&ws, 1));
746 }
747
748 #[test]
749 fn test_get_row_visible_nonexistent_row_is_true() {
750 let ws = WorksheetXml::default();
751 assert!(get_row_visible(&ws, 99));
752 }
753
754 #[test]
755 fn test_get_row_visible_after_hide() {
756 let mut ws = sample_ws();
757 set_row_visible(&mut ws, 1, false).unwrap();
758 assert!(!get_row_visible(&ws, 1));
759 }
760
761 #[test]
762 fn test_get_row_visible_after_hide_then_show() {
763 let mut ws = sample_ws();
764 set_row_visible(&mut ws, 1, false).unwrap();
765 set_row_visible(&mut ws, 1, true).unwrap();
766 assert!(get_row_visible(&ws, 1));
767 }
768
769 #[test]
770 fn test_get_row_outline_level_default_is_zero() {
771 let ws = sample_ws();
772 assert_eq!(get_row_outline_level(&ws, 1), 0);
773 }
774
775 #[test]
776 fn test_get_row_outline_level_nonexistent_row() {
777 let ws = WorksheetXml::default();
778 assert_eq!(get_row_outline_level(&ws, 99), 0);
779 }
780
781 #[test]
782 fn test_get_row_outline_level_after_set() {
783 let mut ws = sample_ws();
784 set_row_outline_level(&mut ws, 1, 5).unwrap();
785 assert_eq!(get_row_outline_level(&ws, 1), 5);
786 }
787
788 #[test]
789 fn test_get_row_outline_level_after_clear() {
790 let mut ws = sample_ws();
791 set_row_outline_level(&mut ws, 1, 3).unwrap();
792 set_row_outline_level(&mut ws, 1, 0).unwrap();
793 assert_eq!(get_row_outline_level(&ws, 1), 0);
794 }
795
796 #[test]
799 fn test_get_rows_empty_sheet() {
800 let ws = WorksheetXml::default();
801 let sst = SharedStringTable::new();
802 let rows = get_rows(&ws, &sst).unwrap();
803 assert!(rows.is_empty());
804 }
805
806 #[test]
807 fn test_get_rows_returns_numeric_values() {
808 let ws = sample_ws();
809 let sst = SharedStringTable::new();
810 let rows = get_rows(&ws, &sst).unwrap();
811
812 assert_eq!(rows.len(), 3);
813
814 assert_eq!(rows[0].0, 1);
816 assert_eq!(rows[0].1.len(), 2);
817 assert_eq!(rows[0].1[0].0, 1);
818 assert_eq!(rows[0].1[0].1, CellValue::Number(10.0));
819 assert_eq!(rows[0].1[1].0, 2);
820 assert_eq!(rows[0].1[1].1, CellValue::Number(20.0));
821
822 assert_eq!(rows[1].0, 2);
824 assert_eq!(rows[1].1.len(), 1);
825 assert_eq!(rows[1].1[0].0, 1);
826 assert_eq!(rows[1].1[0].1, CellValue::Number(30.0));
827
828 assert_eq!(rows[2].0, 5);
830 assert_eq!(rows[2].1.len(), 1);
831 assert_eq!(rows[2].1[0].0, 3);
832 assert_eq!(rows[2].1[0].1, CellValue::Number(50.0));
833 }
834
835 #[test]
836 fn test_get_rows_shared_strings() {
837 let mut sst = SharedStringTable::new();
838 sst.add("hello");
839 sst.add("world");
840
841 let mut ws = WorksheetXml::default();
842 ws.sheet_data = SheetData {
843 rows: vec![Row {
844 r: 1,
845 spans: None,
846 s: None,
847 custom_format: None,
848 ht: None,
849 hidden: None,
850 custom_height: None,
851 outline_level: None,
852 cells: vec![
853 Cell {
854 r: "A1".into(),
855 col: 1,
856 s: None,
857 t: CellTypeTag::SharedString,
858 v: Some("0".to_string()),
859 f: None,
860 is: None,
861 },
862 Cell {
863 r: "B1".into(),
864 col: 2,
865 s: None,
866 t: CellTypeTag::SharedString,
867 v: Some("1".to_string()),
868 f: None,
869 is: None,
870 },
871 ],
872 }],
873 };
874
875 let rows = get_rows(&ws, &sst).unwrap();
876 assert_eq!(rows.len(), 1);
877 assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
878 assert_eq!(rows[0].1[1].1, CellValue::String("world".to_string()));
879 }
880
881 #[test]
882 fn test_get_rows_mixed_types() {
883 let mut sst = SharedStringTable::new();
884 sst.add("text");
885
886 let mut ws = WorksheetXml::default();
887 ws.sheet_data = SheetData {
888 rows: vec![Row {
889 r: 1,
890 spans: None,
891 s: None,
892 custom_format: None,
893 ht: None,
894 hidden: None,
895 custom_height: None,
896 outline_level: None,
897 cells: vec![
898 Cell {
899 r: "A1".into(),
900 col: 1,
901 s: None,
902 t: CellTypeTag::SharedString,
903 v: Some("0".to_string()),
904 f: None,
905 is: None,
906 },
907 Cell {
908 r: "B1".into(),
909 col: 2,
910 s: None,
911 t: CellTypeTag::None,
912 v: Some("42.5".to_string()),
913 f: None,
914 is: None,
915 },
916 Cell {
917 r: "C1".into(),
918 col: 3,
919 s: None,
920 t: CellTypeTag::Boolean,
921 v: Some("1".to_string()),
922 f: None,
923 is: None,
924 },
925 Cell {
926 r: "D1".into(),
927 col: 4,
928 s: None,
929 t: CellTypeTag::Error,
930 v: Some("#DIV/0!".to_string()),
931 f: None,
932 is: None,
933 },
934 ],
935 }],
936 };
937
938 let rows = get_rows(&ws, &sst).unwrap();
939 assert_eq!(rows.len(), 1);
940 assert_eq!(rows[0].1[0].1, CellValue::String("text".to_string()));
941 assert_eq!(rows[0].1[1].1, CellValue::Number(42.5));
942 assert_eq!(rows[0].1[2].1, CellValue::Bool(true));
943 assert_eq!(rows[0].1[3].1, CellValue::Error("#DIV/0!".to_string()));
944 }
945
946 #[test]
947 fn test_get_rows_skips_rows_with_no_cells() {
948 let mut ws = WorksheetXml::default();
949 ws.sheet_data = SheetData {
950 rows: vec![
951 Row {
952 r: 1,
953 spans: None,
954 s: None,
955 custom_format: None,
956 ht: None,
957 hidden: None,
958 custom_height: None,
959 outline_level: None,
960 cells: vec![Cell {
961 r: "A1".into(),
962 col: 1,
963 s: None,
964 t: CellTypeTag::None,
965 v: Some("1".to_string()),
966 f: None,
967 is: None,
968 }],
969 },
970 Row {
972 r: 2,
973 spans: None,
974 s: None,
975 custom_format: None,
976 ht: Some(30.0),
977 hidden: None,
978 custom_height: Some(true),
979 outline_level: None,
980 cells: vec![],
981 },
982 Row {
983 r: 3,
984 spans: None,
985 s: None,
986 custom_format: None,
987 ht: None,
988 hidden: None,
989 custom_height: None,
990 outline_level: None,
991 cells: vec![Cell {
992 r: "A3".into(),
993 col: 1,
994 s: None,
995 t: CellTypeTag::None,
996 v: Some("3".to_string()),
997 f: None,
998 is: None,
999 }],
1000 },
1001 ],
1002 };
1003
1004 let sst = SharedStringTable::new();
1005 let rows = get_rows(&ws, &sst).unwrap();
1006 assert_eq!(rows.len(), 2);
1008 assert_eq!(rows[0].0, 1);
1009 assert_eq!(rows[1].0, 3);
1010 }
1011
1012 #[test]
1013 fn test_get_rows_with_formula() {
1014 let mut ws = WorksheetXml::default();
1015 ws.sheet_data = SheetData {
1016 rows: vec![Row {
1017 r: 1,
1018 spans: None,
1019 s: None,
1020 custom_format: None,
1021 ht: None,
1022 hidden: None,
1023 custom_height: None,
1024 outline_level: None,
1025 cells: vec![Cell {
1026 r: "A1".into(),
1027 col: 1,
1028 s: None,
1029 t: CellTypeTag::None,
1030 v: Some("42".to_string()),
1031 f: Some(sheetkit_xml::worksheet::CellFormula {
1032 t: None,
1033 reference: None,
1034 si: None,
1035 value: Some("B1+C1".to_string()),
1036 }),
1037 is: None,
1038 }],
1039 }],
1040 };
1041
1042 let sst = SharedStringTable::new();
1043 let rows = get_rows(&ws, &sst).unwrap();
1044 assert_eq!(rows.len(), 1);
1045 match &rows[0].1[0].1 {
1046 CellValue::Formula { expr, result } => {
1047 assert_eq!(expr, "B1+C1");
1048 assert_eq!(*result, Some(Box::new(CellValue::Number(42.0))));
1049 }
1050 _ => panic!("expected Formula"),
1051 }
1052 }
1053
1054 #[test]
1055 fn test_get_rows_with_inline_string() {
1056 let mut ws = WorksheetXml::default();
1057 ws.sheet_data = SheetData {
1058 rows: vec![Row {
1059 r: 1,
1060 spans: None,
1061 s: None,
1062 custom_format: None,
1063 ht: None,
1064 hidden: None,
1065 custom_height: None,
1066 outline_level: None,
1067 cells: vec![Cell {
1068 r: "A1".into(),
1069 col: 1,
1070 s: None,
1071 t: CellTypeTag::InlineString,
1072 v: None,
1073 f: None,
1074 is: Some(sheetkit_xml::worksheet::InlineString {
1075 t: Some("inline text".to_string()),
1076 }),
1077 }],
1078 }],
1079 };
1080
1081 let sst = SharedStringTable::new();
1082 let rows = get_rows(&ws, &sst).unwrap();
1083 assert_eq!(rows.len(), 1);
1084 assert_eq!(rows[0].1[0].1, CellValue::String("inline text".to_string()));
1085 }
1086
1087 #[test]
1090 fn test_get_row_style_default_is_zero() {
1091 let ws = WorksheetXml::default();
1092 assert_eq!(get_row_style(&ws, 1), 0);
1093 }
1094
1095 #[test]
1096 fn test_get_row_style_nonexistent_row_is_zero() {
1097 let ws = sample_ws();
1098 assert_eq!(get_row_style(&ws, 99), 0);
1099 }
1100
1101 #[test]
1102 fn test_set_row_style_applies_style() {
1103 let mut ws = sample_ws();
1104 set_row_style(&mut ws, 1, 5).unwrap();
1105
1106 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
1107 assert_eq!(row.s, Some(5));
1108 assert_eq!(row.custom_format, Some(true));
1109 }
1110
1111 #[test]
1112 fn test_set_row_style_applies_to_existing_cells() {
1113 let mut ws = sample_ws();
1114 set_row_style(&mut ws, 1, 3).unwrap();
1115
1116 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
1117 for cell in &row.cells {
1118 assert_eq!(cell.s, Some(3));
1119 }
1120 }
1121
1122 #[test]
1123 fn test_get_row_style_after_set() {
1124 let mut ws = sample_ws();
1125 set_row_style(&mut ws, 2, 7).unwrap();
1126 assert_eq!(get_row_style(&ws, 2), 7);
1127 }
1128
1129 #[test]
1130 fn test_set_row_style_creates_row_if_missing() {
1131 let mut ws = WorksheetXml::default();
1132 set_row_style(&mut ws, 5, 2).unwrap();
1133
1134 assert_eq!(ws.sheet_data.rows.len(), 1);
1135 assert_eq!(ws.sheet_data.rows[0].r, 5);
1136 assert_eq!(ws.sheet_data.rows[0].s, Some(2));
1137 }
1138
1139 #[test]
1140 fn test_set_row_style_zero_clears_custom_format() {
1141 let mut ws = sample_ws();
1142 set_row_style(&mut ws, 1, 5).unwrap();
1143 set_row_style(&mut ws, 1, 0).unwrap();
1144
1145 let row = ws.sheet_data.rows.iter().find(|r| r.r == 1).unwrap();
1146 assert_eq!(row.s, Some(0));
1147 assert_eq!(row.custom_format, None);
1148 }
1149
1150 #[test]
1151 fn test_set_row_style_row_zero_returns_error() {
1152 let mut ws = WorksheetXml::default();
1153 let result = set_row_style(&mut ws, 0, 1);
1154 assert!(result.is_err());
1155 }
1156}