1use crate::cell::CellValue;
21use crate::error::{Error, Result};
22use crate::sst::SharedStringTable;
23use crate::utils::cell_ref::cell_name_to_coordinates;
24use crate::utils::constants::{MAX_COLUMNS, MAX_ROWS, MAX_ROW_HEIGHT};
25
26use sheetkit_xml::worksheet::{
27 Cell, CellFormula, CellTypeTag, Col, Cols, CompactCellRef, MergeCell, MergeCells, Pane, Row,
28 SheetData, SheetView, SheetViews, WorksheetXml,
29};
30
31const XML_DECLARATION: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#;
33
34const MAX_OUTLINE_LEVEL: u8 = 7;
36
37#[derive(Debug, Clone, Default)]
39pub struct StreamRowOptions {
40 pub height: Option<f64>,
42 pub visible: Option<bool>,
44 pub outline_level: Option<u8>,
46 pub style_id: Option<u32>,
48}
49
50#[derive(Debug, Clone, Default)]
52struct StreamColOptions {
53 width: Option<f64>,
54 style_id: Option<u32>,
55 hidden: Option<bool>,
56 outline_level: Option<u8>,
57}
58
59#[derive(Debug)]
65pub struct StreamWriter {
66 sheet_name: String,
67 rows: Vec<Row>,
68 last_row: u32,
69 started: bool,
70 finished: bool,
71 col_widths: Vec<(u32, u32, f64)>,
72 col_options: Vec<(u32, StreamColOptions)>,
73 sst: SharedStringTable,
74 merge_cells: Vec<String>,
75 freeze_pane: Option<(u32, u32, String)>,
77}
78
79impl StreamWriter {
80 pub fn new(sheet_name: &str) -> Self {
82 Self {
83 sheet_name: sheet_name.to_string(),
84 rows: Vec::new(),
85 last_row: 0,
86 started: false,
87 finished: false,
88 col_widths: Vec::new(),
89 col_options: Vec::new(),
90 sst: SharedStringTable::new(),
91 merge_cells: Vec::new(),
92 freeze_pane: None,
93 }
94 }
95
96 pub fn sheet_name(&self) -> &str {
98 &self.sheet_name
99 }
100
101 pub fn set_col_width(&mut self, col: u32, width: f64) -> Result<()> {
104 self.set_col_width_range(col, col, width)
105 }
106
107 pub fn set_col_width_range(&mut self, min_col: u32, max_col: u32, width: f64) -> Result<()> {
110 if self.finished {
111 return Err(Error::StreamAlreadyFinished);
112 }
113 if self.started {
114 return Err(Error::StreamColumnsAfterRows);
115 }
116 if min_col == 0 || max_col == 0 || min_col > MAX_COLUMNS || max_col > MAX_COLUMNS {
117 return Err(Error::InvalidColumnNumber(if min_col == 0 {
118 min_col
119 } else {
120 max_col
121 }));
122 }
123 self.col_widths.push((min_col, max_col, width));
124 Ok(())
125 }
126
127 pub fn set_col_style(&mut self, col: u32, style_id: u32) -> Result<()> {
130 self.ensure_col_configurable(col)?;
131 self.get_or_create_col_options(col).style_id = Some(style_id);
132 Ok(())
133 }
134
135 pub fn set_col_visible(&mut self, col: u32, visible: bool) -> Result<()> {
138 self.ensure_col_configurable(col)?;
139 self.get_or_create_col_options(col).hidden = Some(!visible);
140 Ok(())
141 }
142
143 pub fn set_col_outline_level(&mut self, col: u32, level: u8) -> Result<()> {
146 self.ensure_col_configurable(col)?;
147 if level > MAX_OUTLINE_LEVEL {
148 return Err(Error::OutlineLevelExceeded {
149 level,
150 max: MAX_OUTLINE_LEVEL,
151 });
152 }
153 self.get_or_create_col_options(col).outline_level = Some(level);
154 Ok(())
155 }
156
157 pub fn set_freeze_panes(&mut self, top_left_cell: &str) -> Result<()> {
163 if self.finished {
164 return Err(Error::StreamAlreadyFinished);
165 }
166 if self.started {
167 return Err(Error::StreamColumnsAfterRows);
168 }
169 let (col, row) = cell_name_to_coordinates(top_left_cell)?;
170 if col == 1 && row == 1 {
171 return Err(Error::InvalidCellReference(
172 "freeze pane at A1 has no effect".to_string(),
173 ));
174 }
175 self.freeze_pane = Some((col - 1, row - 1, top_left_cell.to_string()));
176 Ok(())
177 }
178
179 pub fn write_row_with_options(
181 &mut self,
182 row: u32,
183 values: &[CellValue],
184 options: &StreamRowOptions,
185 ) -> Result<()> {
186 self.write_row_impl(row, values, None, Some(options))
187 }
188
189 pub fn write_row(&mut self, row: u32, values: &[CellValue]) -> Result<()> {
192 self.write_row_impl(row, values, None, None)
193 }
194
195 pub fn write_row_with_style(
197 &mut self,
198 row: u32,
199 values: &[CellValue],
200 style_id: u32,
201 ) -> Result<()> {
202 self.write_row_impl(row, values, Some(style_id), None)
203 }
204
205 pub fn add_merge_cell(&mut self, reference: &str) -> Result<()> {
209 if self.finished {
210 return Err(Error::StreamAlreadyFinished);
211 }
212 let parts: Vec<&str> = reference.split(':').collect();
214 if parts.len() != 2 {
215 return Err(Error::InvalidMergeCellReference(reference.to_string()));
216 }
217 cell_name_to_coordinates(parts[0])
218 .map_err(|_| Error::InvalidMergeCellReference(reference.to_string()))?;
219 cell_name_to_coordinates(parts[1])
220 .map_err(|_| Error::InvalidMergeCellReference(reference.to_string()))?;
221 self.merge_cells.push(reference.to_string());
222 Ok(())
223 }
224
225 pub fn finish(&mut self) -> Result<Vec<u8>> {
227 if self.finished {
228 return Err(Error::StreamAlreadyFinished);
229 }
230 self.finished = true;
231 let ws = self.build_worksheet_xml();
232 let body = quick_xml::se::to_string(&ws).map_err(|e| Error::Internal(e.to_string()))?;
233 let mut xml = String::with_capacity(body.len() + 60);
234 xml.push_str(XML_DECLARATION);
235 xml.push('\n');
236 xml.push_str(&body);
237 Ok(xml.into_bytes())
238 }
239
240 pub fn sst(&self) -> &SharedStringTable {
242 &self.sst
243 }
244
245 pub fn into_parts(mut self) -> Result<(Vec<u8>, SharedStringTable)> {
247 let xml = self.finish()?;
248 Ok((xml, self.sst))
249 }
250
251 pub fn into_worksheet_parts(mut self) -> Result<(WorksheetXml, SharedStringTable)> {
254 if self.finished {
255 return Err(Error::StreamAlreadyFinished);
256 }
257 self.finished = true;
258 let ws = self.build_worksheet_xml_owned();
259 Ok((ws, self.sst))
260 }
261
262 fn ensure_col_configurable(&self, col: u32) -> Result<()> {
265 if self.finished {
266 return Err(Error::StreamAlreadyFinished);
267 }
268 if self.started {
269 return Err(Error::StreamColumnsAfterRows);
270 }
271 if col == 0 || col > MAX_COLUMNS {
272 return Err(Error::InvalidColumnNumber(col));
273 }
274 Ok(())
275 }
276
277 fn get_or_create_col_options(&mut self, col: u32) -> &mut StreamColOptions {
279 if let Some(pos) = self.col_options.iter().position(|(c, _)| *c == col) {
280 &mut self.col_options[pos].1
281 } else {
282 self.col_options.push((col, StreamColOptions::default()));
283 let last = self.col_options.len() - 1;
284 &mut self.col_options[last].1
285 }
286 }
287
288 fn build_worksheet_xml(&self) -> WorksheetXml {
290 self.build_worksheet_xml_inner(self.rows.clone())
291 }
292
293 fn build_worksheet_xml_owned(&mut self) -> WorksheetXml {
295 let rows = std::mem::take(&mut self.rows);
296 self.build_worksheet_xml_inner(rows)
297 }
298
299 fn build_worksheet_xml_inner(&self, rows: Vec<Row>) -> WorksheetXml {
301 let sheet_views = self
302 .freeze_pane
303 .as_ref()
304 .map(|(x_split, y_split, top_left_cell)| {
305 let active_pane = match (*x_split > 0, *y_split > 0) {
306 (true, true) => "bottomRight",
307 (true, false) => "topRight",
308 (false, true) => "bottomLeft",
309 (false, false) => unreachable!(),
310 };
311 SheetViews {
312 sheet_views: vec![SheetView {
313 tab_selected: Some(true),
314 zoom_scale: None,
315 workbook_view_id: 0,
316 pane: Some(Pane {
317 x_split: if *x_split > 0 { Some(*x_split) } else { None },
318 y_split: if *y_split > 0 { Some(*y_split) } else { None },
319 top_left_cell: Some(top_left_cell.clone()),
320 active_pane: Some(active_pane.to_string()),
321 state: Some("frozen".to_string()),
322 }),
323 selection: vec![],
324 }],
325 }
326 });
327
328 let cols = {
329 let has_widths = !self.col_widths.is_empty();
330 let has_options = !self.col_options.is_empty();
331 if has_widths || has_options {
332 let mut col_defs = Vec::new();
333 for &(min, max, width) in &self.col_widths {
334 col_defs.push(Col {
335 min,
336 max,
337 width: Some(width),
338 style: None,
339 hidden: None,
340 custom_width: Some(true),
341 outline_level: None,
342 });
343 }
344 for &(col_num, ref opts) in &self.col_options {
345 col_defs.push(Col {
346 min: col_num,
347 max: col_num,
348 width: opts.width,
349 style: opts.style_id,
350 hidden: opts.hidden,
351 custom_width: if opts.width.is_some() {
352 Some(true)
353 } else {
354 None
355 },
356 outline_level: opts.outline_level.filter(|&l| l > 0),
357 });
358 }
359 Some(Cols { cols: col_defs })
360 } else {
361 None
362 }
363 };
364
365 let merge_cells = if self.merge_cells.is_empty() {
366 None
367 } else {
368 Some(MergeCells {
369 count: Some(self.merge_cells.len() as u32),
370 merge_cells: self
371 .merge_cells
372 .iter()
373 .map(|r| MergeCell {
374 reference: r.clone(),
375 })
376 .collect(),
377 })
378 };
379
380 WorksheetXml {
381 sheet_views,
382 cols,
383 sheet_data: SheetData { rows },
384 merge_cells,
385 ..WorksheetXml::default()
386 }
387 }
388
389 fn write_row_impl(
391 &mut self,
392 row: u32,
393 values: &[CellValue],
394 cell_style_id: Option<u32>,
395 options: Option<&StreamRowOptions>,
396 ) -> Result<()> {
397 if self.finished {
398 return Err(Error::StreamAlreadyFinished);
399 }
400
401 if row == 0 || row > MAX_ROWS {
403 return Err(Error::InvalidRowNumber(row));
404 }
405
406 if row <= self.last_row {
408 return Err(Error::StreamRowAlreadyWritten { row });
409 }
410
411 if values.len() > MAX_COLUMNS as usize {
413 return Err(Error::InvalidColumnNumber(values.len() as u32));
414 }
415
416 if let Some(opts) = options {
418 if let Some(height) = opts.height {
419 if height > MAX_ROW_HEIGHT {
420 return Err(Error::RowHeightExceeded {
421 height,
422 max: MAX_ROW_HEIGHT,
423 });
424 }
425 }
426 if let Some(level) = opts.outline_level {
427 if level > MAX_OUTLINE_LEVEL {
428 return Err(Error::OutlineLevelExceeded {
429 level,
430 max: MAX_OUTLINE_LEVEL,
431 });
432 }
433 }
434 }
435
436 self.started = true;
437 self.last_row = row;
438
439 let mut cells = Vec::new();
440
441 for (i, value) in values.iter().enumerate() {
442 let col = (i as u32) + 1;
443
444 match value {
445 CellValue::Empty => continue,
446 CellValue::String(s) => {
447 let idx = self.sst.add(s);
448 cells.push(Cell {
449 r: CompactCellRef::from_coordinates(col, row),
450 col,
451 s: cell_style_id,
452 t: CellTypeTag::SharedString,
453 v: Some(idx.to_string()),
454 f: None,
455 is: None,
456 });
457 }
458 CellValue::Number(n) => {
459 cells.push(Cell {
460 r: CompactCellRef::from_coordinates(col, row),
461 col,
462 s: cell_style_id,
463 t: CellTypeTag::None,
464 v: Some(n.to_string()),
465 f: None,
466 is: None,
467 });
468 }
469 CellValue::Date(serial) => {
470 cells.push(Cell {
471 r: CompactCellRef::from_coordinates(col, row),
472 col,
473 s: cell_style_id,
474 t: CellTypeTag::None,
475 v: Some(serial.to_string()),
476 f: None,
477 is: None,
478 });
479 }
480 CellValue::Bool(b) => {
481 let val = if *b { "1" } else { "0" };
482 cells.push(Cell {
483 r: CompactCellRef::from_coordinates(col, row),
484 col,
485 s: cell_style_id,
486 t: CellTypeTag::Boolean,
487 v: Some(val.to_string()),
488 f: None,
489 is: None,
490 });
491 }
492 CellValue::Formula { expr, .. } => {
493 cells.push(Cell {
494 r: CompactCellRef::from_coordinates(col, row),
495 col,
496 s: cell_style_id,
497 t: CellTypeTag::None,
498 v: None,
499 f: Some(CellFormula {
500 t: None,
501 reference: None,
502 si: None,
503 value: Some(expr.clone()),
504 }),
505 is: None,
506 });
507 }
508 CellValue::Error(e) => {
509 cells.push(Cell {
510 r: CompactCellRef::from_coordinates(col, row),
511 col,
512 s: cell_style_id,
513 t: CellTypeTag::Error,
514 v: Some(e.clone()),
515 f: None,
516 is: None,
517 });
518 }
519 CellValue::RichString(runs) => {
520 let plain = crate::rich_text::rich_text_to_plain(runs);
521 let idx = self.sst.add(&plain);
522 cells.push(Cell {
523 r: CompactCellRef::from_coordinates(col, row),
524 col,
525 s: cell_style_id,
526 t: CellTypeTag::SharedString,
527 v: Some(idx.to_string()),
528 f: None,
529 is: None,
530 });
531 }
532 }
533 }
534
535 let mut xml_row = Row {
536 r: row,
537 spans: None,
538 s: None,
539 custom_format: None,
540 ht: None,
541 hidden: None,
542 custom_height: None,
543 outline_level: None,
544 cells,
545 };
546
547 if let Some(opts) = options {
548 if let Some(height) = opts.height {
549 xml_row.ht = Some(height);
550 xml_row.custom_height = Some(true);
551 }
552 if let Some(false) = opts.visible {
553 xml_row.hidden = Some(true);
554 }
555 if let Some(level) = opts.outline_level {
556 if level > 0 {
557 xml_row.outline_level = Some(level);
558 }
559 }
560 if let Some(sid) = opts.style_id {
561 xml_row.s = Some(sid);
562 xml_row.custom_format = Some(true);
563 }
564 }
565
566 self.rows.push(xml_row);
567 Ok(())
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use sheetkit_xml::worksheet::WorksheetXml;
575
576 #[test]
577 fn test_basic_write_and_finish() {
578 let mut sw = StreamWriter::new("Sheet1");
579 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
580 .unwrap();
581 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(30)])
582 .unwrap();
583 let xml_bytes = sw.finish().unwrap();
584 let xml = String::from_utf8(xml_bytes).unwrap();
585
586 assert!(xml.contains("<?xml version=\"1.0\""));
587 assert!(xml.contains("<worksheet"));
588 assert!(xml.contains("<sheetData>"));
589 assert!(xml.contains("</sheetData>"));
590 assert!(xml.contains("</worksheet>"));
591 }
592
593 #[test]
594 fn test_parse_output_xml_back() {
595 let mut sw = StreamWriter::new("TestSheet");
596 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
597 .unwrap();
598 let xml_bytes = sw.finish().unwrap();
599 let xml_str = String::from_utf8(xml_bytes).unwrap();
600
601 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
603 assert_eq!(ws.sheet_data.rows.len(), 1);
604 assert_eq!(ws.sheet_data.rows[0].r, 1);
605 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
606 assert_eq!(
608 ws.sheet_data.rows[0].cells[0].t,
609 sheetkit_xml::worksheet::CellTypeTag::SharedString
610 );
611 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
612 assert_eq!(
614 ws.sheet_data.rows[0].cells[1].t,
615 sheetkit_xml::worksheet::CellTypeTag::None
616 );
617 assert_eq!(ws.sheet_data.rows[0].cells[1].v, Some("42".to_string()));
618 assert_eq!(ws.sheet_data.rows[0].cells[1].r, "B1");
619 }
620
621 #[test]
622 fn test_string_values_populate_sst() {
623 let mut sw = StreamWriter::new("Sheet1");
624 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from("World")])
625 .unwrap();
626
627 assert_eq!(sw.sst().len(), 2);
628 assert_eq!(sw.sst().get(0), Some("Hello"));
629 assert_eq!(sw.sst().get(1), Some("World"));
630 }
631
632 #[test]
633 fn test_number_value() {
634 let mut sw = StreamWriter::new("Sheet1");
635 sw.write_row(1, &[CellValue::from(3.15)]).unwrap();
636 let xml_bytes = sw.finish().unwrap();
637 let xml = String::from_utf8(xml_bytes).unwrap();
638
639 assert!(xml.contains("<v>3.15</v>"));
640 }
641
642 #[test]
643 fn test_bool_values() {
644 let mut sw = StreamWriter::new("Sheet1");
645 sw.write_row(1, &[CellValue::from(true), CellValue::from(false)])
646 .unwrap();
647 let xml_bytes = sw.finish().unwrap();
648 let xml = String::from_utf8(xml_bytes).unwrap();
649
650 assert!(xml.contains("<v>1</v>"));
651 assert!(xml.contains("<v>0</v>"));
652 }
653
654 #[test]
655 fn test_formula_value() {
656 let mut sw = StreamWriter::new("Sheet1");
657 sw.write_row(
658 1,
659 &[CellValue::Formula {
660 expr: "SUM(A2:A10)".to_string(),
661 result: None,
662 }],
663 )
664 .unwrap();
665 let xml_bytes = sw.finish().unwrap();
666 let xml = String::from_utf8(xml_bytes).unwrap();
667
668 assert!(xml.contains("SUM(A2:A10)"));
669 }
670
671 #[test]
672 fn test_error_value() {
673 let mut sw = StreamWriter::new("Sheet1");
674 sw.write_row(1, &[CellValue::Error("#DIV/0!".to_string())])
675 .unwrap();
676 let xml_bytes = sw.finish().unwrap();
677 let xml = String::from_utf8(xml_bytes).unwrap();
678
679 assert!(xml.contains("#DIV/0!"));
680 }
681
682 #[test]
683 fn test_empty_values_are_skipped() {
684 let mut sw = StreamWriter::new("Sheet1");
685 sw.write_row(
686 1,
687 &[CellValue::from("A"), CellValue::Empty, CellValue::from("C")],
688 )
689 .unwrap();
690 let xml_bytes = sw.finish().unwrap();
691 let xml_str = String::from_utf8(xml_bytes).unwrap();
692
693 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
695 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
696 assert_eq!(ws.sheet_data.rows[0].cells[0].r, "A1");
697 assert_eq!(ws.sheet_data.rows[0].cells[1].r, "C1");
698 }
699
700 #[test]
701 fn test_write_row_with_style() {
702 let mut sw = StreamWriter::new("Sheet1");
703 sw.write_row_with_style(1, &[CellValue::from("Styled")], 5)
704 .unwrap();
705 let xml_bytes = sw.finish().unwrap();
706 let xml = String::from_utf8(xml_bytes).unwrap();
707
708 assert!(xml.contains("s=\"5\""));
709 }
710
711 #[test]
712 fn test_set_col_width_before_rows() {
713 let mut sw = StreamWriter::new("Sheet1");
714 sw.set_col_width(1, 20.0).unwrap();
715 sw.write_row(1, &[CellValue::from("data")]).unwrap();
716 let xml_bytes = sw.finish().unwrap();
717 let xml_str = String::from_utf8(xml_bytes).unwrap();
718
719 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
720 let cols = ws.cols.unwrap();
721 assert_eq!(cols.cols.len(), 1);
722 assert_eq!(cols.cols[0].min, 1);
723 assert_eq!(cols.cols[0].max, 1);
724 assert_eq!(cols.cols[0].width, Some(20.0));
725 assert_eq!(cols.cols[0].custom_width, Some(true));
726 }
727
728 #[test]
729 fn test_set_col_width_range() {
730 let mut sw = StreamWriter::new("Sheet1");
731 sw.set_col_width_range(1, 3, 15.5).unwrap();
732 let xml_bytes = sw.finish().unwrap();
733 let xml_str = String::from_utf8(xml_bytes).unwrap();
734
735 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
736 let cols = ws.cols.unwrap();
737 assert_eq!(cols.cols.len(), 1);
738 assert_eq!(cols.cols[0].min, 1);
739 assert_eq!(cols.cols[0].max, 3);
740 assert_eq!(cols.cols[0].width, Some(15.5));
741 }
742
743 #[test]
744 fn test_col_width_in_output_xml() {
745 let mut sw = StreamWriter::new("Sheet1");
746 sw.set_col_width(2, 25.0).unwrap();
747 let xml_bytes = sw.finish().unwrap();
748 let xml_str = String::from_utf8(xml_bytes).unwrap();
749
750 let cols_pos = xml_str.find("<cols>").unwrap();
752 let sheet_data_pos = xml_str.find("<sheetData").unwrap();
753 assert!(cols_pos < sheet_data_pos);
754 }
755
756 #[test]
757 fn test_col_width_after_rows_returns_error() {
758 let mut sw = StreamWriter::new("Sheet1");
759 sw.write_row(1, &[CellValue::from("data")]).unwrap();
760 let result = sw.set_col_width(1, 20.0);
761 assert!(result.is_err());
762 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
763 }
764
765 #[test]
766 fn test_rows_in_order_succeeds() {
767 let mut sw = StreamWriter::new("Sheet1");
768 sw.write_row(1, &[CellValue::from("a")]).unwrap();
769 sw.write_row(2, &[CellValue::from("b")]).unwrap();
770 sw.write_row(3, &[CellValue::from("c")]).unwrap();
771 sw.finish().unwrap();
772 }
773
774 #[test]
775 fn test_rows_with_gaps_succeeds() {
776 let mut sw = StreamWriter::new("Sheet1");
777 sw.write_row(1, &[CellValue::from("a")]).unwrap();
778 sw.write_row(3, &[CellValue::from("b")]).unwrap();
779 sw.write_row(5, &[CellValue::from("c")]).unwrap();
780 let xml_bytes = sw.finish().unwrap();
781 let xml_str = String::from_utf8(xml_bytes).unwrap();
782
783 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
784 assert_eq!(ws.sheet_data.rows.len(), 3);
785 assert_eq!(ws.sheet_data.rows[0].r, 1);
786 assert_eq!(ws.sheet_data.rows[1].r, 3);
787 assert_eq!(ws.sheet_data.rows[2].r, 5);
788 }
789
790 #[test]
791 fn test_duplicate_row_number_fails() {
792 let mut sw = StreamWriter::new("Sheet1");
793 sw.write_row(1, &[CellValue::from("a")]).unwrap();
794 let result = sw.write_row(1, &[CellValue::from("b")]);
795 assert!(result.is_err());
796 assert!(matches!(
797 result.unwrap_err(),
798 Error::StreamRowAlreadyWritten { row: 1 }
799 ));
800 }
801
802 #[test]
803 fn test_row_zero_fails() {
804 let mut sw = StreamWriter::new("Sheet1");
805 let result = sw.write_row(0, &[CellValue::from("a")]);
806 assert!(result.is_err());
807 assert!(matches!(result.unwrap_err(), Error::InvalidRowNumber(0)));
808 }
809
810 #[test]
811 fn test_row_out_of_order_fails() {
812 let mut sw = StreamWriter::new("Sheet1");
813 sw.write_row(5, &[CellValue::from("a")]).unwrap();
814 let result = sw.write_row(3, &[CellValue::from("b")]);
815 assert!(result.is_err());
816 assert!(matches!(
817 result.unwrap_err(),
818 Error::StreamRowAlreadyWritten { row: 3 }
819 ));
820 }
821
822 #[test]
823 fn test_merge_cells_in_output() {
824 let mut sw = StreamWriter::new("Sheet1");
825 sw.write_row(1, &[CellValue::from("Merged")]).unwrap();
826 sw.add_merge_cell("A1:B1").unwrap();
827 let xml_bytes = sw.finish().unwrap();
828 let xml_str = String::from_utf8(xml_bytes).unwrap();
829
830 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
831 let mc = ws.merge_cells.unwrap();
832 assert_eq!(mc.merge_cells.len(), 1);
833 assert_eq!(mc.merge_cells[0].reference, "A1:B1");
834 }
835
836 #[test]
837 fn test_multiple_merge_cells() {
838 let mut sw = StreamWriter::new("Sheet1");
839 sw.write_row(1, &[CellValue::from("a")]).unwrap();
840 sw.add_merge_cell("A1:B1").unwrap();
841 sw.add_merge_cell("C1:D1").unwrap();
842 let xml_bytes = sw.finish().unwrap();
843 let xml_str = String::from_utf8(xml_bytes).unwrap();
844
845 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
846 let mc = ws.merge_cells.unwrap();
847 assert_eq!(mc.merge_cells.len(), 2);
848 assert_eq!(mc.merge_cells[0].reference, "A1:B1");
849 assert_eq!(mc.merge_cells[1].reference, "C1:D1");
850 }
851
852 #[test]
853 fn test_finish_twice_returns_error() {
854 let mut sw = StreamWriter::new("Sheet1");
855 sw.write_row(1, &[CellValue::from("a")]).unwrap();
856 sw.finish().unwrap();
857 let result = sw.finish();
858 assert!(result.is_err());
859 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
860 }
861
862 #[test]
863 fn test_write_after_finish_returns_error() {
864 let mut sw = StreamWriter::new("Sheet1");
865 sw.write_row(1, &[CellValue::from("a")]).unwrap();
866 sw.finish().unwrap();
867 let result = sw.write_row(2, &[CellValue::from("b")]);
868 assert!(result.is_err());
869 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
870 }
871
872 #[test]
873 fn test_finish_with_no_rows() {
874 let mut sw = StreamWriter::new("Sheet1");
875 let xml_bytes = sw.finish().unwrap();
876 let xml_str = String::from_utf8(xml_bytes).unwrap();
877
878 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
880 assert!(ws.sheet_data.rows.is_empty());
881 }
882
883 #[test]
884 fn test_finish_with_cols_and_no_rows() {
885 let mut sw = StreamWriter::new("Sheet1");
886 sw.set_col_width(1, 20.0).unwrap();
887 let xml_bytes = sw.finish().unwrap();
888 let xml_str = String::from_utf8(xml_bytes).unwrap();
889
890 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
891 assert!(ws.cols.is_some());
892 assert!(ws.sheet_data.rows.is_empty());
893 }
894
895 #[test]
896 fn test_into_parts() {
897 let mut sw = StreamWriter::new("Sheet1");
898 sw.write_row(1, &[CellValue::from("Hello")]).unwrap();
899 let (xml_bytes, sst) = sw.into_parts().unwrap();
900
901 assert!(!xml_bytes.is_empty());
902 assert_eq!(sst.len(), 1);
903 assert_eq!(sst.get(0), Some("Hello"));
904 }
905
906 #[test]
907 fn test_sst_deduplication() {
908 let mut sw = StreamWriter::new("Sheet1");
909 sw.write_row(1, &[CellValue::from("same"), CellValue::from("same")])
910 .unwrap();
911 sw.write_row(2, &[CellValue::from("same"), CellValue::from("other")])
912 .unwrap();
913
914 assert_eq!(sw.sst().len(), 2); assert_eq!(sw.sst().get(0), Some("same"));
916 assert_eq!(sw.sst().get(1), Some("other"));
917 }
918
919 #[test]
920 fn test_large_scale_10000_rows() {
921 let mut sw = StreamWriter::new("BigSheet");
922 for i in 1..=10_000u32 {
923 sw.write_row(
924 i,
925 &[
926 CellValue::from(format!("Row {}", i)),
927 CellValue::from(i as i32),
928 CellValue::from(i as f64 * 1.5),
929 ],
930 )
931 .unwrap();
932 }
933 let xml_bytes = sw.finish().unwrap();
934 let xml_str = String::from_utf8(xml_bytes).unwrap();
935
936 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
938 assert_eq!(ws.sheet_data.rows.len(), 10_000);
939 assert_eq!(ws.sheet_data.rows[0].r, 1);
940 assert_eq!(ws.sheet_data.rows[9999].r, 10_000);
941 }
942
943 #[test]
944 fn test_large_sst_dedup() {
945 let mut sw = StreamWriter::new("Sheet1");
946 for i in 1..=1000u32 {
948 sw.write_row(
949 i,
950 &[
951 CellValue::from("Alpha"),
952 CellValue::from("Beta"),
953 CellValue::from("Gamma"),
954 ],
955 )
956 .unwrap();
957 }
958 assert_eq!(sw.sst().len(), 3);
960 }
961
962 #[test]
963 fn test_xml_escape_in_formula() {
964 let mut sw = StreamWriter::new("Sheet1");
965 sw.write_row(
966 1,
967 &[CellValue::Formula {
968 expr: "IF(A1>0,\"yes\",\"no\")".to_string(),
969 result: None,
970 }],
971 )
972 .unwrap();
973 let xml_bytes = sw.finish().unwrap();
974 let xml = String::from_utf8(xml_bytes).unwrap();
975 assert!(xml.contains(">"));
977 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
979 let formula = ws.sheet_data.rows[0].cells[0].f.as_ref().unwrap();
980 assert_eq!(formula.value, Some("IF(A1>0,\"yes\",\"no\")".to_string()));
981 }
982
983 #[test]
984 fn test_add_merge_cell_invalid_reference() {
985 let mut sw = StreamWriter::new("Sheet1");
986 let result = sw.add_merge_cell("A1B2");
988 assert!(result.is_err());
989 assert!(matches!(
990 result.unwrap_err(),
991 Error::InvalidMergeCellReference(_)
992 ));
993 }
994
995 #[test]
996 fn test_add_merge_cell_invalid_cell_name() {
997 let mut sw = StreamWriter::new("Sheet1");
998 let result = sw.add_merge_cell("ZZZ:B2");
1000 assert!(result.is_err());
1001 assert!(matches!(
1002 result.unwrap_err(),
1003 Error::InvalidMergeCellReference(_)
1004 ));
1005 }
1006
1007 #[test]
1008 fn test_add_merge_cell_empty_reference() {
1009 let mut sw = StreamWriter::new("Sheet1");
1010 let result = sw.add_merge_cell("");
1011 assert!(result.is_err());
1012 assert!(matches!(
1013 result.unwrap_err(),
1014 Error::InvalidMergeCellReference(_)
1015 ));
1016 }
1017
1018 #[test]
1019 fn test_add_merge_cell_after_finish_fails() {
1020 let mut sw = StreamWriter::new("Sheet1");
1021 sw.finish().unwrap();
1022 let result = sw.add_merge_cell("A1:B1");
1023 assert!(result.is_err());
1024 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1025 }
1026
1027 #[test]
1028 fn test_set_col_width_after_finish_fails() {
1029 let mut sw = StreamWriter::new("Sheet1");
1030 sw.finish().unwrap();
1031 let result = sw.set_col_width(1, 10.0);
1032 assert!(result.is_err());
1033 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1034 }
1035
1036 #[test]
1037 fn test_sheet_name_getter() {
1038 let sw = StreamWriter::new("MySheet");
1039 assert_eq!(sw.sheet_name(), "MySheet");
1040 }
1041
1042 #[test]
1043 fn test_all_value_types_in_single_row() {
1044 let mut sw = StreamWriter::new("Sheet1");
1045 sw.write_row(
1046 1,
1047 &[
1048 CellValue::from("text"),
1049 CellValue::from(42),
1050 CellValue::from(3.15),
1051 CellValue::from(true),
1052 CellValue::from(false),
1053 CellValue::Formula {
1054 expr: "A1+B1".to_string(),
1055 result: None,
1056 },
1057 CellValue::Error("#N/A".to_string()),
1058 CellValue::Empty,
1059 ],
1060 )
1061 .unwrap();
1062 let xml_bytes = sw.finish().unwrap();
1063 let xml_str = String::from_utf8(xml_bytes).unwrap();
1064
1065 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
1067 assert_eq!(ws.sheet_data.rows[0].cells.len(), 7);
1069 }
1070
1071 #[test]
1072 fn test_col_width_invalid_column_zero() {
1073 let mut sw = StreamWriter::new("Sheet1");
1074 let result = sw.set_col_width(0, 10.0);
1075 assert!(result.is_err());
1076 assert!(matches!(result.unwrap_err(), Error::InvalidColumnNumber(0)));
1077 }
1078
1079 #[test]
1080 fn test_write_row_with_options_height() {
1081 let mut sw = StreamWriter::new("Sheet1");
1082 let opts = StreamRowOptions {
1083 height: Some(30.0),
1084 ..Default::default()
1085 };
1086 sw.write_row_with_options(1, &[CellValue::from("tall row")], &opts)
1087 .unwrap();
1088 let xml_bytes = sw.finish().unwrap();
1089 let xml = String::from_utf8(xml_bytes).unwrap();
1090
1091 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1093 assert_eq!(ws.sheet_data.rows[0].ht, Some(30.0));
1094 assert_eq!(ws.sheet_data.rows[0].custom_height, Some(true));
1095 }
1096
1097 #[test]
1098 fn test_write_row_with_options_hidden() {
1099 let mut sw = StreamWriter::new("Sheet1");
1100 let opts = StreamRowOptions {
1101 visible: Some(false),
1102 ..Default::default()
1103 };
1104 sw.write_row_with_options(1, &[CellValue::from("hidden")], &opts)
1105 .unwrap();
1106 let xml_bytes = sw.finish().unwrap();
1107 let xml = String::from_utf8(xml_bytes).unwrap();
1108
1109 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1110 assert_eq!(ws.sheet_data.rows[0].hidden, Some(true));
1111 }
1112
1113 #[test]
1114 fn test_write_row_with_options_outline_level() {
1115 let mut sw = StreamWriter::new("Sheet1");
1116 let opts = StreamRowOptions {
1117 outline_level: Some(3),
1118 ..Default::default()
1119 };
1120 sw.write_row_with_options(1, &[CellValue::from("grouped")], &opts)
1121 .unwrap();
1122 let xml_bytes = sw.finish().unwrap();
1123 let xml = String::from_utf8(xml_bytes).unwrap();
1124
1125 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1126 assert_eq!(ws.sheet_data.rows[0].outline_level, Some(3));
1127 }
1128
1129 #[test]
1130 fn test_write_row_with_options_style() {
1131 let mut sw = StreamWriter::new("Sheet1");
1132 let opts = StreamRowOptions {
1133 style_id: Some(2),
1134 ..Default::default()
1135 };
1136 sw.write_row_with_options(1, &[CellValue::from("styled row")], &opts)
1137 .unwrap();
1138 let xml_bytes = sw.finish().unwrap();
1139 let xml = String::from_utf8(xml_bytes).unwrap();
1140
1141 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1142 assert_eq!(ws.sheet_data.rows[0].s, Some(2));
1143 assert_eq!(ws.sheet_data.rows[0].custom_format, Some(true));
1144 }
1145
1146 #[test]
1147 fn test_write_row_with_options_all() {
1148 let mut sw = StreamWriter::new("Sheet1");
1149 let opts = StreamRowOptions {
1150 height: Some(25.5),
1151 visible: Some(false),
1152 outline_level: Some(2),
1153 style_id: Some(7),
1154 };
1155 sw.write_row_with_options(1, &[CellValue::from("all options")], &opts)
1156 .unwrap();
1157 let xml_bytes = sw.finish().unwrap();
1158 let xml = String::from_utf8(xml_bytes).unwrap();
1159
1160 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1161 let row = &ws.sheet_data.rows[0];
1162 assert_eq!(row.ht, Some(25.5));
1163 assert_eq!(row.custom_height, Some(true));
1164 assert_eq!(row.hidden, Some(true));
1165 assert_eq!(row.outline_level, Some(2));
1166 assert_eq!(row.s, Some(7));
1167 assert_eq!(row.custom_format, Some(true));
1168 }
1169
1170 #[test]
1171 fn test_write_row_with_options_height_exceeded() {
1172 let mut sw = StreamWriter::new("Sheet1");
1173 let opts = StreamRowOptions {
1174 height: Some(500.0),
1175 ..Default::default()
1176 };
1177 let result = sw.write_row_with_options(1, &[CellValue::from("too tall")], &opts);
1178 assert!(result.is_err());
1179 assert!(matches!(
1180 result.unwrap_err(),
1181 Error::RowHeightExceeded { .. }
1182 ));
1183 }
1184
1185 #[test]
1186 fn test_write_row_with_options_outline_level_exceeded() {
1187 let mut sw = StreamWriter::new("Sheet1");
1188 let opts = StreamRowOptions {
1189 outline_level: Some(8),
1190 ..Default::default()
1191 };
1192 let result = sw.write_row_with_options(1, &[CellValue::from("bad level")], &opts);
1193 assert!(result.is_err());
1194 }
1195
1196 #[test]
1197 fn test_write_row_with_options_visible_true_no_hidden_attr() {
1198 let mut sw = StreamWriter::new("Sheet1");
1199 let opts = StreamRowOptions {
1200 visible: Some(true),
1201 ..Default::default()
1202 };
1203 sw.write_row_with_options(1, &[CellValue::from("visible")], &opts)
1204 .unwrap();
1205 let xml_bytes = sw.finish().unwrap();
1206 let xml = String::from_utf8(xml_bytes).unwrap();
1207
1208 assert!(!xml.contains("hidden="));
1210 }
1211
1212 #[test]
1213 fn test_col_style() {
1214 let mut sw = StreamWriter::new("Sheet1");
1215 sw.set_col_style(1, 3).unwrap();
1216 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1217 let xml_bytes = sw.finish().unwrap();
1218 let xml = String::from_utf8(xml_bytes).unwrap();
1219
1220 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1221 let cols = ws.cols.unwrap();
1222 let styled_col = cols.cols.iter().find(|c| c.style.is_some()).unwrap();
1223 assert_eq!(styled_col.style, Some(3));
1224 assert_eq!(styled_col.min, 1);
1225 assert_eq!(styled_col.max, 1);
1226 }
1227
1228 #[test]
1229 fn test_col_visible() {
1230 let mut sw = StreamWriter::new("Sheet1");
1231 sw.set_col_visible(2, false).unwrap();
1232 sw.write_row(1, &[CellValue::from("a"), CellValue::from("b")])
1233 .unwrap();
1234 let xml_bytes = sw.finish().unwrap();
1235 let xml = String::from_utf8(xml_bytes).unwrap();
1236
1237 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1238 let cols = ws.cols.unwrap();
1239 let hidden_col = cols.cols.iter().find(|c| c.hidden == Some(true)).unwrap();
1240 assert_eq!(hidden_col.min, 2);
1241 assert_eq!(hidden_col.max, 2);
1242 }
1243
1244 #[test]
1245 fn test_col_outline_level() {
1246 let mut sw = StreamWriter::new("Sheet1");
1247 sw.set_col_outline_level(3, 2).unwrap();
1248 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1249 let xml_bytes = sw.finish().unwrap();
1250 let xml = String::from_utf8(xml_bytes).unwrap();
1251
1252 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1253 let cols = ws.cols.unwrap();
1254 let outlined_col = cols
1255 .cols
1256 .iter()
1257 .find(|c| c.outline_level.is_some())
1258 .unwrap();
1259 assert_eq!(outlined_col.outline_level, Some(2));
1260 assert_eq!(outlined_col.min, 3);
1261 assert_eq!(outlined_col.max, 3);
1262 }
1263
1264 #[test]
1265 fn test_col_outline_level_exceeded() {
1266 let mut sw = StreamWriter::new("Sheet1");
1267 let result = sw.set_col_outline_level(1, 8);
1268 assert!(result.is_err());
1269 }
1270
1271 #[test]
1272 fn test_col_style_after_rows_error() {
1273 let mut sw = StreamWriter::new("Sheet1");
1274 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1275 let result = sw.set_col_style(1, 1);
1276 assert!(result.is_err());
1277 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1278 }
1279
1280 #[test]
1281 fn test_col_visible_after_rows_error() {
1282 let mut sw = StreamWriter::new("Sheet1");
1283 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1284 let result = sw.set_col_visible(1, false);
1285 assert!(result.is_err());
1286 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1287 }
1288
1289 #[test]
1290 fn test_col_outline_after_rows_error() {
1291 let mut sw = StreamWriter::new("Sheet1");
1292 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1293 let result = sw.set_col_outline_level(1, 1);
1294 assert!(result.is_err());
1295 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1296 }
1297
1298 #[test]
1299 fn test_freeze_panes_rows() {
1300 let mut sw = StreamWriter::new("Sheet1");
1301 sw.set_freeze_panes("A2").unwrap();
1302 sw.write_row(1, &[CellValue::from("header")]).unwrap();
1303 sw.write_row(2, &[CellValue::from("data")]).unwrap();
1304 let xml_bytes = sw.finish().unwrap();
1305 let xml_str = String::from_utf8(xml_bytes).unwrap();
1306
1307 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
1308 let views = ws.sheet_views.unwrap();
1309 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1310 assert_eq!(pane.y_split, Some(1));
1311 assert_eq!(pane.top_left_cell, Some("A2".to_string()));
1312 assert_eq!(pane.active_pane, Some("bottomLeft".to_string()));
1313 assert_eq!(pane.state, Some("frozen".to_string()));
1314 assert_eq!(pane.x_split, None);
1316 }
1317
1318 #[test]
1319 fn test_freeze_panes_cols() {
1320 let mut sw = StreamWriter::new("Sheet1");
1321 sw.set_freeze_panes("B1").unwrap();
1322 sw.write_row(1, &[CellValue::from("a"), CellValue::from("b")])
1323 .unwrap();
1324 let xml_bytes = sw.finish().unwrap();
1325 let xml_str = String::from_utf8(xml_bytes).unwrap();
1326
1327 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
1328 let views = ws.sheet_views.unwrap();
1329 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1330 assert_eq!(pane.x_split, Some(1));
1331 assert_eq!(pane.top_left_cell, Some("B1".to_string()));
1332 assert_eq!(pane.active_pane, Some("topRight".to_string()));
1333 assert_eq!(pane.state, Some("frozen".to_string()));
1334 assert_eq!(pane.y_split, None);
1336 }
1337
1338 #[test]
1339 fn test_freeze_panes_both() {
1340 let mut sw = StreamWriter::new("Sheet1");
1341 sw.set_freeze_panes("C3").unwrap();
1342 sw.write_row(1, &[CellValue::from("a")]).unwrap();
1343 let xml_bytes = sw.finish().unwrap();
1344 let xml_str = String::from_utf8(xml_bytes).unwrap();
1345
1346 let ws: WorksheetXml = quick_xml::de::from_str(&xml_str).unwrap();
1347 let views = ws.sheet_views.unwrap();
1348 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1349 assert_eq!(pane.x_split, Some(2));
1350 assert_eq!(pane.y_split, Some(2));
1351 assert_eq!(pane.top_left_cell, Some("C3".to_string()));
1352 assert_eq!(pane.active_pane, Some("bottomRight".to_string()));
1353 assert_eq!(pane.state, Some("frozen".to_string()));
1354 }
1355
1356 #[test]
1357 fn test_freeze_panes_after_rows_error() {
1358 let mut sw = StreamWriter::new("Sheet1");
1359 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1360 let result = sw.set_freeze_panes("A2");
1361 assert!(result.is_err());
1362 assert!(matches!(result.unwrap_err(), Error::StreamColumnsAfterRows));
1363 }
1364
1365 #[test]
1366 fn test_freeze_panes_a1_error() {
1367 let mut sw = StreamWriter::new("Sheet1");
1368 let result = sw.set_freeze_panes("A1");
1369 assert!(result.is_err());
1370 assert!(matches!(
1371 result.unwrap_err(),
1372 Error::InvalidCellReference(_)
1373 ));
1374 }
1375
1376 #[test]
1377 fn test_freeze_panes_invalid_cell_error() {
1378 let mut sw = StreamWriter::new("Sheet1");
1379 let result = sw.set_freeze_panes("ZZZZ1");
1380 assert!(result.is_err());
1381 }
1382
1383 #[test]
1384 fn test_freeze_panes_appears_before_cols() {
1385 let mut sw = StreamWriter::new("Sheet1");
1386 sw.set_freeze_panes("A2").unwrap();
1387 sw.set_col_width(1, 20.0).unwrap();
1388 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1389 let xml_bytes = sw.finish().unwrap();
1390 let xml = String::from_utf8(xml_bytes).unwrap();
1391
1392 let views_pos = xml.find("<sheetView").unwrap();
1393 let cols_pos = xml.find("<cols>").unwrap();
1394 let data_pos = xml.find("<sheetData").unwrap();
1395 assert!(views_pos < cols_pos);
1396 assert!(cols_pos < data_pos);
1397 }
1398
1399 #[test]
1400 fn test_freeze_panes_after_finish_error() {
1401 let mut sw = StreamWriter::new("Sheet1");
1402 sw.finish().unwrap();
1403 let result = sw.set_freeze_panes("A2");
1404 assert!(result.is_err());
1405 assert!(matches!(result.unwrap_err(), Error::StreamAlreadyFinished));
1406 }
1407
1408 #[test]
1409 fn test_no_freeze_panes_no_sheet_views() {
1410 let mut sw = StreamWriter::new("Sheet1");
1411 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1412 let xml_bytes = sw.finish().unwrap();
1413 let xml = String::from_utf8(xml_bytes).unwrap();
1414
1415 assert!(!xml.contains("<sheetView"));
1417 }
1418
1419 #[test]
1420 fn test_write_row_backward_compat() {
1421 let mut sw = StreamWriter::new("Sheet1");
1423 sw.write_row(1, &[CellValue::from("hello")]).unwrap();
1424 let xml_bytes = sw.finish().unwrap();
1425 let xml = String::from_utf8(xml_bytes).unwrap();
1426
1427 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1428 let row = &ws.sheet_data.rows[0];
1429 assert_eq!(row.r, 1);
1430 assert!(row.ht.is_none());
1432 assert!(row.hidden.is_none());
1433 assert!(row.outline_level.is_none());
1434 assert!(row.custom_format.is_none());
1435 }
1436
1437 #[test]
1438 fn test_write_row_with_style_backward_compat() {
1439 let mut sw = StreamWriter::new("Sheet1");
1441 sw.write_row_with_style(1, &[CellValue::from("styled")], 5)
1442 .unwrap();
1443 let xml_bytes = sw.finish().unwrap();
1444 let xml = String::from_utf8(xml_bytes).unwrap();
1445
1446 assert!(xml.contains("s=\"5\""));
1448 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1450 assert!(ws.sheet_data.rows[0].custom_format.is_none());
1451 }
1452
1453 #[test]
1454 fn test_col_options_combined_with_widths() {
1455 let mut sw = StreamWriter::new("Sheet1");
1456 sw.set_col_width(1, 20.0).unwrap();
1457 sw.set_col_style(2, 5).unwrap();
1458 sw.set_col_visible(3, false).unwrap();
1459 sw.write_row(
1460 1,
1461 &[
1462 CellValue::from("a"),
1463 CellValue::from("b"),
1464 CellValue::from("c"),
1465 ],
1466 )
1467 .unwrap();
1468 let xml_bytes = sw.finish().unwrap();
1469 let xml = String::from_utf8(xml_bytes).unwrap();
1470
1471 let ws: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
1472 let cols = ws.cols.unwrap();
1473 assert_eq!(cols.cols.len(), 3);
1474 }
1475
1476 #[test]
1477 fn test_into_worksheet_parts_basic() {
1478 let mut sw = StreamWriter::new("Sheet1");
1479 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1480 .unwrap();
1481 sw.write_row(2, &[CellValue::from("World"), CellValue::from(99)])
1482 .unwrap();
1483 let (ws, sst) = sw.into_worksheet_parts().unwrap();
1484 assert_eq!(ws.sheet_data.rows.len(), 2);
1485 assert_eq!(ws.sheet_data.rows[0].r, 1);
1486 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
1487 assert_eq!(ws.sheet_data.rows[1].r, 2);
1488 assert_eq!(sst.len(), 2);
1489 }
1490
1491 #[test]
1492 fn test_into_worksheet_parts_sst() {
1493 let mut sw = StreamWriter::new("Sheet1");
1494 sw.write_row(1, &[CellValue::from("Alpha"), CellValue::from("Beta")])
1495 .unwrap();
1496 let (ws, sst) = sw.into_worksheet_parts().unwrap();
1497 assert_eq!(sst.len(), 2);
1498 assert_eq!(sst.get(0), Some("Alpha"));
1499 assert_eq!(sst.get(1), Some("Beta"));
1500 assert_eq!(ws.sheet_data.rows[0].cells[0].v, Some("0".to_string()));
1502 assert_eq!(ws.sheet_data.rows[0].cells[1].v, Some("1".to_string()));
1503 assert_eq!(
1504 ws.sheet_data.rows[0].cells[0].t,
1505 sheetkit_xml::worksheet::CellTypeTag::SharedString
1506 );
1507 }
1508
1509 #[test]
1510 fn test_into_worksheet_parts_cell_col_set() {
1511 let mut sw = StreamWriter::new("Sheet1");
1512 sw.write_row(
1513 1,
1514 &[CellValue::from("A"), CellValue::Empty, CellValue::from("C")],
1515 )
1516 .unwrap();
1517 let (ws, _) = sw.into_worksheet_parts().unwrap();
1518 assert_eq!(ws.sheet_data.rows[0].cells.len(), 2);
1519 assert_eq!(ws.sheet_data.rows[0].cells[0].col, 1);
1520 assert_eq!(ws.sheet_data.rows[0].cells[0].r.as_str(), "A1");
1521 assert_eq!(ws.sheet_data.rows[0].cells[1].col, 3);
1522 assert_eq!(ws.sheet_data.rows[0].cells[1].r.as_str(), "C1");
1523 }
1524
1525 #[test]
1526 fn test_into_worksheet_parts_freeze_panes() {
1527 let mut sw = StreamWriter::new("Sheet1");
1528 sw.set_freeze_panes("C3").unwrap();
1529 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1530 let (ws, _) = sw.into_worksheet_parts().unwrap();
1531 assert!(ws.sheet_views.is_some());
1532 let views = ws.sheet_views.unwrap();
1533 let pane = views.sheet_views[0].pane.as_ref().unwrap();
1534 assert_eq!(pane.x_split, Some(2));
1535 assert_eq!(pane.y_split, Some(2));
1536 assert_eq!(pane.top_left_cell, Some("C3".to_string()));
1537 assert_eq!(pane.state, Some("frozen".to_string()));
1538 }
1539
1540 #[test]
1541 fn test_into_worksheet_parts_cols() {
1542 let mut sw = StreamWriter::new("Sheet1");
1543 sw.set_col_width(1, 20.0).unwrap();
1544 sw.set_col_style(2, 5).unwrap();
1545 sw.write_row(1, &[CellValue::from("data")]).unwrap();
1546 let (ws, _) = sw.into_worksheet_parts().unwrap();
1547 assert!(ws.cols.is_some());
1548 let cols = ws.cols.unwrap();
1549 assert_eq!(cols.cols.len(), 2);
1550 }
1551
1552 #[test]
1553 fn test_into_worksheet_parts_merge_cells() {
1554 let mut sw = StreamWriter::new("Sheet1");
1555 sw.write_row(1, &[CellValue::from("Merged")]).unwrap();
1556 sw.add_merge_cell("A1:B1").unwrap();
1557 let (ws, _) = sw.into_worksheet_parts().unwrap();
1558 assert!(ws.merge_cells.is_some());
1559 let mc = ws.merge_cells.unwrap();
1560 assert_eq!(mc.merge_cells.len(), 1);
1561 assert_eq!(mc.merge_cells[0].reference, "A1:B1");
1562 }
1563}