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